@tanstack/ai-client 0.30.0 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -13
- package/dist/esm/byok/client.d.ts +6 -1
- package/dist/esm/byok/client.js +15 -6
- package/dist/esm/byok/client.js.map +1 -1
- package/dist/esm/byok/passkey.js +16 -0
- package/dist/esm/byok/passkey.js.map +1 -1
- package/dist/esm/chat-client.d.ts +0 -2
- package/dist/esm/chat-client.js +2 -6
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.js +19 -16
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/web-mcp-tools.d.ts +49 -0
- package/dist/esm/web-mcp-tools.js +80 -0
- package/dist/esm/web-mcp-tools.js.map +1 -0
- package/package.json +3 -3
- package/src/byok/client.ts +28 -7
- package/src/byok/passkey.ts +38 -0
- package/src/chat-client.ts +2 -10
- package/src/connection-adapters.ts +23 -19
- package/src/index.ts +7 -0
- package/src/web-mcp-tools.ts +211 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-adapters.js","names":[],"sources":["../../src/connection-adapters.ts"],"sourcesContent":["import {\n EventType,\n getChunkRunId as getNormalizedChunkRunId,\n restoreInboundChunk,\n tanstackMetadata,\n uiMessagesToWire,\n withTanstackMetadata,\n} from '@tanstack/ai/client'\nimport { ByokMissingError, isByokMissingBody } from '@tanstack/ai/byok'\nimport {\n createResponseStreamTextDecoder,\n getResponseStreamReader,\n} from './response-stream'\nimport { parseSseDataLine } from './sse-utils'\nimport type {\n ModelMessage,\n RunAgentResumeItem,\n RunErrorEvent,\n StreamChunk,\n UIMessage,\n} from '@tanstack/ai/client'\nimport type { ChatFetcher, ChatPendingInterrupt } from './types'\nimport { normalizeMessagesDates } from './message-date-normalizer'\n\n/**\n * Associates connect-wrapped chunks with the run they were produced under.\n * Content events (TEXT_MESSAGE_CONTENT, TOOL_CALL_*, …) carry no `runId` of\n * their own, so the connect wrapper stamps the caller's run id here. Lets\n * run-scoped consumers (e.g. clear-during-stream suppression) attribute those\n * otherwise-runless chunks to their originating request.\n */\nconst chunkRunIds = new WeakMap<StreamChunk, string>()\n\n/**\n * Resolve a chunk's run id, preferring the value on the chunk itself\n * (RUN_STARTED / RUN_FINISHED / RUN_ERROR carry one) and falling back to the\n * run the connect wrapper stamped it with.\n */\nexport function getChunkRunId(chunk: StreamChunk): string | undefined {\n // Prefer the client's request run id (stamped in `chunkRunIds`) over a\n // provider-assigned `chunk.runId`. Interrupt continuation correlation needs\n // the client's run identity to win when a provider stamps its own id; for\n // resumable reconnect/join the two ids match, so precedence is moot there.\n const requestRunId = chunkRunIds.get(chunk)\n return requestRunId ?? getNormalizedChunkRunId(chunk)\n}\n\n/**\n * Thrown when an SSE/HTTP stream ends with a non-empty unterminated buffer.\n * Indicates the connection was cut mid-line (server crash, dropped TCP, proxy\n * timeout) so the partial content cannot be safely parsed.\n */\nexport class StreamTruncatedError extends Error {\n constructor() {\n super(\n 'Stream ended with unterminated trailing data — connection was likely cut short.',\n )\n this.name = 'StreamTruncatedError'\n }\n}\n\nclass StreamReadError extends Error {\n constructor(cause: unknown) {\n super('Stream response body read failed', { cause })\n this.name = 'StreamReadError'\n }\n}\n\n/**\n * Thrown when a durable (id-tagged) run's stream ends with no terminal event\n * and a reconnect makes no forward progress — the run cannot complete, so the\n * consumer must not be left silently hanging on a stream that just stops.\n */\nexport class DurableStreamIncompleteError extends Error {\n constructor() {\n super(\n 'Durable run ended without a terminal event and could not resume — the run did not complete.',\n )\n this.name = 'DurableStreamIncompleteError'\n }\n}\n\n/**\n * Thrown when a durable run exceeds its reconnect ceiling. Bounds the\n * otherwise-unbounded reconnect loop so a flapping producer (or a proxy that\n * rolls the socket after every event) surfaces a failure instead of\n * reconnecting without end.\n */\nexport class StreamReconnectLimitError extends Error {\n constructor(attempts: number) {\n super(\n `Durable run exceeded its reconnect ceiling of ${attempts} attempts — giving up.`,\n )\n this.name = 'StreamReconnectLimitError'\n }\n}\n\n/**\n * Reconnect bounding for resumable streams. A constant throttle delay prevents a\n * hot loop against the origin, and the ceiling bounds a pathologically failing\n * run — but only counts CONSECUTIVE reconnects that made no forward progress.\n */\nexport interface ReconnectOptions {\n /**\n * Ceiling on the number of CONSECUTIVE reconnects that deliver no new events,\n * before failing with {@link StreamReconnectLimitError}. The counter resets to\n * zero whenever a reconnect makes forward progress, so a healthy long run —\n * even one behind a proxy that rolls the socket after every event — never\n * approaches it; the ceiling only fires when the run is genuinely stuck\n * (reconnecting repeatedly without receiving anything new). Default 5.\n */\n maxAttempts?: number\n /** Delay between reconnect attempts, in ms, to avoid hammering. Default 250. */\n delayMs?: number\n}\n\ninterface ResolvedReconnectOptions {\n maxAttempts: number\n delayMs: number\n}\n\nfunction resolveReconnectOptions(\n options: ReconnectOptions | undefined,\n): ResolvedReconnectOptions {\n const maxAttempts = options?.maxAttempts ?? 5\n const delayMs = options?.delayMs ?? 250\n // Reject non-finite / negative bounds up front: a NaN or Infinity maxAttempts\n // would make the ceiling ineffective (unbounded reconnects), and a non-finite\n // delayMs would remove throttling. Fail loudly on misconfiguration.\n if (!Number.isInteger(maxAttempts) || maxAttempts < 0) {\n throw new Error(\n `Invalid reconnect.maxAttempts: ${maxAttempts}. Must be a non-negative integer.`,\n )\n }\n if (!Number.isFinite(delayMs) || delayMs < 0) {\n throw new Error(\n `Invalid reconnect.delayMs: ${delayMs}. Must be a non-negative finite number.`,\n )\n }\n return { maxAttempts, delayMs }\n}\n\n/**\n * Reconnect bookkeeping shared by every resumable-stream driver: de-dupes\n * offsets, tracks the last acknowledged offset, honors the SSE empty-id reset\n * convention, and bounds consecutive no-progress reconnects behind a\n * throttling delay. Extracted out of {@link resumableStream} so a WebSocket\n * reconnect driver can reuse the exact same semantics.\n */\nexport interface ReconnectTracker {\n /** The most recently accepted (non-duplicate, non-empty) offset, if any. */\n readonly lastEventId: string | undefined\n /**\n * Record an incoming offset. Returns `'reset'` for an empty id (SSE's\n * resume-cursor reset — clears the de-dupe set and `lastEventId`),\n * `'duplicate'` for an already-seen id, and `'new'` otherwise (including\n * `undefined`, which is untracked — no offset to remember).\n */\n note: (id: string | undefined) => 'new' | 'duplicate' | 'reset'\n /**\n * Throttle before a reconnect attempt. Resets the no-progress counter when\n * `madeProgress` is true; otherwise increments it and throws\n * {@link StreamReconnectLimitError} once it exceeds the configured ceiling.\n */\n waitBeforeReconnect: (\n madeProgress: boolean,\n signal?: AbortSignal,\n ) => Promise<void>\n}\n\n/** Create a {@link ReconnectTracker} bound to the given reconnect bounds. */\nexport function createReconnectTracker(\n options?: ReconnectOptions,\n): ReconnectTracker {\n const reconnect = resolveReconnectOptions(options)\n // Retains every delivered offset for the run's lifetime. Intentionally\n // bounded by run length (not evicted): a conforming server replays strictly\n // after the acknowledged offset, so this only needs to catch the single\n // boundary event on reconnect, but keeping the full set keeps de-dup\n // correct even if a server replays a wider overlap.\n const seen = new Set<string>()\n let lastEventId: string | undefined\n let reconnectAttempts = 0\n return {\n get lastEventId() {\n return lastEventId\n },\n note(id) {\n if (id === undefined) return 'new'\n if (id === '') {\n // SSE spec: an empty `id:` resets the resume cursor. Drop the last\n // offset and clear the de-dupe set; the chunk itself still delivers.\n lastEventId = undefined\n seen.clear()\n return 'reset'\n }\n if (seen.has(id)) return 'duplicate'\n seen.add(id)\n lastEventId = id\n return 'new'\n },\n // Bound only CONSECUTIVE no-progress reconnects. A reconnect that made\n // forward progress resets the counter, so a healthy long run (even one\n // whose socket rolls after every event) never approaches the ceiling; it\n // fires only when the run is genuinely stuck — reconnecting repeatedly\n // with nothing new.\n async waitBeforeReconnect(madeProgress, signal) {\n if (madeProgress) {\n reconnectAttempts = 0\n } else {\n reconnectAttempts += 1\n if (reconnectAttempts > reconnect.maxAttempts) {\n throw new StreamReconnectLimitError(reconnect.maxAttempts)\n }\n }\n await abortableDelay(reconnect.delayMs, signal)\n },\n }\n}\n\n/** Resolve after `ms`, or immediately once `signal` aborts. Never rejects. */\nfunction abortableDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0 || signal?.aborted) return Promise.resolve()\n return new Promise((resolve) => {\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nfunction generateRunId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n}\n\n/**\n * Asserts an id is present when synthesizing a terminal event. The chat\n * client always supplies `runContext.threadId` / `runContext.runId`, so an\n * absent id at this layer indicates the adapter was wired up by a caller\n * that bypassed that contract — surface it rather than fabricating one.\n */\nfunction requireSyntheticId(\n value: string | undefined,\n field: 'threadId' | 'runId',\n): string {\n if (!value) {\n throw new Error(\n `Cannot synthesize terminal event: ${field} not supplied via runContext and not observed in the upstream stream.`,\n )\n }\n return value\n}\n\n/**\n * Merge custom headers into request headers\n */\nfunction mergeHeaders(\n customHeaders?: Record<string, string> | Headers,\n): Record<string, string> {\n if (!customHeaders) {\n return {}\n }\n if (customHeaders instanceof Headers) {\n const result: Record<string, string> = {}\n customHeaders.forEach((value, key) => {\n result[key] = value\n })\n return result\n }\n return customHeaders\n}\n\n/**\n * Request header carrying the client-chosen run id to a delivery-durability\n * sink. The durable log is then keyed by the SAME id the client already holds,\n * so a later join/resume can address the run without first reading back a\n * server-generated id. Sent as a header — NOT a query param — so the POST URL\n * stays byte-identical to a plain, non-durable request; a server that isn't\n * durable simply ignores the header. (The GET join path keeps `?runId` in the\n * query, since a GET has no body/handler contract to disturb.)\n */\nconst RUN_ID_HEADER = 'X-Run-Id'\n\nfunction runIdHeader(runId: string | undefined): Record<string, string> {\n return runId === undefined ? {} : { [RUN_ID_HEADER]: runId }\n}\n\nfunction withSearchParams(url: string, values: Record<string, string>): string {\n const hashIndex = url.indexOf('#')\n const hash = hashIndex === -1 ? '' : url.slice(hashIndex)\n const withoutHash = hashIndex === -1 ? url : url.slice(0, hashIndex)\n const queryIndex = withoutHash.indexOf('?')\n const base =\n queryIndex === -1 ? withoutHash : withoutHash.slice(0, queryIndex)\n const search = new URLSearchParams(\n queryIndex === -1 ? '' : withoutHash.slice(queryIndex + 1),\n )\n for (const [key, value] of Object.entries(values)) search.set(key, value)\n const query = search.toString()\n return `${base}${query.length === 0 ? '' : `?${query}`}${hash}`\n}\n\n/**\n * Read lines from a stream (newline-delimited)\n */\nasync function* readStreamLines(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n abortSignal?: AbortSignal,\n): AsyncGenerator<string> {\n try {\n const decoder = createResponseStreamTextDecoder()\n let buffer = ''\n\n while (!abortSignal?.aborted) {\n let result: ReadableStreamReadResult<Uint8Array>\n try {\n result = await reader.read()\n } catch (error) {\n if (abortSignal?.aborted) return\n throw new StreamReadError(error)\n }\n const { done, value } = result\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n // Strip a trailing CR so a CRLF stream matches the LF path (and the\n // XHR reader). Without this an exact-equality check like the `[DONE]`\n // sentinel in linesToSSEEvents would miss `data: [DONE]\\r`.\n const normalized = line.endsWith('\\r') ? line.slice(0, -1) : line\n if (normalized.trim()) {\n yield normalized\n }\n }\n }\n\n // Flush the decoder: a connection cut mid-multibyte-character leaves bytes\n // held inside the streaming TextDecoder. Draining them here (as U+FFFD)\n // makes the trailing-buffer check below see the incomplete tail and report\n // truncation instead of silently swallowing it.\n buffer += decoder.decode()\n\n // A non-empty trailing buffer means the connection was cut mid-line.\n // Surface this as an error so the chat client transitions to 'error'\n // state instead of silently presenting a partial stream as success.\n // Skip when the consumer aborted — a user-initiated stop() interrupting\n // mid-line is expected, not a truncation bug.\n if (buffer.trim() && !abortSignal?.aborted) {\n throw new StreamTruncatedError()\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/** A parsed stream chunk paired with its adapter-owned delivery offset (if any). */\ninterface StreamEvent {\n chunk: StreamChunk\n id?: string\n}\n\n/**\n * Type guard for a durable NDJSON envelope `{ id, chunk }`. NDJSON has no\n * native event-id field, so durability rides the offset inside the payload.\n * A bare `StreamChunk` always has a top-level `type`, and the envelope never\n * does, so the two forms are unambiguous — a non-durable line stays bare.\n */\nfunction isNdjsonEnvelope(\n value: unknown,\n): value is { id: string; chunk: StreamChunk } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'chunk' in value &&\n 'id' in value &&\n typeof (value as { id: unknown }).id === 'string' &&\n !('type' in value)\n )\n}\n\n/** Rebuild pre-wire extras after SSE/NDJSON ingest. */\nfunction restoreInboundUsage(chunk: StreamChunk): StreamChunk {\n return restoreInboundChunk(chunk)\n}\n\nfunction sseChunkModel(chunk: StreamChunk): string | undefined {\n const tanstackModel = tanstackMetadata(chunk)?.model\n if (typeof tanstackModel === 'string') return tanstackModel\n const usage = 'usage' in chunk ? chunk.usage : undefined\n if (Array.isArray(usage)) {\n const model = (usage[0] as { model?: unknown } | undefined)?.model\n if (typeof model === 'string') return model\n }\n return undefined\n}\n\n/**\n * Parse SSE-format lines into stream events, pairing each chunk with the `id:`\n * offset of the event it arrived on. Shared by the fetch- and XHR-backed SSE\n * adapters so both track delivery offsets identically.\n *\n * Accepts either `data: {...}` lines or bare JSON lines. Skips comments\n * starting with `:` (proxies and CDNs inject these as keepalives) and the\n * `event:` / `retry:` SSE control fields. A `[DONE]` sentinel is treated as a\n * terminal event: a synthesized RUN_FINISHED is yielded using the most recent\n * upstream `threadId` / `runId` (falling back to `fallbackIds`), so the\n * consumer sees a clean terminal event with real correlation ids.\n *\n * A JSON parse failure throws — the consumer surfaces it as an error.\n */\nasync function* linesToSSEEvents(\n lines: AsyncIterable<string>,\n fallbackIds?: { threadId?: string; runId?: string },\n): AsyncGenerator<StreamEvent> {\n let lastThreadId: string | undefined\n let lastRunId: string | undefined\n let lastModel: string | undefined\n let pendingId: string | undefined\n for await (const line of lines) {\n if (line === 'id' || line.startsWith('id:')) {\n // SSE spec: strip a single leading space after the colon, preserve the\n // rest verbatim so an opaque adapter offset round-trips exactly (do NOT\n // trim, which would mangle a legitimate offset). An empty value is kept as\n // '' and resets the resume cursor downstream (see resumableStream).\n const rawId = line === 'id' ? '' : line.slice(3)\n pendingId = rawId.startsWith(' ') ? rawId.slice(1) : rawId\n continue\n }\n // Assumes the durability wire emits one `id:` immediately followed by one\n // `data:` per event (both shipped sinks do). `pendingId` attaches to the\n // next data line and is cleared after it; blank-line event boundaries are\n // stripped upstream, so a hand-rolled server that emits an id-only event or\n // a persistent `id:` across events is not supported here.\n if (\n line.startsWith(':') ||\n line.startsWith('event:') ||\n line.startsWith('retry:')\n ) {\n continue\n }\n const data = parseSseDataLine(line)\n if (data === '[DONE]') {\n yield {\n chunk: withTanstackMetadata(\n {\n type: EventType.RUN_FINISHED,\n threadId: lastThreadId ?? fallbackIds?.threadId ?? '',\n runId: lastRunId ?? fallbackIds?.runId ?? '',\n timestamp: Date.now(),\n },\n {\n finishReason: 'stop',\n ...(lastModel !== undefined ? { model: lastModel } : {}),\n },\n ) as StreamChunk,\n }\n return\n }\n const chunk = restoreInboundUsage(JSON.parse(data) as StreamChunk)\n if ('threadId' in chunk && typeof chunk.threadId === 'string') {\n lastThreadId = chunk.threadId\n }\n if ('runId' in chunk && typeof chunk.runId === 'string') {\n lastRunId = chunk.runId\n }\n const model = sseChunkModel(chunk)\n if (model !== undefined) lastModel = model\n const id = pendingId\n pendingId = undefined\n yield { chunk, ...(id !== undefined ? { id } : {}) }\n }\n}\n\n/**\n * Parse NDJSON-format lines into stream events. Durable streams emit each line\n * as an `{ id, chunk }` envelope carrying the delivery offset; non-durable\n * streams emit bare chunks. Both are auto-detected (see {@link isNdjsonEnvelope}),\n * so an untagged stream behaves exactly as a plain single fetch used to.\n */\nasync function* linesToNdjsonEvents(\n lines: AsyncIterable<string>,\n): AsyncGenerator<StreamEvent> {\n for await (const line of lines) {\n const parsed = JSON.parse(line) as unknown\n if (isNdjsonEnvelope(parsed)) {\n yield { chunk: restoreInboundUsage(parsed.chunk), id: parsed.id }\n } else {\n yield { chunk: restoreInboundUsage(parsed as StreamChunk) }\n }\n }\n}\n\nasync function assertResponseOk(response: Response): Promise<void> {\n if (response.ok) return\n if (response.status === 401) {\n const body: unknown = await response\n .clone()\n .json()\n .catch(() => null)\n if (isByokMissingBody(body)) {\n throw new ByokMissingError(body.error.provider)\n }\n }\n throw new Error(\n `HTTP error! status: ${response.status} ${response.statusText}`,\n )\n}\n\nfunction errorFromXhrStatus(xhr: XMLHttpRequest): Error {\n if (xhr.status === 401) {\n let parsed: unknown = null\n try {\n parsed = JSON.parse(xhr.responseText)\n } catch {\n parsed = null\n }\n if (isByokMissingBody(parsed)) {\n return new ByokMissingError(parsed.error.provider)\n }\n }\n return new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`)\n}\n\n/**\n * GET the hydration endpoint for a thread and parse its JSON `{ messages,\n * activeRun }` body. This is the transport-agnostic reconnect probe: keyed on\n * the STABLE thread id, it returns the stored transcript and — if a run is still\n * generating — a cursor the caller tails via `joinRun`. Shared by every fetch/\n * XHR adapter so the client never has to know which transport is in use.\n */\nasync function fetchThreadHydration(\n fetchClient: typeof globalThis.fetch,\n url: string,\n headers: Record<string, string>,\n credentials: RequestCredentials,\n threadId: string,\n): Promise<ChatHydrationResult> {\n const response = await fetchClient(withSearchParams(url, { threadId }), {\n method: 'GET',\n headers: { Accept: 'application/json', ...headers },\n credentials,\n })\n await assertResponseOk(response)\n const data = (await response.json()) as {\n messages?: Array<UIMessage>\n activeRun?: { runId?: unknown } | null\n interrupts?: {\n runId?: unknown\n pending?: unknown\n } | null\n }\n const activeRun =\n data.activeRun && typeof data.activeRun.runId === 'string'\n ? { runId: data.activeRun.runId }\n : null\n const interrupts =\n data.interrupts &&\n typeof data.interrupts.runId === 'string' &&\n Array.isArray(data.interrupts.pending) &&\n data.interrupts.pending.length > 0\n ? {\n runId: data.interrupts.runId,\n pending: data.interrupts.pending as Array<ChatPendingInterrupt>,\n }\n : null\n return {\n messages: Array.isArray(data.messages)\n ? normalizeMessagesDates(data.messages)\n : [],\n activeRun,\n interrupts,\n }\n}\n\n/**\n * GET the hydration endpoint for a generation thread and parse its JSON\n * `{ resumeSnapshot, activeRun }` body. Mirrors {@link fetchThreadHydration} for\n * the generation clients: keyed on the stable thread id, it returns the last\n * generation's resume snapshot (re-validated client-side before adoption) and —\n * if a run is still generating — a cursor. Shared by every fetch/XHR adapter.\n */\nasync function fetchGenerationHydration(\n fetchClient: typeof globalThis.fetch,\n url: string,\n headers: Record<string, string>,\n credentials: RequestCredentials,\n threadId: string,\n): Promise<GenerationHydrationResult> {\n const response = await fetchClient(withSearchParams(url, { threadId }), {\n method: 'GET',\n headers: { Accept: 'application/json', ...headers },\n credentials,\n })\n await assertResponseOk(response)\n const raw: unknown = await response.json()\n // A 200 carrying `null` is a legitimate hydration miss — the server has no\n // record for this thread — and reading `.activeRun` off `null` would throw.\n if (raw === null) {\n return { resumeSnapshot: null, activeRun: null }\n }\n // Any OTHER non-object body is a broken endpoint, not an empty thread.\n // Reporting it as a miss would present a misconfigured route as a fresh\n // thread; the client surfaces this through its own error channel instead.\n if (typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error(\n `Generation hydration expected a JSON object from ${url}, received ${Array.isArray(raw) ? 'an array' : typeof raw}.`,\n )\n }\n const data = raw as {\n resumeSnapshot?: GenerationHydrationResult['resumeSnapshot']\n activeRun?: { runId?: unknown } | null\n }\n const activeRun =\n data.activeRun && typeof data.activeRun.runId === 'string'\n ? { runId: data.activeRun.runId }\n : null\n return {\n resumeSnapshot: data.resumeSnapshot ?? null,\n activeRun,\n }\n}\n\n/** Yield SSE stream events (chunk + offset) from a fetch Response body. */\nasync function* responseToSSEEvents(\n response: Response,\n abortSignal?: AbortSignal,\n fallbackIds?: { threadId?: string; runId?: string },\n): AsyncGenerator<StreamEvent> {\n await assertResponseOk(response)\n const reader = getResponseStreamReader(response)\n yield* linesToSSEEvents(readStreamLines(reader, abortSignal), fallbackIds)\n}\n\n/** Yield NDJSON stream events (chunk + offset) from a fetch Response body. */\nasync function* responseToNdjsonEvents(\n response: Response,\n abortSignal?: AbortSignal,\n): AsyncGenerator<StreamEvent> {\n await assertResponseOk(response)\n const reader = getResponseStreamReader(response)\n yield* linesToNdjsonEvents(readStreamLines(reader, abortSignal))\n}\n\nasync function* responseToSSEChunks(\n response: Response,\n abortSignal?: AbortSignal,\n): AsyncGenerator<StreamChunk> {\n for await (const { chunk } of responseToSSEEvents(response, abortSignal)) {\n yield chunk\n }\n}\n\n/**\n * A re-issuable event source. Given extra headers (a `Last-Event-ID` on a\n * reconnect) and an abort signal, it opens the transport and yields stream\n * events. {@link resumableStream} calls it once per attempt, so each call MUST\n * open a fresh underlying request (a new fetch or a new XHR).\n */\ntype StreamEventSource = (\n extraHeaders: Record<string, string>,\n abortSignal?: AbortSignal,\n) => AsyncIterable<StreamEvent>\n\n/**\n * Build a fetch-backed {@link StreamEventSource}. `parseResponse` decodes the\n * body into events (SSE or NDJSON) — the reconnect engine is identical for both.\n */\nfunction fetchEventSource(\n fetchClient: typeof globalThis.fetch,\n url: string,\n requestInit: RequestInit,\n parseResponse: (\n response: Response,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamEvent>,\n): StreamEventSource {\n return async function* (extraHeaders, abortSignal) {\n let response: Response\n try {\n response = await fetchClient(url, {\n ...requestInit,\n headers: {\n ...(requestInit.headers as Record<string, string> | undefined),\n ...extraHeaders,\n },\n ...(abortSignal ? { signal: abortSignal } : {}),\n })\n } catch (error) {\n // A fetch REJECTION (device offline, DNS blip, connection refused) is a\n // recoverable transport failure, not a fatal one — surface it as\n // StreamReadError so resumableStream retries from the last offset, mirroring\n // the XHR path (whose onerror wraps the same way). On a genuine abort this\n // wraps the AbortError too, but that's harmless: resumableStream checks\n // `abortSignal.aborted` first and returns, so the wrapped error's type is\n // never inspected. Without an offset (initial connect / non-durable), it\n // still surfaces as a hard failure.\n throw new StreamReadError(error)\n }\n yield* parseResponse(response, abortSignal)\n }\n}\n\n/**\n * Drive a {@link StreamEventSource} with native-style resumability. Each event's\n * adapter-owned delivery offset (its `id`) is remembered; if the connection\n * drops or ends before a terminal event, the source is re-opened with a\n * `Last-Event-ID` header so the server replays strictly after the last offset.\n * Already-seen offsets are de-duped, so an overlapping replay is safe.\n *\n * When the server does NOT tag events (no durability), no offset is ever seen,\n * so no reconnect happens — behaviour is identical to a plain single request.\n * This engine is transport-agnostic: fetch/XHR × SSE/NDJSON all share it, the\n * only difference being the {@link StreamEventSource} they pass in.\n */\nasync function* resumableStream(\n openEventSource: StreamEventSource,\n abortSignal?: AbortSignal,\n reconnectOptions?: ReconnectOptions,\n): AsyncGenerator<StreamChunk> {\n const tracker = createReconnectTracker(reconnectOptions)\n\n for (;;) {\n if (abortSignal?.aborted) return\n const extraHeaders: Record<string, string> =\n tracker.lastEventId !== undefined\n ? { 'Last-Event-ID': tracker.lastEventId }\n : {}\n\n let sawTerminal = false\n let progressed = false\n try {\n for await (const { chunk, id } of openEventSource(\n extraHeaders,\n abortSignal,\n )) {\n if (tracker.note(id) === 'duplicate') continue\n progressed = true\n if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {\n sawTerminal = true\n }\n yield chunk\n // Do NOT stop on a terminal mid-source: an agent loop emits one\n // RUN_STARTED/RUN_FINISHED pair PER turn, so a tool-calling run carries\n // several RUN_FINISHED events before the run is truly done. Returning on\n // the first one would drop every subsequent turn (the tool result and\n // the final answer). Instead, drain the event source to its natural end\n // — the server closes the response only when the run is actually\n // complete — and use `sawTerminal` below to decide done-vs-reconnect.\n }\n } catch (error) {\n if (abortSignal?.aborted) return\n // A transport drop is resumable once we hold an offset — retry from it,\n // even if THIS attempt made no new progress. A caught-up run whose parked\n // long-poll socket drops (or a proxy that drops just after replaying the\n // de-duped overlap) is transient, not fatal; the consecutive-no-progress\n // ceiling in waitBeforeReconnect already bounds a genuinely stuck flapper,\n // so a per-attempt progress requirement here would only convert\n // recoverable drops into hard failures on flaky (mobile/edge) networks.\n // Without an offset (a non-durable stream), surface the failure.\n if (\n (error instanceof StreamTruncatedError ||\n error instanceof StreamReadError) &&\n tracker.lastEventId !== undefined\n ) {\n await tracker.waitBeforeReconnect(progressed, abortSignal)\n continue\n }\n throw error\n }\n\n if (abortSignal?.aborted) return\n\n // The source ended after delivering a terminal event: the run is genuinely\n // finished (for an agentic run this is the LAST turn's terminal, since we no\n // longer stop on intermediate ones). Stop — reconnecting a durable run here\n // would re-open past the final offset and see an empty window.\n if (sawTerminal) return\n\n if (tracker.lastEventId !== undefined) {\n // A durable (id-tagged) run.\n if (progressed) {\n // Clean end WITHOUT a terminal event but we advanced — the producer is\n // still going (or the socket rolled over). Reconnect from the last\n // offset (backing off to avoid a hot loop against the origin). Progress\n // resets the no-progress ceiling.\n await tracker.waitBeforeReconnect(true, abortSignal)\n continue\n }\n // Ended without a terminal event AND made no forward progress on this\n // pass: the run cannot complete. Surface an error rather than returning\n // silently, which would leave the consumer with neither a terminal event\n // nor a failure.\n //\n // Invariant this relies on: a durable transport must never surface an\n // empty long-poll window as a CLEAN end while the producer is still\n // alive. Both shipped backends honor it — memoryStream parks until data\n // or completion, and durableStream keeps one continuous response across\n // windows — so this fires only on a genuinely complete-but-unterminated\n // log. A custom StreamDurability transport that ends a response empty\n // mid-run would trip this; keep the response open until data or terminal.\n throw new DurableStreamIncompleteError()\n }\n\n // A non-durable (untagged) stream that ended cleanly. Legitimate — the\n // upper layer synthesizes a terminal event. Stop.\n return\n }\n}\n\n/**\n * Per-send context provided by the chat client to the connection adapter.\n * The adapter combines this with serialized messages to build a full\n * AG-UI `RunAgentInput` payload.\n */\nexport interface RunAgentInputContext {\n threadId: string\n runId: string\n parentRunId?: string\n /** AG-UI interrupt resume entries returned to the server on a follow-up run. */\n resume?: Array<RunAgentResumeItem>\n /** Client-declared tools to advertise in the request payload. */\n clientTools?: Array<{\n name: string\n description: string\n parameters: unknown\n }>\n /** Arbitrary user-controlled passthrough data. */\n forwardedProps?: Record<string, unknown>\n /** Extra request headers for this run (e.g. BYOK keys). POST only. */\n headers?: Record<string, string>\n}\n\nexport interface ConnectConnectionAdapter {\n /**\n * Connect and return an async iterable of StreamChunks.\n */\n connect: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n runContext?: RunAgentInputContext,\n ) => AsyncIterable<StreamChunk>\n /**\n * Fetch server-driven hydration for a generation `threadId`: the last\n * generation's resume snapshot, plus a cursor to a run still generating if\n * one exists. The generation client calls this itself on mount when\n * `persistence: true` (no loader/prop) and repaints the snapshot — it never\n * auto-starts a run. Read-only JSON GET (`?threadId`), so it is\n * transport-agnostic. Optional and feature-detected exactly like the chat\n * `hydrate` handler.\n */\n hydrateGeneration?: (threadId: string) => Promise<GenerationHydrationResult>\n /**\n * Re-attach to a run that is still generating and replay it from the start\n * (read-only `?offset=-1&runId` against the delivery-durability log). The\n * generation client tails this on mount when hydration reports a run still in\n * flight, so a dropped connection or a full reload finishes the generation in\n * place — the same durability replay the chat client uses. Optional and\n * feature-detected; present on `fetchServerSentEvents` / `fetchHttpStream`.\n */\n joinRun?: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n /**\n * Fetch server-driven hydration for a chat `threadId`: the stored transcript\n * plus a cursor to an in-flight run and any pending interrupts. The chat\n * client calls this itself on mount when `persistence: true` (no loader/prop)\n * and repaints it — it never auto-sends. Read-only JSON GET (`?threadId`), so\n * it is transport-agnostic. Optional and feature-detected; present on\n * `fetchServerSentEvents` / `fetchHttpStream`, and on `stream()` /\n * `rpcStream()` when supplied via {@link StreamConnectionHandlers}.\n */\n hydrate?: (threadId: string) => Promise<ChatHydrationResult>\n}\n\n/**\n * Server-resolved hydration for a generation thread. `resumeSnapshot` is the\n * last generation's lightweight snapshot (validated client-side before it is\n * adopted); `activeRun` is a cursor to a run still generating for the thread\n * (or `null`).\n *\n * Field-for-field compatible with `@tanstack/ai-persistence`'s\n * `ReconstructedGeneration` (the body `reconstructGeneration` returns) — the\n * client never imports that package, so this is a structural contract, not a\n * shared type. Two deliberate widenings on this side: `schemaVersion` is\n * optional (the server always writes `1`, but a hand-written fixture need not),\n * and `status` also admits `'idle'`, which the server's mapper never emits.\n * Only a client-local snapshot reaches it, when `stop()` retires a cancelled\n * run.\n */\nexport interface GenerationHydrationResult {\n resumeSnapshot: {\n schemaVersion?: 1\n resumeState: { threadId: string; runId: string } | null\n status: 'idle' | 'running' | 'complete' | 'error'\n result?: unknown\n error?: { message: string; code?: string }\n activity?: string\n } | null\n activeRun: { runId: string } | null\n}\n\n/**\n * Server-resolved hydration for a thread. `messages` is the stored transcript;\n * `activeRun` is a cursor to a run still generating for the thread (or `null`).\n * Keyed on the STABLE thread id — the client never handles a run id, so a turn\n * that spans several runs (interrupt/tool continuations) reconnects correctly.\n */\nexport interface ChatHydrationResult {\n messages: Array<UIMessage>\n activeRun: { runId: string } | null\n /**\n * Pending human-in-the-loop interrupts for the thread and the run they paused,\n * so a reload (or another device) re-prompts the approval from the server. The\n * client restores them exactly as a persisted resume snapshot would.\n */\n interrupts: {\n runId: string\n pending: Array<ChatPendingInterrupt>\n } | null\n}\n\n/**\n * A {@link ConnectConnectionAdapter} that also supports joining an existing run\n * (a second tab, or re-attaching after a full reload) via `joinRun`, replaying\n * the ordered stream from the start off the server's delivery-durability sink.\n */\nexport interface ResumableConnectConnectionAdapter extends ConnectConnectionAdapter {\n /**\n * Join an in-flight or finished run by id, replaying from the start\n * (`?offset=-1`). Read-only — sends no messages.\n */\n joinRun: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n /**\n * Fetch server-authoritative hydration for `threadId`: the stored transcript,\n * and a cursor to an in-flight run if one exists. The client calls this itself\n * on mount (no loader/prop), then tails `activeRun` via `joinRun`. Read-only\n * JSON GET (`?threadId`), so it is transport-agnostic regardless of how the\n * delivery stream is served.\n */\n hydrate?: (threadId: string) => Promise<ChatHydrationResult>\n}\n\nexport interface SubscribeConnectionAdapter {\n /**\n * Subscribe to stream chunks.\n */\n subscribe: (abortSignal?: AbortSignal) => AsyncIterable<StreamChunk>\n /**\n * Send a request; chunks arrive through subscribe().\n */\n send: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n runContext?: RunAgentInputContext,\n ) => Promise<void>\n /**\n * Re-attach to an existing run by id, replaying its stream from the start off\n * the server's delivery-durability sink. Present only when the underlying\n * connection is resumable (a `ResumableConnectConnectionAdapter`). Used to\n * rejoin an in-flight run after a full page reload.\n */\n joinRun?: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n /**\n * Server-authoritative hydration for a thread (transcript + in-flight-run\n * cursor). Present only when the underlying connection supports it. The client\n * calls it on mount to re-hydrate without any app-side loader or prop.\n */\n hydrate?: (threadId: string) => Promise<ChatHydrationResult>\n}\n\n/**\n * Connection adapter union.\n * Provide either `connect`, or `subscribe` + `send`.\n */\nexport type ConnectionAdapter =\n | ConnectConnectionAdapter\n | SubscribeConnectionAdapter\n\n/**\n * Normalize a ConnectionAdapter to subscribe/send operations.\n *\n * If a connection provides native subscribe/send, that mode is used.\n * Otherwise, connect() is wrapped using an async queue.\n */\nexport function normalizeConnectionAdapter(\n connection: ConnectionAdapter | undefined,\n): SubscribeConnectionAdapter {\n if (!connection) {\n throw new Error('Connection adapter is required')\n }\n\n const hasConnect = 'connect' in connection\n const hasSubscribe = 'subscribe' in connection\n const hasSend = 'send' in connection\n\n if (hasConnect && (hasSubscribe || hasSend)) {\n throw new Error(\n 'Connection adapter must provide either connect or both subscribe and send, not both modes',\n )\n }\n\n if (hasSubscribe && hasSend) {\n const joinRun = (connection as SubscribeConnectionAdapter).joinRun?.bind(\n connection,\n )\n const hydrate = (connection as SubscribeConnectionAdapter).hydrate?.bind(\n connection,\n )\n return {\n subscribe: connection.subscribe.bind(connection),\n send: connection.send.bind(connection),\n ...(joinRun ? { joinRun } : {}),\n ...(hydrate ? { hydrate } : {}),\n }\n }\n\n if (!hasConnect) {\n throw new Error(\n 'Connection adapter must provide either connect or both subscribe and send',\n )\n }\n\n // Legacy connect() wrapper\n let activeBuffer: Array<StreamChunk> = []\n let activeWaiters: Array<(chunk: StreamChunk | null) => void> = []\n\n function push(chunk: StreamChunk, runId?: string): void {\n if (runId) {\n chunkRunIds.set(chunk, runId)\n }\n const waiter = activeWaiters.shift()\n if (waiter) {\n waiter(chunk)\n } else {\n activeBuffer.push(chunk)\n }\n }\n\n async function waitUntilSubscriberIdle(\n abortSignal?: AbortSignal,\n ): Promise<void> {\n // Idle means the subscriber is waiting for the next chunk, so the\n // previous chunk has left processIncomingChunk. Empty waiters with an\n // empty buffer is in-flight delivery, not idle.\n const idle = () =>\n activeBuffer.length === 0 &&\n (activeWaiters.length > 0 || abortSignal?.aborted)\n for (let i = 0; i < 16 && !abortSignal?.aborted; i++) {\n if (idle()) return\n await Promise.resolve()\n }\n let macrotaskWaits = 0\n while (!abortSignal?.aborted) {\n if (idle()) return\n await new Promise<void>((resolve) => setTimeout(resolve, 0))\n macrotaskWaits++\n if (activeWaiters.length === 0 && macrotaskWaits >= 32) return\n }\n }\n\n return {\n subscribe(abortSignal?: AbortSignal): AsyncIterable<StreamChunk> {\n // Transfer ownership to the latest subscriber so only one active\n // subscribe() call receives chunks from the shared connect-wrapper queue.\n const myBuffer: Array<StreamChunk> = activeBuffer.splice(0)\n const myWaiters: Array<(chunk: StreamChunk | null) => void> = []\n activeBuffer = myBuffer\n activeWaiters = myWaiters\n\n return (async function* () {\n while (!abortSignal?.aborted) {\n let chunk: StreamChunk | null\n const buffered = myBuffer.shift()\n if (buffered !== undefined) {\n chunk = buffered\n } else {\n chunk = await new Promise<StreamChunk | null>((resolve) => {\n const onAbort = () => resolve(null)\n myWaiters.push((c) => {\n abortSignal?.removeEventListener('abort', onAbort)\n resolve(c)\n })\n abortSignal?.addEventListener('abort', onAbort, { once: true })\n })\n }\n if (chunk !== null) yield chunk\n }\n })()\n },\n async send(messages, data, abortSignal, runContext) {\n let hasTerminalEvent = false\n let upstreamThreadId: string | undefined\n let upstreamRunId: string | undefined\n try {\n const stream = connection.connect(\n messages,\n data,\n abortSignal,\n runContext,\n )\n for await (const chunk of stream) {\n if ('threadId' in chunk && typeof chunk.threadId === 'string') {\n upstreamThreadId = chunk.threadId\n }\n if ('runId' in chunk && typeof chunk.runId === 'string') {\n upstreamRunId = chunk.runId\n }\n if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {\n hasTerminalEvent = true\n }\n push(chunk, runContext?.runId)\n }\n\n // If the connect stream ended cleanly without a terminal event,\n // synthesize RUN_FINISHED so request-scoped consumers can complete.\n // The event payload may carry an upstream/provider runId when one was\n // observed, but stamp the caller's request runId so getChunkRunId()\n // correlates to activeRunIds / currentRunId (same as real stream chunks).\n if (!abortSignal?.aborted && !hasTerminalEvent) {\n push(\n withTanstackMetadata(\n {\n type: EventType.RUN_FINISHED,\n threadId: requireSyntheticId(\n upstreamThreadId ?? runContext?.threadId,\n 'threadId',\n ),\n runId: requireSyntheticId(\n upstreamRunId ?? runContext?.runId,\n 'runId',\n ),\n timestamp: Date.now(),\n },\n { finishReason: 'stop', model: 'connect-wrapper' },\n ) as StreamChunk,\n runContext?.runId,\n )\n }\n } catch (err) {\n if (!abortSignal?.aborted && !hasTerminalEvent) {\n // Guard synthesis: requireSyntheticId throws when no id is available,\n // and that must not replace the original `err` we are about to\n // rethrow. If we can't synthesize a terminal, the real failure still\n // surfaces below.\n try {\n const message =\n err instanceof Error ? err.message : 'Unknown error in connect()'\n const synthetic: RunErrorEvent = {\n type: EventType.RUN_ERROR,\n threadId: requireSyntheticId(\n upstreamThreadId ?? runContext?.threadId,\n 'threadId',\n ),\n runId: requireSyntheticId(\n upstreamRunId ?? runContext?.runId,\n 'runId',\n ),\n timestamp: Date.now(),\n message,\n }\n push(synthetic, runContext?.runId)\n } catch {\n // fall through to rethrow the original error\n }\n }\n throw err\n }\n await waitUntilSubscriberIdle(abortSignal)\n },\n // Expose joinRun only when the underlying connection is resumable. Require\n // a real function — `'joinRun' in connection` is true for\n // `{ joinRun: undefined }`, which would wrap a non-callable and throw on\n // rehydration rejoin.\n ...(typeof (connection as ResumableConnectConnectionAdapter).joinRun ===\n 'function'\n ? {\n joinRun: (runId: string, abortSignal?: AbortSignal) =>\n (connection as ResumableConnectConnectionAdapter).joinRun(\n runId,\n abortSignal,\n ),\n }\n : {}),\n ...(() => {\n // Capture under the typeof guard so `hydrate` narrows to the function type\n // (no non-null assertion). Present only when the connection supports it.\n const hydrate = (connection as ResumableConnectConnectionAdapter).hydrate\n return typeof hydrate === 'function'\n ? { hydrate: (threadId: string) => hydrate(threadId) }\n : {}\n })(),\n }\n}\n\n/**\n * Options for fetch-based connection adapters\n */\nexport interface FetchConnectionOptions {\n headers?: Record<string, string> | Headers\n credentials?: RequestCredentials\n signal?: AbortSignal\n body?: Record<string, any>\n fetchClient?: typeof globalThis.fetch\n /** Bounding for resumable-SSE reconnection (throttle delay + attempt ceiling). */\n reconnect?: ReconnectOptions\n}\n\n/**\n * Options for XHR-based connection adapters.\n */\nexport interface XhrConnectionOptions {\n headers?: Record<string, string> | Headers\n withCredentials?: boolean\n signal?: AbortSignal\n body?: Record<string, any>\n xhrFactory?: () => XMLHttpRequest\n /** Bounding for resumable reconnection (throttle delay + attempt ceiling). */\n reconnect?: ReconnectOptions\n}\n\ntype ResolvedConnectionOptions = Pick<\n FetchConnectionOptions,\n 'body' | 'headers'\n>\n\nfunction buildRunAgentInputBody(\n messages: Array<UIMessage> | Array<ModelMessage>,\n data: Record<string, any> | undefined,\n runContext: RunAgentInputContext | undefined,\n options: ResolvedConnectionOptions,\n): Record<string, unknown> {\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps`, overridden by per-message `data`.\n const wireMessages = uiMessagesToWire(messages)\n const forwardedProps = {\n ...options.body,\n ...(runContext?.forwardedProps ?? {}),\n ...data,\n }\n\n return {\n threadId: runContext?.threadId ?? generateRunId('thread'),\n runId: runContext?.runId ?? generateRunId('run'),\n ...(runContext?.parentRunId !== undefined && {\n parentRunId: runContext.parentRunId,\n }),\n ...(runContext?.resume !== undefined && { resume: runContext.resume }),\n state: {},\n messages: wireMessages,\n tools: runContext?.clientTools ?? [],\n context: [],\n forwardedProps,\n // Backward-compat mirror of `forwardedProps` under the legacy field name.\n data: { ...forwardedProps },\n }\n}\n\n/**\n * Create a Server-Sent Events connection adapter\n *\n * @param url - The API endpoint URL (or a function that returns the URL)\n * @param options - Fetch options (headers, credentials, body, etc.) or a function that returns options (can be async)\n * @returns A connection adapter for SSE streams\n *\n * @example\n * ```typescript\n * // Static URL\n * const connection = fetchServerSentEvents('/api/chat');\n *\n * // Dynamic URL\n * const connection = fetchServerSentEvents(() => `/api/chat?user=${userId}`);\n *\n * // With options\n * const connection = fetchServerSentEvents('/api/chat', {\n * headers: { 'Authorization': 'Bearer token' }\n * });\n *\n * // With dynamic options\n * const connection = fetchServerSentEvents('/api/chat', () => ({\n * headers: { 'Authorization': `Bearer ${getToken()}` }\n * }));\n *\n * // With additional body data\n * const connection = fetchServerSentEvents('/api/chat', async () => ({\n * body: {\n * provider: 'openai',\n * model: 'gpt-5.5',\n * }\n * }));\n * ```\n */\nexport function fetchServerSentEvents(\n url: string | (() => string),\n options:\n | FetchConnectionOptions\n | (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},\n): ResumableConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n // Resolve URL and options if they are functions\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(resolvedOptions.headers),\n ...mergeHeaders(runContext?.headers),\n ...runIdHeader(runContext?.runId),\n }\n\n // Build AG-UI RunAgentInput payload.\n //\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps` (constructor body /\n // forwardedProps options), overridden by per-message `data` passed\n // to `connection.send`. Runtime values win over static config —\n // this matches the documented \"forwardedProps wins\" semantic.\n const requestBody = buildRunAgentInputBody(\n messages,\n data,\n runContext,\n resolvedOptions,\n )\n\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n // `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`\n // under `exactOptionalPropertyTypes`), so spread it conditionally\n // rather than passing `undefined` explicitly.\n const signal = abortSignal || resolvedOptions.signal\n // POST URL is byte-identical to a plain request; the run id (when set)\n // rides in the X-Run-Id header so durability can key the log by it\n // without changing the request URL existing clients rely on.\n const requestUrl = resolvedUrl\n\n // Resumable SSE: if the server tags events with `id:` offsets (delivery\n // durability), a dropped/rolled-over connection auto-reconnects with a\n // `Last-Event-ID` header and de-dupes the replayed prefix. With no tags,\n // this is a single plain fetch.\n yield* resumableStream(\n fetchEventSource(\n fetchClient,\n requestUrl,\n {\n method: 'POST',\n headers: requestHeaders,\n body: JSON.stringify(requestBody),\n credentials: resolvedOptions.credentials || 'same-origin',\n },\n // Thread the run's ids so a `[DONE]`-terminating server that doesn't\n // stamp them onto events still yields a correlated terminal (parity\n // with the XHR adapter's xhrSSEParser).\n (response, sseSignal) =>\n responseToSSEEvents(response, sseSignal, {\n ...(runContext?.threadId !== undefined\n ? { threadId: runContext.threadId }\n : {}),\n ...(runContext?.runId !== undefined\n ? { runId: runContext.runId }\n : {}),\n }),\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async *joinRun(runId, abortSignal) {\n // Read an in-flight or finished run from the start. `?offset=-1` tells the\n // server's delivery-durability sink to replay from the beginning; `runId`\n // identifies which run. This is a read-only GET — no messages are sent.\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const joinUrl = withSearchParams(resolvedUrl, {\n offset: '-1',\n runId,\n })\n\n const requestHeaders: Record<string, string> = {\n ...mergeHeaders(resolvedOptions.headers),\n }\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n const signal = abortSignal || resolvedOptions.signal\n\n yield* resumableStream(\n fetchEventSource(\n fetchClient,\n joinUrl,\n {\n method: 'GET',\n headers: requestHeaders,\n credentials: resolvedOptions.credentials || 'same-origin',\n },\n // A `[DONE]` during a join correlates to the joined run id.\n (response, sseSignal) =>\n responseToSSEEvents(response, sseSignal, { runId }),\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async hydrate(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n return fetchThreadHydration(\n resolvedOptions.fetchClient ?? fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.credentials || 'same-origin',\n threadId,\n )\n },\n async hydrateGeneration(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n return fetchGenerationHydration(\n resolvedOptions.fetchClient ?? fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.credentials || 'same-origin',\n threadId,\n )\n },\n }\n}\n\n/**\n * Create an HTTP streaming connection adapter (for raw streaming without SSE format)\n *\n * @param url - The API endpoint URL (or a function that returns the URL)\n * @param options - Fetch options (headers, credentials, body, etc.) or a function that returns options (can be async)\n * @returns A connection adapter for HTTP streams\n *\n * @example\n * ```typescript\n * // Static URL\n * const connection = fetchHttpStream('/api/chat');\n *\n * // Dynamic URL\n * const connection = fetchHttpStream(() => `/api/chat?user=${userId}`);\n *\n * // With options\n * const connection = fetchHttpStream('/api/chat', {\n * headers: { 'Authorization': 'Bearer token' }\n * });\n *\n * // With dynamic options\n * const connection = fetchHttpStream('/api/chat', () => ({\n * headers: { 'Authorization': `Bearer ${getToken()}` }\n * }));\n *\n * // With additional body data\n * const connection = fetchHttpStream('/api/chat', async () => ({\n * body: {\n * provider: 'openai',\n * model: 'gpt-5.5',\n * }\n * }));\n * ```\n */\nexport function fetchHttpStream(\n url: string | (() => string),\n options:\n | FetchConnectionOptions\n | (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},\n): ResumableConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n // Resolve URL and options if they are functions\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(resolvedOptions.headers),\n ...mergeHeaders(runContext?.headers),\n ...runIdHeader(runContext?.runId),\n }\n\n // Build AG-UI RunAgentInput payload.\n //\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps` (constructor body /\n // forwardedProps options), overridden by per-message `data` passed\n // to `connection.send`. Runtime values win over static config —\n // this matches the documented \"forwardedProps wins\" semantic.\n const requestBody = buildRunAgentInputBody(\n messages,\n data,\n runContext,\n resolvedOptions,\n )\n\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n // `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`\n // under `exactOptionalPropertyTypes`), so spread it conditionally\n // rather than passing `undefined` explicitly.\n const signal = abortSignal || resolvedOptions.signal\n // POST URL is byte-identical to a plain request; the run id (when set)\n // rides in the X-Run-Id header so durability can key the log by it\n // without changing the request URL existing clients rely on.\n const requestUrl = resolvedUrl\n\n // Resumable NDJSON: if the server envelopes each line with an\n // `{ id, chunk }` offset (delivery durability), a dropped/rolled-over\n // connection auto-reconnects with a `Last-Event-ID` header and de-dupes\n // the replayed prefix. With bare lines (no durability), this is a single\n // plain fetch — identical to before.\n yield* resumableStream(\n fetchEventSource(\n fetchClient,\n requestUrl,\n {\n method: 'POST',\n headers: requestHeaders,\n body: JSON.stringify(requestBody),\n credentials: resolvedOptions.credentials || 'same-origin',\n },\n responseToNdjsonEvents,\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async *joinRun(runId, abortSignal) {\n // Read an in-flight or finished run from the start. `?offset=-1` tells the\n // server's delivery-durability sink to replay from the beginning; `runId`\n // identifies which run. This is a read-only GET — no messages are sent.\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const joinUrl = withSearchParams(resolvedUrl, { offset: '-1', runId })\n const requestHeaders: Record<string, string> = {\n ...mergeHeaders(resolvedOptions.headers),\n }\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n const signal = abortSignal || resolvedOptions.signal\n\n yield* resumableStream(\n fetchEventSource(\n fetchClient,\n joinUrl,\n {\n method: 'GET',\n headers: requestHeaders,\n credentials: resolvedOptions.credentials || 'same-origin',\n },\n responseToNdjsonEvents,\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async hydrate(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n return fetchThreadHydration(\n resolvedOptions.fetchClient ?? fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.credentials || 'same-origin',\n threadId,\n )\n },\n async hydrateGeneration(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n return fetchGenerationHydration(\n resolvedOptions.fetchClient ?? fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.credentials || 'same-origin',\n threadId,\n )\n },\n }\n}\n\ntype XhrConnectionOptionsResolver =\n | XhrConnectionOptions\n | (() => XhrConnectionOptions | Promise<XhrConnectionOptions>)\n\nfunction createDefaultXMLHttpRequest(): XMLHttpRequest {\n if (typeof globalThis.XMLHttpRequest !== 'function') {\n throw new Error('XMLHttpRequest is not available in this runtime')\n }\n\n return new globalThis.XMLHttpRequest()\n}\n\nfunction cleanupXhr(\n xhr: XMLHttpRequest,\n abortSignal: AbortSignal | undefined,\n onAbort: (() => void) | undefined,\n): void {\n xhr.onprogress = null\n xhr.onload = null\n xhr.onerror = null\n xhr.onabort = null\n xhr.onloadend = null\n\n if (abortSignal && onAbort) {\n abortSignal.removeEventListener('abort', onAbort)\n }\n}\n\nfunction readXhrLines(\n xhr: XMLHttpRequest,\n abortSignal?: AbortSignal,\n): AsyncGenerator<string> {\n let offset = 0\n let buffer = ''\n const lines: Array<string> = []\n const waiters: Array<() => void> = []\n let done = false\n let aborted = false\n let error: unknown\n let onAbort: (() => void) | undefined\n\n const wake = () => {\n const waiter = waiters.shift()\n waiter?.()\n }\n\n const enqueueDelta = () => {\n if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {\n error = errorFromXhrStatus(xhr)\n done = true\n return\n }\n\n const responseText = xhr.responseText\n if (responseText.length <= offset) {\n return\n }\n\n buffer += responseText.slice(offset)\n offset = responseText.length\n const splitLines = buffer.split('\\n')\n buffer = splitLines.pop() ?? ''\n\n for (const line of splitLines) {\n const normalized = line.endsWith('\\r') ? line.slice(0, -1) : line\n if (normalized.trim()) {\n lines.push(normalized)\n }\n }\n }\n\n const finish = () => {\n enqueueDelta()\n // Tolerate a transient status === 0 (matches enqueueDelta): a real non-2xx\n // is an error, but status 0 here is not — treat the trailing buffer as a\n // truncation check instead of fabricating a bogus \"status: 0\" error.\n if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {\n error = errorFromXhrStatus(xhr)\n } else if (buffer.trim() && !aborted) {\n error = new StreamTruncatedError()\n }\n done = true\n wake()\n }\n\n xhr.onprogress = () => {\n enqueueDelta()\n wake()\n }\n xhr.onload = finish\n xhr.onerror = () => {\n // Surface as StreamReadError so a durable (id-tagged) run whose socket\n // drops mid-stream is eligible for auto-reconnect, matching the fetch path.\n // A non-durable run has no offset, so resumableStream rethrows it as-is.\n error = new StreamReadError(new Error('XHR request failed'))\n done = true\n wake()\n }\n xhr.onabort = () => {\n aborted = true\n done = true\n wake()\n }\n xhr.onloadend = () => {\n if (!done) {\n finish()\n }\n }\n\n if (abortSignal) {\n onAbort = () => {\n aborted = true\n xhr.abort()\n }\n if (abortSignal.aborted) {\n onAbort()\n } else {\n abortSignal.addEventListener('abort', onAbort, { once: true })\n }\n }\n\n return (async function* () {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const line = lines.shift()\n if (line !== undefined) {\n yield line\n continue\n }\n\n if (error) {\n throw error\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (done || abortSignal?.aborted) {\n return\n }\n\n await new Promise<void>((resolve) => {\n waiters.push(resolve)\n })\n }\n } finally {\n cleanupXhr(xhr, abortSignal, onAbort)\n }\n })()\n}\n\ninterface ConfiguredXhrRequest {\n xhr: XMLHttpRequest\n body: string\n}\n\nfunction createConfiguredXhrRequest(\n url: string,\n options: XhrConnectionOptions,\n messages: Array<UIMessage> | Array<ModelMessage>,\n data: Record<string, any> | undefined,\n runContext: RunAgentInputContext | undefined,\n method: string = 'POST',\n extraHeaders: Record<string, string> = {},\n): ConfiguredXhrRequest {\n const xhr = options.xhrFactory?.() ?? createDefaultXMLHttpRequest()\n xhr.open(method, url)\n if (options.withCredentials !== undefined) {\n xhr.withCredentials = options.withCredentials\n }\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(options.headers),\n ...mergeHeaders(method === 'POST' ? runContext?.headers : undefined),\n // Client-chosen run id for durability (POST only; the GET join carries it\n // in the query instead).\n ...(method === 'POST' ? runIdHeader(runContext?.runId) : {}),\n // Reconnect offset (`Last-Event-ID`) wins over static headers.\n ...extraHeaders,\n }\n\n for (const [name, value] of Object.entries(requestHeaders)) {\n xhr.setRequestHeader(name, value)\n }\n\n const requestBody = buildRunAgentInputBody(\n messages,\n data,\n runContext,\n options,\n )\n\n return { xhr, body: JSON.stringify(requestBody) }\n}\n\nasync function resolveXhrConnectionOptions(\n options: XhrConnectionOptionsResolver,\n): Promise<XhrConnectionOptions> {\n return typeof options === 'function' ? await options() : options\n}\n\n/**\n * Build an XHR-backed {@link StreamEventSource}. `parseLines` decodes the raw\n * newline-delimited body into events (SSE or NDJSON); the reconnect engine is\n * shared with the fetch adapters. A fresh XHR is opened per attempt, so a\n * `Last-Event-ID` reconnect header (via `extraHeaders`) is applied at open time.\n */\nfunction xhrEventSource(\n url: string,\n options: XhrConnectionOptions,\n method: string,\n messages: Array<UIMessage> | Array<ModelMessage>,\n data: Record<string, any> | undefined,\n runContext: RunAgentInputContext | undefined,\n parseLines: (lines: AsyncIterable<string>) => AsyncIterable<StreamEvent>,\n): StreamEventSource {\n return async function* (extraHeaders, abortSignal) {\n const request = createConfiguredXhrRequest(\n url,\n options,\n messages,\n data,\n runContext,\n method,\n extraHeaders,\n )\n const lines = readXhrLines(request.xhr, abortSignal)\n if (abortSignal?.aborted) {\n await lines.next()\n return\n }\n // A read-only join is a bodyless GET; a run POSTs the RunAgentInput payload.\n request.xhr.send(method === 'GET' ? null : request.body)\n try {\n yield* parseLines(lines)\n } finally {\n // Tear the socket down on an early exit (terminal reached or reconnect\n // break) so late bytes stop downloading. When the abort signal fired,\n // `readXhrLines` already aborted — skip here to avoid a double abort().\n if (!abortSignal?.aborted) request.xhr.abort()\n }\n }\n}\n\n/** SSE line parser bound to the run's ids for a `[DONE]` fallback. */\nfunction xhrSSEParser(runContext: RunAgentInputContext | undefined) {\n const fallbackIds: { threadId?: string; runId?: string } = {\n ...(runContext?.threadId !== undefined\n ? { threadId: runContext.threadId }\n : {}),\n ...(runContext?.runId !== undefined ? { runId: runContext.runId } : {}),\n }\n return (lines: AsyncIterable<string>) => linesToSSEEvents(lines, fallbackIds)\n}\n\n/**\n * Create an XMLHttpRequest-backed Server-Sent Events connection adapter.\n *\n * Resumable: against a durable (`id:`-tagged) server response, a dropped socket\n * auto-reconnects with `Last-Event-ID` and de-dupes the replayed prefix, and\n * `joinRun` attaches to an existing run from the start. A non-durable response\n * is a single plain request, exactly as before.\n */\nexport function xhrServerSentEvents(\n url: string | (() => string),\n options: XhrConnectionOptionsResolver = {},\n): ResumableConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n // POST URL is byte-identical to a plain request; the run id (when set)\n // rides in the X-Run-Id header so durability can key the log by it\n // without changing the request URL existing clients rely on.\n const requestUrl = resolvedUrl\n yield* resumableStream(\n xhrEventSource(\n requestUrl,\n resolvedOptions,\n 'POST',\n messages,\n data,\n runContext,\n xhrSSEParser(runContext),\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async *joinRun(runId, abortSignal) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n const joinUrl = withSearchParams(resolvedUrl, { offset: '-1', runId })\n yield* resumableStream(\n xhrEventSource(\n joinUrl,\n resolvedOptions,\n 'GET',\n [],\n undefined,\n undefined,\n // A `[DONE]` during a join correlates to the joined run id (parity\n // with fetchServerSentEvents.joinRun).\n (lines) => linesToSSEEvents(lines, { runId }),\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async hydrate(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n // Hydration is a non-streaming JSON GET, so fetch is fine even for the\n // XHR-backed streaming adapter.\n return fetchThreadHydration(\n fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.withCredentials ? 'include' : 'same-origin',\n threadId,\n )\n },\n async hydrateGeneration(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n // Hydration is a non-streaming JSON GET, so fetch is fine even for the\n // XHR-backed streaming adapter.\n return fetchGenerationHydration(\n fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.withCredentials ? 'include' : 'same-origin',\n threadId,\n )\n },\n }\n}\n\n/**\n * Create an XMLHttpRequest-backed newline-delimited JSON stream adapter.\n *\n * Resumable: against a durable (envelope-tagged) server response, a dropped\n * socket auto-reconnects with `Last-Event-ID` and de-dupes the replayed prefix,\n * and `joinRun` attaches to an existing run from the start. A non-durable\n * (bare-line) response is a single plain request, exactly as before.\n */\nexport function xhrHttpStream(\n url: string | (() => string),\n options: XhrConnectionOptionsResolver = {},\n): ResumableConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n // POST URL is byte-identical to a plain request; the run id (when set)\n // rides in the X-Run-Id header so durability can key the log by it\n // without changing the request URL existing clients rely on.\n const requestUrl = resolvedUrl\n yield* resumableStream(\n xhrEventSource(\n requestUrl,\n resolvedOptions,\n 'POST',\n messages,\n data,\n runContext,\n linesToNdjsonEvents,\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async *joinRun(runId, abortSignal) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n const joinUrl = withSearchParams(resolvedUrl, { offset: '-1', runId })\n yield* resumableStream(\n xhrEventSource(\n joinUrl,\n resolvedOptions,\n 'GET',\n [],\n undefined,\n undefined,\n linesToNdjsonEvents,\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async hydrate(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n // Hydration is a non-streaming JSON GET, so fetch is fine even for the\n // XHR-backed streaming adapter.\n return fetchThreadHydration(\n fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.withCredentials ? 'include' : 'same-origin',\n threadId,\n )\n },\n async hydrateGeneration(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n // Hydration is a non-streaming JSON GET, so fetch is fine even for the\n // XHR-backed streaming adapter.\n return fetchGenerationHydration(\n fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.withCredentials ? 'include' : 'same-origin',\n threadId,\n )\n },\n }\n}\n\nexport interface WebSocketConnectionOptions {\n protocols?: string | Array<string>\n body?: Record<string, unknown>\n reconnect?: ReconnectOptions\n /** Override the WebSocket implementation (tests / non-browser runtimes). */\n WebSocketImpl?: typeof WebSocket\n}\n\nfunction runIdQuery(url: string, runId: string | undefined): string {\n return runId ? withSearchParams(url, { runId }) : url\n}\n\nfunction isPingFrame(parsed: unknown): boolean {\n return (\n typeof parsed === 'object' &&\n parsed !== null &&\n (parsed as { type?: unknown }).type === 'ping'\n )\n}\n\n/** A subscribe() consumer's registration: receives chunks or a fatal error. */\ninterface WebSocketChunkSink {\n push: (chunk: StreamChunk) => void\n fail: (error: unknown) => void\n}\n\n/**\n * A push→pull bridge from socket callbacks to an async iterable: chunks queue\n * until the consumer pulls, a recorded failure rejects the iterator, and\n * `end()` (or the abort signal) finishes it cleanly. Shared by `webSocket()`'s\n * `subscribe()` and `joinRun()`.\n */\nfunction createChunkPipe(\n abortSignal: AbortSignal | undefined,\n onFinally: () => void,\n): {\n push: (chunk: StreamChunk) => void\n fail: (error: unknown) => void\n end: () => void\n iterable: AsyncIterable<StreamChunk>\n} {\n const queue: Array<StreamChunk> = []\n const waiters: Array<(c: StreamChunk | null) => void> = []\n let failure: unknown\n let ended = false\n const wake = () => waiters.shift()?.(null)\n const push = (chunk: StreamChunk) => {\n const w = waiters.shift()\n if (w) w(chunk)\n else queue.push(chunk)\n }\n const fail = (error: unknown) => {\n failure = error\n wake()\n }\n const end = () => {\n ended = true\n wake()\n }\n const onAbort = () => wake()\n abortSignal?.addEventListener('abort', onAbort)\n const iterable = (async function* () {\n try {\n while (!abortSignal?.aborted) {\n // Drain buffered chunks before ever awaiting a new promise — a\n // fatal drop that lands while chunks are still queued (fail()\n // finds no pending waiter, since the consumer hasn't caught up\n // to its buffer yet) must not be lost.\n const buffered = queue.shift()\n if (buffered !== undefined) {\n yield buffered\n continue\n }\n // Buffer exhausted: surface a failure recorded while we were\n // draining, rather than awaiting a promise that will never\n // resolve (the connection is dead — no future push/fail).\n if (failure !== undefined) throw failure\n if (ended) return\n const chunk = await new Promise<StreamChunk | null>((r) =>\n waiters.push(r),\n )\n // The wait resolved because fail() woke us — surface the error\n // instead of treating the null sentinel as a clean end. TS narrows\n // `failure` to `undefined` from the check above and doesn't know\n // the `fail()` closure can reassign it while we were awaiting —\n // this check is very much still reachable.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (failure !== undefined) throw failure\n if (chunk === null) return\n yield chunk\n }\n } finally {\n abortSignal?.removeEventListener('abort', onAbort)\n onFinally()\n }\n })()\n return { push, fail, end, iterable }\n}\n\n/**\n * The send()-driven run currently owning auto-reconnect for a `webSocket()`\n * connection: reconnect is scoped to the run `send()` is driving, so a drop\n * with no established run is surfaced to subscribers rather than auto-resumed.\n */\ninterface WebSocketRunSession {\n runId: string | undefined\n readonly tracker: ReconnectTracker\n sawTerminal: boolean\n /** Made forward progress (a new, non-duplicate chunk) since the last (re)connect. */\n progressed: boolean\n signal: AbortSignal | undefined\n}\n\n/**\n * Full-duplex, conversation-scoped WebSocket connection adapter. Pairs with the\n * server `toWebSocketResponse` / `toWebSocketStream`. `send()` writes a\n * RunAgentInput frame; `subscribe()` yields inbound chunks.\n *\n * Resumable: `send()` establishes a run session backed by a\n * {@link createReconnectTracker}. If the socket closes before a terminal\n * (`RUN_FINISHED`/`RUN_ERROR`) chunk is seen and the run is durable\n * (offset-tagged `{ id, chunk }` envelopes), the socket is reopened at\n * `?runId=&offset=<lastEventId>`, de-duping the replayed boundary. A drop with\n * no offset ever observed (non-durable) surfaces {@link StreamReadError}\n * instead of reconnecting — there is nothing to resume from.\n */\nexport function webSocket(\n url: string | (() => string),\n options: WebSocketConnectionOptions = {},\n): SubscribeConnectionAdapter & {\n joinRun: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n} {\n const Impl = options.WebSocketImpl ?? WebSocket\n let socket: WebSocket | undefined\n // Whether the current socket is the conversation socket ('run') or a\n // read-only replay connection opened by a reconnect ('resume'). Only the\n // conversation socket accepts run frames server-side.\n let socketMode: 'run' | 'resume' | undefined\n // Memoized per-socket open promise. `openOnce` sets `onopen`/`onerror`\n // exactly ONCE, at socket-creation time, and stores the resulting promise\n // here. Without this, `waitOpen` assigning `onopen`/`onerror` on every call\n // would clobber a still-pending prior caller's handlers: `openOnce` reuses\n // the same in-flight socket for concurrent callers (`readyState <= 1`), so a\n // second `send()` issued before the handshake completes would overwrite the\n // first call's handlers and leave its promise permanently unresolved.\n let openPromise: Promise<void> | undefined\n const listeners = new Set<WebSocketChunkSink>()\n let currentSession: WebSocketRunSession | undefined\n\n function failAll(error: unknown): void {\n for (const l of listeners) l.fail(error)\n }\n\n function openOnce(target: string, mode: 'run' | 'resume'): WebSocket {\n // Only the conversation socket is reused — it multiplexes many turns. A\n // 'resume' handshake carries ?offset and must reach the server as its own\n // connection (reusing any open socket would discard that query, so no\n // replay would ever be requested), and a run frame must never be written\n // to a read-only resume socket (the server registers no message listener\n // there, so the frame would be silently ignored).\n if (\n socket &&\n socket.readyState <= 1 &&\n mode === 'run' &&\n socketMode === 'run'\n ) {\n return socket\n }\n const prior = socket\n const ws = options.protocols\n ? new Impl(target, options.protocols)\n : new Impl(target)\n socket = ws\n socketMode = mode\n openPromise = new Promise<void>((resolve, reject) => {\n ws.onopen = () => resolve()\n ws.onerror = (e) => reject(new StreamReadError(e))\n })\n // Attach a no-op handler so a socket nobody awaits can't raise an\n // unhandled rejection if it errors. Awaiters of openPromise still see the rejection.\n openPromise.catch(() => {})\n ws.onmessage = (event: MessageEvent) => {\n // A retired socket (a newer connection took over below) must not keep\n // feeding the shared listeners.\n if (ws !== socket) return\n let parsed: unknown\n try {\n parsed = JSON.parse(String(event.data))\n } catch (error) {\n failAll(new StreamReadError(error))\n return\n }\n if (isPingFrame(parsed)) return\n const envelopeId = isNdjsonEnvelope(parsed) ? parsed.id : undefined\n const chunk = restoreInboundUsage(\n isNdjsonEnvelope(parsed) ? parsed.chunk : (parsed as StreamChunk),\n )\n\n // Thread durable chunks through the active run session's tracker (if\n // any) so a later reconnect knows the last offset and can skip a\n // replayed boundary. A socket with no active session dispatches chunks\n // as-is.\n const session = currentSession\n if (session) {\n if (session.tracker.note(envelopeId) === 'duplicate') return\n session.progressed = true\n if (session.runId === undefined) {\n session.runId = getChunkRunId(chunk)\n }\n if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {\n session.sawTerminal = true\n }\n }\n for (const l of listeners) l.push(chunk)\n }\n ws.onclose = () => {\n // Retired deliberately in favor of a newer connection — not a drop.\n if (ws !== socket) return\n const session = currentSession\n if (!session) {\n // No run session (never established, or cleared by a prior failure).\n // Surface the drop so subscribers do not stay parked on a dead socket.\n failAll(new StreamReadError(new Error('WebSocket connection closed')))\n return\n }\n if (session.signal?.aborted || session.sawTerminal) return\n const lastEventId = session.tracker.lastEventId\n if (lastEventId === undefined) {\n // Non-durable run (no offset ever observed) — nothing to resume\n // from. Surface a hard failure rather than silently reconnecting\n // forever against a server that never tags its events.\n currentSession = undefined\n failAll(new StreamReadError(new Error('WebSocket connection closed')))\n return\n }\n void reconnect(session, lastEventId)\n }\n // Retire a superseded socket (e.g. a lingering resume socket when send()\n // opens the next conversation socket) so two sockets never feed the\n // shared listeners at once. Its handlers see it is no longer current and\n // ignore the close.\n if (prior && prior.readyState <= 1) prior.close()\n return ws\n }\n\n async function reconnect(\n session: WebSocketRunSession,\n offset: string,\n ): Promise<void> {\n try {\n // Bounded by the shared tracker's consecutive-no-progress ceiling —\n // mirrors resumableStream so a flapping server can't reconnect forever.\n await session.tracker.waitBeforeReconnect(\n session.progressed,\n session.signal,\n )\n } catch (error) {\n if (currentSession === session) currentSession = undefined\n failAll(error)\n return\n }\n if (session.signal?.aborted) return\n // A send() issued during the backoff supersedes this resume: a newer run\n // (or a resubmit of this one) already owns a fresh conversation socket,\n // and its turn re-delivers from the durability log — the tracker de-dupes\n // any overlap. Opening the resume socket anyway would retire that live\n // conversation socket.\n if (currentSession !== session) return\n if (socket && socket.readyState <= 1) return\n session.progressed = false\n const base = typeof url === 'function' ? url() : url\n const target = withSearchParams(base, {\n ...(session.runId !== undefined ? { runId: session.runId } : {}),\n offset,\n })\n openOnce(target, 'resume')\n }\n\n function waitOpen(ws: WebSocket): Promise<void> {\n if (ws.readyState === 1) return Promise.resolve()\n // Concurrent callers awaiting the SAME in-flight socket share the SAME\n // memoized promise (set once in `openOnce`), so none of them clobber\n // another's onopen/onerror handler.\n return openPromise ?? Promise.resolve()\n }\n\n return {\n subscribe(abortSignal?: AbortSignal): AsyncIterable<StreamChunk> {\n const pipe = createChunkPipe(abortSignal, () => listeners.delete(sink))\n const sink: WebSocketChunkSink = { push: pipe.push, fail: pipe.fail }\n listeners.add(sink)\n return pipe.iterable\n },\n async send(messages, data, abortSignal, runContext) {\n const target = typeof url === 'function' ? url() : url\n const ws = openOnce(runIdQuery(target, runContext?.runId), 'run')\n await waitOpen(ws)\n // Establish (or continue) the run session this socket is driving, so\n // an unterminated drop can auto-resume it. A distinct runId starts a\n // fresh tracker (a new run's offsets are unrelated to the last one's);\n // the same runId reuses the tracker so a repeat send() on an\n // already-tracked run doesn't lose its de-dupe/offset state.\n if (!currentSession || currentSession.runId !== runContext?.runId) {\n currentSession = {\n runId: runContext?.runId,\n tracker: createReconnectTracker(options.reconnect),\n sawTerminal: false,\n progressed: false,\n signal: abortSignal,\n }\n } else {\n // Same-runId resubmit (e.g. a client-tool continuation): keep the\n // tracker, but this is a NEW turn — with the previous turn's\n // `sawTerminal` left set, a drop during the resubmitted turn would\n // neither reconnect nor surface an error.\n currentSession.signal = abortSignal\n currentSession.sawTerminal = false\n currentSession.progressed = false\n }\n const session = currentSession\n // stop() must reach the server: the conversation socket outlives the\n // turn, so without an abort frame the model keeps generating (and\n // billing) server-side. The frame aborts only this run's turn.\n abortSignal?.addEventListener(\n 'abort',\n () => {\n const abortRunId = session.runId\n const live = socket\n if (\n abortRunId === undefined ||\n session.sawTerminal ||\n socketMode !== 'run' ||\n live === undefined ||\n live.readyState !== 1\n ) {\n return\n }\n try {\n live.send(JSON.stringify({ type: 'abort', runId: abortRunId }))\n } catch {\n // Socket is CLOSING/CLOSED — the server aborts the turn on close.\n }\n },\n { once: true },\n )\n const body = buildRunAgentInputBody(messages, data, runContext, {\n body: options.body,\n })\n ws.send(JSON.stringify(body))\n },\n joinRun(runId, abortSignal): AsyncIterable<StreamChunk> {\n const target = withSearchParams(typeof url === 'function' ? url() : url, {\n offset: '-1',\n runId,\n })\n // A replay handshake must reach the server as its own connection:\n // reusing the conversation socket would discard the ?offset query (no\n // replay ever requested), and the conversation socket must not be\n // replaced by a read-only replay socket. So joinRun owns a dedicated\n // socket and never touches the shared socket or run session.\n const ws = options.protocols\n ? new Impl(target, options.protocols)\n : new Impl(target)\n const pipe = createChunkPipe(abortSignal, () => {\n if (ws.readyState <= 1) ws.close()\n })\n ws.onmessage = (event: MessageEvent) => {\n let parsed: unknown\n try {\n parsed = JSON.parse(String(event.data))\n } catch (error) {\n pipe.fail(new StreamReadError(error))\n return\n }\n if (isPingFrame(parsed)) return\n pipe.push(\n restoreInboundUsage(\n isNdjsonEnvelope(parsed) ? parsed.chunk : (parsed as StreamChunk),\n ),\n )\n }\n ws.onclose = (event?: CloseEvent) => {\n // 1000 = the server finished replaying the log and closed cleanly.\n // Anything else is a drop or a policy refusal (e.g. 1008 \"no resume\n // offset\") and must surface — a joinRun socket never auto-reconnects.\n if (event?.code === 1000) {\n pipe.end()\n return\n }\n const detail = event\n ? `${event.code}${event.reason ? `: ${event.reason}` : ''}`\n : 'unknown'\n pipe.fail(\n new StreamReadError(\n new Error(`WebSocket connection closed (${detail})`),\n ),\n )\n }\n return pipe.iterable\n },\n }\n}\n\n/**\n * Optional persistence handlers for the lightweight adapters (`stream()`,\n * `rpcStream()`). These are one-shot, request-scoped calls with no built-in\n * GET endpoint or second channel, so hydration and run-rejoin only exist if\n * the app supplies them — typically thin wrappers over TanStack Start server\n * functions backed by `@tanstack/ai-persistence` (`getGenerationHydration`)\n * and a delivery-durability log (`memoryStream` / `replayRunStream`).\n *\n * Each handler is spread onto the returned adapter only when defined, so\n * feature detection (`connection.hydrateGeneration` etc.) keeps working.\n */\nexport interface StreamConnectionHandlers {\n /**\n * Server-driven chat hydration for `persistence: true`: the stored\n * transcript for `threadId` plus a cursor to an in-flight run.\n */\n hydrate?: (threadId: string) => Promise<ChatHydrationResult>\n /**\n * Server-driven generation hydration for `persistence: true`: the last\n * generation's resume snapshot for `threadId` plus a cursor to a run still\n * generating. See {@link ConnectConnectionAdapter.hydrateGeneration}.\n */\n hydrateGeneration?: (threadId: string) => Promise<GenerationHydrationResult>\n /**\n * Re-attach to a run still generating and replay it from the start. See\n * {@link ConnectConnectionAdapter.joinRun}.\n */\n joinRun?: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n}\n\n/**\n * Create a direct stream connection adapter (for server functions or direct streams)\n *\n * @param streamFactory - A function that returns an async iterable of StreamChunks\n * @param handlers - Optional persistence handlers (`hydrate`,\n * `hydrateGeneration`, `joinRun`) that let server-driven persistence work\n * without an HTTP endpoint — each is usually a one-line server-function call\n * @returns A connection adapter for direct streams\n *\n * @example\n * ```typescript\n * // With TanStack Start server function\n * const connection = stream(() => serverFunction({ messages }));\n *\n * const client = new ChatClient({ connection });\n *\n * // With generation persistence over server functions\n * const connection = stream(\n * () => generateImageFn({ data: input }),\n * {\n * hydrateGeneration: (threadId) => getImageHydrationFn({ data: threadId }),\n * joinRun: (runId) => joinImageRunFn({ data: runId }),\n * },\n * );\n * ```\n */\nexport function stream(\n streamFactory: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>,\n handlers?: StreamConnectionHandlers,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal) {\n // Pass messages as-is (UIMessages with parts preserved)\n // Server-side chat() handles conversion to ModelMessages\n yield* streamFactory(messages, data, abortSignal)\n },\n ...(handlers?.hydrate ? { hydrate: handlers.hydrate } : {}),\n ...(handlers?.hydrateGeneration\n ? { hydrateGeneration: handlers.hydrateGeneration }\n : {}),\n ...(handlers?.joinRun ? { joinRun: handlers.joinRun } : {}),\n }\n}\n\n/**\n * Wrap a `ChatFetcher` as a `ConnectConnectionAdapter` so the chat client can\n * consume it through the same `subscribe`/`send` plumbing used for SSE /\n * HTTP-stream / RPC connections. May return either a `Response` (parsed as\n * SSE) or an `AsyncIterable<StreamChunk>` (yielded directly).\n *\n * @internal\n */\nexport function fetcherToConnectionAdapter(\n fetcher: ChatFetcher,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n if (!abortSignal) {\n throw new Error(\n 'fetcherToConnectionAdapter requires an AbortSignal — the chat client always supplies one.',\n )\n }\n if (!runContext) {\n throw new Error(\n 'fetcherToConnectionAdapter requires a RunAgentInputContext — the chat client always supplies one.',\n )\n }\n const uiMessages = messages as Array<UIMessage>\n const result = await fetcher(\n {\n messages: uiMessages,\n data,\n threadId: runContext.threadId,\n runId: runContext.runId,\n ...(runContext.parentRunId !== undefined\n ? { parentRunId: runContext.parentRunId }\n : {}),\n ...(runContext.resume !== undefined\n ? { resume: runContext.resume }\n : {}),\n },\n { signal: abortSignal, headers: runContext.headers },\n )\n if (result instanceof Response) {\n yield* responseToSSEChunks(result, abortSignal)\n } else {\n yield* abortableIterable(result, abortSignal)\n }\n },\n }\n}\n\n/**\n * Wrap an AsyncIterable so iteration aborts when `signal` fires. Without\n * this, a fetcher that returns a generator ignoring its signal would leave\n * the for-await loop hanging until the iterable naturally ends.\n */\nasync function* abortableIterable<T>(\n iterable: AsyncIterable<T>,\n signal: AbortSignal,\n): AsyncGenerator<T> {\n if (signal.aborted) return\n const iterator = iterable[Symbol.asyncIterator]()\n const abortPromise = new Promise<{ done: true; value: undefined }>(\n (resolve) => {\n signal.addEventListener(\n 'abort',\n () => resolve({ done: true, value: undefined }),\n { once: true },\n )\n },\n )\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const result = await Promise.race([iterator.next(), abortPromise])\n if (result.done) return\n yield result.value\n }\n } finally {\n await iterator.return?.()\n }\n}\n\n/**\n * Create an RPC stream connection adapter (for RPC-based streaming like Cap'n Web RPC)\n *\n * @param rpcCall - A function that accepts messages and returns an async iterable of StreamChunks\n * @param handlers - Optional persistence handlers (`hydrate`,\n * `hydrateGeneration`, `joinRun`) that let server-driven persistence work\n * without an HTTP endpoint — each is usually a one-line RPC call\n * @returns A connection adapter for RPC streams\n *\n * @example\n * ```typescript\n * // With Cap'n Web RPC\n * const connection = rpcStream((messages, data) =>\n * api.streamMurfResponse(messages, data)\n * );\n *\n * const client = new ChatClient({ connection });\n *\n * // With generation persistence over RPC\n * const connection = rpcStream(\n * (messages, data) => api.streamMurfResponse(messages, data),\n * {\n * hydrateGeneration: (threadId) => api.getGenerationHydration(threadId),\n * joinRun: (runId) => api.replayRun(runId),\n * },\n * );\n * ```\n */\nexport function rpcStream(\n rpcCall: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>,\n handlers?: StreamConnectionHandlers,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal) {\n // Pass messages as-is (UIMessages with parts preserved)\n // Server-side chat() handles conversion to ModelMessages\n yield* rpcCall(messages, data, abortSignal)\n },\n ...(handlers?.hydrate ? { hydrate: handlers.hydrate } : {}),\n ...(handlers?.hydrateGeneration\n ? { hydrateGeneration: handlers.hydrateGeneration }\n : {}),\n ...(handlers?.joinRun ? { joinRun: handlers.joinRun } : {}),\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"connection-adapters.js","names":[],"sources":["../../src/connection-adapters.ts"],"sourcesContent":["import {\n EventType,\n getChunkRunId as getNormalizedChunkRunId,\n restoreInboundChunk,\n tanstackMetadata,\n uiMessagesToWire,\n withTanstackMetadata,\n} from '@tanstack/ai/client'\nimport { ByokMissingError, isByokMissingBody } from '@tanstack/ai/byok'\nimport {\n createResponseStreamTextDecoder,\n getResponseStreamReader,\n} from './response-stream'\nimport { parseSseDataLine } from './sse-utils'\nimport type {\n ModelMessage,\n RunAgentResumeItem,\n RunErrorEvent,\n StreamChunk,\n UIMessage,\n} from '@tanstack/ai/client'\nimport type { ChatFetcher, ChatPendingInterrupt } from './types'\nimport { normalizeMessagesDates } from './message-date-normalizer'\n\n/**\n * Associates connect-wrapped chunks with the run they were produced under.\n * Content events (TEXT_MESSAGE_CONTENT, TOOL_CALL_*, …) carry no `runId` of\n * their own, so the connect wrapper stamps the caller's run id here. Lets\n * run-scoped consumers (e.g. clear-during-stream suppression) attribute those\n * otherwise-runless chunks to their originating request.\n */\nconst chunkRunIds = new WeakMap<StreamChunk, string>()\n\n/**\n * Resolve a chunk's run id, preferring the value on the chunk itself\n * (RUN_STARTED / RUN_FINISHED / RUN_ERROR carry one) and falling back to the\n * run the connect wrapper stamped it with.\n */\nexport function getChunkRunId(chunk: StreamChunk): string | undefined {\n // Prefer the client's request run id (stamped in `chunkRunIds`) over a\n // provider-assigned `chunk.runId`. Interrupt continuation correlation needs\n // the client's run identity to win when a provider stamps its own id; for\n // resumable reconnect/join the two ids match, so precedence is moot there.\n const requestRunId = chunkRunIds.get(chunk)\n return requestRunId ?? getNormalizedChunkRunId(chunk)\n}\n\n/**\n * Thrown when an SSE/HTTP stream ends with a non-empty unterminated buffer.\n * Indicates the connection was cut mid-line (server crash, dropped TCP, proxy\n * timeout) so the partial content cannot be safely parsed.\n */\nexport class StreamTruncatedError extends Error {\n constructor() {\n super(\n 'Stream ended with unterminated trailing data — connection was likely cut short.',\n )\n this.name = 'StreamTruncatedError'\n }\n}\n\nclass StreamReadError extends Error {\n constructor(cause: unknown) {\n super('Stream response body read failed', { cause })\n this.name = 'StreamReadError'\n }\n}\n\n/**\n * Thrown when a durable (id-tagged) run's stream ends with no terminal event\n * and a reconnect makes no forward progress — the run cannot complete, so the\n * consumer must not be left silently hanging on a stream that just stops.\n */\nexport class DurableStreamIncompleteError extends Error {\n constructor() {\n super(\n 'Durable run ended without a terminal event and could not resume — the run did not complete.',\n )\n this.name = 'DurableStreamIncompleteError'\n }\n}\n\n/**\n * Thrown when a durable run exceeds its reconnect ceiling. Bounds the\n * otherwise-unbounded reconnect loop so a flapping producer (or a proxy that\n * rolls the socket after every event) surfaces a failure instead of\n * reconnecting without end.\n */\nexport class StreamReconnectLimitError extends Error {\n constructor(attempts: number) {\n super(\n `Durable run exceeded its reconnect ceiling of ${attempts} attempts — giving up.`,\n )\n this.name = 'StreamReconnectLimitError'\n }\n}\n\n/**\n * Reconnect bounding for resumable streams. A constant throttle delay prevents a\n * hot loop against the origin, and the ceiling bounds a pathologically failing\n * run — but only counts CONSECUTIVE reconnects that made no forward progress.\n */\nexport interface ReconnectOptions {\n /**\n * Ceiling on the number of CONSECUTIVE reconnects that deliver no new events,\n * before failing with {@link StreamReconnectLimitError}. The counter resets to\n * zero whenever a reconnect makes forward progress, so a healthy long run —\n * even one behind a proxy that rolls the socket after every event — never\n * approaches it; the ceiling only fires when the run is genuinely stuck\n * (reconnecting repeatedly without receiving anything new). Default 5.\n */\n maxAttempts?: number\n /** Delay between reconnect attempts, in ms, to avoid hammering. Default 250. */\n delayMs?: number\n}\n\ninterface ResolvedReconnectOptions {\n maxAttempts: number\n delayMs: number\n}\n\nfunction resolveReconnectOptions(\n options: ReconnectOptions | undefined,\n): ResolvedReconnectOptions {\n const maxAttempts = options?.maxAttempts ?? 5\n const delayMs = options?.delayMs ?? 250\n // Reject non-finite / negative bounds up front: a NaN or Infinity maxAttempts\n // would make the ceiling ineffective (unbounded reconnects), and a non-finite\n // delayMs would remove throttling. Fail loudly on misconfiguration.\n if (!Number.isInteger(maxAttempts) || maxAttempts < 0) {\n throw new Error(\n `Invalid reconnect.maxAttempts: ${maxAttempts}. Must be a non-negative integer.`,\n )\n }\n if (!Number.isFinite(delayMs) || delayMs < 0) {\n throw new Error(\n `Invalid reconnect.delayMs: ${delayMs}. Must be a non-negative finite number.`,\n )\n }\n return { maxAttempts, delayMs }\n}\n\n/**\n * Reconnect bookkeeping shared by every resumable-stream driver: de-dupes\n * offsets, tracks the last acknowledged offset, honors the SSE empty-id reset\n * convention, and bounds consecutive no-progress reconnects behind a\n * throttling delay. Extracted out of {@link resumableStream} so a WebSocket\n * reconnect driver can reuse the exact same semantics.\n */\nexport interface ReconnectTracker {\n /** The most recently accepted (non-duplicate, non-empty) offset, if any. */\n readonly lastEventId: string | undefined\n /**\n * Record an incoming offset. Returns `'reset'` for an empty id (SSE's\n * resume-cursor reset — clears the de-dupe set and `lastEventId`),\n * `'duplicate'` for an already-seen id, and `'new'` otherwise (including\n * `undefined`, which is untracked — no offset to remember).\n */\n note: (id: string | undefined) => 'new' | 'duplicate' | 'reset'\n /**\n * Throttle before a reconnect attempt. Resets the no-progress counter when\n * `madeProgress` is true; otherwise increments it and throws\n * {@link StreamReconnectLimitError} once it exceeds the configured ceiling.\n */\n waitBeforeReconnect: (\n madeProgress: boolean,\n signal?: AbortSignal,\n ) => Promise<void>\n}\n\n/** Create a {@link ReconnectTracker} bound to the given reconnect bounds. */\nexport function createReconnectTracker(\n options?: ReconnectOptions,\n): ReconnectTracker {\n const reconnect = resolveReconnectOptions(options)\n // Retains every delivered offset for the run's lifetime. Intentionally\n // bounded by run length (not evicted): a conforming server replays strictly\n // after the acknowledged offset, so this only needs to catch the single\n // boundary event on reconnect, but keeping the full set keeps de-dup\n // correct even if a server replays a wider overlap.\n const seen = new Set<string>()\n let lastEventId: string | undefined\n let reconnectAttempts = 0\n return {\n get lastEventId() {\n return lastEventId\n },\n note(id) {\n if (id === undefined) return 'new'\n if (id === '') {\n // SSE spec: an empty `id:` resets the resume cursor. Drop the last\n // offset and clear the de-dupe set; the chunk itself still delivers.\n lastEventId = undefined\n seen.clear()\n return 'reset'\n }\n if (seen.has(id)) return 'duplicate'\n seen.add(id)\n lastEventId = id\n return 'new'\n },\n // Bound only CONSECUTIVE no-progress reconnects. A reconnect that made\n // forward progress resets the counter, so a healthy long run (even one\n // whose socket rolls after every event) never approaches the ceiling; it\n // fires only when the run is genuinely stuck — reconnecting repeatedly\n // with nothing new.\n async waitBeforeReconnect(madeProgress, signal) {\n if (madeProgress) {\n reconnectAttempts = 0\n } else {\n reconnectAttempts += 1\n if (reconnectAttempts > reconnect.maxAttempts) {\n throw new StreamReconnectLimitError(reconnect.maxAttempts)\n }\n }\n await abortableDelay(reconnect.delayMs, signal)\n },\n }\n}\n\n/** Resolve after `ms`, or immediately once `signal` aborts. Never rejects. */\nfunction abortableDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0 || signal?.aborted) return Promise.resolve()\n return new Promise((resolve) => {\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nfunction generateRunId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n}\n\n/**\n * Asserts an id is present when synthesizing a terminal event. The chat\n * client always supplies `runContext.threadId` / `runContext.runId`, so an\n * absent id at this layer indicates the adapter was wired up by a caller\n * that bypassed that contract — surface it rather than fabricating one.\n */\nfunction requireSyntheticId(\n value: string | undefined,\n field: 'threadId' | 'runId',\n): string {\n if (!value) {\n throw new Error(\n `Cannot synthesize terminal event: ${field} not supplied via runContext and not observed in the upstream stream.`,\n )\n }\n return value\n}\n\n/**\n * Merge custom headers into request headers\n */\nfunction mergeHeaders(\n customHeaders?: Record<string, string> | Headers,\n): Record<string, string> {\n if (!customHeaders) {\n return {}\n }\n if (customHeaders instanceof Headers) {\n const result: Record<string, string> = {}\n customHeaders.forEach((value, key) => {\n result[key] = value\n })\n return result\n }\n return customHeaders\n}\n\n/**\n * Request header carrying the client-chosen run id to a delivery-durability\n * sink. The durable log is then keyed by the SAME id the client already holds,\n * so a later join/resume can address the run without first reading back a\n * server-generated id. Sent as a header — NOT a query param — so the POST URL\n * stays byte-identical to a plain, non-durable request; a server that isn't\n * durable simply ignores the header. (The GET join path keeps `?runId` in the\n * query, since a GET has no body/handler contract to disturb.)\n */\nconst RUN_ID_HEADER = 'X-Run-Id'\n\nfunction runIdHeader(runId: string | undefined): Record<string, string> {\n return runId === undefined ? {} : { [RUN_ID_HEADER]: runId }\n}\n\nfunction withSearchParams(url: string, values: Record<string, string>): string {\n const hashIndex = url.indexOf('#')\n const hash = hashIndex === -1 ? '' : url.slice(hashIndex)\n const withoutHash = hashIndex === -1 ? url : url.slice(0, hashIndex)\n const queryIndex = withoutHash.indexOf('?')\n const base =\n queryIndex === -1 ? withoutHash : withoutHash.slice(0, queryIndex)\n const search = new URLSearchParams(\n queryIndex === -1 ? '' : withoutHash.slice(queryIndex + 1),\n )\n for (const [key, value] of Object.entries(values)) search.set(key, value)\n const query = search.toString()\n return `${base}${query.length === 0 ? '' : `?${query}`}${hash}`\n}\n\n/**\n * Read lines from a stream (newline-delimited)\n */\nasync function* readStreamLines(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n abortSignal?: AbortSignal,\n): AsyncGenerator<string> {\n try {\n const decoder = createResponseStreamTextDecoder()\n let buffer = ''\n\n while (!abortSignal?.aborted) {\n let result: ReadableStreamReadResult<Uint8Array>\n try {\n result = await reader.read()\n } catch (error) {\n if (abortSignal?.aborted) return\n throw new StreamReadError(error)\n }\n const { done, value } = result\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n // Strip a trailing CR so a CRLF stream matches the LF path (and the\n // XHR reader). Without this an exact-equality check like the `[DONE]`\n // sentinel in linesToSSEEvents would miss `data: [DONE]\\r`.\n const normalized = line.endsWith('\\r') ? line.slice(0, -1) : line\n if (normalized.trim()) {\n yield normalized\n }\n }\n }\n\n // Flush the decoder: a connection cut mid-multibyte-character leaves bytes\n // held inside the streaming TextDecoder. Draining them here (as U+FFFD)\n // makes the trailing-buffer check below see the incomplete tail and report\n // truncation instead of silently swallowing it.\n buffer += decoder.decode()\n\n // A non-empty trailing buffer means the connection was cut mid-line.\n // Surface this as an error so the chat client transitions to 'error'\n // state instead of silently presenting a partial stream as success.\n // Skip when the consumer aborted — a user-initiated stop() interrupting\n // mid-line is expected, not a truncation bug.\n if (buffer.trim() && !abortSignal?.aborted) {\n throw new StreamTruncatedError()\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/** A parsed stream chunk paired with its adapter-owned delivery offset (if any). */\ninterface StreamEvent {\n chunk: StreamChunk\n id?: string\n}\n\n/**\n * Type guard for a durable NDJSON envelope `{ id, chunk }`. NDJSON has no\n * native event-id field, so durability rides the offset inside the payload.\n * A bare `StreamChunk` always has a top-level `type`, and the envelope never\n * does, so the two forms are unambiguous — a non-durable line stays bare.\n */\nfunction isNdjsonEnvelope(\n value: unknown,\n): value is { id: string; chunk: StreamChunk } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'chunk' in value &&\n 'id' in value &&\n typeof (value as { id: unknown }).id === 'string' &&\n !('type' in value)\n )\n}\n\n/** Rebuild pre-wire extras after SSE/NDJSON ingest. */\nfunction restoreInboundUsage(chunk: StreamChunk): StreamChunk {\n return restoreInboundChunk(chunk)\n}\n\nfunction sseChunkModel(chunk: StreamChunk): string | undefined {\n const tanstackModel = tanstackMetadata(chunk)?.model\n if (typeof tanstackModel === 'string') return tanstackModel\n const usage = 'usage' in chunk ? chunk.usage : undefined\n if (Array.isArray(usage)) {\n const model = (usage[0] as { model?: unknown } | undefined)?.model\n if (typeof model === 'string') return model\n }\n return undefined\n}\n\n/**\n * Parse SSE-format lines into stream events, pairing each chunk with the `id:`\n * offset of the event it arrived on. Shared by the fetch- and XHR-backed SSE\n * adapters so both track delivery offsets identically.\n *\n * Accepts either `data: {...}` lines or bare JSON lines. Skips comments\n * starting with `:` (proxies and CDNs inject these as keepalives) and the\n * `event:` / `retry:` SSE control fields. A `[DONE]` sentinel is treated as a\n * terminal event: a synthesized RUN_FINISHED is yielded using the most recent\n * upstream `threadId` / `runId` (falling back to `fallbackIds`), so the\n * consumer sees a clean terminal event with real correlation ids.\n *\n * A JSON parse failure throws — the consumer surfaces it as an error.\n */\nasync function* linesToSSEEvents(\n lines: AsyncIterable<string>,\n fallbackIds?: { threadId?: string; runId?: string },\n): AsyncGenerator<StreamEvent> {\n let lastThreadId: string | undefined\n let lastRunId: string | undefined\n let lastModel: string | undefined\n let pendingId: string | undefined\n for await (const line of lines) {\n if (line === 'id' || line.startsWith('id:')) {\n // SSE spec: strip a single leading space after the colon, preserve the\n // rest verbatim so an opaque adapter offset round-trips exactly (do NOT\n // trim, which would mangle a legitimate offset). An empty value is kept as\n // '' and resets the resume cursor downstream (see resumableStream).\n const rawId = line === 'id' ? '' : line.slice(3)\n pendingId = rawId.startsWith(' ') ? rawId.slice(1) : rawId\n continue\n }\n // Assumes the durability wire emits one `id:` immediately followed by one\n // `data:` per event (both shipped sinks do). `pendingId` attaches to the\n // next data line and is cleared after it; blank-line event boundaries are\n // stripped upstream, so a hand-rolled server that emits an id-only event or\n // a persistent `id:` across events is not supported here.\n if (\n line.startsWith(':') ||\n line.startsWith('event:') ||\n line.startsWith('retry:')\n ) {\n continue\n }\n const data = parseSseDataLine(line)\n if (data === '[DONE]') {\n yield {\n chunk: withTanstackMetadata(\n {\n type: EventType.RUN_FINISHED,\n threadId: lastThreadId ?? fallbackIds?.threadId ?? '',\n runId: lastRunId ?? fallbackIds?.runId ?? '',\n timestamp: Date.now(),\n },\n {\n finishReason: 'stop',\n ...(lastModel !== undefined ? { model: lastModel } : {}),\n },\n ) as StreamChunk,\n }\n return\n }\n const chunk = restoreInboundUsage(JSON.parse(data) as StreamChunk)\n if ('threadId' in chunk && typeof chunk.threadId === 'string') {\n lastThreadId = chunk.threadId\n }\n if ('runId' in chunk && typeof chunk.runId === 'string') {\n lastRunId = chunk.runId\n }\n const model = sseChunkModel(chunk)\n if (model !== undefined) lastModel = model\n const id = pendingId\n pendingId = undefined\n yield { chunk, ...(id !== undefined ? { id } : {}) }\n }\n}\n\n/**\n * Parse NDJSON-format lines into stream events. Durable streams emit each line\n * as an `{ id, chunk }` envelope carrying the delivery offset; non-durable\n * streams emit bare chunks. Both are auto-detected (see {@link isNdjsonEnvelope}),\n * so an untagged stream behaves exactly as a plain single fetch used to.\n */\nasync function* linesToNdjsonEvents(\n lines: AsyncIterable<string>,\n): AsyncGenerator<StreamEvent> {\n for await (const line of lines) {\n const parsed = JSON.parse(line) as unknown\n if (isNdjsonEnvelope(parsed)) {\n yield { chunk: restoreInboundUsage(parsed.chunk), id: parsed.id }\n } else {\n yield { chunk: restoreInboundUsage(parsed as StreamChunk) }\n }\n }\n}\n\nasync function assertResponseOk(response: Response): Promise<void> {\n if (response.ok) return\n if (response.status === 401) {\n const body: unknown = await response\n .clone()\n .json()\n .catch(() => null)\n if (isByokMissingBody(body)) {\n throw new ByokMissingError(body.error.provider)\n }\n }\n throw new Error(\n `HTTP error! status: ${response.status} ${response.statusText}`,\n )\n}\n\nfunction errorFromXhrStatus(xhr: XMLHttpRequest): Error {\n if (xhr.status === 401) {\n let parsed: unknown = null\n try {\n parsed = JSON.parse(xhr.responseText)\n } catch {\n parsed = null\n }\n if (isByokMissingBody(parsed)) {\n return new ByokMissingError(parsed.error.provider)\n }\n }\n return new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`)\n}\n\n/**\n * GET the hydration endpoint for a thread and parse its JSON `{ messages,\n * activeRun }` body. This is the transport-agnostic reconnect probe: keyed on\n * the STABLE thread id, it returns the stored transcript and — if a run is still\n * generating — a cursor the caller tails via `joinRun`. Shared by every fetch/\n * XHR adapter so the client never has to know which transport is in use.\n */\nasync function fetchThreadHydration(\n fetchClient: typeof globalThis.fetch,\n url: string,\n headers: Record<string, string>,\n credentials: RequestCredentials,\n threadId: string,\n): Promise<ChatHydrationResult> {\n const response = await fetchClient(withSearchParams(url, { threadId }), {\n method: 'GET',\n headers: { Accept: 'application/json', ...headers },\n credentials,\n })\n await assertResponseOk(response)\n const data = (await response.json()) as {\n messages?: Array<UIMessage>\n activeRun?: { runId?: unknown } | null\n interrupts?: {\n runId?: unknown\n pending?: unknown\n } | null\n }\n const activeRun =\n data.activeRun && typeof data.activeRun.runId === 'string'\n ? { runId: data.activeRun.runId }\n : null\n const interrupts =\n data.interrupts &&\n typeof data.interrupts.runId === 'string' &&\n Array.isArray(data.interrupts.pending) &&\n data.interrupts.pending.length > 0\n ? {\n runId: data.interrupts.runId,\n pending: data.interrupts.pending as Array<ChatPendingInterrupt>,\n }\n : null\n return {\n messages: Array.isArray(data.messages)\n ? normalizeMessagesDates(data.messages)\n : [],\n activeRun,\n interrupts,\n }\n}\n\n/**\n * GET the hydration endpoint for a generation thread and parse its JSON\n * `{ resumeSnapshot, activeRun }` body. Mirrors {@link fetchThreadHydration} for\n * the generation clients: keyed on the stable thread id, it returns the last\n * generation's resume snapshot (re-validated client-side before adoption) and —\n * if a run is still generating — a cursor. Shared by every fetch/XHR adapter.\n */\nasync function fetchGenerationHydration(\n fetchClient: typeof globalThis.fetch,\n url: string,\n headers: Record<string, string>,\n credentials: RequestCredentials,\n threadId: string,\n): Promise<GenerationHydrationResult> {\n const response = await fetchClient(withSearchParams(url, { threadId }), {\n method: 'GET',\n headers: { Accept: 'application/json', ...headers },\n credentials,\n })\n await assertResponseOk(response)\n const raw: unknown = await response.json()\n // A 200 carrying `null` is a legitimate hydration miss — the server has no\n // record for this thread — and reading `.activeRun` off `null` would throw.\n if (raw === null) {\n return { resumeSnapshot: null, activeRun: null }\n }\n // Any OTHER non-object body is a broken endpoint, not an empty thread.\n // Reporting it as a miss would present a misconfigured route as a fresh\n // thread; the client surfaces this through its own error channel instead.\n if (typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error(\n `Generation hydration expected a JSON object from ${url}, received ${Array.isArray(raw) ? 'an array' : typeof raw}.`,\n )\n }\n const data = raw as {\n resumeSnapshot?: GenerationHydrationResult['resumeSnapshot']\n activeRun?: { runId?: unknown } | null\n }\n const activeRun =\n data.activeRun && typeof data.activeRun.runId === 'string'\n ? { runId: data.activeRun.runId }\n : null\n return {\n resumeSnapshot: data.resumeSnapshot ?? null,\n activeRun,\n }\n}\n\n/** Yield SSE stream events (chunk + offset) from a fetch Response body. */\nasync function* responseToSSEEvents(\n response: Response,\n abortSignal?: AbortSignal,\n fallbackIds?: { threadId?: string; runId?: string },\n): AsyncGenerator<StreamEvent> {\n await assertResponseOk(response)\n const reader = getResponseStreamReader(response)\n yield* linesToSSEEvents(readStreamLines(reader, abortSignal), fallbackIds)\n}\n\n/** Yield NDJSON stream events (chunk + offset) from a fetch Response body. */\nasync function* responseToNdjsonEvents(\n response: Response,\n abortSignal?: AbortSignal,\n): AsyncGenerator<StreamEvent> {\n await assertResponseOk(response)\n const reader = getResponseStreamReader(response)\n yield* linesToNdjsonEvents(readStreamLines(reader, abortSignal))\n}\n\nasync function* responseToSSEChunks(\n response: Response,\n abortSignal?: AbortSignal,\n): AsyncGenerator<StreamChunk> {\n for await (const { chunk } of responseToSSEEvents(response, abortSignal)) {\n yield chunk\n }\n}\n\n/**\n * A re-issuable event source. Given extra headers (a `Last-Event-ID` on a\n * reconnect) and an abort signal, it opens the transport and yields stream\n * events. {@link resumableStream} calls it once per attempt, so each call MUST\n * open a fresh underlying request (a new fetch or a new XHR).\n */\ntype StreamEventSource = (\n extraHeaders: Record<string, string>,\n abortSignal?: AbortSignal,\n) => AsyncIterable<StreamEvent>\n\n/**\n * Build a fetch-backed {@link StreamEventSource}. `parseResponse` decodes the\n * body into events (SSE or NDJSON) — the reconnect engine is identical for both.\n */\nfunction fetchEventSource(\n fetchClient: typeof globalThis.fetch,\n url: string,\n requestInit: RequestInit,\n parseResponse: (\n response: Response,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamEvent>,\n): StreamEventSource {\n return async function* (extraHeaders, abortSignal) {\n let response: Response\n try {\n response = await fetchClient(url, {\n ...requestInit,\n headers: {\n ...(requestInit.headers as Record<string, string> | undefined),\n ...extraHeaders,\n },\n ...(abortSignal ? { signal: abortSignal } : {}),\n })\n } catch (error) {\n // A fetch REJECTION (device offline, DNS blip, connection refused) is a\n // recoverable transport failure, not a fatal one — surface it as\n // StreamReadError so resumableStream retries from the last offset, mirroring\n // the XHR path (whose onerror wraps the same way). On a genuine abort this\n // wraps the AbortError too, but that's harmless: resumableStream checks\n // `abortSignal.aborted` first and returns, so the wrapped error's type is\n // never inspected. Without an offset (initial connect / non-durable), it\n // still surfaces as a hard failure.\n throw new StreamReadError(error)\n }\n yield* parseResponse(response, abortSignal)\n }\n}\n\n/**\n * Drive a {@link StreamEventSource} with native-style resumability. Each event's\n * adapter-owned delivery offset (its `id`) is remembered; if the connection\n * drops or ends before a terminal event, the source is re-opened with a\n * `Last-Event-ID` header so the server replays strictly after the last offset.\n * Already-seen offsets are de-duped, so an overlapping replay is safe.\n *\n * When the server does NOT tag events (no durability), no offset is ever seen,\n * so no reconnect happens — behaviour is identical to a plain single request.\n * This engine is transport-agnostic: fetch/XHR × SSE/NDJSON all share it, the\n * only difference being the {@link StreamEventSource} they pass in.\n */\nasync function* resumableStream(\n openEventSource: StreamEventSource,\n abortSignal?: AbortSignal,\n reconnectOptions?: ReconnectOptions,\n): AsyncGenerator<StreamChunk> {\n const tracker = createReconnectTracker(reconnectOptions)\n\n for (;;) {\n if (abortSignal?.aborted) return\n const extraHeaders: Record<string, string> =\n tracker.lastEventId !== undefined\n ? { 'Last-Event-ID': tracker.lastEventId }\n : {}\n\n let sawTerminal = false\n let progressed = false\n try {\n for await (const { chunk, id } of openEventSource(\n extraHeaders,\n abortSignal,\n )) {\n if (tracker.note(id) === 'duplicate') continue\n progressed = true\n if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {\n sawTerminal = true\n }\n yield chunk\n // Do NOT stop on a terminal mid-source: an agent loop emits one\n // RUN_STARTED/RUN_FINISHED pair PER turn, so a tool-calling run carries\n // several RUN_FINISHED events before the run is truly done. Returning on\n // the first one would drop every subsequent turn (the tool result and\n // the final answer). Instead, drain the event source to its natural end\n // — the server closes the response only when the run is actually\n // complete — and use `sawTerminal` below to decide done-vs-reconnect.\n }\n } catch (error) {\n if (abortSignal?.aborted) return\n // A transport drop is resumable once we hold an offset — retry from it,\n // even if THIS attempt made no new progress. A caught-up run whose parked\n // long-poll socket drops (or a proxy that drops just after replaying the\n // de-duped overlap) is transient, not fatal; the consecutive-no-progress\n // ceiling in waitBeforeReconnect already bounds a genuinely stuck flapper,\n // so a per-attempt progress requirement here would only convert\n // recoverable drops into hard failures on flaky (mobile/edge) networks.\n // Without an offset (a non-durable stream), surface the failure.\n if (\n (error instanceof StreamTruncatedError ||\n error instanceof StreamReadError) &&\n tracker.lastEventId !== undefined\n ) {\n await tracker.waitBeforeReconnect(progressed, abortSignal)\n continue\n }\n throw error\n }\n\n if (abortSignal?.aborted) return\n\n // The source ended after delivering a terminal event: the run is genuinely\n // finished (for an agentic run this is the LAST turn's terminal, since we no\n // longer stop on intermediate ones). Stop — reconnecting a durable run here\n // would re-open past the final offset and see an empty window.\n if (sawTerminal) return\n\n if (tracker.lastEventId !== undefined) {\n // A durable (id-tagged) run.\n if (progressed) {\n // Clean end WITHOUT a terminal event but we advanced — the producer is\n // still going (or the socket rolled over). Reconnect from the last\n // offset (backing off to avoid a hot loop against the origin). Progress\n // resets the no-progress ceiling.\n await tracker.waitBeforeReconnect(true, abortSignal)\n continue\n }\n // Ended without a terminal event AND made no forward progress on this\n // pass: the run cannot complete. Surface an error rather than returning\n // silently, which would leave the consumer with neither a terminal event\n // nor a failure.\n //\n // Invariant this relies on: a durable transport must never surface an\n // empty long-poll window as a CLEAN end while the producer is still\n // alive. Both shipped backends honor it — memoryStream parks until data\n // or completion, and durableStream keeps one continuous response across\n // windows — so this fires only on a genuinely complete-but-unterminated\n // log. A custom StreamDurability transport that ends a response empty\n // mid-run would trip this; keep the response open until data or terminal.\n throw new DurableStreamIncompleteError()\n }\n\n // A non-durable (untagged) stream that ended cleanly. Legitimate — the\n // upper layer synthesizes a terminal event. Stop.\n return\n }\n}\n\n/**\n * Per-send context provided by the chat client to the connection adapter.\n * The adapter combines this with serialized messages to build a full\n * AG-UI `RunAgentInput` payload.\n */\nexport interface RunAgentInputContext {\n threadId: string\n runId: string\n parentRunId?: string\n /** AG-UI interrupt resume entries returned to the server on a follow-up run. */\n resume?: Array<RunAgentResumeItem>\n /** Client-declared tools to advertise in the request payload. */\n clientTools?: Array<{\n name: string\n description: string\n parameters: unknown\n }>\n /** Arbitrary user-controlled passthrough data. */\n forwardedProps?: Record<string, unknown>\n /** Extra request headers for this run (e.g. BYOK keys). POST only. */\n headers?: Record<string, string>\n}\n\nexport interface ConnectConnectionAdapter {\n /**\n * Connect and return an async iterable of StreamChunks.\n */\n connect: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n runContext?: RunAgentInputContext,\n ) => AsyncIterable<StreamChunk>\n /**\n * Fetch server-driven hydration for a generation `threadId`: the last\n * generation's resume snapshot, plus a cursor to a run still generating if\n * one exists. The generation client calls this itself on mount when\n * `persistence: true` (no loader/prop) and repaints the snapshot — it never\n * auto-starts a run. Read-only JSON GET (`?threadId`), so it is\n * transport-agnostic. Optional and feature-detected exactly like the chat\n * `hydrate` handler.\n */\n hydrateGeneration?: (threadId: string) => Promise<GenerationHydrationResult>\n /**\n * Re-attach to a run that is still generating and replay it from the start\n * (read-only `?offset=-1&runId` against the delivery-durability log). The\n * generation client tails this on mount when hydration reports a run still in\n * flight, so a dropped connection or a full reload finishes the generation in\n * place — the same durability replay the chat client uses. Optional and\n * feature-detected; present on `fetchServerSentEvents` / `fetchHttpStream`.\n */\n joinRun?: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n /**\n * Fetch server-driven hydration for a chat `threadId`: the stored transcript\n * plus a cursor to an in-flight run and any pending interrupts. The chat\n * client calls this itself on mount when `persistence: true` (no loader/prop)\n * and repaints it — it never auto-sends. Read-only JSON GET (`?threadId`), so\n * it is transport-agnostic. Optional and feature-detected; present on\n * `fetchServerSentEvents` / `fetchHttpStream`, and on `stream()` /\n * `rpcStream()` when supplied via {@link StreamConnectionHandlers}.\n */\n hydrate?: (threadId: string) => Promise<ChatHydrationResult>\n}\n\n/**\n * Server-resolved hydration for a generation thread. `resumeSnapshot` is the\n * last generation's lightweight snapshot (validated client-side before it is\n * adopted); `activeRun` is a cursor to a run still generating for the thread\n * (or `null`).\n *\n * Field-for-field compatible with `@tanstack/ai-persistence`'s\n * `ReconstructedGeneration` (the body `reconstructGeneration` returns) — the\n * client never imports that package, so this is a structural contract, not a\n * shared type. Two deliberate widenings on this side: `schemaVersion` is\n * optional (the server always writes `1`, but a hand-written fixture need not),\n * and `status` also admits `'idle'`, which the server's mapper never emits.\n * Only a client-local snapshot reaches it, when `stop()` retires a cancelled\n * run.\n */\nexport interface GenerationHydrationResult {\n resumeSnapshot: {\n schemaVersion?: 1\n resumeState: { threadId: string; runId: string } | null\n status: 'idle' | 'running' | 'complete' | 'error'\n result?: unknown\n error?: { message: string; code?: string }\n activity?: string\n } | null\n activeRun: { runId: string } | null\n}\n\n/**\n * Server-resolved hydration for a thread. `messages` is the stored transcript;\n * `activeRun` is a cursor to a run still generating for the thread (or `null`).\n * Keyed on the STABLE thread id — the client never handles a run id, so a turn\n * that spans several runs (interrupt/tool continuations) reconnects correctly.\n */\nexport interface ChatHydrationResult {\n messages: Array<UIMessage>\n activeRun: { runId: string } | null\n /**\n * Pending human-in-the-loop interrupts for the thread and the run they paused,\n * so a reload (or another device) re-prompts the approval from the server. The\n * client restores them exactly as a persisted resume snapshot would.\n */\n interrupts: {\n runId: string\n pending: Array<ChatPendingInterrupt>\n } | null\n}\n\n/**\n * A {@link ConnectConnectionAdapter} that also supports joining an existing run\n * (a second tab, or re-attaching after a full reload) via `joinRun`, replaying\n * the ordered stream from the start off the server's delivery-durability sink.\n */\nexport interface ResumableConnectConnectionAdapter extends ConnectConnectionAdapter {\n /**\n * Join an in-flight or finished run by id, replaying from the start\n * (`?offset=-1`). Read-only — sends no messages.\n */\n joinRun: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n /**\n * Fetch server-authoritative hydration for `threadId`: the stored transcript,\n * and a cursor to an in-flight run if one exists. The client calls this itself\n * on mount (no loader/prop), then tails `activeRun` via `joinRun`. Read-only\n * JSON GET (`?threadId`), so it is transport-agnostic regardless of how the\n * delivery stream is served.\n */\n hydrate?: (threadId: string) => Promise<ChatHydrationResult>\n}\n\nexport interface SubscribeConnectionAdapter {\n /**\n * Subscribe to stream chunks.\n */\n subscribe: (abortSignal?: AbortSignal) => AsyncIterable<StreamChunk>\n /**\n * Send a request; chunks arrive through subscribe().\n */\n send: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n runContext?: RunAgentInputContext,\n ) => Promise<void>\n /**\n * Re-attach to an existing run by id, replaying its stream from the start off\n * the server's delivery-durability sink. Present only when the underlying\n * connection is resumable (a `ResumableConnectConnectionAdapter`). Used to\n * rejoin an in-flight run after a full page reload.\n */\n joinRun?: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n /**\n * Server-authoritative hydration for a thread (transcript + in-flight-run\n * cursor). Present only when the underlying connection supports it. The client\n * calls it on mount to re-hydrate without any app-side loader or prop.\n */\n hydrate?: (threadId: string) => Promise<ChatHydrationResult>\n}\n\n/**\n * Connection adapter union.\n * Provide either `connect`, or `subscribe` + `send`.\n */\nexport type ConnectionAdapter =\n | ConnectConnectionAdapter\n | SubscribeConnectionAdapter\n\n/**\n * Normalize a ConnectionAdapter to subscribe/send operations.\n *\n * If a connection provides native subscribe/send, that mode is used.\n * Otherwise, connect() is wrapped using an async queue.\n */\nexport function normalizeConnectionAdapter(\n connection: ConnectionAdapter | undefined,\n): SubscribeConnectionAdapter {\n if (!connection) {\n throw new Error('Connection adapter is required')\n }\n\n const hasConnect = 'connect' in connection\n const hasSubscribe = 'subscribe' in connection\n const hasSend = 'send' in connection\n\n if (hasConnect && (hasSubscribe || hasSend)) {\n throw new Error(\n 'Connection adapter must provide either connect or both subscribe and send, not both modes',\n )\n }\n\n if (hasSubscribe && hasSend) {\n const joinRun = (connection as SubscribeConnectionAdapter).joinRun?.bind(\n connection,\n )\n const hydrate = (connection as SubscribeConnectionAdapter).hydrate?.bind(\n connection,\n )\n return {\n subscribe: connection.subscribe.bind(connection),\n send: connection.send.bind(connection),\n ...(joinRun ? { joinRun } : {}),\n ...(hydrate ? { hydrate } : {}),\n }\n }\n\n if (!hasConnect) {\n throw new Error(\n 'Connection adapter must provide either connect or both subscribe and send',\n )\n }\n\n // Legacy connect() wrapper\n let activeBuffer: Array<StreamChunk> = []\n let activeWaiters: Array<(chunk: StreamChunk | null) => void> = []\n let activeSubscriber: typeof activeWaiters | undefined\n\n function push(chunk: StreamChunk, runId?: string): void {\n if (runId) {\n chunkRunIds.set(chunk, runId)\n }\n const waiter = activeWaiters.shift()\n if (waiter) {\n waiter(chunk)\n } else {\n activeBuffer.push(chunk)\n }\n }\n\n async function waitUntilSubscriberIdle(\n abortSignal?: AbortSignal,\n ): Promise<void> {\n // Idle means the subscriber is waiting for the next chunk, so the\n // previous chunk has left processIncomingChunk. Empty waiters with an\n // empty buffer is in-flight delivery, not idle.\n const idle = () =>\n activeSubscriber !== activeWaiters ||\n (activeBuffer.length === 0 &&\n (activeWaiters.length > 0 || abortSignal?.aborted))\n for (let i = 0; i < 16 && !abortSignal?.aborted; i++) {\n if (idle()) return\n await Promise.resolve()\n }\n while (!abortSignal?.aborted) {\n if (idle()) return\n await new Promise<void>((resolve) => setTimeout(resolve, 0))\n }\n }\n\n return {\n subscribe(abortSignal?: AbortSignal): AsyncIterable<StreamChunk> {\n // Transfer ownership to the latest subscriber so only one active\n // subscribe() call receives chunks from the shared connect-wrapper queue.\n const myBuffer: Array<StreamChunk> = activeBuffer.splice(0)\n const myWaiters: Array<(chunk: StreamChunk | null) => void> = []\n activeBuffer = myBuffer\n activeWaiters = myWaiters\n\n return (async function* () {\n activeSubscriber = myWaiters\n try {\n while (!abortSignal?.aborted) {\n let chunk: StreamChunk | null\n const buffered = myBuffer.shift()\n if (buffered !== undefined) {\n chunk = buffered\n } else {\n chunk = await new Promise<StreamChunk | null>((resolve) => {\n const onAbort = () => resolve(null)\n myWaiters.push((c) => {\n abortSignal?.removeEventListener('abort', onAbort)\n resolve(c)\n })\n abortSignal?.addEventListener('abort', onAbort, { once: true })\n })\n }\n if (chunk !== null) yield chunk\n }\n } finally {\n if (activeSubscriber === myWaiters) activeSubscriber = undefined\n }\n })()\n },\n async send(messages, data, abortSignal, runContext) {\n let hasTerminalEvent = false\n let upstreamThreadId: string | undefined\n let upstreamRunId: string | undefined\n try {\n const stream = connection.connect(\n messages,\n data,\n abortSignal,\n runContext,\n )\n for await (const chunk of stream) {\n if ('threadId' in chunk && typeof chunk.threadId === 'string') {\n upstreamThreadId = chunk.threadId\n }\n if ('runId' in chunk && typeof chunk.runId === 'string') {\n upstreamRunId = chunk.runId\n }\n if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {\n hasTerminalEvent = true\n }\n push(chunk, runContext?.runId)\n }\n\n // If the connect stream ended cleanly without a terminal event,\n // synthesize RUN_FINISHED so request-scoped consumers can complete.\n // The event payload may carry an upstream/provider runId when one was\n // observed, but stamp the caller's request runId so getChunkRunId()\n // correlates to activeRunIds / currentRunId (same as real stream chunks).\n if (!abortSignal?.aborted && !hasTerminalEvent) {\n push(\n withTanstackMetadata(\n {\n type: EventType.RUN_FINISHED,\n threadId: requireSyntheticId(\n upstreamThreadId ?? runContext?.threadId,\n 'threadId',\n ),\n runId: requireSyntheticId(\n upstreamRunId ?? runContext?.runId,\n 'runId',\n ),\n timestamp: Date.now(),\n },\n { finishReason: 'stop', model: 'connect-wrapper' },\n ) as StreamChunk,\n runContext?.runId,\n )\n }\n } catch (err) {\n if (!abortSignal?.aborted && !hasTerminalEvent) {\n // Guard synthesis: requireSyntheticId throws when no id is available,\n // and that must not replace the original `err` we are about to\n // rethrow. If we can't synthesize a terminal, the real failure still\n // surfaces below.\n try {\n const message =\n err instanceof Error ? err.message : 'Unknown error in connect()'\n const synthetic: RunErrorEvent = {\n type: EventType.RUN_ERROR,\n threadId: requireSyntheticId(\n upstreamThreadId ?? runContext?.threadId,\n 'threadId',\n ),\n runId: requireSyntheticId(\n upstreamRunId ?? runContext?.runId,\n 'runId',\n ),\n timestamp: Date.now(),\n message,\n }\n push(synthetic, runContext?.runId)\n } catch {\n // fall through to rethrow the original error\n }\n }\n throw err\n }\n await waitUntilSubscriberIdle(abortSignal)\n },\n // Expose joinRun only when the underlying connection is resumable. Require\n // a real function — `'joinRun' in connection` is true for\n // `{ joinRun: undefined }`, which would wrap a non-callable and throw on\n // rehydration rejoin.\n ...(typeof (connection as ResumableConnectConnectionAdapter).joinRun ===\n 'function'\n ? {\n joinRun: (runId: string, abortSignal?: AbortSignal) =>\n (connection as ResumableConnectConnectionAdapter).joinRun(\n runId,\n abortSignal,\n ),\n }\n : {}),\n ...(() => {\n // Capture under the typeof guard so `hydrate` narrows to the function type\n // (no non-null assertion). Present only when the connection supports it.\n const hydrate = (connection as ResumableConnectConnectionAdapter).hydrate\n return typeof hydrate === 'function'\n ? { hydrate: (threadId: string) => hydrate(threadId) }\n : {}\n })(),\n }\n}\n\n/**\n * Options for fetch-based connection adapters\n */\nexport interface FetchConnectionOptions {\n headers?: Record<string, string> | Headers\n credentials?: RequestCredentials\n signal?: AbortSignal\n body?: Record<string, any>\n fetchClient?: typeof globalThis.fetch\n /** Bounding for resumable-SSE reconnection (throttle delay + attempt ceiling). */\n reconnect?: ReconnectOptions\n}\n\n/**\n * Options for XHR-based connection adapters.\n */\nexport interface XhrConnectionOptions {\n headers?: Record<string, string> | Headers\n withCredentials?: boolean\n signal?: AbortSignal\n body?: Record<string, any>\n xhrFactory?: () => XMLHttpRequest\n /** Bounding for resumable reconnection (throttle delay + attempt ceiling). */\n reconnect?: ReconnectOptions\n}\n\ntype ResolvedConnectionOptions = Pick<\n FetchConnectionOptions,\n 'body' | 'headers'\n>\n\nfunction buildRunAgentInputBody(\n messages: Array<UIMessage> | Array<ModelMessage>,\n data: Record<string, any> | undefined,\n runContext: RunAgentInputContext | undefined,\n options: ResolvedConnectionOptions,\n): Record<string, unknown> {\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps`, overridden by per-message `data`.\n const wireMessages = uiMessagesToWire(messages)\n const forwardedProps = {\n ...options.body,\n ...(runContext?.forwardedProps ?? {}),\n ...data,\n }\n\n return {\n threadId: runContext?.threadId ?? generateRunId('thread'),\n runId: runContext?.runId ?? generateRunId('run'),\n ...(runContext?.parentRunId !== undefined && {\n parentRunId: runContext.parentRunId,\n }),\n ...(runContext?.resume !== undefined && { resume: runContext.resume }),\n state: {},\n messages: wireMessages,\n tools: runContext?.clientTools ?? [],\n context: [],\n forwardedProps,\n // Backward-compat mirror of `forwardedProps` under the legacy field name.\n data: { ...forwardedProps },\n }\n}\n\n/**\n * Create a Server-Sent Events connection adapter\n *\n * @param url - The API endpoint URL (or a function that returns the URL)\n * @param options - Fetch options (headers, credentials, body, etc.) or a function that returns options (can be async)\n * @returns A connection adapter for SSE streams\n *\n * @example\n * ```typescript\n * // Static URL\n * const connection = fetchServerSentEvents('/api/chat');\n *\n * // Dynamic URL\n * const connection = fetchServerSentEvents(() => `/api/chat?user=${userId}`);\n *\n * // With options\n * const connection = fetchServerSentEvents('/api/chat', {\n * headers: { 'Authorization': 'Bearer token' }\n * });\n *\n * // With dynamic options\n * const connection = fetchServerSentEvents('/api/chat', () => ({\n * headers: { 'Authorization': `Bearer ${getToken()}` }\n * }));\n *\n * // With additional body data\n * const connection = fetchServerSentEvents('/api/chat', async () => ({\n * body: {\n * provider: 'openai',\n * model: 'gpt-5.5',\n * }\n * }));\n * ```\n */\nexport function fetchServerSentEvents(\n url: string | (() => string),\n options:\n | FetchConnectionOptions\n | (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},\n): ResumableConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n // Resolve URL and options if they are functions\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(resolvedOptions.headers),\n ...mergeHeaders(runContext?.headers),\n ...runIdHeader(runContext?.runId),\n }\n\n // Build AG-UI RunAgentInput payload.\n //\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps` (constructor body /\n // forwardedProps options), overridden by per-message `data` passed\n // to `connection.send`. Runtime values win over static config —\n // this matches the documented \"forwardedProps wins\" semantic.\n const requestBody = buildRunAgentInputBody(\n messages,\n data,\n runContext,\n resolvedOptions,\n )\n\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n // `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`\n // under `exactOptionalPropertyTypes`), so spread it conditionally\n // rather than passing `undefined` explicitly.\n const signal = abortSignal || resolvedOptions.signal\n // POST URL is byte-identical to a plain request; the run id (when set)\n // rides in the X-Run-Id header so durability can key the log by it\n // without changing the request URL existing clients rely on.\n const requestUrl = resolvedUrl\n\n // Resumable SSE: if the server tags events with `id:` offsets (delivery\n // durability), a dropped/rolled-over connection auto-reconnects with a\n // `Last-Event-ID` header and de-dupes the replayed prefix. With no tags,\n // this is a single plain fetch.\n yield* resumableStream(\n fetchEventSource(\n fetchClient,\n requestUrl,\n {\n method: 'POST',\n headers: requestHeaders,\n body: JSON.stringify(requestBody),\n credentials: resolvedOptions.credentials || 'same-origin',\n },\n // Thread the run's ids so a `[DONE]`-terminating server that doesn't\n // stamp them onto events still yields a correlated terminal (parity\n // with the XHR adapter's xhrSSEParser).\n (response, sseSignal) =>\n responseToSSEEvents(response, sseSignal, {\n ...(runContext?.threadId !== undefined\n ? { threadId: runContext.threadId }\n : {}),\n ...(runContext?.runId !== undefined\n ? { runId: runContext.runId }\n : {}),\n }),\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async *joinRun(runId, abortSignal) {\n // Read an in-flight or finished run from the start. `?offset=-1` tells the\n // server's delivery-durability sink to replay from the beginning; `runId`\n // identifies which run. This is a read-only GET — no messages are sent.\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const joinUrl = withSearchParams(resolvedUrl, {\n offset: '-1',\n runId,\n })\n\n const requestHeaders: Record<string, string> = {\n ...mergeHeaders(resolvedOptions.headers),\n }\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n const signal = abortSignal || resolvedOptions.signal\n\n yield* resumableStream(\n fetchEventSource(\n fetchClient,\n joinUrl,\n {\n method: 'GET',\n headers: requestHeaders,\n credentials: resolvedOptions.credentials || 'same-origin',\n },\n // A `[DONE]` during a join correlates to the joined run id.\n (response, sseSignal) =>\n responseToSSEEvents(response, sseSignal, { runId }),\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async hydrate(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n return fetchThreadHydration(\n resolvedOptions.fetchClient ?? fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.credentials || 'same-origin',\n threadId,\n )\n },\n async hydrateGeneration(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n return fetchGenerationHydration(\n resolvedOptions.fetchClient ?? fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.credentials || 'same-origin',\n threadId,\n )\n },\n }\n}\n\n/**\n * Create an HTTP streaming connection adapter (for raw streaming without SSE format)\n *\n * @param url - The API endpoint URL (or a function that returns the URL)\n * @param options - Fetch options (headers, credentials, body, etc.) or a function that returns options (can be async)\n * @returns A connection adapter for HTTP streams\n *\n * @example\n * ```typescript\n * // Static URL\n * const connection = fetchHttpStream('/api/chat');\n *\n * // Dynamic URL\n * const connection = fetchHttpStream(() => `/api/chat?user=${userId}`);\n *\n * // With options\n * const connection = fetchHttpStream('/api/chat', {\n * headers: { 'Authorization': 'Bearer token' }\n * });\n *\n * // With dynamic options\n * const connection = fetchHttpStream('/api/chat', () => ({\n * headers: { 'Authorization': `Bearer ${getToken()}` }\n * }));\n *\n * // With additional body data\n * const connection = fetchHttpStream('/api/chat', async () => ({\n * body: {\n * provider: 'openai',\n * model: 'gpt-5.5',\n * }\n * }));\n * ```\n */\nexport function fetchHttpStream(\n url: string | (() => string),\n options:\n | FetchConnectionOptions\n | (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},\n): ResumableConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n // Resolve URL and options if they are functions\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(resolvedOptions.headers),\n ...mergeHeaders(runContext?.headers),\n ...runIdHeader(runContext?.runId),\n }\n\n // Build AG-UI RunAgentInput payload.\n //\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps` (constructor body /\n // forwardedProps options), overridden by per-message `data` passed\n // to `connection.send`. Runtime values win over static config —\n // this matches the documented \"forwardedProps wins\" semantic.\n const requestBody = buildRunAgentInputBody(\n messages,\n data,\n runContext,\n resolvedOptions,\n )\n\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n // `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`\n // under `exactOptionalPropertyTypes`), so spread it conditionally\n // rather than passing `undefined` explicitly.\n const signal = abortSignal || resolvedOptions.signal\n // POST URL is byte-identical to a plain request; the run id (when set)\n // rides in the X-Run-Id header so durability can key the log by it\n // without changing the request URL existing clients rely on.\n const requestUrl = resolvedUrl\n\n // Resumable NDJSON: if the server envelopes each line with an\n // `{ id, chunk }` offset (delivery durability), a dropped/rolled-over\n // connection auto-reconnects with a `Last-Event-ID` header and de-dupes\n // the replayed prefix. With bare lines (no durability), this is a single\n // plain fetch — identical to before.\n yield* resumableStream(\n fetchEventSource(\n fetchClient,\n requestUrl,\n {\n method: 'POST',\n headers: requestHeaders,\n body: JSON.stringify(requestBody),\n credentials: resolvedOptions.credentials || 'same-origin',\n },\n responseToNdjsonEvents,\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async *joinRun(runId, abortSignal) {\n // Read an in-flight or finished run from the start. `?offset=-1` tells the\n // server's delivery-durability sink to replay from the beginning; `runId`\n // identifies which run. This is a read-only GET — no messages are sent.\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const joinUrl = withSearchParams(resolvedUrl, { offset: '-1', runId })\n const requestHeaders: Record<string, string> = {\n ...mergeHeaders(resolvedOptions.headers),\n }\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n const signal = abortSignal || resolvedOptions.signal\n\n yield* resumableStream(\n fetchEventSource(\n fetchClient,\n joinUrl,\n {\n method: 'GET',\n headers: requestHeaders,\n credentials: resolvedOptions.credentials || 'same-origin',\n },\n responseToNdjsonEvents,\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async hydrate(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n return fetchThreadHydration(\n resolvedOptions.fetchClient ?? fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.credentials || 'same-origin',\n threadId,\n )\n },\n async hydrateGeneration(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n return fetchGenerationHydration(\n resolvedOptions.fetchClient ?? fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.credentials || 'same-origin',\n threadId,\n )\n },\n }\n}\n\ntype XhrConnectionOptionsResolver =\n | XhrConnectionOptions\n | (() => XhrConnectionOptions | Promise<XhrConnectionOptions>)\n\nfunction createDefaultXMLHttpRequest(): XMLHttpRequest {\n if (typeof globalThis.XMLHttpRequest !== 'function') {\n throw new Error('XMLHttpRequest is not available in this runtime')\n }\n\n return new globalThis.XMLHttpRequest()\n}\n\nfunction cleanupXhr(\n xhr: XMLHttpRequest,\n abortSignal: AbortSignal | undefined,\n onAbort: (() => void) | undefined,\n): void {\n xhr.onprogress = null\n xhr.onload = null\n xhr.onerror = null\n xhr.onabort = null\n xhr.onloadend = null\n\n if (abortSignal && onAbort) {\n abortSignal.removeEventListener('abort', onAbort)\n }\n}\n\nfunction readXhrLines(\n xhr: XMLHttpRequest,\n abortSignal?: AbortSignal,\n): AsyncGenerator<string> {\n let offset = 0\n let buffer = ''\n const lines: Array<string> = []\n const waiters: Array<() => void> = []\n let done = false\n let aborted = false\n let error: unknown\n let onAbort: (() => void) | undefined\n\n const wake = () => {\n const waiter = waiters.shift()\n waiter?.()\n }\n\n const enqueueDelta = () => {\n if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {\n error = errorFromXhrStatus(xhr)\n done = true\n return\n }\n\n const responseText = xhr.responseText\n if (responseText.length <= offset) {\n return\n }\n\n buffer += responseText.slice(offset)\n offset = responseText.length\n const splitLines = buffer.split('\\n')\n buffer = splitLines.pop() ?? ''\n\n for (const line of splitLines) {\n const normalized = line.endsWith('\\r') ? line.slice(0, -1) : line\n if (normalized.trim()) {\n lines.push(normalized)\n }\n }\n }\n\n const finish = () => {\n enqueueDelta()\n // Tolerate a transient status === 0 (matches enqueueDelta): a real non-2xx\n // is an error, but status 0 here is not — treat the trailing buffer as a\n // truncation check instead of fabricating a bogus \"status: 0\" error.\n if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {\n error = errorFromXhrStatus(xhr)\n } else if (buffer.trim() && !aborted) {\n error = new StreamTruncatedError()\n }\n done = true\n wake()\n }\n\n xhr.onprogress = () => {\n enqueueDelta()\n wake()\n }\n xhr.onload = finish\n xhr.onerror = () => {\n // Surface as StreamReadError so a durable (id-tagged) run whose socket\n // drops mid-stream is eligible for auto-reconnect, matching the fetch path.\n // A non-durable run has no offset, so resumableStream rethrows it as-is.\n error = new StreamReadError(new Error('XHR request failed'))\n done = true\n wake()\n }\n xhr.onabort = () => {\n aborted = true\n done = true\n wake()\n }\n xhr.onloadend = () => {\n if (!done) {\n finish()\n }\n }\n\n if (abortSignal) {\n onAbort = () => {\n aborted = true\n xhr.abort()\n }\n if (abortSignal.aborted) {\n onAbort()\n } else {\n abortSignal.addEventListener('abort', onAbort, { once: true })\n }\n }\n\n return (async function* () {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const line = lines.shift()\n if (line !== undefined) {\n yield line\n continue\n }\n\n if (error) {\n throw error\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (done || abortSignal?.aborted) {\n return\n }\n\n await new Promise<void>((resolve) => {\n waiters.push(resolve)\n })\n }\n } finally {\n cleanupXhr(xhr, abortSignal, onAbort)\n }\n })()\n}\n\ninterface ConfiguredXhrRequest {\n xhr: XMLHttpRequest\n body: string\n}\n\nfunction createConfiguredXhrRequest(\n url: string,\n options: XhrConnectionOptions,\n messages: Array<UIMessage> | Array<ModelMessage>,\n data: Record<string, any> | undefined,\n runContext: RunAgentInputContext | undefined,\n method: string = 'POST',\n extraHeaders: Record<string, string> = {},\n): ConfiguredXhrRequest {\n const xhr = options.xhrFactory?.() ?? createDefaultXMLHttpRequest()\n xhr.open(method, url)\n if (options.withCredentials !== undefined) {\n xhr.withCredentials = options.withCredentials\n }\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(options.headers),\n ...mergeHeaders(method === 'POST' ? runContext?.headers : undefined),\n // Client-chosen run id for durability (POST only; the GET join carries it\n // in the query instead).\n ...(method === 'POST' ? runIdHeader(runContext?.runId) : {}),\n // Reconnect offset (`Last-Event-ID`) wins over static headers.\n ...extraHeaders,\n }\n\n for (const [name, value] of Object.entries(requestHeaders)) {\n xhr.setRequestHeader(name, value)\n }\n\n const requestBody = buildRunAgentInputBody(\n messages,\n data,\n runContext,\n options,\n )\n\n return { xhr, body: JSON.stringify(requestBody) }\n}\n\nasync function resolveXhrConnectionOptions(\n options: XhrConnectionOptionsResolver,\n): Promise<XhrConnectionOptions> {\n return typeof options === 'function' ? await options() : options\n}\n\n/**\n * Build an XHR-backed {@link StreamEventSource}. `parseLines` decodes the raw\n * newline-delimited body into events (SSE or NDJSON); the reconnect engine is\n * shared with the fetch adapters. A fresh XHR is opened per attempt, so a\n * `Last-Event-ID` reconnect header (via `extraHeaders`) is applied at open time.\n */\nfunction xhrEventSource(\n url: string,\n options: XhrConnectionOptions,\n method: string,\n messages: Array<UIMessage> | Array<ModelMessage>,\n data: Record<string, any> | undefined,\n runContext: RunAgentInputContext | undefined,\n parseLines: (lines: AsyncIterable<string>) => AsyncIterable<StreamEvent>,\n): StreamEventSource {\n return async function* (extraHeaders, abortSignal) {\n const request = createConfiguredXhrRequest(\n url,\n options,\n messages,\n data,\n runContext,\n method,\n extraHeaders,\n )\n const lines = readXhrLines(request.xhr, abortSignal)\n if (abortSignal?.aborted) {\n await lines.next()\n return\n }\n // A read-only join is a bodyless GET; a run POSTs the RunAgentInput payload.\n request.xhr.send(method === 'GET' ? null : request.body)\n try {\n yield* parseLines(lines)\n } finally {\n // Tear the socket down on an early exit (terminal reached or reconnect\n // break) so late bytes stop downloading. When the abort signal fired,\n // `readXhrLines` already aborted — skip here to avoid a double abort().\n if (!abortSignal?.aborted) request.xhr.abort()\n }\n }\n}\n\n/** SSE line parser bound to the run's ids for a `[DONE]` fallback. */\nfunction xhrSSEParser(runContext: RunAgentInputContext | undefined) {\n const fallbackIds: { threadId?: string; runId?: string } = {\n ...(runContext?.threadId !== undefined\n ? { threadId: runContext.threadId }\n : {}),\n ...(runContext?.runId !== undefined ? { runId: runContext.runId } : {}),\n }\n return (lines: AsyncIterable<string>) => linesToSSEEvents(lines, fallbackIds)\n}\n\n/**\n * Create an XMLHttpRequest-backed Server-Sent Events connection adapter.\n *\n * Resumable: against a durable (`id:`-tagged) server response, a dropped socket\n * auto-reconnects with `Last-Event-ID` and de-dupes the replayed prefix, and\n * `joinRun` attaches to an existing run from the start. A non-durable response\n * is a single plain request, exactly as before.\n */\nexport function xhrServerSentEvents(\n url: string | (() => string),\n options: XhrConnectionOptionsResolver = {},\n): ResumableConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n // POST URL is byte-identical to a plain request; the run id (when set)\n // rides in the X-Run-Id header so durability can key the log by it\n // without changing the request URL existing clients rely on.\n const requestUrl = resolvedUrl\n yield* resumableStream(\n xhrEventSource(\n requestUrl,\n resolvedOptions,\n 'POST',\n messages,\n data,\n runContext,\n xhrSSEParser(runContext),\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async *joinRun(runId, abortSignal) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n const joinUrl = withSearchParams(resolvedUrl, { offset: '-1', runId })\n yield* resumableStream(\n xhrEventSource(\n joinUrl,\n resolvedOptions,\n 'GET',\n [],\n undefined,\n undefined,\n // A `[DONE]` during a join correlates to the joined run id (parity\n // with fetchServerSentEvents.joinRun).\n (lines) => linesToSSEEvents(lines, { runId }),\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async hydrate(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n // Hydration is a non-streaming JSON GET, so fetch is fine even for the\n // XHR-backed streaming adapter.\n return fetchThreadHydration(\n fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.withCredentials ? 'include' : 'same-origin',\n threadId,\n )\n },\n async hydrateGeneration(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n // Hydration is a non-streaming JSON GET, so fetch is fine even for the\n // XHR-backed streaming adapter.\n return fetchGenerationHydration(\n fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.withCredentials ? 'include' : 'same-origin',\n threadId,\n )\n },\n }\n}\n\n/**\n * Create an XMLHttpRequest-backed newline-delimited JSON stream adapter.\n *\n * Resumable: against a durable (envelope-tagged) server response, a dropped\n * socket auto-reconnects with `Last-Event-ID` and de-dupes the replayed prefix,\n * and `joinRun` attaches to an existing run from the start. A non-durable\n * (bare-line) response is a single plain request, exactly as before.\n */\nexport function xhrHttpStream(\n url: string | (() => string),\n options: XhrConnectionOptionsResolver = {},\n): ResumableConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n // POST URL is byte-identical to a plain request; the run id (when set)\n // rides in the X-Run-Id header so durability can key the log by it\n // without changing the request URL existing clients rely on.\n const requestUrl = resolvedUrl\n yield* resumableStream(\n xhrEventSource(\n requestUrl,\n resolvedOptions,\n 'POST',\n messages,\n data,\n runContext,\n linesToNdjsonEvents,\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async *joinRun(runId, abortSignal) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n const joinUrl = withSearchParams(resolvedUrl, { offset: '-1', runId })\n yield* resumableStream(\n xhrEventSource(\n joinUrl,\n resolvedOptions,\n 'GET',\n [],\n undefined,\n undefined,\n linesToNdjsonEvents,\n ),\n signal,\n resolvedOptions.reconnect,\n )\n },\n async hydrate(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n // Hydration is a non-streaming JSON GET, so fetch is fine even for the\n // XHR-backed streaming adapter.\n return fetchThreadHydration(\n fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.withCredentials ? 'include' : 'same-origin',\n threadId,\n )\n },\n async hydrateGeneration(threadId) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n // Hydration is a non-streaming JSON GET, so fetch is fine even for the\n // XHR-backed streaming adapter.\n return fetchGenerationHydration(\n fetch,\n resolvedUrl,\n mergeHeaders(resolvedOptions.headers),\n resolvedOptions.withCredentials ? 'include' : 'same-origin',\n threadId,\n )\n },\n }\n}\n\nexport interface WebSocketConnectionOptions {\n protocols?: string | Array<string>\n body?: Record<string, unknown>\n reconnect?: ReconnectOptions\n /** Override the WebSocket implementation (tests / non-browser runtimes). */\n WebSocketImpl?: typeof WebSocket\n}\n\nfunction runIdQuery(url: string, runId: string | undefined): string {\n return runId ? withSearchParams(url, { runId }) : url\n}\n\nfunction isPingFrame(parsed: unknown): boolean {\n return (\n typeof parsed === 'object' &&\n parsed !== null &&\n (parsed as { type?: unknown }).type === 'ping'\n )\n}\n\n/** A subscribe() consumer's registration: receives chunks or a fatal error. */\ninterface WebSocketChunkSink {\n push: (chunk: StreamChunk) => void\n fail: (error: unknown) => void\n}\n\n/**\n * A push→pull bridge from socket callbacks to an async iterable: chunks queue\n * until the consumer pulls, a recorded failure rejects the iterator, and\n * `end()` (or the abort signal) finishes it cleanly. Shared by `webSocket()`'s\n * `subscribe()` and `joinRun()`.\n */\nfunction createChunkPipe(\n abortSignal: AbortSignal | undefined,\n onFinally: () => void,\n): {\n push: (chunk: StreamChunk) => void\n fail: (error: unknown) => void\n end: () => void\n iterable: AsyncIterable<StreamChunk>\n} {\n const queue: Array<StreamChunk> = []\n const waiters: Array<(c: StreamChunk | null) => void> = []\n let failure: unknown\n let ended = false\n const wake = () => waiters.shift()?.(null)\n const push = (chunk: StreamChunk) => {\n const w = waiters.shift()\n if (w) w(chunk)\n else queue.push(chunk)\n }\n const fail = (error: unknown) => {\n failure = error\n wake()\n }\n const end = () => {\n ended = true\n wake()\n }\n const onAbort = () => wake()\n abortSignal?.addEventListener('abort', onAbort)\n const iterable = (async function* () {\n try {\n while (!abortSignal?.aborted) {\n // Drain buffered chunks before ever awaiting a new promise — a\n // fatal drop that lands while chunks are still queued (fail()\n // finds no pending waiter, since the consumer hasn't caught up\n // to its buffer yet) must not be lost.\n const buffered = queue.shift()\n if (buffered !== undefined) {\n yield buffered\n continue\n }\n // Buffer exhausted: surface a failure recorded while we were\n // draining, rather than awaiting a promise that will never\n // resolve (the connection is dead — no future push/fail).\n if (failure !== undefined) throw failure\n if (ended) return\n const chunk = await new Promise<StreamChunk | null>((r) =>\n waiters.push(r),\n )\n // The wait resolved because fail() woke us — surface the error\n // instead of treating the null sentinel as a clean end. TS narrows\n // `failure` to `undefined` from the check above and doesn't know\n // the `fail()` closure can reassign it while we were awaiting —\n // this check is very much still reachable.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (failure !== undefined) throw failure\n if (chunk === null) return\n yield chunk\n }\n } finally {\n abortSignal?.removeEventListener('abort', onAbort)\n onFinally()\n }\n })()\n return { push, fail, end, iterable }\n}\n\n/**\n * The send()-driven run currently owning auto-reconnect for a `webSocket()`\n * connection: reconnect is scoped to the run `send()` is driving, so a drop\n * with no established run is surfaced to subscribers rather than auto-resumed.\n */\ninterface WebSocketRunSession {\n runId: string | undefined\n readonly tracker: ReconnectTracker\n sawTerminal: boolean\n /** Made forward progress (a new, non-duplicate chunk) since the last (re)connect. */\n progressed: boolean\n signal: AbortSignal | undefined\n}\n\n/**\n * Full-duplex, conversation-scoped WebSocket connection adapter. Pairs with the\n * server `toWebSocketResponse` / `toWebSocketStream`. `send()` writes a\n * RunAgentInput frame; `subscribe()` yields inbound chunks.\n *\n * Resumable: `send()` establishes a run session backed by a\n * {@link createReconnectTracker}. If the socket closes before a terminal\n * (`RUN_FINISHED`/`RUN_ERROR`) chunk is seen and the run is durable\n * (offset-tagged `{ id, chunk }` envelopes), the socket is reopened at\n * `?runId=&offset=<lastEventId>`, de-duping the replayed boundary. A drop with\n * no offset ever observed (non-durable) surfaces {@link StreamReadError}\n * instead of reconnecting — there is nothing to resume from.\n */\nexport function webSocket(\n url: string | (() => string),\n options: WebSocketConnectionOptions = {},\n): SubscribeConnectionAdapter & {\n joinRun: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n} {\n const Impl = options.WebSocketImpl ?? WebSocket\n let socket: WebSocket | undefined\n // Whether the current socket is the conversation socket ('run') or a\n // read-only replay connection opened by a reconnect ('resume'). Only the\n // conversation socket accepts run frames server-side.\n let socketMode: 'run' | 'resume' | undefined\n // Memoized per-socket open promise. `openOnce` sets `onopen`/`onerror`\n // exactly ONCE, at socket-creation time, and stores the resulting promise\n // here. Without this, `waitOpen` assigning `onopen`/`onerror` on every call\n // would clobber a still-pending prior caller's handlers: `openOnce` reuses\n // the same in-flight socket for concurrent callers (`readyState <= 1`), so a\n // second `send()` issued before the handshake completes would overwrite the\n // first call's handlers and leave its promise permanently unresolved.\n let openPromise: Promise<void> | undefined\n const listeners = new Set<WebSocketChunkSink>()\n let currentSession: WebSocketRunSession | undefined\n\n function failAll(error: unknown): void {\n for (const l of listeners) l.fail(error)\n }\n\n function openOnce(target: string, mode: 'run' | 'resume'): WebSocket {\n // Only the conversation socket is reused — it multiplexes many turns. A\n // 'resume' handshake carries ?offset and must reach the server as its own\n // connection (reusing any open socket would discard that query, so no\n // replay would ever be requested), and a run frame must never be written\n // to a read-only resume socket (the server registers no message listener\n // there, so the frame would be silently ignored).\n if (\n socket &&\n socket.readyState <= 1 &&\n mode === 'run' &&\n socketMode === 'run'\n ) {\n return socket\n }\n const prior = socket\n const ws = options.protocols\n ? new Impl(target, options.protocols)\n : new Impl(target)\n socket = ws\n socketMode = mode\n openPromise = new Promise<void>((resolve, reject) => {\n ws.onopen = () => resolve()\n ws.onerror = (e) => reject(new StreamReadError(e))\n })\n // Attach a no-op handler so a socket nobody awaits can't raise an\n // unhandled rejection if it errors. Awaiters of openPromise still see the rejection.\n openPromise.catch(() => {})\n ws.onmessage = (event: MessageEvent) => {\n // A retired socket (a newer connection took over below) must not keep\n // feeding the shared listeners.\n if (ws !== socket) return\n let parsed: unknown\n try {\n parsed = JSON.parse(String(event.data))\n } catch (error) {\n failAll(new StreamReadError(error))\n return\n }\n if (isPingFrame(parsed)) return\n const envelopeId = isNdjsonEnvelope(parsed) ? parsed.id : undefined\n const chunk = restoreInboundUsage(\n isNdjsonEnvelope(parsed) ? parsed.chunk : (parsed as StreamChunk),\n )\n\n // Thread durable chunks through the active run session's tracker (if\n // any) so a later reconnect knows the last offset and can skip a\n // replayed boundary. A socket with no active session dispatches chunks\n // as-is.\n const session = currentSession\n if (session) {\n if (session.tracker.note(envelopeId) === 'duplicate') return\n session.progressed = true\n if (session.runId === undefined) {\n session.runId = getChunkRunId(chunk)\n }\n if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {\n session.sawTerminal = true\n }\n }\n for (const l of listeners) l.push(chunk)\n }\n ws.onclose = () => {\n // Retired deliberately in favor of a newer connection — not a drop.\n if (ws !== socket) return\n const session = currentSession\n if (!session) {\n // No run session (never established, or cleared by a prior failure).\n // Surface the drop so subscribers do not stay parked on a dead socket.\n failAll(new StreamReadError(new Error('WebSocket connection closed')))\n return\n }\n if (session.signal?.aborted || session.sawTerminal) return\n const lastEventId = session.tracker.lastEventId\n if (lastEventId === undefined) {\n // Non-durable run (no offset ever observed) — nothing to resume\n // from. Surface a hard failure rather than silently reconnecting\n // forever against a server that never tags its events.\n currentSession = undefined\n failAll(new StreamReadError(new Error('WebSocket connection closed')))\n return\n }\n void reconnect(session, lastEventId)\n }\n // Retire a superseded socket (e.g. a lingering resume socket when send()\n // opens the next conversation socket) so two sockets never feed the\n // shared listeners at once. Its handlers see it is no longer current and\n // ignore the close.\n if (prior && prior.readyState <= 1) prior.close()\n return ws\n }\n\n async function reconnect(\n session: WebSocketRunSession,\n offset: string,\n ): Promise<void> {\n try {\n // Bounded by the shared tracker's consecutive-no-progress ceiling —\n // mirrors resumableStream so a flapping server can't reconnect forever.\n await session.tracker.waitBeforeReconnect(\n session.progressed,\n session.signal,\n )\n } catch (error) {\n if (currentSession === session) currentSession = undefined\n failAll(error)\n return\n }\n if (session.signal?.aborted) return\n // A send() issued during the backoff supersedes this resume: a newer run\n // (or a resubmit of this one) already owns a fresh conversation socket,\n // and its turn re-delivers from the durability log — the tracker de-dupes\n // any overlap. Opening the resume socket anyway would retire that live\n // conversation socket.\n if (currentSession !== session) return\n if (socket && socket.readyState <= 1) return\n session.progressed = false\n const base = typeof url === 'function' ? url() : url\n const target = withSearchParams(base, {\n ...(session.runId !== undefined ? { runId: session.runId } : {}),\n offset,\n })\n openOnce(target, 'resume')\n }\n\n function waitOpen(ws: WebSocket): Promise<void> {\n if (ws.readyState === 1) return Promise.resolve()\n // Concurrent callers awaiting the SAME in-flight socket share the SAME\n // memoized promise (set once in `openOnce`), so none of them clobber\n // another's onopen/onerror handler.\n return openPromise ?? Promise.resolve()\n }\n\n return {\n subscribe(abortSignal?: AbortSignal): AsyncIterable<StreamChunk> {\n const pipe = createChunkPipe(abortSignal, () => listeners.delete(sink))\n const sink: WebSocketChunkSink = { push: pipe.push, fail: pipe.fail }\n listeners.add(sink)\n return pipe.iterable\n },\n async send(messages, data, abortSignal, runContext) {\n const target = typeof url === 'function' ? url() : url\n const ws = openOnce(runIdQuery(target, runContext?.runId), 'run')\n await waitOpen(ws)\n // Establish (or continue) the run session this socket is driving, so\n // an unterminated drop can auto-resume it. A distinct runId starts a\n // fresh tracker (a new run's offsets are unrelated to the last one's);\n // the same runId reuses the tracker so a repeat send() on an\n // already-tracked run doesn't lose its de-dupe/offset state.\n if (!currentSession || currentSession.runId !== runContext?.runId) {\n currentSession = {\n runId: runContext?.runId,\n tracker: createReconnectTracker(options.reconnect),\n sawTerminal: false,\n progressed: false,\n signal: abortSignal,\n }\n } else {\n // Same-runId resubmit (e.g. a client-tool continuation): keep the\n // tracker, but this is a NEW turn — with the previous turn's\n // `sawTerminal` left set, a drop during the resubmitted turn would\n // neither reconnect nor surface an error.\n currentSession.signal = abortSignal\n currentSession.sawTerminal = false\n currentSession.progressed = false\n }\n const session = currentSession\n // stop() must reach the server: the conversation socket outlives the\n // turn, so without an abort frame the model keeps generating (and\n // billing) server-side. The frame aborts only this run's turn.\n abortSignal?.addEventListener(\n 'abort',\n () => {\n const abortRunId = session.runId\n const live = socket\n if (\n abortRunId === undefined ||\n session.sawTerminal ||\n socketMode !== 'run' ||\n live === undefined ||\n live.readyState !== 1\n ) {\n return\n }\n try {\n live.send(JSON.stringify({ type: 'abort', runId: abortRunId }))\n } catch {\n // Socket is CLOSING/CLOSED — the server aborts the turn on close.\n }\n },\n { once: true },\n )\n const body = buildRunAgentInputBody(messages, data, runContext, {\n body: options.body,\n })\n ws.send(JSON.stringify(body))\n },\n joinRun(runId, abortSignal): AsyncIterable<StreamChunk> {\n const target = withSearchParams(typeof url === 'function' ? url() : url, {\n offset: '-1',\n runId,\n })\n // A replay handshake must reach the server as its own connection:\n // reusing the conversation socket would discard the ?offset query (no\n // replay ever requested), and the conversation socket must not be\n // replaced by a read-only replay socket. So joinRun owns a dedicated\n // socket and never touches the shared socket or run session.\n const ws = options.protocols\n ? new Impl(target, options.protocols)\n : new Impl(target)\n const pipe = createChunkPipe(abortSignal, () => {\n if (ws.readyState <= 1) ws.close()\n })\n ws.onmessage = (event: MessageEvent) => {\n let parsed: unknown\n try {\n parsed = JSON.parse(String(event.data))\n } catch (error) {\n pipe.fail(new StreamReadError(error))\n return\n }\n if (isPingFrame(parsed)) return\n pipe.push(\n restoreInboundUsage(\n isNdjsonEnvelope(parsed) ? parsed.chunk : (parsed as StreamChunk),\n ),\n )\n }\n ws.onclose = (event?: CloseEvent) => {\n // 1000 = the server finished replaying the log and closed cleanly.\n // Anything else is a drop or a policy refusal (e.g. 1008 \"no resume\n // offset\") and must surface — a joinRun socket never auto-reconnects.\n if (event?.code === 1000) {\n pipe.end()\n return\n }\n const detail = event\n ? `${event.code}${event.reason ? `: ${event.reason}` : ''}`\n : 'unknown'\n pipe.fail(\n new StreamReadError(\n new Error(`WebSocket connection closed (${detail})`),\n ),\n )\n }\n return pipe.iterable\n },\n }\n}\n\n/**\n * Optional persistence handlers for the lightweight adapters (`stream()`,\n * `rpcStream()`). These are one-shot, request-scoped calls with no built-in\n * GET endpoint or second channel, so hydration and run-rejoin only exist if\n * the app supplies them — typically thin wrappers over TanStack Start server\n * functions backed by `@tanstack/ai-persistence` (`getGenerationHydration`)\n * and a delivery-durability log (`memoryStream` / `replayRunStream`).\n *\n * Each handler is spread onto the returned adapter only when defined, so\n * feature detection (`connection.hydrateGeneration` etc.) keeps working.\n */\nexport interface StreamConnectionHandlers {\n /**\n * Server-driven chat hydration for `persistence: true`: the stored\n * transcript for `threadId` plus a cursor to an in-flight run.\n */\n hydrate?: (threadId: string) => Promise<ChatHydrationResult>\n /**\n * Server-driven generation hydration for `persistence: true`: the last\n * generation's resume snapshot for `threadId` plus a cursor to a run still\n * generating. See {@link ConnectConnectionAdapter.hydrateGeneration}.\n */\n hydrateGeneration?: (threadId: string) => Promise<GenerationHydrationResult>\n /**\n * Re-attach to a run still generating and replay it from the start. See\n * {@link ConnectConnectionAdapter.joinRun}.\n */\n joinRun?: (\n runId: string,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>\n}\n\n/**\n * Create a direct stream connection adapter (for server functions or direct streams)\n *\n * @param streamFactory - A function that returns an async iterable of StreamChunks\n * @param handlers - Optional persistence handlers (`hydrate`,\n * `hydrateGeneration`, `joinRun`) that let server-driven persistence work\n * without an HTTP endpoint — each is usually a one-line server-function call\n * @returns A connection adapter for direct streams\n *\n * @example\n * ```typescript\n * // With TanStack Start server function\n * const connection = stream(() => serverFunction({ messages }));\n *\n * const client = new ChatClient({ connection });\n *\n * // With generation persistence over server functions\n * const connection = stream(\n * () => generateImageFn({ data: input }),\n * {\n * hydrateGeneration: (threadId) => getImageHydrationFn({ data: threadId }),\n * joinRun: (runId) => joinImageRunFn({ data: runId }),\n * },\n * );\n * ```\n */\nexport function stream(\n streamFactory: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>,\n handlers?: StreamConnectionHandlers,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal) {\n // Pass messages as-is (UIMessages with parts preserved)\n // Server-side chat() handles conversion to ModelMessages\n yield* streamFactory(messages, data, abortSignal)\n },\n ...(handlers?.hydrate ? { hydrate: handlers.hydrate } : {}),\n ...(handlers?.hydrateGeneration\n ? { hydrateGeneration: handlers.hydrateGeneration }\n : {}),\n ...(handlers?.joinRun ? { joinRun: handlers.joinRun } : {}),\n }\n}\n\n/**\n * Wrap a `ChatFetcher` as a `ConnectConnectionAdapter` so the chat client can\n * consume it through the same `subscribe`/`send` plumbing used for SSE /\n * HTTP-stream / RPC connections. May return either a `Response` (parsed as\n * SSE) or an `AsyncIterable<StreamChunk>` (yielded directly).\n *\n * @internal\n */\nexport function fetcherToConnectionAdapter(\n fetcher: ChatFetcher,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n if (!abortSignal) {\n throw new Error(\n 'fetcherToConnectionAdapter requires an AbortSignal — the chat client always supplies one.',\n )\n }\n if (!runContext) {\n throw new Error(\n 'fetcherToConnectionAdapter requires a RunAgentInputContext — the chat client always supplies one.',\n )\n }\n const uiMessages = messages as Array<UIMessage>\n const result = await fetcher(\n {\n messages: uiMessages,\n data,\n threadId: runContext.threadId,\n runId: runContext.runId,\n ...(runContext.parentRunId !== undefined\n ? { parentRunId: runContext.parentRunId }\n : {}),\n ...(runContext.resume !== undefined\n ? { resume: runContext.resume }\n : {}),\n },\n { signal: abortSignal, headers: runContext.headers },\n )\n if (result instanceof Response) {\n yield* responseToSSEChunks(result, abortSignal)\n } else {\n yield* abortableIterable(result, abortSignal)\n }\n },\n }\n}\n\n/**\n * Wrap an AsyncIterable so iteration aborts when `signal` fires. Without\n * this, a fetcher that returns a generator ignoring its signal would leave\n * the for-await loop hanging until the iterable naturally ends.\n */\nasync function* abortableIterable<T>(\n iterable: AsyncIterable<T>,\n signal: AbortSignal,\n): AsyncGenerator<T> {\n if (signal.aborted) return\n const iterator = iterable[Symbol.asyncIterator]()\n const abortPromise = new Promise<{ done: true; value: undefined }>(\n (resolve) => {\n signal.addEventListener(\n 'abort',\n () => resolve({ done: true, value: undefined }),\n { once: true },\n )\n },\n )\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const result = await Promise.race([iterator.next(), abortPromise])\n if (result.done) return\n yield result.value\n }\n } finally {\n await iterator.return?.()\n }\n}\n\n/**\n * Create an RPC stream connection adapter (for RPC-based streaming like Cap'n Web RPC)\n *\n * @param rpcCall - A function that accepts messages and returns an async iterable of StreamChunks\n * @param handlers - Optional persistence handlers (`hydrate`,\n * `hydrateGeneration`, `joinRun`) that let server-driven persistence work\n * without an HTTP endpoint — each is usually a one-line RPC call\n * @returns A connection adapter for RPC streams\n *\n * @example\n * ```typescript\n * // With Cap'n Web RPC\n * const connection = rpcStream((messages, data) =>\n * api.streamMurfResponse(messages, data)\n * );\n *\n * const client = new ChatClient({ connection });\n *\n * // With generation persistence over RPC\n * const connection = rpcStream(\n * (messages, data) => api.streamMurfResponse(messages, data),\n * {\n * hydrateGeneration: (threadId) => api.getGenerationHydration(threadId),\n * joinRun: (runId) => api.replayRun(runId),\n * },\n * );\n * ```\n */\nexport function rpcStream(\n rpcCall: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>,\n handlers?: StreamConnectionHandlers,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal) {\n // Pass messages as-is (UIMessages with parts preserved)\n // Server-side chat() handles conversion to ModelMessages\n yield* rpcCall(messages, data, abortSignal)\n },\n ...(handlers?.hydrate ? { hydrate: handlers.hydrate } : {}),\n ...(handlers?.hydrateGeneration\n ? { hydrateGeneration: handlers.hydrateGeneration }\n : {}),\n ...(handlers?.joinRun ? { joinRun: handlers.joinRun } : {}),\n }\n}\n"],"mappings":"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"}
|