From 1067df187200e79fdc4a5db598de8395d35d1d8d Mon Sep 17 00:00:00 2001 From: Keith Schaab Date: Tue, 30 Sep 2025 08:26:51 -0700 Subject: [PATCH] Fix: A2A server - add liveOutput and response resultsDisplay to the serialized tool call result (closes #9520) (#9788) Co-authored-by: anthony bushong Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- packages/a2a-server/src/agent/task.ts | 80 ++++++++++++++++----------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/packages/a2a-server/src/agent/task.ts b/packages/a2a-server/src/agent/task.ts index b75205fa69..18391d793e 100644 --- a/packages/a2a-server/src/agent/task.ts +++ b/packages/a2a-server/src/agent/task.ts @@ -15,18 +15,17 @@ import { isNodeError, parseAndFormatApiError, safeLiteralReplace, -} from '@google/gemini-cli-core'; -import type { - ToolConfirmationPayload, - CompletedToolCall, - ToolCall, - ToolCallRequestInfo, - ServerGeminiErrorEvent, - ServerGeminiStreamEvent, - ToolCallConfirmationDetails, - Config, - UserTierId, - AnsiOutput, + type AnyDeclarativeTool, + type ToolCall, + type ToolConfirmationPayload, + type CompletedToolCall, + type ToolCallRequestInfo, + type ServerGeminiErrorEvent, + type ServerGeminiStreamEvent, + type ToolCallConfirmationDetails, + type Config, + type UserTierId, + type AnsiOutput, } from '@google/gemini-cli-core'; import type { RequestContext } from '@a2a-js/sdk/server'; import { type ExecutionEventBus } from '@a2a-js/sdk/server'; @@ -54,6 +53,8 @@ import type { } from '../types.js'; import type { PartUnion, Part as genAiPart } from '@google/genai'; +type UnionKeys = T extends T ? keyof T : never; + export class Task { id: string; contextId: string; @@ -436,6 +437,19 @@ export class Task { return scheduler; } + private _pickFields< + T extends ToolCall | AnyDeclarativeTool, + K extends UnionKeys, + >(from: T, ...fields: K[]): Partial { + const ret = {} as Pick; + for (const field of fields) { + if (field in from) { + ret[field] = from[field]; + } + } + return ret as Partial; + } + private toolStatusMessage( tc: ToolCall, taskId: string, @@ -444,33 +458,33 @@ export class Task { const messageParts: Part[] = []; // Create a serializable version of the ToolCall (pick necesssary - // properties/avoic methods causing circular reference errors) - const serializableToolCall: { [key: string]: unknown } = { - request: tc.request, - status: tc.status, - }; - - // For WaitingToolCall type - if ('confirmationDetails' in tc) { - serializableToolCall['confirmationDetails'] = tc.confirmationDetails; - } + // properties/avoid methods causing circular reference errors) + const serializableToolCall: Partial = this._pickFields( + tc, + 'request', + 'status', + 'confirmationDetails', + 'liveOutput', + 'response', + ); if (tc.tool) { - serializableToolCall['tool'] = { - name: tc.tool.name, - displayName: tc.tool.displayName, - description: tc.tool.description, - kind: tc.tool.kind, - isOutputMarkdown: tc.tool.isOutputMarkdown, - canUpdateOutput: tc.tool.canUpdateOutput, - schema: tc.tool.schema, - parameterSchema: tc.tool.parameterSchema, - }; + serializableToolCall.tool = this._pickFields( + tc.tool, + 'name', + 'displayName', + 'description', + 'kind', + 'isOutputMarkdown', + 'canUpdateOutput', + 'schema', + 'parameterSchema', + ) as AnyDeclarativeTool; } messageParts.push({ kind: 'data', - data: serializableToolCall as ToolCall, + data: serializableToolCall, } as Part); return {