diff --git a/dev-packages/e2e-tests/test-applications/node-eve/tests/utils.ts b/dev-packages/e2e-tests/test-applications/node-eve/tests/utils.ts index 50542a116320..93c62a95cf88 100644 --- a/dev-packages/e2e-tests/test-applications/node-eve/tests/utils.ts +++ b/dev-packages/e2e-tests/test-applications/node-eve/tests/utils.ts @@ -31,7 +31,12 @@ export async function runAgentTurn(baseURL: string, message: string): Promise= 0.84 settles each request with `delivery.settled`; older versions end on `session.waiting` / `turn.failed` + if ( + buffer.includes('"type":"delivery.settled"') || + buffer.includes('"type":"session.waiting"') || + buffer.includes('"type":"turn.failed"') + ) { break; } } diff --git a/packages/node/src/eve.ts b/packages/node/src/eve.ts index 4e1d93f20e71..096a3899c76c 100644 --- a/packages/node/src/eve.ts +++ b/packages/node/src/eve.ts @@ -26,6 +26,7 @@ interface EveInstrumentationProvider { events: { 'turn.started': (event: { sessionId: string }) => void; 'step.attempt.started': (event: { scope: { sessionId: string } }) => void; + 'model.started': (event: { scope: { sessionId: string } }) => void; }; } @@ -46,8 +47,8 @@ interface EveInstrumentationProvider { * - runs `Sentry.init` with the given options at server startup (`setup`), adding * {@link eveIntegration} so gen_ai inputs/outputs are recorded by default (eve stamps every AI SDK * call with `recordInputs`/`recordOutputs: false`); - * - tags each turn's AI spans with a Sentry conversation id via `turn.started` / `step.attempt.started` - * events — the instrumentation-provider equivalent of `eveConversationHook`. Provider event handlers + * - tags each turn's AI spans with a Sentry conversation id via `turn.started` and `step.attempt.started` + * (eve < 0.84) / `model.started` (eve >= 0.84) events — the instrumentation-provider equivalent of `eveConversationHook`. Provider event handlers * receive the session id on the event (`event.sessionId` / `event.scope.sessionId`), unlike hook * handlers, which read it from their context. */ @@ -65,6 +66,7 @@ export function eveInstrumentation(options: EveInstrumentationOptions = {}): Eve events: { 'turn.started': event => setConversationIdFromSession(event.sessionId), 'step.attempt.started': event => setConversationIdFromSession(event.scope.sessionId), + 'model.started': event => setConversationIdFromSession(event.scope.sessionId), }, }; } diff --git a/packages/node/test/eve.test.ts b/packages/node/test/eve.test.ts index 11176944a0a5..b3d9a457a056 100644 --- a/packages/node/test/eve.test.ts +++ b/packages/node/test/eve.test.ts @@ -7,11 +7,11 @@ describe('eveInstrumentation', () => { vi.restoreAllMocks(); }); - test('exposes a setup and turn.started / step.attempt.started events', () => { + test('exposes a setup and turn.started / step.attempt.started / model.started events', () => { const provider = eveInstrumentation(); expect(typeof provider.setup).toBe('function'); - expect(Object.keys(provider.events).sort()).toEqual(['step.attempt.started', 'turn.started']); + expect(Object.keys(provider.events).sort()).toEqual(['model.started', 'step.attempt.started', 'turn.started']); }); test('sets the session id as the conversation id from turn.started (event.sessionId)', () => { @@ -30,6 +30,14 @@ describe('eveInstrumentation', () => { expect(setConversationId).toHaveBeenCalledWith('sess_resumed'); }); + test('sets it from model.started (event.scope.sessionId), which replaced step.attempt.started in eve 0.84', () => { + const setConversationId = vi.spyOn(SentryCore, 'setConversationId').mockImplementation(() => undefined); + + eveInstrumentation().events['model.started']({ scope: { sessionId: 'sess_model' } }); + + expect(setConversationId).toHaveBeenCalledWith('sess_model'); + }); + test('honors a custom getConversationId', () => { const setConversationId = vi.spyOn(SentryCore, 'setConversationId').mockImplementation(() => undefined);