mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-20 15:00:54 -07:00
feat(cli): refactor tool rendering to declarative ToolDisplay system
This change completes the transition of the interactive agent session (`useAgentStream`) to a declarative-first tool rendering system. Key changes: - Reverted experimental `ToolDisplay` logic from legacy UI components (`DenseToolMessage`, etc.) to establish a clean baseline. - Introduced `HistoryItemToolDisplayGroup` and `ToolGroupDisplay` component in CLI. - Added `display` property to `ToolCallRequestInfo` to carry declarative UI info natively. - Populated tool request display information at the source (`Turn.ts` and `Scheduler.ts`) using dynamic descriptions from tool invocations. - Updated `useAgentStream` to emit the new history item type, providing a standalone rendering path for interactive sessions. - Ensured tool descriptions are updated when arguments are modified during confirmation.
This commit is contained in:
@@ -236,6 +236,7 @@ export function translateEvent(
|
||||
requestId: event.value.callId,
|
||||
name: event.value.name,
|
||||
args: event.value.args,
|
||||
display: event.value.display,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
@@ -281,7 +282,6 @@ export function translateEvent(
|
||||
((x: never) => {
|
||||
throw new Error(`Unhandled event type: ${JSON.stringify(x)}`);
|
||||
})(event);
|
||||
break;
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
@@ -264,6 +264,10 @@ export class GeminiChat {
|
||||
);
|
||||
}
|
||||
|
||||
get loopContext(): AgentLoopContext {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
async initialize(
|
||||
resumedSessionData?: ResumedSessionData,
|
||||
kind: 'main' | 'subagent' = 'main',
|
||||
|
||||
@@ -29,6 +29,7 @@ import { parseThought, type ThoughtSummary } from '../utils/thoughtUtils.js';
|
||||
import type { ModelConfigKey } from '../services/modelConfigService.js';
|
||||
import { getCitations } from '../utils/generateContentResponseUtilities.js';
|
||||
import { LlmRole } from '../telemetry/types.js';
|
||||
import { populateToolDisplay } from '../agent/tool-display-utils.js';
|
||||
|
||||
import {
|
||||
type ToolCallRequestInfo,
|
||||
@@ -408,13 +409,36 @@ export class Turn {
|
||||
traceId?: string,
|
||||
): ServerGeminiStreamEvent | null {
|
||||
const name = fnCall.name || 'undefined_tool_name';
|
||||
const args = fnCall.args || {};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const args = (fnCall.args as Record<string, unknown>) || {};
|
||||
const callId = fnCall.id ?? `${name}_${Date.now()}_${this.callCounter++}`;
|
||||
|
||||
const tool = this.chat.loopContext.toolRegistry.getTool(name);
|
||||
let display;
|
||||
if (tool) {
|
||||
let invocation;
|
||||
try {
|
||||
invocation = tool.build(args);
|
||||
} catch {
|
||||
// Ignore build errors for request display purposes
|
||||
}
|
||||
display = populateToolDisplay({
|
||||
name,
|
||||
invocation,
|
||||
displayName: tool.displayName,
|
||||
});
|
||||
|
||||
// Fallback to static description if invocation failed or didn't provide one
|
||||
if (!display.description) {
|
||||
display.description = tool.description;
|
||||
}
|
||||
}
|
||||
|
||||
const toolCallRequest: ToolCallRequestInfo = {
|
||||
callId,
|
||||
name,
|
||||
args,
|
||||
display,
|
||||
isClientInitiated: false,
|
||||
prompt_id: this.prompt_id,
|
||||
traceId,
|
||||
|
||||
@@ -36,6 +36,7 @@ import { getToolSuggestion } from '../utils/tool-utils.js';
|
||||
import { runInDevTraceSpan } from '../telemetry/trace.js';
|
||||
import { logToolCall } from '../telemetry/loggers.js';
|
||||
import { ToolCallEvent } from '../telemetry/types.js';
|
||||
import { populateToolDisplay } from '../agent/tool-display-utils.js';
|
||||
import type { EditorType } from '../utils/editor.js';
|
||||
import {
|
||||
MessageBusType,
|
||||
@@ -380,6 +381,16 @@ export class Scheduler {
|
||||
() => {
|
||||
try {
|
||||
const invocation = tool.build(request.args);
|
||||
if (!request.display) {
|
||||
request.display = populateToolDisplay({
|
||||
name: tool.name,
|
||||
invocation,
|
||||
displayName: tool.displayName,
|
||||
});
|
||||
if (!request.display.description) {
|
||||
request.display.description = tool.description;
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: CoreToolCallStatus.Validating,
|
||||
request,
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
ToolConfirmationOutcome,
|
||||
ToolResultDisplay,
|
||||
AnyToolInvocation,
|
||||
ToolDisplay,
|
||||
ToolCallConfirmationDetails,
|
||||
AnyDeclarativeTool,
|
||||
} from '../tools/tools.js';
|
||||
@@ -172,10 +173,15 @@ export class SchedulerStateManager {
|
||||
const call = this.activeCalls.get(callId);
|
||||
if (!call || call.status === CoreToolCallStatus.Error) return;
|
||||
|
||||
const display: ToolDisplay = call.request.display
|
||||
? { ...call.request.display }
|
||||
: { name: call.request.name };
|
||||
display.description = newInvocation.getDescription();
|
||||
|
||||
this.activeCalls.set(
|
||||
callId,
|
||||
this.patchCall(call, {
|
||||
request: { ...call.request, args: newArgs },
|
||||
request: { ...call.request, args: newArgs, display },
|
||||
invocation: newInvocation,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -37,6 +37,8 @@ export interface ToolCallRequestInfo {
|
||||
callId: string;
|
||||
name: string;
|
||||
args: Record<string, unknown>;
|
||||
/** Tool-controlled display information. */
|
||||
display?: ToolDisplay;
|
||||
/**
|
||||
* The original name and arguments of the tool requested by the model.
|
||||
* This is used for tail calls to ensure the final response and log retains
|
||||
|
||||
Reference in New Issue
Block a user