Skip to content

Commit 469092a

Browse files
fix(cli): provide JSON output for AgentExecutionStopped in non-interactive mode (#26504)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 80d2690 commit 469092a

3 files changed

Lines changed: 155 additions & 0 deletions

File tree

packages/cli/src/nonInteractiveCli.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,6 +2045,77 @@ describe('runNonInteractive', () => {
20452045
expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1);
20462046
});
20472047

2048+
it('should write JSON output when AgentExecutionStopped event occurs', async () => {
2049+
vi.mocked(mockConfig.getOutputFormat).mockReturnValue(OutputFormat.JSON);
2050+
vi.spyOn(uiTelemetryService, 'getMetrics').mockReturnValue(
2051+
MOCK_SESSION_METRICS,
2052+
);
2053+
2054+
const events: ServerGeminiStreamEvent[] = [
2055+
{ type: GeminiEventType.Content, value: 'Partial content' },
2056+
{
2057+
type: GeminiEventType.AgentExecutionStopped,
2058+
value: { reason: 'Stopped by hook' },
2059+
},
2060+
];
2061+
2062+
mockGeminiClient.sendMessageStream.mockReturnValue(
2063+
createStreamFromEvents(events),
2064+
);
2065+
2066+
await runNonInteractive({
2067+
config: mockConfig,
2068+
settings: mockSettings,
2069+
input: 'test stop',
2070+
prompt_id: 'prompt-id-stop-json',
2071+
});
2072+
2073+
expect(processStdoutSpy).toHaveBeenCalledWith(
2074+
JSON.stringify(
2075+
{
2076+
session_id: 'test-session-id',
2077+
response: 'Partial content',
2078+
stats: MOCK_SESSION_METRICS,
2079+
warnings: ['Agent execution stopped: Stopped by hook'],
2080+
},
2081+
null,
2082+
2,
2083+
),
2084+
);
2085+
});
2086+
2087+
it('should emit result event when AgentExecutionStopped event occurs in streaming JSON mode', async () => {
2088+
vi.mocked(mockConfig.getOutputFormat).mockReturnValue(
2089+
OutputFormat.STREAM_JSON,
2090+
);
2091+
vi.spyOn(uiTelemetryService, 'getMetrics').mockReturnValue(
2092+
MOCK_SESSION_METRICS,
2093+
);
2094+
2095+
const events: ServerGeminiStreamEvent[] = [
2096+
{ type: GeminiEventType.Content, value: 'Partial content' },
2097+
{
2098+
type: GeminiEventType.AgentExecutionStopped,
2099+
value: { reason: 'Stopped by hook' },
2100+
},
2101+
];
2102+
2103+
mockGeminiClient.sendMessageStream.mockReturnValue(
2104+
createStreamFromEvents(events),
2105+
);
2106+
2107+
await runNonInteractive({
2108+
config: mockConfig,
2109+
settings: mockSettings,
2110+
input: 'test stop',
2111+
prompt_id: 'prompt-id-stop-stream',
2112+
});
2113+
2114+
const output = getWrittenOutput();
2115+
expect(output).toContain('"type":"result"');
2116+
expect(output).toContain('"status":"success"');
2117+
});
2118+
20482119
it('should handle AgentExecutionBlocked event', async () => {
20492120
const allEvents: ServerGeminiStreamEvent[] = [
20502121
{

packages/cli/src/nonInteractiveCli.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,20 @@ export async function runNonInteractive(
400400
durationMs,
401401
),
402402
});
403+
} else if (config.getOutputFormat() === OutputFormat.JSON) {
404+
const formatter = new JsonFormatter();
405+
const stats = uiTelemetryService.getMetrics();
406+
textOutput.write(
407+
formatter.format(
408+
config.getSessionId(),
409+
responseText,
410+
stats,
411+
undefined,
412+
[...warnings, stopMessage],
413+
),
414+
);
415+
} else {
416+
textOutput.ensureTrailingNewline(); // Ensure a final newline
403417
}
404418
return;
405419
} else if (event.type === GeminiEventType.AgentExecutionBlocked) {

packages/cli/src/nonInteractiveCliAgentSession.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,6 +2208,76 @@ describe('runNonInteractive', () => {
22082208
expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1);
22092209
});
22102210

2211+
it('should write JSON output when AgentExecutionStopped event occurs', async () => {
2212+
vi.mocked(mockConfig.getOutputFormat).mockReturnValue(OutputFormat.JSON);
2213+
vi.spyOn(uiTelemetryService, 'getMetrics').mockReturnValue(
2214+
MOCK_SESSION_METRICS,
2215+
);
2216+
2217+
const events: ServerGeminiStreamEvent[] = [
2218+
{ type: GeminiEventType.Content, value: 'Partial content' },
2219+
{
2220+
type: GeminiEventType.AgentExecutionStopped,
2221+
value: { reason: 'Stopped by hook' },
2222+
},
2223+
];
2224+
2225+
mockGeminiClient.sendMessageStream.mockReturnValue(
2226+
createStreamFromEvents(events),
2227+
);
2228+
2229+
await runNonInteractive({
2230+
config: mockConfig,
2231+
settings: mockSettings,
2232+
input: 'test stop',
2233+
prompt_id: 'prompt-id-stop-json',
2234+
});
2235+
2236+
expect(processStdoutSpy).toHaveBeenCalledWith(
2237+
JSON.stringify(
2238+
{
2239+
session_id: 'test-session-id',
2240+
response: 'Partial content',
2241+
stats: MOCK_SESSION_METRICS,
2242+
},
2243+
null,
2244+
2,
2245+
),
2246+
);
2247+
});
2248+
2249+
it('should emit result event when AgentExecutionStopped event occurs in streaming JSON mode', async () => {
2250+
vi.mocked(mockConfig.getOutputFormat).mockReturnValue(
2251+
OutputFormat.STREAM_JSON,
2252+
);
2253+
vi.spyOn(uiTelemetryService, 'getMetrics').mockReturnValue(
2254+
MOCK_SESSION_METRICS,
2255+
);
2256+
2257+
const events: ServerGeminiStreamEvent[] = [
2258+
{ type: GeminiEventType.Content, value: 'Partial content' },
2259+
{
2260+
type: GeminiEventType.AgentExecutionStopped,
2261+
value: { reason: 'Stopped by hook' },
2262+
},
2263+
];
2264+
2265+
mockGeminiClient.sendMessageStream.mockReturnValue(
2266+
createStreamFromEvents(events),
2267+
);
2268+
2269+
await runNonInteractive({
2270+
config: mockConfig,
2271+
settings: mockSettings,
2272+
input: 'test stop',
2273+
prompt_id: 'prompt-id-stop-stream',
2274+
});
2275+
2276+
const output = getWrittenOutput();
2277+
expect(output).toContain('"type":"result"');
2278+
expect(output).toContain('"status":"success"');
2279+
});
2280+
22112281
it('should handle AgentExecutionBlocked event', async () => {
22122282
const allEvents: ServerGeminiStreamEvent[] = [
22132283
{

0 commit comments

Comments
 (0)