Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion core/src/agents/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import {Content, createUserContent, FunctionCall, Part} from '@google/genai';
import {isEmpty} from 'lodash-es';

import {InvocationContext} from '../agents/invocation_context.js';
import {createEvent, Event, getFunctionCalls} from '../events/event.js';
import {
createEvent,
Event,
getFunctionCalls,
getFunctionResponses,
} from '../events/event.js';
import {mergeEventActions} from '../events/event_actions.js';
import {BaseTool} from '../tools/base_tool.js';
import {ToolConfirmation} from '../tools/tool_confirmation.js';
Expand Down Expand Up @@ -564,3 +569,44 @@ export function mergeParallelFunctionResponseEvents(
}

// TODO - b/425992518: support function call in live connection.

/**
* Finds the function call event that matches the function call ID.
* Mirrors Python ADK's `find_event_by_function_call_id`.
*/
export function findEventByFunctionCallId(
events: Event[],
functionCallId: string,
endIndex: number = events.length,
): Event | undefined {
for (let i = endIndex - 1; i >= 0; i--) {
const event = events[i];
const functionCalls = getFunctionCalls(event);
for (const functionCall of functionCalls) {
if (functionCall.id === functionCallId) {
return event;
}
}
}
return undefined;
}

/**
* Finds the function call event that matches the function response ID of the last event.
* Mirrors Python ADK's `find_matching_function_call`.
*/
export function findMatchingFunctionCall(events: Event[]): Event | undefined {
if (!events.length) {
return undefined;
}
const lastEvent = events[events.length - 1];
const functionResponses = getFunctionResponses(lastEvent);
if (!functionResponses.length || !functionResponses[0].id) {
return undefined;
}
return findEventByFunctionCallId(
events,
functionResponses[0].id,
events.length - 1,
);
}
4 changes: 4 additions & 0 deletions core/src/apps/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import {BaseAgent, isBaseAgent} from '../agents/base_agent.js';
import {BasePlugin} from '../plugins/base_plugin.js';
import {ResumabilityConfig} from './resumability_config.js';

const VALID_APP_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_-]*$/;

Expand Down Expand Up @@ -50,6 +51,7 @@ export interface AppOptions {
name: string;
rootAgent: BaseAgent;
plugins?: BasePlugin[];
resumabilityConfig?: ResumabilityConfig;
}

/**
Expand All @@ -70,6 +72,7 @@ export class App {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
readonly rootAgent: BaseAgent | any;
readonly plugins: BasePlugin[];
readonly resumabilityConfig?: ResumabilityConfig;

constructor(options: AppOptions) {
validateAppName(options.name);
Expand All @@ -90,5 +93,6 @@ export class App {
this.name = options.name;
this.rootAgent = options.rootAgent;
this.plugins = options.plugins ?? [];
this.resumabilityConfig = options.resumabilityConfig;
}
}
36 changes: 36 additions & 0 deletions core/src/apps/resumability_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* The configuration of resumability for an application or runner.
*
* The "resumability" in ADK refers to the ability to:
* 1. pause an invocation upon a long-running function call.
* 2. resume an invocation from the last event, if it's paused or failed midway
* through.
*/
export interface ResumabilityConfig {
/**
* Whether the app/runner supports agent resumption.
* If enabled, resumption routing based on matching function responses will be active.
*/
isResumable: boolean;
}

/**
* Creates a {@link ResumabilityConfig} with default values.
*
* @param params Optional partial {@link ResumabilityConfig} overriding defaults.
* @returns A merged {@link ResumabilityConfig} object.
*/
export function createResumabilityConfig(
params: Partial<ResumabilityConfig> = {},
): ResumabilityConfig {
return {
isResumable: false,
...params,
};
}
16 changes: 14 additions & 2 deletions core/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export type {
SingleAgentCallback,
} from './agents/base_agent.js';
export {Context} from './agents/context.js';
export {functionsExportedForTestingOnly} from './agents/functions.js';
export {
findEventByFunctionCallId,
findMatchingFunctionCall,
functionsExportedForTestingOnly,
} from './agents/functions.js';
export {InvocationContext} from './agents/invocation_context.js';
export type {InvocationContextParams} from './agents/invocation_context.js';
export {LiveRequestQueue} from './agents/live_request_queue.js';
Expand Down Expand Up @@ -58,6 +62,8 @@ export {StreamingMode} from './agents/run_config.js';
export type {RunConfig} from './agents/run_config.js';
export {SequentialAgent, isSequentialAgent} from './agents/sequential_agent.js';
export type {TranscriptionEntry} from './agents/transcription_entry.js';
export {createResumabilityConfig} from './apps/resumability_config.js';
export type {ResumabilityConfig} from './apps/resumability_config.js';
export type {
BaseArtifactService,
DeleteArtifactRequest,
Expand Down Expand Up @@ -190,7 +196,13 @@ export type {
ToolCallPolicyContext,
} from './plugins/security_plugin.js';
export {InMemoryRunner} from './runner/in_memory_runner.js';
export {Runner, isRunner} from './runner/runner.js';
export {
Runner,
determineAgentForResumption,
findEventByLastFunctionResponseId,
isRoutableLlmAgent,
isRunner,
} from './runner/runner.js';
export type {RunnerConfig} from './runner/runner.js';
export {BaseSessionService} from './sessions/base_session_service.js';
export type {
Expand Down
15 changes: 13 additions & 2 deletions core/src/runner/in_memory_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import {BaseAgent} from '../agents/base_agent.js';
import {App} from '../apps/app.js';
import {ResumabilityConfig} from '../apps/resumability_config.js';
import {InMemoryArtifactService} from '../artifacts/in_memory_artifact_service.js';
import {InMemoryMemoryService} from '../memory/in_memory_memory_service.js';
import {BasePlugin} from '../plugins/base_plugin.js';
Expand Down Expand Up @@ -37,18 +38,27 @@ export class InMemoryRunner extends Runner {
* Creates a new InMemoryRunner instance.
*
* @param params The configuration for the runner.
* @param params.app An optional application instance to run.
* @param params.agent The root agent to run.
* @param params.appName The application name. Defaults to `'InMemoryRunner'`.
* @param params.plugins An optional list of plugins.
* @param params.app An optional application instance to run.
* @param params.resumabilityConfig An optional resumability configuration.
*/
constructor(params: {
app?: App;
agent?: BaseAgent;
appName?: string;
plugins?: BasePlugin[];
resumabilityConfig?: ResumabilityConfig;
}) {
const {agent, appName = 'InMemoryRunner', plugins = [], app} = params;
const {
agent,
appName = 'InMemoryRunner',
plugins = [],
app,
resumabilityConfig,
} = params;

super({
app,
appName,
Expand All @@ -57,6 +67,7 @@ export class InMemoryRunner extends Runner {
artifactService: new InMemoryArtifactService(),
sessionService: new InMemorySessionService(),
memoryService: new InMemoryMemoryService(),
resumabilityConfig: app?.resumabilityConfig ?? resumabilityConfig,
});
}
}
Loading
Loading