/** * Unit tests for the chat wire-protocol SSOT (`src/chat-protocol/`) — * the decoder state machine, the encoders, and their round-trip. The * adapter-level golden fixtures * (`src/components/chat/hooks/__tests__/sse-stream-golden.test.ts`) pin * the full hook path; these pin the protocol module in isolation. */ import { describe, it, expect } from 'vitest' import { createSseFrameDecoder, escapeThinkingTags, encodeLeadingFrame, encodeEndOfLeading, encodeTextDelta, encodeTrailingUsageFrame, stripSentinelBytes, decodeNatsChunk, END_OF_LEADING, TRAILER_SENTINEL, FRAME_TERMINATOR, type ChatStreamEvent, type SseTrailingUsageFrame, } from '../index' const enc = new TextEncoder() /** Run a full stream through a fresh decoder: push each chunk, then end(). */ function decodeAll(chunks: Array): ChatStreamEvent[] { const decoder = createSseFrameDecoder() const out: ChatStreamEvent[] = [] for (const c of chunks) { out.push(...decoder.push(typeof c === 'string' ? enc.encode(c) : c)) } out.push(...decoder.end()) return out } const TRAILER: SseTrailingUsageFrame = { kind: 'usage', stage: 'end', input_tokens: 1234, output_tokens: 56, hit_rate_pct: 81.5, breakdown: { haikuRewriter: { input: 10, output: 4 }, routedAnswer: { model: 'claude-sonnet-x', complexity: 'default', thinkingBudget: 0 }, }, } describe('createSseFrameDecoder', () => { it('decodes everything arriving in ONE chunk (frames + sentinel + text + trailer)', () => { const wire = '{"status":"thinking"}\0' + '{"kind":"thinking-delta","text":"hmm "}\0' + '{"sources":[{"index":1}],"model":"m","modelLabel":"M","provider":"anthropic","contextWindowMaxTokens":200000,"conversationId":"conv-1"}\0' + '{"kind":"usage","stage":"start","input_tokens":11}\0' + END_OF_LEADING + 'One-chunk answer.' + TRAILER_SENTINEL + JSON.stringify(TRAILER) const events = decodeAll([wire]) expect(events).toEqual([ { type: 'status', phase: 'thinking' }, { type: 'thinking-delta', text: 'hmm ' }, { type: 'metadata', sources: [{ index: 1 }], provider: 'anthropic', modelLabel: 'M', modelName: 'm', contextWindowMaxTokens: 200000, scrollAnchor: undefined, conversationId: 'conv-1', }, { type: 'usage', stage: 'start', input_tokens: 11, cache_read_input_tokens: undefined, cache_creation_input_tokens: undefined, }, { type: 'turn-start' }, { type: 'text-delta', text: 'One-chunk answer.' }, { type: 'usage', stage: 'end', input_tokens: 1234, output_tokens: 56, cache_read_input_tokens: undefined, cache_creation_input_tokens: undefined, hit_rate_pct: 81.5, telemetry: undefined, breakdown: TRAILER.breakdown, debug: undefined, }, ]) }) it('parses a trailer split across pushes only at end()', () => { const decoder = createSseFrameDecoder() const events: ChatStreamEvent[] = [] events.push(...decoder.push(enc.encode(END_OF_LEADING + 'answer'))) events.push( ...decoder.push(enc.encode(TRAILER_SENTINEL + '{"kind":"usage","stage":"end","input_to')), ) events.push(...decoder.push(enc.encode('kens":42,"output_tokens":9}'))) // No usage event until end() — trailer runs to stream end. expect(events.map((e) => e.type)).toEqual(['turn-start', 'text-delta']) const endEvents = decoder.end() expect(endEvents).toHaveLength(1) expect(endEvents[0]).toMatchObject({ type: 'usage', stage: 'end', input_tokens: 42, output_tokens: 9, }) }) it('end() is IDEMPOTENT: a second call emits the usage frame zero more times', () => { // Adapters routinely call end() from BOTH their completion path and a // `finally`; a re-emitted usage event would double the displayed token // cost. const decoder = createSseFrameDecoder() decoder.push( enc.encode( END_OF_LEADING + 'answer' + TRAILER_SENTINEL + '{"kind":"usage","stage":"end","input_tokens":42,"output_tokens":9}', ), ) const first = decoder.end() expect(first).toHaveLength(1) expect(first[0]).toMatchObject({ type: 'usage', stage: 'end', input_tokens: 42 }) expect(decoder.end()).toEqual([]) expect(decoder.end()).toEqual([]) }) it('silently ignores a malformed trailer at end()', () => { const decoder = createSseFrameDecoder() decoder.push(enc.encode(END_OF_LEADING + 'x' + TRAILER_SENTINEL + 'not json')) expect(decoder.end()).toEqual([]) }) it('decodes a multi-byte UTF-8 character split across pushes intact', () => { const flamingo = enc.encode('🦩') // F0 9F A6 A9 const events = decodeAll([ END_OF_LEADING + 'A wild ', flamingo.slice(0, 2), flamingo.slice(2), ' appears', ]) const text = events .filter((e): e is Extract => e.type === 'text-delta') .map((e) => e.text) .join('') expect(text).toBe('A wild 🦩 appears') // The partial code point decodes to an EMPTY delta first (legacy // parity: text-mode chunks are emitted unconditionally). expect(events.map((e) => e.type)).toEqual([ 'turn-start', 'text-delta', 'text-delta', 'text-delta', 'text-delta', ]) }) it('emits thinking deltas VERBATIM (append-only) — escape(concat) === concat(escape), even with a tag split across deltas', () => { const events = decodeAll([ '{"kind":"thinking-delta","text":"use carefully"}\0', ]) const deltas = events.filter( (e): e is Extract => e.type === 'thinking-delta', ) expect(deltas.map((d) => d.text)).toEqual(['use carefully']) const raw = deltas.map((d) => d.text).join('') expect(raw).toBe('use