Skip to content

Commit 91494b9

Browse files
committed
added streaming render of dynamic tool call generation in subagent terminal UI
1 parent ccbad47 commit 91494b9

7 files changed

Lines changed: 60 additions & 3 deletions

File tree

packages/cli/src/ui/components/messages/SubagentHistoryDisplay.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import type {
1212
SubagentToolResponseHistoryItem,
1313
SubagentThoughtHistoryItem,
1414
SubagentToolSummaryHistoryItem,
15+
DynamicToolCallChunkHistoryItem,
1516
} from '../../types.js';
1617
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
1718
import { SubagentToolCallDisplay } from './SubagentToolCallDisplay.js';
18-
import { debugLogger } from '@google/gemini-cli-core';
19+
// import { debugLogger } from '@google/gemini-cli-core';
1920

2021
interface SubagentHistoryDisplayProps {
2122
history: SubagentHistoryItem[];
@@ -27,6 +28,7 @@ type SubagentTurn = {
2728
toolCall?: SubagentToolCallHistoryItem;
2829
toolResponse?: SubagentToolResponseHistoryItem;
2930
toolSummary?: SubagentToolSummaryHistoryItem;
31+
dynamicToolCall?: DynamicToolCallChunkHistoryItem;
3032
subagentHistory?: SubagentHistoryItem[];
3133
};
3234

@@ -93,6 +95,13 @@ export const SubagentHistoryDisplay: React.FC<SubagentHistoryDisplayProps> = ({
9395
lastTurn.thought = item;
9496
}
9597
break;
98+
case 'dynamic_tool_call_chunk':
99+
// debugLogger.log('overriding dynamic tool call chunk');
100+
lastTurn.dynamicToolCall = {
101+
type: 'dynamic_tool_call_chunk',
102+
data: { chunk: item.data.chunk },
103+
};
104+
break;
96105
case 'tool_call':
97106
if (lastTurn.toolCall) {
98107
acc.push({ toolCall: item });
@@ -108,7 +117,7 @@ export const SubagentHistoryDisplay: React.FC<SubagentHistoryDisplayProps> = ({
108117
break;
109118
case 'tool_summary_chunk':
110119
// Overwrite the previous toolSummary with the new chunk, enabling streaming.
111-
debugLogger.log('overriding tool summary chunk');
120+
// debugLogger.log('overriding tool summary chunk');
112121
lastTurn.toolSummary = {
113122
type: 'tool_summary',
114123
data: { summary: item.data.summary },
@@ -195,6 +204,20 @@ export const SubagentHistoryDisplay: React.FC<SubagentHistoryDisplayProps> = ({
195204
/>
196205
</Box>
197206
)}
207+
{turn.dynamicToolCall && (
208+
<Box
209+
flexDirection="column"
210+
marginBottom={1}
211+
width={availableWidth}
212+
borderStyle="single"
213+
borderColor="green"
214+
paddingX={1}
215+
paddingY={0}
216+
>
217+
<Text color="green">Generating Tool Call:</Text>
218+
<Text>{turn.dynamicToolCall.data.chunk}</Text>
219+
</Box>
220+
)}
198221
{turn.toolCall && (
199222
<Box width={availableWidth}>
200223
<SubagentToolCallDisplay

packages/cli/src/ui/hooks/useReactToolScheduler.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ export function useReactToolScheduler(
110110
'GEMINI_SUBAGENT_START::': 'start',
111111
'GEMINI_SUBAGENT_THOUGHT::': 'thought',
112112
'GEMINI_SUBAGENT_TOOL_CALL::': 'tool_call',
113+
'GEMINI_SUBAGENT_DYNAMIC_TOOL_CALL_CHUNK::':
114+
'dynamic_tool_call_chunk',
113115
'GEMINI_SUBAGENT_TOOL_RESPONSE::': 'tool_response',
114116
'GEMINI_SUBAGENT_TOOL_SUMMARY::': 'tool_summary',
115117
'GEMINI_SUBAGENT_TOOL_SUMMARY_CHUNK::': 'tool_summary_chunk',
@@ -160,6 +162,15 @@ export function useReactToolScheduler(
160162
...currentHistory.slice(0, -1),
161163
newHistoryItem,
162164
];
165+
} else if (
166+
newHistoryItem.type === 'dynamic_tool_call_chunk' &&
167+
lastHistoryItem?.type === 'dynamic_tool_call_chunk'
168+
) {
169+
// Replace the last tool call chunk with the new one
170+
newHistory = [
171+
...currentHistory.slice(0, -1),
172+
newHistoryItem,
173+
];
163174
} else if (
164175
(newHistoryItem.type === 'tool_summary_chunk' ||
165176
newHistoryItem.type === 'tool_summary') &&

packages/cli/src/ui/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,8 @@ export type SubagentHistoryItem =
406406
| { type: 'interrupted'; data: { message: string } }
407407
| { type: 'user_message'; data: { message: string } }
408408
| SubagentToolSummaryHistoryItem
409-
| SubagentToolSummaryChunkHistoryItem;
409+
| SubagentToolSummaryChunkHistoryItem
410+
| DynamicToolCallChunkHistoryItem;
410411

411412
export type SubagentToolSummaryChunkHistoryItem = {
412413
type: 'tool_summary_chunk';
@@ -418,6 +419,11 @@ export type SubagentToolSummaryHistoryItem = {
418419
data: { summary: string };
419420
};
420421

422+
export type DynamicToolCallChunkHistoryItem = {
423+
type: 'dynamic_tool_call_chunk';
424+
data: { chunk: string };
425+
};
426+
421427
export type SubagentThoughtHistoryItem = Extract<
422428
SubagentHistoryItem,
423429
{ type: 'thought' }

packages/core/src/agents/executor.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,11 @@ export class AgentExecutor<TOutput extends z.ZodTypeAny> {
10761076
functionCall.name,
10771077
goal,
10781078
this.definition.modelConfig,
1079+
(chunk) => {
1080+
this.emitActivity('DYNAMIC_TOOL_CALL_CHUNK', {
1081+
chunk,
1082+
});
1083+
},
10791084
);
10801085
// Preserve the original goal in the new function call's arguments for summarization.
10811086
if (goal) {

packages/core/src/agents/invocation.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ export class SubagentInvocation<
136136
)}`,
137137
);
138138
break;
139+
case 'DYNAMIC_TOOL_CALL_CHUNK':
140+
updateOutput(
141+
`GEMINI_SUBAGENT_DYNAMIC_TOOL_CALL_CHUNK::${JSON.stringify(
142+
activity.data,
143+
)}`,
144+
);
145+
break;
139146
case 'ERROR':
140147
updateOutput(
141148
`GEMINI_SUBAGENT_ERROR::${JSON.stringify(activity.data)}`,

packages/core/src/agents/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface SubagentActivityEvent {
4646
agentName: string;
4747
type:
4848
| 'TOOL_CALL_START'
49+
| 'DYNAMIC_TOOL_CALL_CHUNK'
4950
| 'TOOL_CALL_END'
5051
| 'THOUGHT_CHUNK'
5152
| 'ERROR'

packages/core/src/services/toolCallService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export class ToolCallService {
8484
toolName: string,
8585
goal: string,
8686
modelConfig: ModelConfig | OllamaModelConfig,
87+
onChunk?: (chunk: string) => void,
8788
): Promise<FunctionCall> {
8889
const tool = this.toolRegistry.getTool(toolName);
8990
if (!tool) {
@@ -144,6 +145,9 @@ Respond with only the JSON for the tool call. Do not include any other text or m
144145

145146
if (text) {
146147
textResponse = text;
148+
if (onChunk) {
149+
onChunk(text);
150+
}
147151
}
148152
}
149153
}

0 commit comments

Comments
 (0)