import { describe, expect, it } from 'vitest' import type { HistoryEntry } from '@deepseek-ai/dsh-client-connection/client' import { createAssistantMessage, createUserMessage } from '@deepseek-ai/dsh-llm' import type { SessionEvent } from '@deepseek-ai/dsh-session/types' import { inspectRequests } from '../src/client/sessions/request-inspection.ts' const at = (seq: number, type: string, data: unknown): SessionEvent => ({ seq, time: 1_700_000_000_000 + seq, type, data }) as SessionEvent const entriesOf = (events: readonly SessionEvent[]): HistoryEntry[] => events.map(event => ({ event })) describe('inspectRequests', () => { it('projects ordinary and compaction calls into one chronological request stream', () => { const events = [ at(0, 'step/start', { turn: 1, step: 1 }), at(1, 'request/header', { reason: 'initial', header: { config: { provider: 'fake', model: 'model' }, system: 'system', tools: [{ name: 'read', description: 'Read a file.', parameters: { type: 'object' }, }], }, }), at(2, 'tool/call', { turn: 1, step: 1, callId: 'call-1', name: 'read', arguments: '{}', }), at(3, 'assistant/message', { turn: 1, step: 1, message: createAssistantMessage({ content: [{ type: 'text', text: 'done' }], source: { provider: 'fake', model: 'model' }, }), usage: { inputTokens: 5, outputTokens: 2 }, }), at(4, 'step/end', { turn: 1, step: 1 }), at(5, 'compact/start', { turn: 1 }), at(6, 'compact/summary', { summary: [{ type: 'text', text: 'summary' }], rawOutput: [ { type: 'reasoning', text: 'thought' }, { type: 'text', text: 'summary' }, ], provider: 'fake', model: 'compact-model', usage: { inputTokens: 8, outputTokens: 3 }, }), at(7, 'user/message', createUserMessage({ content: [{ type: 'text', text: 'checkpoint' }], source: { kind: 'plugin', plugin: 'compact' }, })), at(8, 'compact/end', { turn: 1 }), ] const snapshot = inspectRequests(entriesOf(events)) expect(snapshot.requests).toMatchObject([ { purpose: 'assistant', startSeq: 0, resultSeq: 3, status: 'complete', prompt: { config: { provider: 'fake', model: 'model' }, system: 'system', }, promptChange: { seq: 1, kind: 'initial' }, }, { purpose: 'compaction', startSeq: 5, resultSeq: 6, replacementSeq: 7, status: 'complete', summary: [{ type: 'text', text: 'summary' }], }, ]) expect(snapshot.callSchemas.get('call-1')?.name).toBe('read') }) it('captures schemas for nested tool dispatches from the active request header', () => { const snapshot = inspectRequests(entriesOf([ at(0, 'request/header', { reason: 'initial', header: { config: { provider: 'fake', model: 'model' }, tools: [{ name: 'read', description: 'Read a file.', parameters: { type: 'object' }, }], }, }), at(1, 'tool/code-dispatch-start', { parentCallId: 'parent', subCallId: 'nested', name: 'read', arguments: {}, }), ])) expect(snapshot.callSchemas.get('nested')?.name).toBe('read') }) it('keeps chunk-reported usage through request failure and prefers it to message fallback', () => { const chunkUsage = { inputTokens: 21, outputTokens: 3 } const retryUsage = { inputTokens: 5, outputTokens: 2, cacheReadTokens: 8, reasoningTokens: 1, } const snapshot = inspectRequests(entriesOf([ at(0, 'step/start', { turn: 1, step: 1 }), at(1, 'assistant/chunk', { turn: 1, step: 1, chunk: { type: 'usage', usage: chunkUsage }, }), at(2, 'llm/retry', { turn: 1, step: 1, retry: 1, maxRetries: 2, delayMs: 100, failure: { message: 'rate limited' }, }), at(3, 'assistant/chunk', { turn: 1, step: 1, chunk: { type: 'usage', usage: retryUsage }, }), at(4, 'assistant/message', { turn: 1, step: 1, message: createAssistantMessage({ content: [{ type: 'text', text: 'recovered' }], source: { provider: 'fake', model: 'model' }, }), usage: { inputTokens: 1, outputTokens: 1 }, }), ])) expect(snapshot.requests[0]).toMatchObject({ status: 'complete', usage: { inputTokens: 26, outputTokens: 5, cacheReadTokens: 8, reasoningTokens: 1, }, }) }) it('treats a scrubbed durable-fixture tool catalog as unavailable', () => { const snapshot = inspectRequests(entriesOf([ at(0, 'step/start', { turn: 1, step: 1 }), at(1, 'request/header', { reason: 'initial', header: { config: { provider: 'fake', model: 'model' }, tools: '{{tools}}', }, }), at(2, 'tool/call', { turn: 1, step: 1, callId: 'call-1', name: 'read', arguments: '{}', }), ])) expect(snapshot.callSchemas).toEqual(new Map()) expect(snapshot.requests[0]?.prompt?.tools).toEqual([]) }) })