/** * SSE wire-protocol decoder — a timer-free synchronous state machine, * mechanically extracted from `useSseChatAdapter`'s inline * `createDocStreamFn` parser. Byte-for-byte behavior parity with the * legacy parser is the contract (the golden fixtures in * `src/components/chat/hooks/__tests__/sse-stream-golden.test.ts` pin it * through the full hook path), including its quirks: * * - A leading `\0`-terminated block that fails JSON.parse flips the * stream into text mode and the WHOLE buffer (including the `\0`) * is emitted as answer text (legacy no-frame stream fallback). This * transition is marked `turn-start { implicit: true }`. * - In text mode only `\x1F` is scanned — literal `\0` / `\x1E` bytes * pass through into the answer; the FIRST literal `\x1F` flips into * trailer mode and everything after is captured as the trailer * (golden fixture (d) characterizes this mis-framing; the fix lives * in `encode.ts`'s sentinel stripping, NOT here). * - The `\x1E` sentinel and the `\x1F` trailer can arrive in ONE TCP * chunk (fixed-answer responses) — the post-sentinel slice is * re-scanned for the trailer sentinel. * - Multi-byte UTF-8 across chunk boundaries survives via a single * streaming TextDecoder (`{ stream: true }` on every push; no final * flush — a trailing partial code point is dropped, as legacy did). * - `end()` parses the accumulated trailer (malformed → silently * ignored) and drops any un-terminated leading buffer, as legacy did. * It is IDEMPOTENT (a deliberate deviation from legacy): repeat calls * emit nothing, so an adapter that ends in both its completion path * and its `finally` cannot double-count the usage frame. * * State flow: leading → (sentinel | parse-failure) → text → (\x1F) → * trailer-accumulate → end(). * * Server-safe: no React, no timers, TextDecoder only. */ import type { ChatStreamEvent } from './events'; export interface SseFrameDecoder { /** Feed raw response bytes; returns the events they produced. */ push(bytes: Uint8Array): ChatStreamEvent[]; /** * Signal end-of-stream; parses the trailing usage frame if present. * IDEMPOTENT — every call after the first returns `[]`. Adapters * routinely call this from BOTH their completion path and a `finally`, * and a re-emitted `usage`/`stage:'end'` event would double-count token * usage (displayed cost doubles). */ end(): ChatStreamEvent[]; } export { escapeThinkingTags } from './leading-frames'; export declare function createSseFrameDecoder(): SseFrameDecoder; //# sourceMappingURL=decode.d.ts.map