diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 777391f0ca..bfed46fe98 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -21,6 +21,7 @@ import { FatalInputError, CoreEvent, CoreToolCallStatus, + debugLogger, } from '@google/gemini-cli-core'; import type { Part } from '@google/genai'; import { runNonInteractive } from './nonInteractiveCli.js'; @@ -1703,6 +1704,9 @@ describe('runNonInteractive', () => { ); it('should log error when tool recording fails', async () => { + const errorSpy = vi + .spyOn(debugLogger, 'error') + .mockImplementation(() => {}); const toolCallEvent: ServerGeminiStreamEvent = { type: GeminiEventType.ToolCallRequest, value: { @@ -1769,7 +1773,12 @@ describe('runNonInteractive', () => { // The LegacyAgentSession silently catches recording failures // (they shouldn't break the loop). Verify the loop continued - // and produced output. + // and logged the error. + expect(errorSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'Error recording completed tool call information', + ), + ); expect(getWrittenOutput()).toContain('Done'); }); diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index f903fff681..8056896015 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -27,6 +27,7 @@ import { Scheduler, ROOT_SCHEDULER_ID, LegacyAgentSession, + ToolErrorType, } from '@google/gemini-cli-core'; import type { Part } from '@google/genai'; @@ -381,7 +382,10 @@ export async function runNonInteractive({ output: displayText, error: event.isError ? { - type: 'TOOL_EXECUTION_ERROR', + type: + typeof event.data?.['errorType'] === 'string' + ? event.data['errorType'] + : 'TOOL_EXECUTION_ERROR', message: errorMsg, } : undefined, @@ -400,10 +404,21 @@ export async function runNonInteractive({ event.name, new Error(errorMsg), config, - undefined, + typeof event.data?.['errorType'] === 'string' + ? event.data['errorType'] + : undefined, displayText, ); } + if ( + event.isError && + event.data?.['errorType'] === ToolErrorType.STOP_EXECUTION + ) { + const stopMessage = `Agent execution stopped: ${errorMsg}`; + if (config.getOutputFormat() === OutputFormat.TEXT) { + process.stderr.write(`${stopMessage}\n`); + } + } break; } case 'error': { diff --git a/packages/core/src/agent/legacy-agent-session.ts b/packages/core/src/agent/legacy-agent-session.ts index 903dc36487..2fb1e6378f 100644 --- a/packages/core/src/agent/legacy-agent-session.ts +++ b/packages/core/src/agent/legacy-agent-session.ts @@ -16,6 +16,8 @@ import type { Scheduler } from '../scheduler/scheduler.js'; import type { Config } from '../config/config.js'; import type { ToolCallRequestInfo } from '../scheduler/types.js'; import { ToolErrorType } from '../tools/tool-error.js'; +import { recordToolCallInteractions } from '../code_assist/telemetry.js'; +import { debugLogger } from '../utils/debugLogger.js'; import { translateEvent, createTranslationState, @@ -261,7 +263,16 @@ export class LegacyAgentSession implements AgentSession { ], } : {}), - ...(response.data ? { data: response.data } : {}), + ...(response.data || response.errorType + ? { + data: { + ...(response.data || {}), + ...(response.errorType + ? { errorType: response.errorType } + : {}), + }, + } + : {}), }), ]); @@ -277,8 +288,12 @@ export class LegacyAgentSession implements AgentSession { this._client .getChat() .recordCompletedToolCalls(currentModel, completedToolCalls); - } catch { - // Recording failures shouldn't break the loop + + await recordToolCallInteractions(this._config, completedToolCalls); + } catch (error) { + debugLogger.error( + `Error recording completed tool call information: ${error}`, + ); } // Check if a tool requested stop execution diff --git a/packages/core/src/agent/mock.ts b/packages/core/src/agent/mock.ts index 7baeb61a83..2d7d8c716c 100644 --- a/packages/core/src/agent/mock.ts +++ b/packages/core/src/agent/mock.ts @@ -74,6 +74,7 @@ export class MockAgentSession implements AgentSession { const now = new Date().toISOString(); for (const eventData of events) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion const event: AgentEvent = { ...eventData, id: eventData.id ?? `e-${this._nextEventId++}`, @@ -172,6 +173,7 @@ export class MockAgentSession implements AgentSession { const normalizedResponse: AgentEvent[] = []; for (const eventData of response) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion const event: AgentEvent = { ...eventData, id: eventData.id ?? `e-${this._nextEventId++}`,