Skip to content
33 changes: 29 additions & 4 deletions core/src/a2a/a2a_remote_agent_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ export interface UserFunctionCall {
}

/**
* Returns a UserFunctionCall when the event at index has a FunctionResponse.
* Returns a UserFunctionCall when the event at `index` contains a
* FunctionResponse that can be traced back to a preceding FunctionCall event.
*
* @param session - The session whose event history to inspect.
* @param index - Index of the candidate event to examine.
* @returns The matching `UserFunctionCall`, or `undefined` if the event at
* `index` is not a user function-response event or has no preceding call.
*/
export function getUserFunctionCallAt(
session: Session,
Expand Down Expand Up @@ -62,6 +68,10 @@ export function getUserFunctionCallAt(

/**
* Checks if an event contains a function call with the given ID.
*
* @param event - The event to inspect.
* @param callId - The function call ID to look for.
* @returns `true` if a part in the event has a matching `functionCall.id`.
*/
export function isFunctionCallEvent(event: AdkEvent, callId: string): boolean {
if (!event || !event.content || !event.content.parts) {
Expand All @@ -75,6 +85,10 @@ export function isFunctionCallEvent(event: AdkEvent, callId: string): boolean {

/**
* Finds the first part with a FunctionResponse and returns the call ID.
*
* @param event - The event to inspect.
* @returns The `id` of the first FunctionResponse part, or `undefined` if
* none is found.
*/
export function getFunctionResponseCallId(event: AdkEvent): string | undefined {
if (!event || !event.content || !event.content.parts) {
Expand All @@ -89,8 +103,13 @@ export function getFunctionResponseCallId(event: AdkEvent): string | undefined {
}

/**
* Returns content parts for all events not present in the remote session
* and a2a contextId if found in a remote agent event metadata.
* Returns A2A content parts for all events not yet seen by the remote agent,
* along with the A2A context ID found in the most recent remote agent event.
*
* @param ctx - The current invocation context, used to identify the remote
* agent's authored events.
* @param session - The local session whose event history to diff.
* @returns An object with the missing `parts` and an optional `contextId`.
*/
export function toMissingRemoteSessionParts(
ctx: InvocationContext,
Expand Down Expand Up @@ -137,7 +156,13 @@ export function toMissingRemoteSessionParts(
}

/**
* Wraps an agent event as a user message for context.
* Wraps an agent event as a user message so it can be sent as context to a
* remote agent that only accepts user-role messages.
*
* @param ctx - The current invocation context.
* @param agentEvent - The agent-authored event to reframe as a user message.
* @returns A new event with `author: 'user'` whose parts summarise the
* original agent event's text, function calls, and function responses.
*/
export function presentAsUserMessage(
ctx: InvocationContext,
Expand Down
15 changes: 14 additions & 1 deletion core/src/a2a/event_converter_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ import {toA2AParts, toGenAIPart, toGenAIParts} from './part_converter_utils.js';

/**
* Converts a session Event to an A2A Message.
*
* @param event - The ADK event to convert.
* @param appName - The name of the ADK application.
* @param userId - The ID of the current user.
* @param sessionId - The ID of the current session.
* @returns An A2A message with the event's parts and metadata.
*/
export function toA2AMessage(
event: AdkEvent,
Expand All @@ -63,7 +69,14 @@ export function toA2AMessage(
}

/**
* Converts an A2A Event to a Session Event.
* Converts an A2A Event to an ADK Session Event.
*
* @param event - The A2A event to convert (message, task, artifact update, or
* status update).
* @param invocationId - The ADK invocation ID to attach to the resulting event.
* @param agentName - The name of the agent to use as the event author.
* @returns The converted ADK event, or `undefined` if the A2A event type
* produces no content.
*/
export function toAdkEvent(
event: A2AEvent,
Expand Down
18 changes: 18 additions & 0 deletions core/src/a2a/metadata_converter_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export enum AdkMetadataKeys {

/**
* Creates ADK Event metadata from an A2A Event.
*
* @param a2aEvent - The A2A event to extract metadata from.
* @returns A record containing the ADK task ID and context ID keys.
*/
export function getAdkEventMetadata(
a2aEvent: A2AEvent,
Expand All @@ -58,6 +61,12 @@ export function getAdkEventMetadata(

/**
* Creates A2A Event metadata from an ADK Event.
*
* @param adkEvent - The ADK event to extract metadata from.
* @param appName - The name of the ADK application.
* @param userId - The ID of the current user.
* @param sessionId - The ID of the current session.
* @returns A record of A2A metadata keys populated from the ADK event.
*/
export function getA2AEventMetadata(
adkEvent: AdkEvent,
Expand Down Expand Up @@ -91,6 +100,11 @@ export function getA2AEventMetadata(

/**
* Creates A2A Session metadata from ADK Event invocation metadata.
*
* @param appName - The name of the ADK application.
* @param userId - The ID of the current user.
* @param sessionId - The ID of the current session.
* @returns A record of A2A metadata keys for app name, user ID, and session ID.
*/
export function getA2ASessionMetadata({
appName,
Expand All @@ -110,6 +124,10 @@ export function getA2ASessionMetadata({

/**
* Creates A2A Event metadata from ADK Event actions.
*
* @param actions - The ADK event actions to extract escalation and transfer
* metadata from.
* @returns A record with escalate and transfer-to-agent A2A metadata keys.
*/
export function getA2AEventMetadataFromActions(
actions: AdkEventActions,
Expand Down
57 changes: 51 additions & 6 deletions core/src/a2a/part_converter_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ enum DataPartType {
}

/**
* Converts GenAI Parts to A2A Parts.
* Converts an array of GenAI Parts to A2A Parts.
*
* @param parts - The GenAI parts to convert. Defaults to an empty array.
* @param longRunningToolIDs - IDs of function calls that are long-running.
* @returns An array of A2A parts.
*/
export function toA2AParts(
parts: GenAIPart[] = [],
Expand All @@ -45,7 +49,11 @@ export function toA2AParts(
}

/**
* Converts a GenAI Part to an A2A Part.
* Converts a single GenAI Part to the appropriate A2A Part type.
*
* @param part - The GenAI part to convert.
* @param longRunningToolIDs - IDs of function calls that are long-running.
* @returns The corresponding A2A part (text, file, or data).
*/
export function toA2APart(
part: GenAIPart,
Expand All @@ -64,6 +72,9 @@ export function toA2APart(

/**
* Converts a GenAI Text Part to an A2A Text Part.
*
* @param part - The GenAI part containing a text field.
* @returns An A2A text part, with thought metadata attached if applicable.
*/
export function toA2ATextPart(part: GenAIPart): A2APart {
const a2aPart: A2APart = {kind: 'text', text: part.text || ''};
Expand All @@ -79,6 +90,10 @@ export function toA2ATextPart(part: GenAIPart): A2APart {

/**
* Converts a GenAI File Part to an A2A File Part.
*
* @param part - The GenAI part containing `fileData` or `inlineData`.
* @returns An A2A file part with URI or bytes depending on the source.
* @throws {Error} If the part contains neither `fileData` nor `inlineData`.
*/
export function toA2AFilePart(part: GenAIPart): A2APart {
const metadata: Record<string, unknown> = {};
Expand Down Expand Up @@ -112,7 +127,11 @@ export function toA2AFilePart(part: GenAIPart): A2APart {
}

/**
* Converts a GenAI Data Part to an A2A Data Part.
* Converts a GenAI Data Part (function call/response or code) to an A2A Data Part.
*
* @param part - The GenAI part containing structured data.
* @param longRunningToolIDs - IDs of function calls that are long-running.
* @returns An A2A data part with the appropriate type metadata.
*/
export function toA2ADataPart(
part: GenAIPart,
Expand Down Expand Up @@ -172,6 +191,12 @@ export function toA2ADataPart(
};
}

/**
* Converts an A2A Message to a GenAI Content object.
*
* @param a2aMessage - The A2A message to convert.
* @returns A GenAI user or model content object based on the message role.
*/
export function toGenAIContent(a2aMessage: Message): GenAIContent {
const parts = toGenAIParts(a2aMessage.parts);

Expand All @@ -181,14 +206,21 @@ export function toGenAIContent(a2aMessage: Message): GenAIContent {
}

/**
* Converts an A2A Part to a GenAI Part.
* Converts an array of A2A Parts to GenAI Parts.
*
* @param a2aParts - The A2A parts to convert.
* @returns An array of GenAI parts.
*/
export function toGenAIParts(a2aParts: A2APart[]): GenAIPart[] {
return a2aParts.map((a2aPart) => toGenAIPart(a2aPart));
}

/**
* Converts an A2A Part to a GenAI Part.
* Converts a single A2A Part to the appropriate GenAI Part type.
*
* @param a2aPart - The A2A part to convert.
* @returns The corresponding GenAI part.
* @throws {Error} If the A2A part has an unrecognized `kind`.
*/
export function toGenAIPart(a2aPart: A2APart): GenAIPart {
if (a2aPart.kind === 'text') {
Expand All @@ -208,6 +240,9 @@ export function toGenAIPart(a2aPart: A2APart): GenAIPart {

/**
* Converts an A2A Text Part to a GenAI Part.
*
* @param a2aPart - The A2A text part to convert.
* @returns A GenAI part with `text` and optional `thought` flag.
*/
export function toGenAIPartText(a2aPart: A2ATextPart): GenAIPart {
return {
Expand All @@ -218,6 +253,11 @@ export function toGenAIPartText(a2aPart: A2ATextPart): GenAIPart {

/**
* Converts an A2A File Part to a GenAI Part.
*
* @param a2aPart - The A2A file part containing bytes or a URI.
* @returns A GenAI part with `inlineData` or `fileData` depending on the
* source, with optional video metadata attached.
* @throws {Error} If the file part contains neither bytes nor a URI.
*/
export function toGenAIPartFile(a2aPart: A2AFilePart): GenAIPart {
const part: GenAIPart = {};
Expand Down Expand Up @@ -247,7 +287,12 @@ export function toGenAIPartFile(a2aPart: A2AFilePart): GenAIPart {
}

/**
* Converts an A2A Data Part to a GenAI Part.
* Converts an A2A Data Part to the appropriate GenAI Part.
*
* @param a2aPart - The A2A data part containing structured data and type metadata.
* @returns A GenAI part with the appropriate function call, function response,
* or code execution fields, falling back to a JSON text part if the type is
* unrecognized.
*/
export function toGenAIPartData(a2aPart: A2ADataPart): GenAIPart {
if (!a2aPart.data) {
Expand Down
7 changes: 7 additions & 0 deletions core/src/agents/processors/basic_llm_request_processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ import {BaseLlmRequestProcessor} from './base_llm_processor.js';
* connect settings.
*/
export class BasicLlmRequestProcessor extends BaseLlmRequestProcessor {
/**
* Populates model name, generation config, output schema, and live connect
* settings on the request from the agent and run config.
*
* @param invocationContext - The current invocation context.
* @param llmRequest - The request object to populate in place.
*/
// eslint-disable-next-line require-yield
override async *runAsync(
invocationContext: InvocationContext,
Expand Down
7 changes: 7 additions & 0 deletions core/src/agents/processors/identity_llm_request_processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ import {BaseLlmRequestProcessor} from './base_llm_processor.js';
* informing the model of the agent's name and description.
*/
export class IdentityLlmRequestProcessor extends BaseLlmRequestProcessor {
/**
* Appends agent name and description as identity instructions to the system
* prompt of the request.
*
* @param invocationContext - The current invocation context.
* @param llmRequest - The request object to append instructions to.
*/
// eslint-disable-next-line require-yield
override async *runAsync(
invocationContext: InvocationContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ import {BaseLlmRequestProcessor} from './base_llm_processor.js';
* corresponding tools before the next LLM turn.
*/
export class RequestConfirmationLlmRequestProcessor extends BaseLlmRequestProcessor {
/** Handles tool confirmation information to build the LLM request. */
/**
* Resumes tool calls that were paused for user confirmation, re-invoking
* them with the confirmed or denied decision before the next LLM turn.
*
* @param invocationContext - The current invocation context, including the
* session event history used to locate pending confirmation responses.
* @yields Function response events for tools that have been confirmed and
* are ready to resume.
*/
override async *runAsync(
invocationContext: InvocationContext,
): AsyncGenerator<Event, void, void> {
Expand Down
17 changes: 17 additions & 0 deletions core/src/context/summarizers/llm_summarizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ import {BaseLlm} from '../../models/base_llm.js';
import {LlmRequest} from '../../models/llm_request.js';
import {BaseSummarizer} from './base_summarizer.js';

/** Options for constructing an {@link LlmSummarizer}. */
export interface LlmSummarizerOptions {
/** The LLM instance used to generate the summary. */
llm: BaseLlm;
/**
* Optional system prompt prepended to the formatted events. Defaults to a
* built-in summarization prompt when omitted.
*/
prompt?: string;
}

Expand All @@ -32,11 +38,22 @@ export class LlmSummarizer implements BaseSummarizer {
private readonly llm: BaseLlm;
private readonly prompt: string;

/**
* @param options - Configuration specifying the LLM and optional prompt.
*/
constructor(options: LlmSummarizerOptions) {
this.llm = options.llm;
this.prompt = options.prompt || DEFAULT_PROMPT;
}

/**
* Summarizes a list of events into a single {@link CompactedEvent} using the
* configured LLM.
*
* @param events - The events to summarize. Must be non-empty.
* @returns A promise resolving to the compacted representation.
* @throws {Error} If `events` is empty or the LLM returns no content.
*/
async summarize(events: Event[]): Promise<CompactedEvent> {
if (events.length === 0) {
throw new Error('Cannot summarize an empty list of events.');
Expand Down
Loading
Loading