From 6c9f6444178e64d128c13f913188d3fff6a4a6a6 Mon Sep 17 00:00:00 2001 From: Adam Weidman Date: Fri, 20 Mar 2026 10:58:58 -0400 Subject: [PATCH] refactor(core): treat max session turns as stream end --- .../core/src/agent/event-translator.test.ts | 38 +++++++++++++++---- packages/core/src/agent/event-translator.ts | 15 +++++--- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/packages/core/src/agent/event-translator.test.ts b/packages/core/src/agent/event-translator.test.ts index 596bc23f6b..a7a6a50711 100644 --- a/packages/core/src/agent/event-translator.test.ts +++ b/packages/core/src/agent/event-translator.test.ts @@ -181,7 +181,13 @@ describe('translateEvent', () => { it('stringifies object resultDisplay correctly', () => { state.streamStartEmitted = true; state.pendingToolNames.set('call-3', 'diff_tool'); - const objectDisplay = { type: 'FileDiff', before: 'a', after: 'b' }; + const objectDisplay = { + fileDiff: '@@ -1 +1 @@\n-a\n+b', + fileName: 'test.txt', + filePath: '/tmp/test.txt', + originalContent: 'a', + newContent: 'b', + }; const event: ServerGeminiStreamEvent = { type: GeminiEventType.ToolCallResponse, value: { @@ -402,19 +408,17 @@ describe('translateEvent', () => { }); describe('MaxSessionTurns events', () => { - it('emits a non-fatal max-turns error event', () => { + it('emits stream_end with max_turns', () => { state.streamStartEmitted = true; const event: ServerGeminiStreamEvent = { type: GeminiEventType.MaxSessionTurns, }; const result = translateEvent(event, state); expect(result).toHaveLength(1); - const err = result[0] as AgentEvent<'error'>; - expect(err.type).toBe('error'); - expect(err.fatal).toBe(false); - expect(err.status).toBe('RESOURCE_EXHAUSTED'); - expect(err._meta?.['code']).toBe('MAX_TURNS_EXCEEDED'); - expect(err.message).toBe('Maximum session turns exceeded'); + const streamEnd = result[0] as AgentEvent<'stream_end'>; + expect(streamEnd.type).toBe('stream_end'); + expect(streamEnd.reason).toBe('max_turns'); + expect(streamEnd.data).toEqual({ code: 'MAX_TURNS_EXCEEDED' }); }); }); @@ -628,6 +632,24 @@ describe('mapFinishReason', () => { it('maps PROHIBITED_CONTENT to refusal', () => { expect(mapFinishReason(FinishReason.PROHIBITED_CONTENT)).toBe('refusal'); }); + + it('maps IMAGE_SAFETY to refusal', () => { + expect(mapFinishReason(FinishReason.IMAGE_SAFETY)).toBe('refusal'); + }); + + it('maps IMAGE_PROHIBITED_CONTENT to refusal', () => { + expect(mapFinishReason(FinishReason.IMAGE_PROHIBITED_CONTENT)).toBe( + 'refusal', + ); + }); + + it('maps UNEXPECTED_TOOL_CALL to failed', () => { + expect(mapFinishReason(FinishReason.UNEXPECTED_TOOL_CALL)).toBe('failed'); + }); + + it('maps NO_IMAGE to failed', () => { + expect(mapFinishReason(FinishReason.NO_IMAGE)).toBe('failed'); + }); }); describe('mapHttpToGrpcStatus', () => { diff --git a/packages/core/src/agent/event-translator.ts b/packages/core/src/agent/event-translator.ts index 7f0381eb38..63307c1685 100644 --- a/packages/core/src/agent/event-translator.ts +++ b/packages/core/src/agent/event-translator.ts @@ -157,11 +157,11 @@ export function translateEvent( case GeminiEventType.MaxSessionTurns: ensureStreamStart(state, out); out.push( - makeEvent('error', state, { - status: 'RESOURCE_EXHAUSTED', - message: 'Maximum session turns exceeded', - fatal: false, - _meta: { code: 'MAX_TURNS_EXCEEDED' }, + makeEvent('stream_end', state, { + reason: 'max_turns', + data: { + code: 'MAX_TURNS_EXCEEDED', + }, }), ); break; @@ -338,12 +338,15 @@ export function mapFinishReason( case 'BLOCKLIST': case 'PROHIBITED_CONTENT': case 'SPII': + case 'IMAGE_SAFETY': + case 'IMAGE_PROHIBITED_CONTENT': return 'refusal'; case 'MALFORMED_FUNCTION_CALL': case 'OTHER': + case 'UNEXPECTED_TOOL_CALL': + case 'NO_IMAGE': return 'failed'; default: - ((_x: never) => {})(reason); return 'failed'; } }