diff --git a/packages/core/src/agent/event-translator.test.ts b/packages/core/src/agent/event-translator.test.ts index a7a6a50711..6d70dae294 100644 --- a/packages/core/src/agent/event-translator.test.ts +++ b/packages/core/src/agent/event-translator.test.ts @@ -44,21 +44,21 @@ describe('translateEvent', () => { }); describe('Content events', () => { - it('emits stream_start + message for first content event', () => { + it('emits agent_start + message for first content event', () => { const event: ServerGeminiStreamEvent = { type: GeminiEventType.Content, value: 'Hello world', }; const result = translateEvent(event, state); expect(result).toHaveLength(2); - expect(result[0]?.type).toBe('stream_start'); + expect(result[0]?.type).toBe('agent_start'); expect(result[1]?.type).toBe('message'); const msg = result[1] as AgentEvent<'message'>; expect(msg.role).toBe('agent'); expect(msg.content).toEqual([{ type: 'text', text: 'Hello world' }]); }); - it('skips stream_start for subsequent content events', () => { + it('skips agent_start for subsequent content events', () => { state.streamStartEmitted = true; const event: ServerGeminiStreamEvent = { type: GeminiEventType.Content, @@ -301,14 +301,14 @@ describe('translateEvent', () => { }); describe('ModelInfo events', () => { - it('emits stream_start and session_update when no stream started yet', () => { + it('emits agent_start and session_update when no stream started yet', () => { const event: ServerGeminiStreamEvent = { type: GeminiEventType.ModelInfo, value: 'gemini-2.5-pro', }; const result = translateEvent(event, state); expect(result).toHaveLength(2); - expect(result[0]?.type).toBe('stream_start'); + expect(result[0]?.type).toBe('agent_start'); expect(result[1]?.type).toBe('session_update'); const sessionUpdate = result[1] as AgentEvent<'session_update'>; expect(sessionUpdate.model).toBe('gemini-2.5-pro'); @@ -329,7 +329,7 @@ describe('translateEvent', () => { }); describe('AgentExecutionStopped events', () => { - it('emits stream_end with the final stop message in data.message', () => { + it('emits agent_end with the final stop message in data.message', () => { state.streamStartEmitted = true; const event: ServerGeminiStreamEvent = { type: GeminiEventType.AgentExecutionStopped, @@ -341,8 +341,8 @@ describe('translateEvent', () => { }; const result = translateEvent(event, state); expect(result).toHaveLength(1); - const streamEnd = result[0] as AgentEvent<'stream_end'>; - expect(streamEnd.type).toBe('stream_end'); + const streamEnd = result[0] as AgentEvent<'agent_end'>; + expect(streamEnd.type).toBe('agent_end'); expect(streamEnd.reason).toBe('completed'); expect(streamEnd.data).toEqual({ message: 'Stopped by hook' }); }); @@ -355,7 +355,7 @@ describe('translateEvent', () => { }; const result = translateEvent(event, state); expect(result).toHaveLength(1); - const streamEnd = result[0] as AgentEvent<'stream_end'>; + const streamEnd = result[0] as AgentEvent<'agent_end'>; expect(streamEnd.data).toEqual({ message: 'hook' }); }); }); @@ -408,22 +408,22 @@ describe('translateEvent', () => { }); describe('MaxSessionTurns events', () => { - it('emits stream_end with max_turns', () => { + it('emits agent_end with max_turns', () => { state.streamStartEmitted = true; const event: ServerGeminiStreamEvent = { type: GeminiEventType.MaxSessionTurns, }; const result = translateEvent(event, state); expect(result).toHaveLength(1); - const streamEnd = result[0] as AgentEvent<'stream_end'>; - expect(streamEnd.type).toBe('stream_end'); + const streamEnd = result[0] as AgentEvent<'agent_end'>; + expect(streamEnd.type).toBe('agent_end'); expect(streamEnd.reason).toBe('max_turns'); expect(streamEnd.data).toEqual({ code: 'MAX_TURNS_EXCEEDED' }); }); }); describe('Finished events', () => { - it('emits usage + stream_end for STOP', () => { + it('emits usage for STOP', () => { state.streamStartEmitted = true; state.model = 'gemini-2.5-pro'; const event: ServerGeminiStreamEvent = { @@ -438,27 +438,23 @@ describe('translateEvent', () => { }, }; const result = translateEvent(event, state); - expect(result).toHaveLength(2); + expect(result).toHaveLength(1); const usage = result[0] as AgentEvent<'usage'>; expect(usage.model).toBe('gemini-2.5-pro'); expect(usage.inputTokens).toBe(100); expect(usage.outputTokens).toBe(50); expect(usage.cachedTokens).toBe(10); - - const end = result[1] as AgentEvent<'stream_end'>; - expect(end.reason).toBe('completed'); }); - it('emits stream_end without usage when no metadata', () => { + it('emits nothing when no usage metadata is present', () => { state.streamStartEmitted = true; const event: ServerGeminiStreamEvent = { type: GeminiEventType.Finished, value: { reason: undefined, usageMetadata: undefined }, }; const result = translateEvent(event, state); - expect(result).toHaveLength(1); - expect(result[0]?.type).toBe('stream_end'); + expect(result).toHaveLength(0); }); }); @@ -480,15 +476,15 @@ describe('translateEvent', () => { }); describe('UserCancelled events', () => { - it('emits stream_end with reason aborted', () => { + it('emits agent_end with reason aborted', () => { state.streamStartEmitted = true; const event: ServerGeminiStreamEvent = { type: GeminiEventType.UserCancelled, }; const result = translateEvent(event, state); expect(result).toHaveLength(1); - const end = result[0] as AgentEvent<'stream_end'>; - expect(end.type).toBe('stream_end'); + const end = result[0] as AgentEvent<'agent_end'>; + expect(end.type).toBe('agent_end'); expect(end.reason).toBe('aborted'); }); }); diff --git a/packages/core/src/agent/event-translator.ts b/packages/core/src/agent/event-translator.ts index 63307c1685..c601330786 100644 --- a/packages/core/src/agent/event-translator.ts +++ b/packages/core/src/agent/event-translator.ts @@ -77,7 +77,7 @@ function makeEvent( function ensureStreamStart(state: TranslationState, out: AgentEvent[]): void { if (!state.streamStartEmitted) { - out.push(makeEvent('stream_start', state, {})); + out.push(makeEvent('agent_start', state, {})); state.streamStartEmitted = true; } } @@ -148,7 +148,7 @@ export function translateEvent( case GeminiEventType.UserCancelled: ensureStreamStart(state, out); out.push( - makeEvent('stream_end', state, { + makeEvent('agent_end', state, { reason: 'aborted', }), ); @@ -157,7 +157,7 @@ export function translateEvent( case GeminiEventType.MaxSessionTurns: ensureStreamStart(state, out); out.push( - makeEvent('stream_end', state, { + makeEvent('agent_end', state, { reason: 'max_turns', data: { code: 'MAX_TURNS_EXCEEDED', @@ -173,7 +173,7 @@ export function translateEvent( kind: 'loop_detected', }), ); - // No stream_end — the stream continues. Consumer decides how to handle: + // No agent_end — the stream continues. Consumer decides how to handle: // non-interactive emits a warning, interactive shows a confirmation dialog. break; @@ -191,7 +191,7 @@ export function translateEvent( case GeminiEventType.AgentExecutionStopped: ensureStreamStart(state, out); out.push( - makeEvent('stream_end', state, { + makeEvent('agent_end', state, { reason: 'completed', data: { message: event.value.systemMessage?.trim() || event.value.reason, @@ -285,18 +285,11 @@ function handleFinished( state: TranslationState, out: AgentEvent[], ): void { - ensureStreamStart(state, out); - if (value.usageMetadata) { + ensureStreamStart(state, out); const usage = mapUsage(value.usageMetadata, state.model); out.push(makeEvent('usage', state, usage)); } - - out.push( - makeEvent('stream_end', state, { - reason: mapFinishReason(value.reason), - }), - ); } // --------------------------------------------------------------------------- @@ -319,7 +312,7 @@ function handleError( // --------------------------------------------------------------------------- /** - * Maps a Gemini FinishReason to a StreamEndReason. + * Maps a Gemini FinishReason to an AgentEnd reason. */ export function mapFinishReason( reason: FinishReason | undefined,