refactor(cli): migrate core tools to native ToolDisplay property and fix UI rendering (#25186)

This commit is contained in:
Michael Bleigh
2026-05-06 14:23:26 -07:00
committed by GitHub
parent 4a10751b49
commit 90304b279c
33 changed files with 1033 additions and 57 deletions
+10 -8
View File
@@ -236,6 +236,7 @@ export function translateEvent(
requestId: event.value.callId,
name: event.value.name,
args: event.value.args,
display: event.value.display,
}),
);
break;
@@ -243,13 +244,15 @@ export function translateEvent(
case GeminiEventType.ToolCallResponse: {
ensureStreamStart(state, out);
const data = buildToolResponseData(event.value);
const display: ToolDisplay | undefined = event.value.resultDisplay
? {
result: toolResultDisplayToDisplayContent(
event.value.resultDisplay,
),
}
: undefined;
const display: ToolDisplay | undefined =
event.value.display ??
(event.value.resultDisplay
? {
result: toolResultDisplayToDisplayContent(
event.value.resultDisplay,
),
}
: undefined);
out.push(
makeEvent('tool_response', state, {
requestId: event.value.callId,
@@ -279,7 +282,6 @@ export function translateEvent(
((x: never) => {
throw new Error(`Unhandled event type: ${JSON.stringify(x)}`);
})(event);
break;
}
return out;
@@ -102,7 +102,10 @@ function makeCompletedToolCall(
response: {
callId,
responseParts: [{ text: responseText }],
resultDisplay: undefined,
resultDisplay: responseText,
display: {
result: { type: 'text', text: responseText },
},
error: undefined,
errorType: undefined,
},
@@ -426,6 +429,12 @@ describe('LegacyAgentSession', () => {
(e): e is AgentEvent<'tool_response'> => e.type === 'tool_response',
);
expect(toolResp?.name).toBe('read_file');
expect(toolResp?.display).toEqual(
expect.objectContaining({
name: 'read_file',
result: { type: 'text', text: 'file contents' },
}),
);
expect(toolResp?.content).toEqual([
{ type: 'text', text: 'file contents' },
]);
@@ -266,6 +266,7 @@ export class LegacyAgentProtocol implements AgentProtocol {
invocation: 'invocation' in tc ? tc.invocation : undefined,
resultDisplay: response.resultDisplay,
displayName: 'tool' in tc ? tc.tool?.displayName : undefined,
display: response.display,
});
const data = buildToolResponseData(response);
@@ -21,18 +21,21 @@ export function populateToolDisplay({
invocation,
resultDisplay,
displayName,
display: prevDisplay,
}: {
name: string;
invocation?: ToolInvocation<object, ToolResult>;
resultDisplay?: ToolResultDisplay;
displayName?: string;
display?: ToolDisplay;
}): ToolDisplay {
const display: ToolDisplay = {
name: displayName || name,
description: invocation?.getDescription?.(),
...prevDisplay,
};
if (resultDisplay) {
if (resultDisplay !== undefined && display.result === undefined) {
display.result = toolResultDisplayToDisplayContent(resultDisplay);
}
@@ -91,7 +94,7 @@ export function renderDisplayDiff(diff: DisplayDiff): string {
* Useful for fallback displays or non-interactive environments.
*/
export function displayContentToString(
display: DisplayContent | undefined,
display: DisplayContent | undefined | null,
): string | undefined {
if (!display) {
return undefined;
+39 -3
View File
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type { AnsiOutput } from '../utils/terminalSerializer.js';
import type { Kind } from '../tools/tools.js';
export type WithMeta = { _meta?: Record<string, unknown> };
@@ -182,13 +183,48 @@ export type DisplayDiff = {
beforeText: string;
afterText: string;
};
export type DisplayContent = DisplayText | DisplayDiff;
export type DisplayTerminal = {
type: 'terminal';
pid?: string;
exitCode?: number;
ansi?: AnsiOutput;
};
export type DisplayAgent = {
type: 'agent';
threadId: string;
};
export type DisplayContent =
| DisplayText
| DisplayDiff
| DisplayTerminal
| DisplayAgent;
export type ToolDisplayFormat =
/**
* Displays as compact when user has enabled compact tools, box otherwise.
* This is the default format if none is selected.
**/
| 'auto'
/** Always display this tool in compact format. */
| 'compact'
/** Always display this tool in full box format. */
| 'box'
/** Hide this tool from the event history. */
| 'hidden'
/** Display this tool as a message-like notice. */
| 'notice';
export interface ToolDisplay {
/** A display name for the tool. */
name?: string;
/** A short description of what the tool is doing. */
description?: string;
resultSummary?: string;
result?: DisplayContent;
/** A short, one-line summary of the tool's results. */
resultSummary?: string | null;
result?: DisplayContent | null;
/** A tool may specify its preferred display format. */
format?: ToolDisplayFormat;
}
export interface ToolRequest {