agents 0.15.0 → 0.16.0
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/dist/{agent-tool-types-VPsjVYL0.d.ts → agent-tool-types-NofdbL9X.d.ts} +57 -4
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-BGpgfpJT.d.ts → agent-tools-DLquv-dp.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/browser/ai.d.ts +126 -7
- package/dist/browser/ai.js +73 -29
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/index.d.ts +81 -69
- package/dist/browser/index.js +3 -2
- package/dist/browser/tanstack-ai.d.ts +13 -7
- package/dist/browser/tanstack-ai.js +18 -19
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/chat/index.d.ts +111 -5
- package/dist/chat/index.js +207 -35
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/{classPrivateFieldGet2-Beqsfu2Z.js → classPrivateFieldGet2-CZ7QjTXN.js} +5 -5
- package/dist/{classPrivateMethodInitSpec-B5ko1s2R.js → classPrivateMethodInitSpec-D-0__zd9.js} +2 -2
- package/dist/client.d.ts +19 -2
- package/dist/client.js +31 -11
- package/dist/client.js.map +1 -1
- package/dist/{compaction-helpers-BEUILPss.d.ts → compaction-helpers-DVcu5lPN.d.ts} +91 -12
- package/dist/connector-D6yYzYHg.js +1080 -0
- package/dist/connector-D6yYzYHg.js.map +1 -0
- package/dist/connector-DXursxV5.d.ts +340 -0
- package/dist/experimental/memory/session/index.d.ts +75 -12
- package/dist/experimental/memory/session/index.js +226 -21
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/memory/utils/index.d.ts +2 -2
- package/dist/{index-CPe1OtI0.d.ts → index-B7IbEeze.d.ts} +32 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +116 -45
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/react.d.ts +12 -1
- package/dist/react.js +101 -30
- package/dist/react.js.map +1 -1
- package/dist/{retries-CF_HKSlJ.d.ts → retries-CwlpAGet.d.ts} +35 -5
- package/dist/retries.d.ts +9 -5
- package/dist/retries.js +87 -1
- package/dist/retries.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/skills/index.js +2 -2
- package/dist/sub-routing.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/package.json +10 -10
- package/dist/shared-4CAYLCTO.d.ts +0 -34
- package/dist/shared-wyII629d.js +0 -432
- package/dist/shared-wyII629d.js.map +0 -1
package/dist/chat/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["textEncoder","sendIfOpen","isWebSocketClosedSendError"],"sources":["../../src/chat/sanitize.ts","../../src/chat/stream-accumulator.ts","../../src/chat/turn-queue.ts","../../src/chat/submit-concurrency.ts","../../src/chat/broadcast-state.ts","../../src/chat/protocol.ts","../../src/chat/resumable-stream.ts","../../src/chat/sql-batch.ts","../../src/chat/client-tools.ts","../../src/chat/continuation-state.ts","../../src/chat/abort-registry.ts","../../src/chat/tool-state.ts","../../src/chat/parse-protocol.ts","../../src/chat/message-reconciler.ts","../../src/chat/recovery.ts"],"sourcesContent":["/**\n * Message sanitization and row-size enforcement utilities.\n *\n * Shared by @cloudflare/ai-chat and @cloudflare/think to ensure persistence\n * hygiene: stripping ephemeral provider metadata and compacting\n * oversized messages before writing to SQLite.\n */\n\nimport type { ProviderMetadata, ReasoningUIPart, UIMessage } from \"ai\";\nimport { truncateToolOutput } from \"./tool-output-truncation\";\n\nconst textEncoder = new TextEncoder();\n\n/** Maximum serialized message size before compaction (bytes). 1.8MB with headroom below SQLite's 2MB limit. */\nexport const ROW_MAX_BYTES = 1_800_000;\n\n/** Measure UTF-8 byte length of a string. */\nexport function byteLength(s: string): number {\n return textEncoder.encode(s).byteLength;\n}\n\n/**\n * Sanitize a message for persistence by removing ephemeral provider-specific\n * data that should not be stored or sent back in subsequent requests.\n *\n * 1. Strips OpenAI ephemeral fields (itemId, reasoningEncryptedContent)\n * 2. Filters truly empty reasoning parts (no text, no remaining providerMetadata)\n */\nexport function sanitizeMessage(message: UIMessage): UIMessage {\n const strippedParts = message.parts.map((part) => {\n let sanitizedPart = part;\n\n if (\n \"providerMetadata\" in sanitizedPart &&\n sanitizedPart.providerMetadata &&\n typeof sanitizedPart.providerMetadata === \"object\" &&\n \"openai\" in sanitizedPart.providerMetadata\n ) {\n sanitizedPart = stripOpenAIMetadata(sanitizedPart, \"providerMetadata\");\n }\n\n if (\n \"callProviderMetadata\" in sanitizedPart &&\n sanitizedPart.callProviderMetadata &&\n typeof sanitizedPart.callProviderMetadata === \"object\" &&\n \"openai\" in sanitizedPart.callProviderMetadata\n ) {\n sanitizedPart = stripOpenAIMetadata(\n sanitizedPart,\n \"callProviderMetadata\"\n );\n }\n\n return sanitizedPart;\n }) as UIMessage[\"parts\"];\n\n const sanitizedParts = strippedParts.filter((part) => {\n if (part.type === \"reasoning\") {\n const reasoningPart = part as ReasoningUIPart;\n if (!reasoningPart.text || reasoningPart.text.trim() === \"\") {\n if (\n \"providerMetadata\" in reasoningPart &&\n reasoningPart.providerMetadata &&\n typeof reasoningPart.providerMetadata === \"object\" &&\n Object.keys(reasoningPart.providerMetadata).length > 0\n ) {\n return true;\n }\n return false;\n }\n }\n return true;\n });\n\n return { ...message, parts: sanitizedParts };\n}\n\nfunction stripOpenAIMetadata<T extends UIMessage[\"parts\"][number]>(\n part: T,\n metadataKey: \"providerMetadata\" | \"callProviderMetadata\"\n): T {\n const metadata = (part as Record<string, unknown>)[metadataKey] as {\n openai?: Record<string, unknown>;\n [key: string]: unknown;\n };\n\n if (!metadata?.openai) return part;\n\n const {\n itemId: _itemId,\n reasoningEncryptedContent: _rec,\n ...restOpenai\n } = metadata.openai;\n\n const hasOtherOpenaiFields = Object.keys(restOpenai).length > 0;\n const { openai: _openai, ...restMetadata } = metadata;\n\n let newMetadata: ProviderMetadata | undefined;\n if (hasOtherOpenaiFields) {\n newMetadata = { ...restMetadata, openai: restOpenai } as ProviderMetadata;\n } else if (Object.keys(restMetadata).length > 0) {\n newMetadata = restMetadata as ProviderMetadata;\n }\n\n const { [metadataKey]: _oldMeta, ...restPart } = part as Record<\n string,\n unknown\n >;\n\n if (newMetadata) {\n return { ...restPart, [metadataKey]: newMetadata } as T;\n }\n return restPart as T;\n}\n\n/**\n * Enforce SQLite row size limits by compacting tool outputs and text parts\n * when a serialized message exceeds the safety threshold (1.8MB).\n *\n * Compaction strategy:\n * 1. Compact tool outputs over 1KB while preserving structured output shape\n * 2. If still too big, truncate text parts from oldest to newest\n */\nexport function enforceRowSizeLimit(message: UIMessage): UIMessage {\n let json = JSON.stringify(message);\n let size = byteLength(json);\n if (size <= ROW_MAX_BYTES) return message;\n\n if (message.role !== \"assistant\") {\n return truncateTextParts(message);\n }\n\n const compactedParts = message.parts.map((part) => {\n if (\n \"output\" in part &&\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"output-available\"\n ) {\n const output = (part as { output: unknown }).output;\n const truncated = truncateToolOutput(output, 1000);\n if (truncated.truncated) {\n return {\n ...part,\n output: truncated.output\n };\n }\n }\n return part;\n }) as UIMessage[\"parts\"];\n\n let result: UIMessage = { ...message, parts: compactedParts };\n\n json = JSON.stringify(result);\n size = byteLength(json);\n if (size <= ROW_MAX_BYTES) return result;\n\n return truncateTextParts(result);\n}\n\nfunction truncateTextParts(message: UIMessage): UIMessage {\n const parts = [...message.parts];\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n if (part.type === \"text\" && \"text\" in part) {\n const text = (part as { text: string }).text;\n if (text.length > 1000) {\n parts[i] = {\n ...part,\n text:\n `[Text truncated for storage (${text.length} chars). ` +\n `First 500 chars: ${text.slice(0, 500)}...]`\n } as UIMessage[\"parts\"][number];\n\n const candidate = { ...message, parts };\n if (byteLength(JSON.stringify(candidate)) <= ROW_MAX_BYTES) {\n break;\n }\n }\n }\n }\n\n return { ...message, parts };\n}\n","/**\n * StreamAccumulator — unified chunk-to-message builder.\n *\n * Used by @cloudflare/ai-chat (server + client) and @cloudflare/think\n * to incrementally build a UIMessage from stream chunks. Wraps\n * applyChunkToParts and handles the metadata chunk types (start, finish,\n * message-metadata, error) that applyChunkToParts does not cover.\n *\n * The accumulator signals domain-specific concerns (early persistence,\n * cross-message tool updates) via ChunkAction returns — callers handle\n * these according to their context.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { applyChunkToParts, type StreamChunkData } from \"./message-builder\";\n\nfunction asMetadata(value: unknown): Record<string, unknown> | undefined {\n if (value != null && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\nexport interface StreamAccumulatorOptions {\n messageId: string;\n continuation?: boolean;\n existingParts?: UIMessage[\"parts\"];\n existingMetadata?: Record<string, unknown>;\n}\n\nexport type ChunkAction =\n | {\n type: \"start\";\n messageId?: string;\n metadata?: Record<string, unknown>;\n }\n | {\n type: \"finish\";\n finishReason?: string;\n metadata?: Record<string, unknown>;\n }\n | { type: \"message-metadata\"; metadata: Record<string, unknown> }\n | { type: \"tool-approval-request\"; toolCallId: string }\n | {\n type: \"cross-message-tool-update\";\n updateType: \"output-available\" | \"output-error\";\n toolCallId: string;\n output?: unknown;\n errorText?: string;\n preliminary?: boolean;\n }\n | { type: \"error\"; error: string };\n\nexport interface ChunkResult {\n handled: boolean;\n action?: ChunkAction;\n}\n\nexport class StreamAccumulator {\n messageId: string;\n readonly parts: UIMessage[\"parts\"];\n metadata?: Record<string, unknown>;\n private _isContinuation: boolean;\n\n constructor(options: StreamAccumulatorOptions) {\n this.messageId = options.messageId;\n this._isContinuation = options.continuation ?? false;\n this.parts = options.existingParts ? [...options.existingParts] : [];\n this.metadata = options.existingMetadata\n ? { ...options.existingMetadata }\n : undefined;\n }\n\n applyChunk(chunk: StreamChunkData): ChunkResult {\n const handled = applyChunkToParts(this.parts, chunk);\n\n // Detect tool-approval-request for early persistence signaling\n if (chunk.type === \"tool-approval-request\" && chunk.toolCallId) {\n return {\n handled,\n action: { type: \"tool-approval-request\", toolCallId: chunk.toolCallId }\n };\n }\n\n // Detect cross-message tool output/error: applyChunkToParts returns true\n // for recognized types but silently does nothing when the toolCallId\n // doesn't exist in the current parts array.\n if (\n (chunk.type === \"tool-output-available\" ||\n chunk.type === \"tool-output-error\") &&\n chunk.toolCallId\n ) {\n const foundInParts = this.parts.some(\n (p) => \"toolCallId\" in p && p.toolCallId === chunk.toolCallId\n );\n if (!foundInParts) {\n return {\n handled,\n action: {\n type: \"cross-message-tool-update\",\n updateType:\n chunk.type === \"tool-output-available\"\n ? \"output-available\"\n : \"output-error\",\n toolCallId: chunk.toolCallId,\n output: chunk.output,\n errorText: chunk.errorText,\n preliminary: chunk.preliminary\n }\n };\n }\n }\n\n if (!handled) {\n switch (chunk.type) {\n case \"start\": {\n if (chunk.messageId != null && !this._isContinuation) {\n this.messageId = chunk.messageId;\n }\n const startMeta = asMetadata(chunk.messageMetadata);\n if (startMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...startMeta }\n : { ...startMeta };\n }\n return {\n handled: true,\n action: {\n type: \"start\",\n messageId: chunk.messageId,\n metadata: startMeta\n }\n };\n }\n case \"finish\": {\n const finishMeta = asMetadata(chunk.messageMetadata);\n if (finishMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...finishMeta }\n : { ...finishMeta };\n }\n const finishReason =\n \"finishReason\" in chunk\n ? (chunk.finishReason as string)\n : undefined;\n return {\n handled: true,\n action: {\n type: \"finish\",\n finishReason,\n metadata: finishMeta\n }\n };\n }\n case \"message-metadata\": {\n const msgMeta = asMetadata(chunk.messageMetadata);\n if (msgMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...msgMeta }\n : { ...msgMeta };\n }\n return {\n handled: true,\n action: {\n type: \"message-metadata\",\n metadata: msgMeta ?? {}\n }\n };\n }\n case \"finish-step\": {\n return { handled: true };\n }\n case \"error\": {\n return {\n handled: true,\n action: {\n type: \"error\",\n error: chunk.errorText ?? JSON.stringify(chunk)\n }\n };\n }\n }\n }\n\n return { handled };\n }\n\n /** Snapshot the current state as a UIMessage. */\n toMessage(): UIMessage {\n return {\n id: this.messageId,\n role: \"assistant\",\n parts: [...this.parts],\n ...(this.metadata != null && { metadata: this.metadata })\n } as UIMessage;\n }\n\n /**\n * Merge this accumulator's message into an existing message array.\n * Handles continuation (walk backward for last assistant), replacement\n * (update existing by messageId), or append (new message).\n */\n mergeInto(messages: UIMessage[]): UIMessage[] {\n let existingIdx = messages.findIndex((m) => m.id === this.messageId);\n\n if (existingIdx < 0 && this._isContinuation) {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"assistant\") {\n existingIdx = i;\n break;\n }\n }\n }\n\n const messageId =\n existingIdx >= 0 ? messages[existingIdx].id : this.messageId;\n\n const partialMessage: UIMessage = {\n id: messageId,\n role: \"assistant\",\n parts: [...this.parts],\n ...(this.metadata != null && { metadata: this.metadata })\n } as UIMessage;\n\n if (existingIdx >= 0) {\n const updated = [...messages];\n updated[existingIdx] = partialMessage;\n return updated;\n }\n return [...messages, partialMessage];\n }\n}\n","/**\n * TurnQueue — serial async queue with generation-based invalidation.\n *\n * Serializes async work via a promise chain, tracks which request is\n * currently active, and lets callers invalidate all queued work by\n * advancing a generation counter.\n *\n * Used by @cloudflare/ai-chat (full concurrency policy spectrum) and\n * @cloudflare/think (simple serial queue) to prevent overlapping\n * chat turns.\n */\n\nexport type TurnResult<T> =\n | { status: \"completed\"; value: T }\n | { status: \"stale\" };\n\nexport interface EnqueueOptions {\n /**\n * Generation to bind this turn to. Defaults to the current generation\n * at the time of the `enqueue` call. If the queue's generation has\n * advanced past this value by the time the turn reaches the front,\n * `fn` is not called and `{ status: \"stale\" }` is returned.\n */\n generation?: number;\n}\n\nexport class TurnQueue {\n private _queue: Promise<void> = Promise.resolve();\n private _generation = 0;\n private _activeRequestId: string | null = null;\n private _countsByGeneration = new Map<number, number>();\n\n get generation(): number {\n return this._generation;\n }\n\n get activeRequestId(): string | null {\n return this._activeRequestId;\n }\n\n get isActive(): boolean {\n return this._activeRequestId !== null;\n }\n\n async enqueue<T>(\n requestId: string,\n fn: () => Promise<T>,\n options?: EnqueueOptions\n ): Promise<TurnResult<T>> {\n const previousTurn = this._queue;\n let releaseTurn!: () => void;\n const capturedGeneration = options?.generation ?? this._generation;\n\n this._countsByGeneration.set(\n capturedGeneration,\n (this._countsByGeneration.get(capturedGeneration) ?? 0) + 1\n );\n\n this._queue = new Promise<void>((resolve) => {\n releaseTurn = resolve;\n });\n\n await previousTurn;\n\n if (this._generation !== capturedGeneration) {\n this._decrementCount(capturedGeneration);\n releaseTurn();\n return { status: \"stale\" };\n }\n\n this._activeRequestId = requestId;\n try {\n const value = await fn();\n return { status: \"completed\", value };\n } finally {\n this._activeRequestId = null;\n this._decrementCount(capturedGeneration);\n releaseTurn();\n }\n }\n\n /**\n * Advance the generation counter. All turns enqueued under older\n * generations will be skipped when they reach the front of the queue.\n */\n reset(): void {\n this._generation++;\n }\n\n /**\n * Wait until the queue is fully drained (no pending or active turns).\n */\n async waitForIdle(): Promise<void> {\n let queue: Promise<void>;\n do {\n queue = this._queue;\n await queue;\n } while (this._queue !== queue);\n }\n\n /**\n * Number of active + queued turns for a given generation.\n * Defaults to the current generation.\n */\n queuedCount(generation?: number): number {\n return this._countsByGeneration.get(generation ?? this._generation) ?? 0;\n }\n\n private _decrementCount(generation: number): void {\n const count = (this._countsByGeneration.get(generation) ?? 1) - 1;\n if (count <= 0) {\n this._countsByGeneration.delete(generation);\n } else {\n this._countsByGeneration.set(generation, count);\n }\n }\n}\n","import type { MessageConcurrency } from \"./lifecycle\";\n\nexport type NormalizedMessageConcurrency =\n | \"queue\"\n | \"latest\"\n | \"merge\"\n | \"drop\"\n | {\n strategy: \"debounce\";\n debounceMs: number;\n };\n\nexport type SubmitConcurrencyDecision = {\n action: \"execute\" | \"drop\";\n strategy: NormalizedMessageConcurrency | null;\n submitSequence: number | null;\n debounceUntilMs: number | null;\n};\n\nexport class SubmitConcurrencyController {\n private _submitSequence = 0;\n private _latestOverlappingSubmitSequence = 0;\n private _pendingEnqueueCount = 0;\n private _resetEpoch = 0;\n private _activeDebounceTimers = new Set<ReturnType<typeof setTimeout>>();\n private _activeDebounceResolves = new Set<() => void>();\n\n constructor(private readonly options: { defaultDebounceMs: number }) {}\n\n get pendingEnqueueCount(): number {\n return this._pendingEnqueueCount;\n }\n\n get overlappingSubmitCount(): number {\n return this._latestOverlappingSubmitSequence;\n }\n\n decide(options: {\n concurrency: MessageConcurrency;\n isSubmitMessage: boolean;\n queuedTurns: number;\n }): SubmitConcurrencyDecision {\n const queuedTurnsInCurrentEpoch =\n options.queuedTurns + this._pendingEnqueueCount;\n\n if (!options.isSubmitMessage || queuedTurnsInCurrentEpoch === 0) {\n return {\n action: \"execute\",\n strategy: null,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n const concurrency = this.normalize(options.concurrency);\n if (concurrency === \"drop\") {\n return {\n action: \"drop\",\n strategy: concurrency,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n if (concurrency === \"queue\") {\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n const submitSequence = ++this._submitSequence;\n this._latestOverlappingSubmitSequence = submitSequence;\n\n if (concurrency === \"latest\" || concurrency === \"merge\") {\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence,\n debounceUntilMs: null\n };\n }\n\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence,\n debounceUntilMs: Date.now() + concurrency.debounceMs\n };\n }\n\n /**\n * Mark a submit as accepted and in-flight between admission and turn\n * queue registration. Returns an idempotent `release()` function that\n * must be called when the submit either reaches the turn queue or is\n * abandoned. The returned function is bound to the controller's reset\n * epoch — releases from before the most recent `reset()` are no-ops,\n * so post-reset submits keep an accurate count.\n */\n beginEnqueue(): () => void {\n this._pendingEnqueueCount++;\n const epoch = this._resetEpoch;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n if (this._resetEpoch !== epoch) return;\n this._pendingEnqueueCount = Math.max(0, this._pendingEnqueueCount - 1);\n };\n }\n\n isSuperseded(submitSequence: number | null): boolean {\n return (\n submitSequence !== null &&\n submitSequence < this._latestOverlappingSubmitSequence\n );\n }\n\n async waitForTimestamp(timestampMs: number): Promise<void> {\n const remainingMs = timestampMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n const wrappedResolve = () => {\n this._activeDebounceResolves.delete(wrappedResolve);\n resolve();\n };\n const timer = setTimeout(() => {\n this._activeDebounceTimers.delete(timer);\n wrappedResolve();\n }, remainingMs);\n\n this._activeDebounceTimers.add(timer);\n this._activeDebounceResolves.add(wrappedResolve);\n });\n }\n\n cancelActiveDebounce(): void {\n for (const timer of this._activeDebounceTimers) {\n clearTimeout(timer);\n }\n this._activeDebounceTimers.clear();\n\n const resolves = [...this._activeDebounceResolves];\n this._activeDebounceResolves.clear();\n for (const resolve of resolves) {\n resolve();\n }\n }\n\n reset(): void {\n this._resetEpoch++;\n this._pendingEnqueueCount = 0;\n this.cancelActiveDebounce();\n }\n\n async waitForIdle(waitForQueueIdle: () => Promise<void>): Promise<void> {\n while (true) {\n await waitForQueueIdle();\n if (this._pendingEnqueueCount === 0) return;\n await new Promise<void>((resolve) => setTimeout(resolve, 5));\n }\n }\n\n private normalize(\n concurrency: MessageConcurrency\n ): NormalizedMessageConcurrency {\n if (typeof concurrency === \"string\") {\n return concurrency;\n }\n\n const debounceMs = concurrency.debounceMs;\n\n return {\n strategy: \"debounce\",\n debounceMs:\n typeof debounceMs === \"number\" &&\n Number.isFinite(debounceMs) &&\n debounceMs >= 0\n ? debounceMs\n : this.options.defaultDebounceMs\n };\n }\n}\n","/**\n * Broadcast stream state machine.\n *\n * Manages the lifecycle of a StreamAccumulator for broadcast/resume\n * streams — the path where this client is *observing* a stream owned\n * by another tab or resumed after reconnect, rather than the transport-\n * owned path that feeds directly into useChat.\n *\n * The transition function is pure (no React, no WebSocket, no side\n * effects). Callers dispatch events and apply the returned state +\n * messagesUpdate. Side effects (sending ACKs, calling onData) stay\n * in the caller.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { StreamAccumulator } from \"./stream-accumulator\";\nimport type { StreamChunkData } from \"./message-builder\";\n\n// ── State ──────────────────────────────────────────────────────────\n\nexport type BroadcastStreamState =\n | { status: \"idle\" }\n | {\n status: \"observing\";\n streamId: string;\n accumulator: StreamAccumulator;\n };\n\n// ── Events ─────────────────────────────────────────────────────────\n\nexport type BroadcastStreamEvent =\n | {\n type: \"response\";\n streamId: string;\n /** Fallback message ID for a new accumulator (ignored if one exists for this stream). */\n messageId: string;\n chunkData?: unknown;\n done?: boolean;\n error?: boolean;\n replay?: boolean;\n replayComplete?: boolean;\n continuation?: boolean;\n /** Required when continuation=true so the accumulator can pick up existing parts. */\n currentMessages?: UIMessage[];\n }\n | {\n type: \"resume-fallback\";\n streamId: string;\n messageId: string;\n }\n | { type: \"clear\" };\n\n// ── Result ─────────────────────────────────────────────────────────\n\nexport interface TransitionResult {\n state: BroadcastStreamState;\n messagesUpdate?: (prev: UIMessage[]) => UIMessage[];\n isStreaming: boolean;\n}\n\n// ── Transition ─────────────────────────────────────────────────────\n\nexport function transition(\n state: BroadcastStreamState,\n event: BroadcastStreamEvent\n): TransitionResult {\n switch (event.type) {\n case \"clear\":\n return { state: { status: \"idle\" }, isStreaming: false };\n\n case \"resume-fallback\": {\n const accumulator = new StreamAccumulator({\n messageId: event.messageId\n });\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n isStreaming: true\n };\n }\n\n case \"response\": {\n let accumulator: StreamAccumulator;\n\n if (state.status === \"idle\" || state.streamId !== event.streamId) {\n let messageId = event.messageId;\n let existingParts: UIMessage[\"parts\"] | undefined;\n let existingMetadata: Record<string, unknown> | undefined;\n\n if (event.continuation && event.currentMessages) {\n for (let i = event.currentMessages.length - 1; i >= 0; i--) {\n if (event.currentMessages[i].role === \"assistant\") {\n messageId = event.currentMessages[i].id;\n existingParts = [...event.currentMessages[i].parts];\n if (event.currentMessages[i].metadata != null) {\n existingMetadata = {\n ...(event.currentMessages[i].metadata as Record<\n string,\n unknown\n >)\n };\n }\n break;\n }\n }\n }\n\n accumulator = new StreamAccumulator({\n messageId,\n continuation: event.continuation,\n existingParts,\n existingMetadata\n });\n } else {\n accumulator = state.accumulator;\n }\n\n if (event.chunkData) {\n accumulator.applyChunk(event.chunkData as StreamChunkData);\n }\n\n let messagesUpdate: ((prev: UIMessage[]) => UIMessage[]) | undefined;\n\n if (event.done) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n return {\n state: { status: \"idle\" },\n messagesUpdate,\n isStreaming: false\n };\n }\n\n if (event.chunkData && !event.replay) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n } else if (event.replayComplete) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n }\n\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n messagesUpdate,\n isStreaming: true\n };\n }\n }\n}\n","/**\n * Wire protocol message type constants for the cf_agent_chat_* protocol.\n *\n * These are the string values used on the wire between agent servers and\n * clients. Both @cloudflare/ai-chat (via its MessageType enum) and\n * @cloudflare/think use these values.\n */\nexport const CHAT_MESSAGE_TYPES = {\n CHAT_MESSAGES: \"cf_agent_chat_messages\",\n USE_CHAT_REQUEST: \"cf_agent_use_chat_request\",\n USE_CHAT_RESPONSE: \"cf_agent_use_chat_response\",\n CHAT_CLEAR: \"cf_agent_chat_clear\",\n CHAT_REQUEST_CANCEL: \"cf_agent_chat_request_cancel\",\n STREAM_RESUMING: \"cf_agent_stream_resuming\",\n STREAM_RESUME_ACK: \"cf_agent_stream_resume_ack\",\n STREAM_RESUME_REQUEST: \"cf_agent_stream_resume_request\",\n STREAM_RESUME_NONE: \"cf_agent_stream_resume_none\",\n TOOL_RESULT: \"cf_agent_tool_result\",\n TOOL_APPROVAL: \"cf_agent_tool_approval\",\n MESSAGE_UPDATED: \"cf_agent_message_updated\",\n // Server→client: a durable chat turn is being recovered (interrupted by a\n // deploy/eviction or a stream-stall watchdog abort and now resuming). Sent\n // when a recovery continuation is scheduled and cleared on every terminal\n // outcome; `@cloudflare/think` also replays it on connect so a client that\n // joins mid-recovery learns it. Purely a progress hint — backward-compatible\n // (clients that don't understand it ignore it). See issue #1620.\n CHAT_RECOVERING: \"cf_agent_chat_recovering\"\n} as const;\n","/**\n * ResumableStream: Standalone class for buffering, persisting, and replaying\n * stream chunks in SQLite. Extracted from AIChatAgent to separate concerns.\n *\n * Handles:\n * - Chunk buffering (batched writes to SQLite for performance)\n * - Stream lifecycle (start, complete, error)\n * - Chunk replay for reconnecting clients\n * - Stale stream cleanup\n * - Active stream restoration after agent restart\n */\n\nimport { nanoid } from \"nanoid\";\nimport type { Connection } from \"agents\";\nimport { CHAT_MESSAGE_TYPES } from \"./protocol\";\n\n/** Number of chunks to pack into a single SQLite row before flushing */\nconst CHUNK_BUFFER_SIZE = 10;\n/** Maximum buffer size to prevent memory issues on rapid reconnections */\nconst CHUNK_BUFFER_MAX_SIZE = 100;\n/**\n * Max accumulated raw chunk bytes packed into one row before forcing a flush.\n * The SQLite row limit is 2 MB; packing serializes bodies into a JSON array,\n * which re-escapes their contents (quotes/backslashes), so we keep the raw\n * total well under the limit to leave generous headroom for escaping overhead.\n * A chunk larger than this is flushed as its own (unwrapped) row.\n */\nconst SEGMENT_MAX_BYTES = 512_000;\n/** Default cleanup interval for old streams (ms) - every 10 minutes */\nconst CLEANUP_INTERVAL_MS = 10 * 60 * 1000;\n/** Default age threshold for cleaning up completed streams (ms) - 24 hours */\nconst CLEANUP_AGE_THRESHOLD_MS = 24 * 60 * 60 * 1000;\n/** Shared encoder for UTF-8 byte length measurement */\nconst textEncoder = new TextEncoder();\n\n/**\n * A stored row body is either a single chunk body (a JSON object string —\n * legacy per-chunk rows and single-chunk segments) or a packed segment (a JSON\n * array of chunk body strings). Unpack to the individual chunk bodies in order.\n *\n * Stored chunk bodies are always serialized JSON *objects*, never arrays, so\n * `Array.isArray` reliably distinguishes a packed segment from a single body.\n */\nfunction unpackSegmentBody(rowBody: string): string[] {\n try {\n const parsed = JSON.parse(rowBody);\n if (Array.isArray(parsed)) {\n return parsed as string[];\n }\n } catch {\n // Not valid JSON — treat as a single opaque body.\n }\n return [rowBody];\n}\n\nfunction sendIfOpen(connection: Connection, message: string): boolean {\n try {\n connection.send(message);\n return true;\n } catch (error) {\n if (isWebSocketClosedSendError(error)) return false;\n throw error;\n }\n}\n\nfunction isWebSocketClosedSendError(error: unknown): boolean {\n return (\n error instanceof TypeError &&\n error.message.includes(\"WebSocket send() after close\")\n );\n}\n\n/**\n * Stored stream chunk for resumable streaming\n */\ntype StreamChunk = {\n id: string;\n stream_id: string;\n body: string;\n chunk_index: number;\n created_at: number;\n};\n\n/**\n * Stream metadata for tracking active streams\n */\ntype StreamMetadata = {\n id: string;\n request_id: string;\n status: \"streaming\" | \"completed\" | \"error\";\n created_at: number;\n completed_at: number | null;\n /**\n * The assistant message id this stream is producing, captured when the\n * stream starts. This is the SAME id the live path persists under, so orphan\n * recovery (#1691) can re-associate reconstructed chunks with the correct\n * message even when the provider stream carries no `start.messageId`. Null on\n * legacy rows written before this column existed.\n */\n message_id: string | null;\n};\n\n/**\n * Minimal SQL interface matching Agent's this.sql tagged template.\n * Allows ResumableStream to work with the Agent's SQLite without\n * depending on the full Agent class.\n */\nexport type SqlTaggedTemplate = {\n <T = Record<string, unknown>>(\n strings: TemplateStringsArray,\n ...values: (string | number | boolean | null)[]\n ): T[];\n};\n\nexport class ResumableStream {\n private _activeStreamId: string | null = null;\n private _activeRequestId: string | null = null;\n /** Monotonic row-ordering index; one increment per flushed segment row. */\n private _segmentIndex = 0;\n\n /**\n * Whether the active stream was started in this instance (true) or\n * restored from SQLite after hibernation/restart (false). An orphaned\n * stream has no live LLM reader — the ReadableStream was lost when the\n * DO was evicted.\n */\n private _isLive = false;\n\n private _chunkBuffer: Array<{ streamId: string; body: string }> = [];\n private _chunkBufferBytes = 0;\n private _isFlushingChunks = false;\n private _lastCleanupTime = 0;\n\n constructor(private sql: SqlTaggedTemplate) {\n // Create tables for stream chunks and metadata\n this.sql`create table if not exists cf_ai_chat_stream_chunks (\n id text primary key,\n stream_id text not null,\n body text not null,\n chunk_index integer not null,\n created_at integer not null\n )`;\n\n this.sql`create table if not exists cf_ai_chat_stream_metadata (\n id text primary key,\n request_id text not null,\n status text not null,\n created_at integer not null,\n completed_at integer\n )`;\n\n // Backward-compatible migration (#1691): add the column that durably links a\n // stream to its assistant message. Tables created before this release lack\n // it, so `alter table add column` is idempotent — the duplicate-column\n // error (and ONLY that error) is swallowed on an already-migrated table.\n this._migrateMetadataColumns();\n\n this.sql`create index if not exists idx_stream_chunks_stream_id \n on cf_ai_chat_stream_chunks(stream_id, chunk_index)`;\n\n // Restore any active stream from a previous session\n this.restore();\n }\n\n /**\n * Add the #1691 recovery column to the metadata table for rows created before\n * it existed. Inspects the current schema and only runs `alter table` when the\n * column is absent — idempotent across Durable Object restarts, with no\n * error-driven control flow.\n */\n private _migrateMetadataColumns() {\n const columns =\n this.sql<{ name: string }>`\n select name from pragma_table_info('cf_ai_chat_stream_metadata')\n ` ?? [];\n const hasMessageId = columns.some((column) => column.name === \"message_id\");\n if (!hasMessageId) {\n this\n .sql`alter table cf_ai_chat_stream_metadata add column message_id text`;\n }\n }\n\n // ── State accessors ────────────────────────────────────────────────\n\n get activeStreamId(): string | null {\n return this._activeStreamId;\n }\n\n get activeRequestId(): string | null {\n return this._activeRequestId;\n }\n\n hasActiveStream(): boolean {\n return this._activeStreamId !== null;\n }\n\n /**\n * Whether the active stream has a live LLM reader (started in this\n * instance) vs being restored from SQLite after hibernation (orphaned).\n */\n get isLive(): boolean {\n return this._isLive;\n }\n\n // ── Stream lifecycle ───────────────────────────────────────────────\n\n /**\n * Start tracking a new stream for resumable streaming.\n * Creates metadata entry in SQLite and sets up tracking state.\n * @param requestId - The unique ID of the chat request\n * @returns The generated stream ID\n */\n start(requestId: string, options: { messageId?: string } = {}): string {\n // Flush any pending chunks from previous streams to prevent mixing\n this.flushBuffer();\n\n const streamId = nanoid();\n this._activeStreamId = streamId;\n this._activeRequestId = requestId;\n this._segmentIndex = 0;\n this._isLive = true;\n\n const messageId = options.messageId ?? null;\n\n this.sql`\n insert into cf_ai_chat_stream_metadata (id, request_id, status, created_at, message_id)\n values (${streamId}, ${requestId}, 'streaming', ${Date.now()}, ${messageId})\n `;\n\n return streamId;\n }\n\n /**\n * The assistant message id an orphaned stream was producing — the same id the\n * live path persists under, so recovery re-associates reconstructed chunks\n * with the correct message (#1691). Returns null when the row is missing or\n * is a legacy row written before the `message_id` column existed.\n */\n getStreamMessageId(streamId: string): string | null {\n const rows = this.sql<{ message_id: string | null }>`\n select message_id from cf_ai_chat_stream_metadata\n where id = ${streamId}\n `;\n if (!rows || rows.length === 0) return null;\n return rows[0].message_id ?? null;\n }\n\n /**\n * Mark a stream as completed and flush any pending chunks.\n * @param streamId - The stream to mark as completed\n */\n complete(streamId: string) {\n this.flushBuffer();\n\n this.sql`\n update cf_ai_chat_stream_metadata \n set status = 'completed', completed_at = ${Date.now()} \n where id = ${streamId}\n `;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._segmentIndex = 0;\n this._isLive = false;\n\n // Periodically clean up old streams\n this._maybeCleanupOldStreams();\n }\n\n /**\n * Mark a stream as errored and clean up state.\n * @param streamId - The stream to mark as errored\n */\n markError(streamId: string) {\n this.flushBuffer();\n\n this.sql`\n update cf_ai_chat_stream_metadata \n set status = 'error', completed_at = ${Date.now()} \n where id = ${streamId}\n `;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._segmentIndex = 0;\n this._isLive = false;\n }\n\n // ── Chunk storage ──────────────────────────────────────────────────\n\n /** Maximum chunk body size before skipping storage (bytes). Prevents SQLite row limit crash. */\n private static CHUNK_MAX_BYTES = 1_800_000;\n\n /**\n * Buffer a stream chunk for batch write to SQLite.\n * Chunks exceeding the row size limit are skipped to prevent crashes.\n * The chunk is still broadcast to live clients (caller handles that),\n * but will be missing from replay on reconnection.\n * @param streamId - The stream this chunk belongs to\n * @param body - The serialized chunk body\n */\n storeChunk(streamId: string, body: string) {\n // Guard against chunks that would exceed SQLite row limit.\n // The chunk is still broadcast to live clients; only replay storage is skipped.\n const bodyBytes = textEncoder.encode(body).byteLength;\n if (bodyBytes > ResumableStream.CHUNK_MAX_BYTES) {\n console.warn(\n `[ResumableStream] Skipping oversized chunk (${bodyBytes} bytes) ` +\n `to prevent SQLite row limit crash. Live clients still receive it.`\n );\n return;\n }\n\n // Force flush if buffer is at max to prevent memory issues\n if (this._chunkBuffer.length >= CHUNK_BUFFER_MAX_SIZE) {\n this.flushBuffer();\n }\n\n // Byte guard: keep a packed segment safely under the SQLite row limit. If\n // the buffer already holds chunks and adding this body would push the\n // segment past the threshold, flush first so this chunk starts a fresh\n // segment. A single large chunk therefore ends up alone and is written\n // unwrapped by flushBuffer (no array-escaping inflation).\n if (\n this._chunkBuffer.length > 0 &&\n this._chunkBufferBytes + bodyBytes > SEGMENT_MAX_BYTES\n ) {\n this.flushBuffer();\n }\n\n this._chunkBuffer.push({ streamId, body });\n this._chunkBufferBytes += bodyBytes;\n\n // Flush when buffer reaches the per-segment chunk threshold\n if (this._chunkBuffer.length >= CHUNK_BUFFER_SIZE) {\n this.flushBuffer();\n }\n }\n\n /**\n * Flush the buffered chunks to SQLite as a single packed row.\n * Uses a lock to prevent concurrent flush operations.\n *\n * The whole buffer becomes one row: a single-chunk segment is stored\n * unwrapped (legacy object format) so a large chunk avoids array-escaping\n * inflation, while a multi-chunk segment stores a JSON array of bodies. This\n * collapses N chunk rows into one, cutting rows written / stored / scanned.\n */\n flushBuffer() {\n if (this._isFlushingChunks || this._chunkBuffer.length === 0) {\n return;\n }\n\n this._isFlushingChunks = true;\n try {\n const chunks = this._chunkBuffer;\n this._chunkBuffer = [];\n this._chunkBufferBytes = 0;\n\n // All chunks in a buffer belong to the same stream: start() flushes\n // before switching streams, so the buffer is never cross-stream.\n const streamId = chunks[0].streamId;\n const segmentBody =\n chunks.length === 1\n ? chunks[0].body\n : JSON.stringify(chunks.map((chunk) => chunk.body));\n\n this.sql`\n insert into cf_ai_chat_stream_chunks (id, stream_id, body, chunk_index, created_at)\n values (${nanoid()}, ${streamId}, ${segmentBody}, ${this._segmentIndex}, ${Date.now()})\n `;\n this._segmentIndex++;\n } finally {\n this._isFlushingChunks = false;\n }\n }\n\n // ── Chunk replay ───────────────────────────────────────────────────\n\n /**\n * Send stored stream chunks to a connection for replay.\n * Chunks are marked with replay: true so the client can batch-apply them.\n *\n * Three outcomes:\n * - **Live stream**: sends chunks + `replayComplete` — client flushes and\n * continues receiving live chunks from the LLM reader.\n * - **Orphaned stream** (restored from SQLite after hibernation, no reader):\n * sends chunks + `done` and completes the stream. The caller should\n * reconstruct and persist the partial message from the stored chunks.\n * - **Completed during replay** (defensive): sends chunks + `done`.\n *\n * All sends use {@link sendIfOpen}, so a WebSocket closing mid-replay\n * does not throw. If the connection drops while iterating chunks the\n * stream is left active so the next reconnect can retry.\n *\n * @param connection - The WebSocket connection\n * @param requestId - The original request ID\n * @returns The stream ID if the stream was orphaned and finalized, null otherwise.\n * When non-null the caller should reconstruct the message from chunks.\n */\n replayChunks(connection: Connection, requestId: string): string | null {\n const streamId = this._activeStreamId;\n if (!streamId) return null;\n\n this.flushBuffer();\n\n const chunks = this.sql<StreamChunk>`\n select * from cf_ai_chat_stream_chunks \n where stream_id = ${streamId} \n order by chunk_index asc\n `;\n\n for (const chunk of chunks || []) {\n for (const body of unpackSegmentBody(chunk.body)) {\n if (\n !sendIfOpen(\n connection,\n JSON.stringify({\n body,\n done: false,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true\n })\n )\n ) {\n // Connection closed mid-replay — leave the stream active so the\n // next reconnect can retry from the start.\n return null;\n }\n }\n }\n\n if (this._activeStreamId !== streamId) {\n // Stream completed between our check above and now — send done.\n // In practice this cannot happen (DO is single-threaded and replay is\n // synchronous), but we guard defensively in case the flow changes.\n sendIfOpen(\n connection,\n JSON.stringify({\n body: \"\",\n done: true,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true\n })\n );\n return null;\n }\n\n if (!this._isLive) {\n // Orphaned stream — restored from SQLite after hibernation but the\n // LLM ReadableStream reader was lost. No more live chunks will ever\n // arrive, so finalize it: best-effort send done, then mark completed\n // in SQLite. The orphan-cleanup decision is committed regardless of\n // whether this particular connection received the done frame, so the\n // caller can persist the reconstructed message.\n sendIfOpen(\n connection,\n JSON.stringify({\n body: \"\",\n done: true,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true\n })\n );\n this.complete(streamId);\n return streamId;\n }\n\n // Stream is still active with a live reader — signal that replay is\n // complete so the client can flush accumulated parts to React state.\n // Without this, replayed chunks sit in activeStreamRef unflushed\n // until the next live chunk arrives.\n sendIfOpen(\n connection,\n JSON.stringify({\n body: \"\",\n done: false,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true,\n replayComplete: true\n })\n );\n return null;\n }\n\n replayCompletedChunksByRequestId(\n connection: Connection,\n requestId: string\n ): boolean {\n this.flushBuffer();\n\n const streams = this.sql<StreamMetadata>`\n select * from cf_ai_chat_stream_metadata\n where request_id = ${requestId}\n and status = 'completed'\n order by created_at desc\n limit 1\n `;\n const stream = streams[0];\n if (!stream) return false;\n\n const chunks = this.sql<StreamChunk>`\n select * from cf_ai_chat_stream_chunks\n where stream_id = ${stream.id}\n order by chunk_index asc\n `;\n\n for (const chunk of chunks || []) {\n for (const body of unpackSegmentBody(chunk.body)) {\n if (\n !sendIfOpen(\n connection,\n JSON.stringify({\n body,\n done: false,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true\n })\n )\n ) {\n return false;\n }\n }\n }\n\n return sendIfOpen(\n connection,\n JSON.stringify({\n body: \"\",\n done: true,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true\n })\n );\n }\n\n // ── Restore / cleanup ──────────────────────────────────────────────\n\n /**\n * Restore active stream state if the agent was restarted during streaming.\n * All streams are restored regardless of age — stale cleanup happens\n * lazily in _maybeCleanupOldStreams after recovery has had its chance.\n */\n restore() {\n const activeStreams = this.sql<StreamMetadata>`\n select * from cf_ai_chat_stream_metadata \n where status = 'streaming' \n order by created_at desc \n limit 1\n `;\n\n if (activeStreams && activeStreams.length > 0) {\n const stream = activeStreams[0];\n this._activeStreamId = stream.id;\n this._activeRequestId = stream.request_id;\n\n // Resume the segment row-ordering index past the highest stored value.\n const lastChunk = this.sql<{ max_index: number }>`\n select max(chunk_index) as max_index \n from cf_ai_chat_stream_chunks \n where stream_id = ${this._activeStreamId}\n `;\n this._segmentIndex =\n lastChunk && lastChunk[0]?.max_index != null\n ? lastChunk[0].max_index + 1\n : 0;\n }\n }\n\n /**\n * Clear all stream data (called on chat history clear).\n */\n clearAll() {\n this._chunkBuffer = [];\n this._chunkBufferBytes = 0;\n this.sql`delete from cf_ai_chat_stream_chunks`;\n this.sql`delete from cf_ai_chat_stream_metadata`;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._segmentIndex = 0;\n }\n\n /**\n * Drop all stream tables (called on destroy).\n */\n destroy() {\n this.flushBuffer();\n this.sql`drop table if exists cf_ai_chat_stream_chunks`;\n this.sql`drop table if exists cf_ai_chat_stream_metadata`;\n this._activeStreamId = null;\n this._activeRequestId = null;\n }\n\n // ── Internal ───────────────────────────────────────────────────────\n\n private _maybeCleanupOldStreams() {\n const now = Date.now();\n if (now - this._lastCleanupTime < CLEANUP_INTERVAL_MS) {\n return;\n }\n this._lastCleanupTime = now;\n\n const cutoff = now - CLEANUP_AGE_THRESHOLD_MS;\n this.sql`\n delete from cf_ai_chat_stream_chunks \n where stream_id in (\n select id from cf_ai_chat_stream_metadata \n where status in ('completed', 'error') and completed_at < ${cutoff}\n )\n `;\n this.sql`\n delete from cf_ai_chat_stream_metadata \n where status in ('completed', 'error') and completed_at < ${cutoff}\n `;\n\n // Clean up abandoned \"streaming\" rows. These are orphaned streams that\n // were never completed or recovered (e.g. non-durable agents that never\n // reconnected). By this point, fiber recovery has already had its chance\n // to claim them — safe to delete.\n this.sql`\n delete from cf_ai_chat_stream_chunks\n where stream_id in (\n select id from cf_ai_chat_stream_metadata\n where status = 'streaming' and created_at < ${cutoff}\n )\n `;\n this.sql`\n delete from cf_ai_chat_stream_metadata\n where status = 'streaming' and created_at < ${cutoff}\n `;\n }\n\n // ── Test helpers (matching old AIChatAgent test API) ────────────────\n\n /**\n * Return the stored chunks for a stream as individual chunk bodies in order,\n * unpacking packed segment rows. The returned `chunk_index` is a running\n * per-chunk sequence (0, 1, 2, …) — stable across calls because rows are\n * append-only — so callers can use it as a monotonic chunk sequence.\n */\n getStreamChunks(\n streamId: string\n ): Array<{ body: string; chunk_index: number }> {\n const rows =\n this.sql<{ body: string }>`\n select body from cf_ai_chat_stream_chunks \n where stream_id = ${streamId} \n order by chunk_index asc\n ` || [];\n const out: Array<{ body: string; chunk_index: number }> = [];\n let index = 0;\n for (const row of rows) {\n for (const body of unpackSegmentBody(row.body)) {\n out.push({ body, chunk_index: index });\n index++;\n }\n }\n return out;\n }\n\n /** @internal For testing only */\n getStreamMetadata(\n streamId: string\n ): { status: string; request_id: string } | null {\n const result = this.sql<{ status: string; request_id: string }>`\n select status, request_id from cf_ai_chat_stream_metadata \n where id = ${streamId}\n `;\n return result && result.length > 0 ? result[0] : null;\n }\n\n /** @internal For testing only */\n getAllStreamMetadata(): Array<{\n id: string;\n status: string;\n request_id: string;\n created_at: number;\n }> {\n return (\n this.sql<{\n id: string;\n status: string;\n request_id: string;\n created_at: number;\n }>`select id, status, request_id, created_at from cf_ai_chat_stream_metadata` ||\n []\n );\n }\n\n /** @internal For testing only */\n insertStaleStream(streamId: string, requestId: string, ageMs: number): void {\n const createdAt = Date.now() - ageMs;\n this.sql`\n insert into cf_ai_chat_stream_metadata (id, request_id, status, created_at)\n values (${streamId}, ${requestId}, 'streaming', ${createdAt})\n `;\n }\n}\n","/**\n * Helpers for building batched SQLite statements that run through the Agent's\n * `sql` tagged template (which interleaves a `?` placeholder between every\n * string fragment). Used to collapse per-row INSERT/DELETE loops into a small\n * number of multi-row statements.\n *\n * SQLite (Durable Object / D1) caps bound parameters at 100 per query, so\n * callers must chunk their inputs to stay within {@link MAX_BOUND_PARAMS}.\n * See https://developers.cloudflare.com/d1/platform/limits/\n */\n\n/** Maximum bound parameters allowed in a single SQLite (DO / D1) query. */\nexport const MAX_BOUND_PARAMS = 100;\n\n/**\n * Attach a self-referential `raw` property so a plain string[] satisfies the\n * TemplateStringsArray shape. `sql` only reads indexed string fragments, so\n * `raw` is never consumed — this just keeps the type system happy.\n */\nfunction asTemplateStringsArray(parts: string[]): TemplateStringsArray {\n (parts as unknown as { raw: readonly string[] }).raw = parts;\n return parts as unknown as TemplateStringsArray;\n}\n\n/**\n * Build a TemplateStringsArray for a single-column `IN (...)` clause. Produces\n * fragments for:\n * `${prefix}(?, ?, ...)`\n *\n * @throws if `count` is less than 1.\n */\nexport function buildInClauseStrings(\n prefix: string,\n count: number\n): TemplateStringsArray {\n if (count < 1) {\n throw new Error(`buildInClauseStrings requires count >= 1 (got ${count})`);\n }\n const parts = new Array<string>(count + 1);\n parts[0] = `${prefix}(`;\n for (let i = 1; i < count; i++) {\n parts[i] = \", \";\n }\n parts[count] = \")\";\n return asTemplateStringsArray(parts);\n}\n","/**\n * Client tool schema handling for the cf_agent_chat protocol.\n *\n * Converts client-provided tool schemas (JSON wire format) into AI SDK\n * tool definitions. These tools have no `execute` function — when the\n * model calls them, the tool call is sent back to the client.\n *\n * Used by both @cloudflare/ai-chat and @cloudflare/think.\n */\n\nimport type { JSONSchema7, Tool, ToolSet } from \"ai\";\nimport { tool, jsonSchema } from \"ai\";\n\n/**\n * Wire-format tool schema sent from the client.\n * Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema`\n * because Zod schemas cannot be serialized over the wire.\n */\nexport type ClientToolSchema = {\n /** Unique name for the tool */\n name: string;\n /** Human-readable description of what the tool does */\n description?: Tool[\"description\"];\n /** JSON Schema defining the tool's input parameters */\n parameters?: JSONSchema7;\n};\n\n/**\n * Converts client tool schemas to AI SDK tool format.\n *\n * These tools have no `execute` function — when the AI model calls them,\n * the tool call is sent back to the client for execution.\n *\n * @param clientTools - Array of tool schemas from the client\n * @returns Record of AI SDK tools that can be spread into your tools object\n */\nexport function createToolsFromClientSchemas(\n clientTools?: ClientToolSchema[]\n): ToolSet {\n if (!clientTools || clientTools.length === 0) {\n return {};\n }\n\n const seenNames = new Set<string>();\n for (const t of clientTools) {\n if (seenNames.has(t.name)) {\n console.warn(\n `[createToolsFromClientSchemas] Duplicate tool name \"${t.name}\" found. Later definitions will override earlier ones.`\n );\n }\n seenNames.add(t.name);\n }\n\n return Object.fromEntries(\n clientTools.map((t) => [\n t.name,\n tool({\n description: t.description ?? \"\",\n inputSchema: jsonSchema(t.parameters ?? { type: \"object\" })\n })\n ])\n );\n}\n","/**\n * ContinuationState — shared state container for auto-continuation lifecycle.\n *\n * Tracks pending, deferred, and active continuation state for the\n * tool-result → auto-continue flow. Both AIChatAgent and Think use this\n * to manage which connection/tools/body a continuation turn should use\n * and to coordinate with clients requesting stream resume.\n *\n * The scheduling algorithm (prerequisite chaining, debounce, TurnQueue\n * enrollment) stays in the host — this class only manages the data.\n */\n\nimport { CHAT_MESSAGE_TYPES } from \"./protocol\";\nimport type { ClientToolSchema } from \"./client-tools\";\n\nconst MSG_STREAM_RESUME_NONE = CHAT_MESSAGE_TYPES.STREAM_RESUME_NONE;\n\nfunction sendIfOpen(\n connection: ContinuationConnection,\n message: string\n): boolean {\n try {\n connection.send(message);\n return true;\n } catch (error) {\n if (isWebSocketClosedSendError(error)) return false;\n throw error;\n }\n}\n\nfunction isWebSocketClosedSendError(error: unknown): boolean {\n return (\n error instanceof TypeError &&\n error.message.includes(\"WebSocket send() after close\")\n );\n}\n\n/**\n * Minimal connection interface for sending WebSocket messages.\n * Matches the Connection type from agents without importing it.\n * Uses a permissive send signature so Connection (which extends\n * WebSocket with its own send overload) is structurally assignable.\n */\nexport interface ContinuationConnection {\n readonly id: string;\n send(message: string): void;\n}\n\nexport interface ContinuationPending<\n TConnection extends ContinuationConnection = ContinuationConnection\n> {\n connection: TConnection;\n connectionId: string | null;\n requestId: string;\n clientTools?: ClientToolSchema[];\n body?: Record<string, unknown>;\n errorPrefix: string | null;\n prerequisite: Promise<boolean> | null;\n pastCoalesce: boolean;\n}\n\nexport interface ContinuationDeferred<\n TConnection extends ContinuationConnection = ContinuationConnection\n> {\n connection: TConnection;\n connectionId: string | null;\n clientTools?: ClientToolSchema[];\n body?: Record<string, unknown>;\n errorPrefix: string;\n prerequisite: Promise<boolean> | null;\n}\n\nexport class ContinuationState<\n TConnection extends ContinuationConnection = ContinuationConnection\n> {\n pending: ContinuationPending<TConnection> | null = null;\n deferred: ContinuationDeferred<TConnection> | null = null;\n activeRequestId: string | null = null;\n activeConnectionId: string | null = null;\n awaitingConnections: Map<string, TConnection> = new Map();\n\n /** Clear pending state and awaiting connections (without sending RESUME_NONE). */\n clearPending(): void {\n this.pending = null;\n this.awaitingConnections.clear();\n }\n\n clearDeferred(): void {\n this.deferred = null;\n }\n\n clearAll(): void {\n this.clearPending();\n this.clearDeferred();\n this.activeRequestId = null;\n this.activeConnectionId = null;\n }\n\n /**\n * Mark a connection as no longer available without canceling the\n * continuation it initiated.\n */\n releaseConnection(connectionId: string): void {\n this.awaitingConnections.delete(connectionId);\n if (this.pending?.connectionId === connectionId) {\n this.pending = { ...this.pending, connectionId: null };\n }\n if (this.deferred?.connectionId === connectionId) {\n this.deferred = { ...this.deferred, connectionId: null };\n }\n if (this.activeConnectionId === connectionId) {\n this.activeConnectionId = null;\n }\n }\n\n /**\n * Send STREAM_RESUME_NONE to all connections waiting for a\n * continuation stream to start, then clear the map.\n */\n sendResumeNone(): void {\n const msg = JSON.stringify({ type: MSG_STREAM_RESUME_NONE });\n for (const connection of this.awaitingConnections.values()) {\n sendIfOpen(connection, msg);\n }\n this.awaitingConnections.clear();\n }\n\n /**\n * Flush awaiting connections by notifying each one via the provided\n * callback (typically sends STREAM_RESUMING), then clear.\n */\n flushAwaitingConnections(notify: (conn: TConnection) => void): void {\n for (const connection of this.awaitingConnections.values()) {\n notify(connection);\n }\n this.awaitingConnections.clear();\n }\n\n /**\n * Transition pending → active. Called when the continuation stream\n * actually starts. Moves request/connection IDs to active slots,\n * clears pending fields.\n */\n activatePending(): void {\n if (!this.pending) return;\n this.activeRequestId = this.pending.requestId;\n this.activeConnectionId = this.pending.connectionId;\n this.pending = null;\n }\n\n /**\n * Transition deferred → pending. Called when a continuation turn\n * completes and there's a deferred follow-up waiting.\n *\n * Returns the new pending state (so the host can enqueue the turn),\n * or null if there was nothing deferred.\n */\n activateDeferred(\n generateRequestId: () => string\n ): ContinuationPending<TConnection> | null {\n if (this.pending || !this.deferred) return null;\n\n const d = this.deferred;\n this.deferred = null;\n this.activeRequestId = null;\n this.activeConnectionId = null;\n\n this.pending = {\n connection: d.connection,\n connectionId: d.connectionId,\n requestId: generateRequestId(),\n clientTools: d.clientTools,\n body: d.body,\n errorPrefix: d.errorPrefix,\n prerequisite: d.prerequisite,\n pastCoalesce: false\n };\n\n if (d.connectionId !== null) {\n this.awaitingConnections.set(d.connectionId, d.connection);\n }\n return this.pending;\n }\n}\n","/**\n * AbortRegistry — manages per-request AbortControllers.\n *\n * Shared between AIChatAgent and Think for chat turn cancellation.\n * Each request gets its own AbortController keyed by request ID.\n * Controllers are created lazily on first signal access.\n */\n\nconst NOOP = () => {};\n\nexport class AbortRegistry {\n private controllers = new Map<string, AbortController>();\n\n /**\n * Get or create an AbortController for the given ID and return its signal.\n * Creates the controller lazily on first access.\n */\n getSignal(id: string): AbortSignal | undefined {\n if (typeof id !== \"string\") {\n return undefined;\n }\n\n if (!this.controllers.has(id)) {\n this.controllers.set(id, new AbortController());\n }\n\n return this.controllers.get(id)!.signal;\n }\n\n /**\n * Get the signal for an existing controller without creating one.\n * Returns undefined if no controller exists for this ID.\n */\n getExistingSignal(id: string): AbortSignal | undefined {\n return this.controllers.get(id)?.signal;\n }\n\n /**\n * Cancel a specific request by aborting its controller. Optionally\n * propagate a reason — surfaces as `signal.reason` on the registry's\n * controller and through any `AbortError` it produces downstream.\n */\n cancel(id: string, reason?: unknown): void {\n this.controllers.get(id)?.abort(reason);\n }\n\n /** Remove a controller after the request completes. */\n remove(id: string): void {\n this.controllers.delete(id);\n }\n\n /**\n * Abort all pending requests and clear the registry. Optionally propagate a\n * reason — surfaces as `signal.reason` on each controller and through any\n * `AbortError` it produces downstream, exactly like {@link cancel}.\n */\n destroyAll(reason?: unknown): void {\n for (const controller of this.controllers.values()) {\n controller.abort(reason);\n }\n this.controllers.clear();\n }\n\n /** Check if a controller exists for the given ID. */\n has(id: string): boolean {\n return this.controllers.has(id);\n }\n\n /** Number of tracked controllers. */\n get size(): number {\n return this.controllers.size;\n }\n\n /**\n * Link an external `AbortSignal` to the controller for `id`. When the\n * external signal aborts, the registry's controller is cancelled —\n * propagating the abort reason — exactly the same way an internal\n * cancel would (e.g. via a `chat-request-cancel` WebSocket message).\n *\n * This is the integration point for callers that drive a chat turn\n * programmatically and want to cancel it from outside without knowing\n * the internally-generated request id (e.g. the helper-as-sub-agent\n * pattern, where a parent's `AbortSignal` from the AI SDK tool\n * `execute` needs to land inside a `Think.saveMessages` call running\n * on a child DO).\n *\n * Behavior:\n *\n * - Passing `undefined` is a no-op and returns a no-op detacher, so\n * callers can unconditionally call this with `options?.signal`.\n * - If the external signal is already aborted, the registry's\n * controller is created (if needed) and cancelled synchronously.\n * - Otherwise a one-shot `abort` listener is attached. The returned\n * function detaches it.\n *\n * **Always call the returned detacher in a `finally` block** — the\n * external signal may outlive the request (a parent chat turn that\n * drives many helper turns reuses one signal across all of them) and\n * leaving listeners attached pins closures and grows the listener\n * list on each turn.\n *\n * @returns A detacher function. Call it after the request finishes\n * (success or failure) to remove the abort listener from `signal`.\n */\n linkExternal(id: string, signal: AbortSignal | undefined): () => void {\n if (!signal) return NOOP;\n\n if (signal.aborted) {\n // Ensure the registry controller for `id` exists, then cancel it.\n // Calling getSignal first means an early external abort still\n // produces a controller for downstream observers (`getExistingSignal`)\n // rather than a silently-empty registry.\n this.getSignal(id);\n this.cancel(id, signal.reason);\n return NOOP;\n }\n\n const listener = () => this.cancel(id, signal.reason);\n signal.addEventListener(\"abort\", listener, { once: true });\n return () => signal.removeEventListener(\"abort\", listener);\n }\n}\n","/**\n * Tool State — shared update builders and applicator for tool part state changes.\n *\n * Used by both AIChatAgent and Think to apply tool results and approvals\n * to message parts. Each agent handles find-message, persist, and broadcast\n * in their own way; this module provides the state matching and update logic.\n */\n\n/**\n * Describes an update to apply to a tool part.\n */\nexport type ToolPartUpdate = {\n toolCallId: string;\n matchStates: string[];\n apply: (part: Record<string, unknown>) => Record<string, unknown>;\n};\n\n/**\n * Apply a tool part update to a parts array.\n * Finds the first part matching `update.toolCallId` in one of `update.matchStates`,\n * applies the update immutably, and returns the new parts array with the index.\n *\n * Returns `null` if no matching part was found.\n */\nexport function applyToolUpdate(\n parts: Array<Record<string, unknown>>,\n update: ToolPartUpdate\n): { parts: Array<Record<string, unknown>>; index: number } | null {\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n if (\n \"toolCallId\" in part &&\n part.toolCallId === update.toolCallId &&\n \"state\" in part &&\n update.matchStates.includes(part.state as string)\n ) {\n const updatedParts = [...parts];\n updatedParts[i] = update.apply(part);\n return { parts: updatedParts, index: i };\n }\n }\n return null;\n}\n\n/**\n * Build an update descriptor for applying a tool result.\n *\n * Matches parts in `input-available`, `approval-requested`, or `approval-responded` state.\n * Sets state to `output-available` (with output) or `output-error` (with errorText).\n */\nexport function toolResultUpdate(\n toolCallId: string,\n output: unknown,\n overrideState?: \"output-error\",\n errorText?: string\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\n \"input-available\",\n \"approval-requested\",\n \"approval-responded\"\n ],\n apply: (part) => ({\n ...part,\n ...(overrideState === \"output-error\"\n ? {\n state: \"output-error\",\n errorText: errorText ?? \"Tool execution denied by user\"\n }\n : { state: \"output-available\", output, preliminary: false })\n })\n };\n}\n\n/**\n * Build an update descriptor for a terminal tool result that belongs to a\n * tool part in a *different* (earlier) assistant message than the one\n * currently being streamed.\n *\n * This is the \"cross-message\" case: an approved server tool executes during a\n * continuation stream, but its tool part lives in the assistant message that\n * originally requested it. `StreamAccumulator` surfaces this as a\n * `cross-message-tool-update` action because the accumulator only owns the\n * current turn's new content and cannot mutate a part from a prior message.\n *\n * Compared to {@link toolResultUpdate} this builder is deliberately more\n * defensive, mirroring the equivalent fallback in `@cloudflare/ai-chat`:\n *\n * - It matches the broad set of pre-terminal **and** terminal states, so a\n * provider that replays the entire prior tool round-trip during a\n * continuation (notably the OpenAI Responses API — issue #1404) still\n * resolves to the same part instead of silently missing it.\n * - It is **first-write-wins**: a chunk arriving for a tool that already holds\n * a terminal result is treated as a replay and the existing output is never\n * overwritten. In that case `apply` returns the *same part reference*, which\n * callers use as an idempotent-no-op signal to skip the durable write and a\n * redundant `MESSAGE_UPDATED` broadcast.\n * - It preserves a streamed `preliminary` flag when one is present, otherwise\n * marks the result final (`preliminary: false`).\n */\nexport function crossMessageToolResultUpdate(\n toolCallId: string,\n updateType: \"output-available\" | \"output-error\",\n output?: unknown,\n errorText?: string,\n preliminary?: boolean\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\n \"input-streaming\",\n \"input-available\",\n \"approval-requested\",\n \"approval-responded\",\n \"output-available\",\n \"output-error\",\n \"output-denied\"\n ],\n apply: (part) => {\n if (\n part.state === \"output-available\" ||\n part.state === \"output-error\" ||\n part.state === \"output-denied\"\n ) {\n return part;\n }\n if (updateType === \"output-error\") {\n return {\n ...part,\n state: \"output-error\",\n errorText: errorText ?? \"Tool execution failed\"\n };\n }\n return {\n ...part,\n state: \"output-available\",\n output,\n preliminary: preliminary ?? false\n };\n }\n };\n}\n\n/**\n * Build an update descriptor for applying a tool approval.\n *\n * Matches parts in `input-available` or `approval-requested` state.\n * Sets state to `approval-responded` (if approved) or `output-denied` (if denied).\n */\nexport function toolApprovalUpdate(\n toolCallId: string,\n approved: boolean\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\"input-available\", \"approval-requested\"],\n apply: (part) => ({\n ...part,\n state: approved ? \"approval-responded\" : \"output-denied\",\n approval: {\n ...(part.approval as Record<string, unknown> | undefined),\n approved\n }\n })\n };\n}\n","/**\n * Protocol Message Parser — typed parsing of cf_agent_chat_* WebSocket messages.\n *\n * Parses raw WebSocket messages into a discriminated union of protocol events.\n * Both AIChatAgent and Think can use this instead of manual JSON.parse + type checking.\n */\n\nimport { CHAT_MESSAGE_TYPES } from \"./protocol\";\n\n/**\n * Discriminated union of all incoming chat protocol events.\n *\n * Each agent handles the events it cares about and ignores the rest.\n * Returns `null` for non-JSON messages or unrecognized types.\n */\nexport type ChatProtocolEvent =\n | {\n type: \"chat-request\";\n id: string;\n init: { method?: string; body?: string; [key: string]: unknown };\n }\n | { type: \"clear\" }\n | { type: \"cancel\"; id: string }\n | {\n type: \"tool-result\";\n toolCallId: string;\n toolName: string;\n output: unknown;\n state?: string;\n errorText?: string;\n autoContinue?: boolean;\n clientTools?: Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>;\n }\n | {\n type: \"tool-approval\";\n toolCallId: string;\n approved: boolean;\n autoContinue?: boolean;\n }\n | { type: \"stream-resume-request\" }\n | { type: \"stream-resume-ack\"; id: string }\n | { type: \"messages\"; messages: unknown[] };\n\n/**\n * Parse a raw WebSocket message string into a typed protocol event.\n *\n * Returns `null` if the message is not valid JSON or not a recognized\n * protocol message type. Callers should fall through to the user's\n * `onMessage` handler when `null` is returned.\n *\n * @example\n * ```typescript\n * const event = parseProtocolMessage(rawMessage);\n * if (!event) return userOnMessage(connection, rawMessage);\n *\n * switch (event.type) {\n * case \"chat-request\": { ... }\n * case \"clear\": { ... }\n * case \"tool-result\": { ... }\n * }\n * ```\n */\nexport function parseProtocolMessage(raw: string): ChatProtocolEvent | null {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n\n const wireType = data.type as string | undefined;\n if (!wireType) return null;\n\n switch (wireType) {\n case CHAT_MESSAGE_TYPES.USE_CHAT_REQUEST:\n return {\n type: \"chat-request\",\n id: data.id as string,\n init: (data.init as { method?: string; body?: string }) ?? {}\n };\n\n case CHAT_MESSAGE_TYPES.CHAT_CLEAR:\n return { type: \"clear\" };\n\n case CHAT_MESSAGE_TYPES.CHAT_REQUEST_CANCEL:\n return { type: \"cancel\", id: data.id as string };\n\n case CHAT_MESSAGE_TYPES.TOOL_RESULT:\n return {\n type: \"tool-result\",\n toolCallId: data.toolCallId as string,\n toolName: (data.toolName as string) ?? \"\",\n output: data.output,\n state: data.state as string | undefined,\n errorText: data.errorText as string | undefined,\n autoContinue: data.autoContinue as boolean | undefined,\n clientTools: data.clientTools as\n | Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>\n | undefined\n };\n\n case CHAT_MESSAGE_TYPES.TOOL_APPROVAL:\n return {\n type: \"tool-approval\",\n toolCallId: data.toolCallId as string,\n approved: data.approved as boolean,\n autoContinue: data.autoContinue as boolean | undefined\n };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_REQUEST:\n return { type: \"stream-resume-request\" };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_ACK:\n return { type: \"stream-resume-ack\", id: data.id as string };\n\n case CHAT_MESSAGE_TYPES.CHAT_MESSAGES:\n return {\n type: \"messages\",\n messages: (data.messages as unknown[]) ?? []\n };\n\n default:\n return null;\n }\n}\n","/**\n * Message reconciliation — pure functions for aligning client messages\n * with server state during persistence.\n *\n * Three strategies applied in order:\n * 1. Merge server-known tool outputs into stale client messages\n * 2. Reconcile assistant IDs (exact match → content-key → toolCallId)\n * 3. Per-message toolCallId dedup for persistence\n */\n\nimport type { UIMessage } from \"ai\";\n\n/**\n * Reconcile incoming client messages against server state.\n *\n * 1. Merges server-known tool outputs into incoming messages that still\n * show stale states (input-available, approval-requested, approval-responded)\n * 2. Reconciles assistant IDs: exact match → content-key match → toolCallId match\n *\n * @param incoming - Messages from the client\n * @param serverMessages - Current server-side messages (source of truth)\n * @param sanitizeForContentKey - Function to sanitize a message before computing\n * its content key (typically strips ephemeral provider metadata)\n * @returns Reconciled messages ready for persistence\n */\nexport function reconcileMessages(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[],\n sanitizeForContentKey?: (message: UIMessage) => UIMessage\n): UIMessage[] {\n const withMergedToolOutputs = mergeServerToolOutputs(\n incoming,\n serverMessages\n );\n return reconcileAssistantIds(\n withMergedToolOutputs,\n serverMessages,\n sanitizeForContentKey\n );\n}\n\n/**\n * For a single message, resolve its ID by matching toolCallId against server state.\n * Prevents duplicate DB rows when client IDs differ from server IDs.\n * Tool call IDs are unique per conversation, so matching is safe regardless of state.\n */\nexport function resolveToolMergeId(\n message: UIMessage,\n serverMessages: readonly UIMessage[]\n): UIMessage {\n if (message.role !== \"assistant\") {\n return message;\n }\n\n for (const part of message.parts) {\n if (\"toolCallId\" in part && part.toolCallId) {\n const toolCallId = part.toolCallId as string;\n const existing = findMessageByToolCallId(serverMessages, toolCallId);\n if (existing && existing.id !== message.id) {\n return { ...message, id: existing.id };\n }\n }\n }\n\n return message;\n}\n\n/**\n * Content key for assistant messages used for dedup of identical short replies.\n * Returns JSON of sanitized parts, or undefined for non-assistant messages.\n */\nexport function assistantContentKey(\n message: UIMessage,\n sanitize?: (message: UIMessage) => UIMessage\n): string | undefined {\n if (message.role !== \"assistant\") {\n return undefined;\n }\n const sanitized = sanitize ? sanitize(message) : message;\n return JSON.stringify(sanitized.parts);\n}\n\nfunction mergeServerToolOutputs(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[]\n): UIMessage[] {\n // Index the server's RESOLVED tool parts so a stale client part (still in a\n // pre-output state) can't clobber the server's terminal state on persist.\n // All three terminal states must be protected, not just `output-available`:\n // otherwise a client that hasn't seen the server's `output-error` /\n // `output-denied` yet would persist its stale `input-available` over the\n // resolved result, losing the error/denial.\n const serverResolvedParts = new Map<string, Record<string, unknown>>();\n for (const msg of serverMessages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n const record = part as Record<string, unknown>;\n if (\n \"toolCallId\" in record &&\n \"state\" in record &&\n (record.state === \"output-available\" ||\n record.state === \"output-error\" ||\n record.state === \"output-denied\")\n ) {\n serverResolvedParts.set(record.toolCallId as string, record);\n }\n }\n }\n\n if (serverResolvedParts.size === 0) return incoming;\n\n return incoming.map((msg) => {\n if (msg.role !== \"assistant\") return msg;\n\n let hasChanges = false;\n const updatedParts = msg.parts.map((part) => {\n const record = part as Record<string, unknown>;\n if (\n \"toolCallId\" in record &&\n \"state\" in record &&\n (record.state === \"input-available\" ||\n record.state === \"approval-requested\" ||\n record.state === \"approval-responded\") &&\n serverResolvedParts.has(record.toolCallId as string)\n ) {\n hasChanges = true;\n const server = serverResolvedParts.get(record.toolCallId as string)!;\n // Overlay the server's resolved state, keeping the client part's\n // identity/input. Carry ONLY the result field that belongs to the\n // server's terminal state — so a stray `output` left on an\n // `output-error` part can't ride along and be misread as a result.\n const merged: Record<string, unknown> = {\n ...part,\n state: server.state\n };\n if (server.state === \"output-available\") {\n if (\"output\" in server) merged.output = server.output;\n } else if (server.state === \"output-error\") {\n if (\"errorText\" in server) merged.errorText = server.errorText;\n } else if (server.state === \"output-denied\") {\n if (\"approval\" in server) merged.approval = server.approval;\n }\n return merged;\n }\n return part;\n }) as UIMessage[\"parts\"];\n\n return hasChanges ? { ...msg, parts: updatedParts } : msg;\n });\n}\n\nfunction reconcileAssistantIds(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[],\n sanitize?: (message: UIMessage) => UIMessage\n): UIMessage[] {\n if (serverMessages.length === 0) return incoming;\n\n const claimedServerIndices = new Set<number>();\n const exactMatchMap = new Map<number, number>();\n\n for (let i = 0; i < incoming.length; i++) {\n const serverIdx = serverMessages.findIndex(\n (sm, si) => !claimedServerIndices.has(si) && sm.id === incoming[i].id\n );\n if (serverIdx !== -1) {\n claimedServerIndices.add(serverIdx);\n exactMatchMap.set(i, serverIdx);\n }\n }\n\n return incoming.map((incomingMessage, incomingIdx) => {\n if (exactMatchMap.has(incomingIdx)) {\n return incomingMessage;\n }\n\n if (\n incomingMessage.role !== \"assistant\" ||\n hasToolCallPart(incomingMessage)\n ) {\n return incomingMessage;\n }\n\n const incomingKey = assistantContentKey(incomingMessage, sanitize);\n if (!incomingKey) {\n return incomingMessage;\n }\n\n for (let i = 0; i < serverMessages.length; i++) {\n if (claimedServerIndices.has(i)) continue;\n\n const serverMessage = serverMessages[i];\n if (\n serverMessage.role !== \"assistant\" ||\n hasToolCallPart(serverMessage)\n ) {\n continue;\n }\n\n if (assistantContentKey(serverMessage, sanitize) === incomingKey) {\n claimedServerIndices.add(i);\n return { ...incomingMessage, id: serverMessage.id };\n }\n }\n\n return incomingMessage;\n });\n}\n\nfunction hasToolCallPart(message: UIMessage): boolean {\n return message.parts.some((part) => \"toolCallId\" in part);\n}\n\nfunction findMessageByToolCallId(\n messages: readonly UIMessage[],\n toolCallId: string\n): UIMessage | undefined {\n for (const msg of messages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n if (\"toolCallId\" in part && part.toolCallId === toolCallId) {\n return msg;\n }\n }\n }\n return undefined;\n}\n","import type { UIMessage } from \"ai\";\nimport type { ClientToolSchema } from \"./client-tools\";\n\nexport type ChatFiberSnapshot<Kind extends string = string> = {\n kind: Kind;\n version: 1;\n requestId: string;\n recoveryRootRequestId?: string;\n continuation: boolean;\n latestMessageId?: string;\n latestMessageRole?: string;\n latestUserMessageId?: string;\n startedAt: number;\n lastBody?: Record<string, unknown>;\n lastClientTools?: ClientToolSchema[];\n};\n\nexport function createChatFiberSnapshot<Kind extends string>({\n kind,\n requestId,\n recoveryRootRequestId,\n continuation,\n messages,\n lastBody,\n lastClientTools\n}: {\n kind: Kind;\n requestId: string;\n recoveryRootRequestId?: string;\n continuation: boolean;\n messages: UIMessage[];\n lastBody?: Record<string, unknown>;\n lastClientTools?: ClientToolSchema[];\n}): ChatFiberSnapshot<Kind> {\n const latestMessage =\n messages.length > 0 ? messages[messages.length - 1] : undefined;\n let latestUser: UIMessage | undefined;\n\n for (let index = messages.length - 1; index >= 0; index--) {\n if (messages[index].role === \"user\") {\n latestUser = messages[index];\n break;\n }\n }\n\n return {\n kind,\n version: 1,\n requestId,\n recoveryRootRequestId,\n continuation,\n latestMessageId: latestMessage?.id,\n latestMessageRole: latestMessage?.role,\n latestUserMessageId: latestUser?.id,\n startedAt: Date.now(),\n lastBody,\n lastClientTools\n };\n}\n\nexport function wrapChatFiberSnapshot<Kind extends string>(\n key: string,\n snapshot: ChatFiberSnapshot<Kind>,\n user: unknown | null\n): Record<string, unknown> {\n return { [key]: snapshot, user };\n}\n\nexport function unwrapChatFiberSnapshot<Kind extends string>(\n key: string,\n value: unknown,\n expectedKind?: Kind\n): {\n snapshot: ChatFiberSnapshot<Kind> | null;\n user: unknown | null;\n} {\n if (typeof value !== \"object\" || value === null || !(key in value)) {\n return { snapshot: null, user: value };\n }\n\n const envelope = value as Record<string, unknown>;\n const snapshot = envelope[key];\n if (typeof snapshot !== \"object\" || snapshot === null) {\n return { snapshot: null, user: value };\n }\n const candidate = snapshot as Record<string, unknown>;\n if (\n candidate.version !== 1 ||\n (expectedKind !== undefined && candidate.kind !== expectedKind) ||\n typeof candidate.requestId !== \"string\" ||\n typeof candidate.continuation !== \"boolean\"\n ) {\n return { snapshot: null, user: value };\n }\n\n return {\n snapshot: snapshot as ChatFiberSnapshot<Kind>,\n user: envelope.user ?? null\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["textEncoder","sendIfOpen","isWebSocketClosedSendError"],"sources":["../../src/chat/sanitize.ts","../../src/chat/stream-accumulator.ts","../../src/chat/turn-queue.ts","../../src/chat/submit-concurrency.ts","../../src/chat/broadcast-state.ts","../../src/chat/protocol.ts","../../src/chat/resumable-stream.ts","../../src/chat/sql-batch.ts","../../src/chat/client-tools.ts","../../src/chat/continuation-state.ts","../../src/chat/abort-registry.ts","../../src/chat/tool-state.ts","../../src/chat/parse-protocol.ts","../../src/chat/message-reconciler.ts","../../src/chat/recovery.ts"],"sourcesContent":["/**\n * Message sanitization and row-size enforcement utilities.\n *\n * Shared by @cloudflare/ai-chat and @cloudflare/think to ensure persistence\n * hygiene: stripping ephemeral provider metadata and compacting\n * oversized messages before writing to SQLite.\n */\n\nimport type { ProviderMetadata, ReasoningUIPart, UIMessage } from \"ai\";\nimport { truncateToolOutput } from \"./tool-output-truncation\";\n\nconst textEncoder = new TextEncoder();\n\n/** Maximum serialized message size before compaction (bytes). 1.8MB with headroom below SQLite's 2MB limit. */\nexport const ROW_MAX_BYTES = 1_800_000;\n\n/** Measure UTF-8 byte length of a string. */\nexport function byteLength(s: string): number {\n return textEncoder.encode(s).byteLength;\n}\n\n/**\n * Sanitize a message for persistence by removing ephemeral provider-specific\n * data that should not be stored or sent back in subsequent requests.\n *\n * 1. Strips OpenAI ephemeral fields (itemId, reasoningEncryptedContent)\n * 2. Filters truly empty reasoning parts (no text, no remaining providerMetadata)\n */\nexport function sanitizeMessage(message: UIMessage): UIMessage {\n const strippedParts = message.parts.map((part) => {\n let sanitizedPart = part;\n\n if (\n \"providerMetadata\" in sanitizedPart &&\n sanitizedPart.providerMetadata &&\n typeof sanitizedPart.providerMetadata === \"object\" &&\n \"openai\" in sanitizedPart.providerMetadata\n ) {\n sanitizedPart = stripOpenAIMetadata(sanitizedPart, \"providerMetadata\");\n }\n\n if (\n \"callProviderMetadata\" in sanitizedPart &&\n sanitizedPart.callProviderMetadata &&\n typeof sanitizedPart.callProviderMetadata === \"object\" &&\n \"openai\" in sanitizedPart.callProviderMetadata\n ) {\n sanitizedPart = stripOpenAIMetadata(\n sanitizedPart,\n \"callProviderMetadata\"\n );\n }\n\n return sanitizedPart;\n }) as UIMessage[\"parts\"];\n\n const sanitizedParts = strippedParts.filter((part) => {\n if (part.type === \"reasoning\") {\n const reasoningPart = part as ReasoningUIPart;\n if (!reasoningPart.text || reasoningPart.text.trim() === \"\") {\n if (\n \"providerMetadata\" in reasoningPart &&\n reasoningPart.providerMetadata &&\n typeof reasoningPart.providerMetadata === \"object\" &&\n Object.keys(reasoningPart.providerMetadata).length > 0\n ) {\n return true;\n }\n return false;\n }\n }\n return true;\n });\n\n return { ...message, parts: sanitizedParts };\n}\n\nfunction stripOpenAIMetadata<T extends UIMessage[\"parts\"][number]>(\n part: T,\n metadataKey: \"providerMetadata\" | \"callProviderMetadata\"\n): T {\n const metadata = (part as Record<string, unknown>)[metadataKey] as {\n openai?: Record<string, unknown>;\n [key: string]: unknown;\n };\n\n if (!metadata?.openai) return part;\n\n const {\n itemId: _itemId,\n reasoningEncryptedContent: _rec,\n ...restOpenai\n } = metadata.openai;\n\n const hasOtherOpenaiFields = Object.keys(restOpenai).length > 0;\n const { openai: _openai, ...restMetadata } = metadata;\n\n let newMetadata: ProviderMetadata | undefined;\n if (hasOtherOpenaiFields) {\n newMetadata = { ...restMetadata, openai: restOpenai } as ProviderMetadata;\n } else if (Object.keys(restMetadata).length > 0) {\n newMetadata = restMetadata as ProviderMetadata;\n }\n\n const { [metadataKey]: _oldMeta, ...restPart } = part as Record<\n string,\n unknown\n >;\n\n if (newMetadata) {\n return { ...restPart, [metadataKey]: newMetadata } as T;\n }\n return restPart as T;\n}\n\n/**\n * Enforce SQLite row size limits by compacting tool outputs and text parts\n * when a serialized message exceeds the safety threshold (1.8MB).\n *\n * Compaction strategy:\n * 1. Compact tool outputs over 1KB while preserving structured output shape\n * 2. If still too big, truncate text parts from oldest to newest\n */\nexport function enforceRowSizeLimit(message: UIMessage): UIMessage {\n let json = JSON.stringify(message);\n let size = byteLength(json);\n if (size <= ROW_MAX_BYTES) return message;\n\n if (message.role !== \"assistant\") {\n return truncateTextParts(message);\n }\n\n const compactedParts = message.parts.map((part) => {\n if (\n \"output\" in part &&\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"output-available\"\n ) {\n const output = (part as { output: unknown }).output;\n const truncated = truncateToolOutput(output, 1000);\n if (truncated.truncated) {\n return {\n ...part,\n output: truncated.output\n };\n }\n }\n return part;\n }) as UIMessage[\"parts\"];\n\n let result: UIMessage = { ...message, parts: compactedParts };\n\n json = JSON.stringify(result);\n size = byteLength(json);\n if (size <= ROW_MAX_BYTES) return result;\n\n return truncateTextParts(result);\n}\n\nfunction truncateTextParts(message: UIMessage): UIMessage {\n const parts = [...message.parts];\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n if (part.type === \"text\" && \"text\" in part) {\n const text = (part as { text: string }).text;\n if (text.length > 1000) {\n parts[i] = {\n ...part,\n text:\n `[Text truncated for storage (${text.length} chars). ` +\n `First 500 chars: ${text.slice(0, 500)}...]`\n } as UIMessage[\"parts\"][number];\n\n const candidate = { ...message, parts };\n if (byteLength(JSON.stringify(candidate)) <= ROW_MAX_BYTES) {\n break;\n }\n }\n }\n }\n\n return { ...message, parts };\n}\n","/**\n * StreamAccumulator — unified chunk-to-message builder.\n *\n * Used by @cloudflare/ai-chat (server + client) and @cloudflare/think\n * to incrementally build a UIMessage from stream chunks. Wraps\n * applyChunkToParts and handles the metadata chunk types (start, finish,\n * message-metadata, error) that applyChunkToParts does not cover.\n *\n * The accumulator signals domain-specific concerns (early persistence,\n * cross-message tool updates) via ChunkAction returns — callers handle\n * these according to their context.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { applyChunkToParts, type StreamChunkData } from \"./message-builder\";\n\nfunction asMetadata(value: unknown): Record<string, unknown> | undefined {\n if (value != null && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\nexport interface StreamAccumulatorOptions {\n messageId: string;\n continuation?: boolean;\n existingParts?: UIMessage[\"parts\"];\n existingMetadata?: Record<string, unknown>;\n}\n\nexport type ChunkAction =\n | {\n type: \"start\";\n messageId?: string;\n metadata?: Record<string, unknown>;\n }\n | {\n type: \"finish\";\n finishReason?: string;\n metadata?: Record<string, unknown>;\n }\n | { type: \"message-metadata\"; metadata: Record<string, unknown> }\n | { type: \"tool-approval-request\"; toolCallId: string }\n | {\n type: \"cross-message-tool-update\";\n updateType: \"output-available\" | \"output-error\";\n toolCallId: string;\n output?: unknown;\n errorText?: string;\n preliminary?: boolean;\n }\n | { type: \"error\"; error: string };\n\nexport interface ChunkResult {\n handled: boolean;\n action?: ChunkAction;\n}\n\nexport class StreamAccumulator {\n messageId: string;\n readonly parts: UIMessage[\"parts\"];\n metadata?: Record<string, unknown>;\n private _isContinuation: boolean;\n\n constructor(options: StreamAccumulatorOptions) {\n this.messageId = options.messageId;\n this._isContinuation = options.continuation ?? false;\n this.parts = options.existingParts ? [...options.existingParts] : [];\n this.metadata = options.existingMetadata\n ? { ...options.existingMetadata }\n : undefined;\n }\n\n applyChunk(chunk: StreamChunkData): ChunkResult {\n const handled = applyChunkToParts(this.parts, chunk);\n\n // Detect tool-approval-request for early persistence signaling\n if (chunk.type === \"tool-approval-request\" && chunk.toolCallId) {\n return {\n handled,\n action: { type: \"tool-approval-request\", toolCallId: chunk.toolCallId }\n };\n }\n\n // Detect cross-message tool output/error: applyChunkToParts returns true\n // for recognized types but silently does nothing when the toolCallId\n // doesn't exist in the current parts array.\n if (\n (chunk.type === \"tool-output-available\" ||\n chunk.type === \"tool-output-error\") &&\n chunk.toolCallId\n ) {\n const foundInParts = this.parts.some(\n (p) => \"toolCallId\" in p && p.toolCallId === chunk.toolCallId\n );\n if (!foundInParts) {\n return {\n handled,\n action: {\n type: \"cross-message-tool-update\",\n updateType:\n chunk.type === \"tool-output-available\"\n ? \"output-available\"\n : \"output-error\",\n toolCallId: chunk.toolCallId,\n output: chunk.output,\n errorText: chunk.errorText,\n preliminary: chunk.preliminary\n }\n };\n }\n }\n\n if (!handled) {\n switch (chunk.type) {\n case \"start\": {\n if (chunk.messageId != null && !this._isContinuation) {\n this.messageId = chunk.messageId;\n }\n const startMeta = asMetadata(chunk.messageMetadata);\n if (startMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...startMeta }\n : { ...startMeta };\n }\n return {\n handled: true,\n action: {\n type: \"start\",\n messageId: chunk.messageId,\n metadata: startMeta\n }\n };\n }\n case \"finish\": {\n const finishMeta = asMetadata(chunk.messageMetadata);\n if (finishMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...finishMeta }\n : { ...finishMeta };\n }\n const finishReason =\n \"finishReason\" in chunk\n ? (chunk.finishReason as string)\n : undefined;\n return {\n handled: true,\n action: {\n type: \"finish\",\n finishReason,\n metadata: finishMeta\n }\n };\n }\n case \"message-metadata\": {\n const msgMeta = asMetadata(chunk.messageMetadata);\n if (msgMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...msgMeta }\n : { ...msgMeta };\n }\n return {\n handled: true,\n action: {\n type: \"message-metadata\",\n metadata: msgMeta ?? {}\n }\n };\n }\n case \"finish-step\": {\n return { handled: true };\n }\n case \"error\": {\n return {\n handled: true,\n action: {\n type: \"error\",\n error: chunk.errorText ?? JSON.stringify(chunk)\n }\n };\n }\n }\n }\n\n return { handled };\n }\n\n /** Snapshot the current state as a UIMessage. */\n toMessage(): UIMessage {\n return {\n id: this.messageId,\n role: \"assistant\",\n parts: [...this.parts],\n ...(this.metadata != null && { metadata: this.metadata })\n } as UIMessage;\n }\n\n /**\n * Merge this accumulator's message into an existing message array.\n * Handles continuation (walk backward for last assistant), replacement\n * (update existing by messageId), or append (new message).\n */\n mergeInto(messages: UIMessage[]): UIMessage[] {\n let existingIdx = messages.findIndex((m) => m.id === this.messageId);\n\n if (existingIdx < 0 && this._isContinuation) {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"assistant\") {\n existingIdx = i;\n break;\n }\n }\n }\n\n const messageId =\n existingIdx >= 0 ? messages[existingIdx].id : this.messageId;\n\n const partialMessage: UIMessage = {\n id: messageId,\n role: \"assistant\",\n parts: [...this.parts],\n ...(this.metadata != null && { metadata: this.metadata })\n } as UIMessage;\n\n if (existingIdx >= 0) {\n const updated = [...messages];\n updated[existingIdx] = partialMessage;\n return updated;\n }\n return [...messages, partialMessage];\n }\n}\n","/**\n * TurnQueue — serial async queue with generation-based invalidation.\n *\n * Serializes async work via a promise chain, tracks which request is\n * currently active, and lets callers invalidate all queued work by\n * advancing a generation counter.\n *\n * Used by @cloudflare/ai-chat (full concurrency policy spectrum) and\n * @cloudflare/think (simple serial queue) to prevent overlapping\n * chat turns.\n */\n\nexport type TurnResult<T> =\n | { status: \"completed\"; value: T }\n | { status: \"stale\" };\n\nexport interface EnqueueOptions {\n /**\n * Generation to bind this turn to. Defaults to the current generation\n * at the time of the `enqueue` call. If the queue's generation has\n * advanced past this value by the time the turn reaches the front,\n * `fn` is not called and `{ status: \"stale\" }` is returned.\n */\n generation?: number;\n}\n\nexport class TurnQueue {\n private _queue: Promise<void> = Promise.resolve();\n private _generation = 0;\n private _activeRequestId: string | null = null;\n private _countsByGeneration = new Map<number, number>();\n\n get generation(): number {\n return this._generation;\n }\n\n get activeRequestId(): string | null {\n return this._activeRequestId;\n }\n\n get isActive(): boolean {\n return this._activeRequestId !== null;\n }\n\n async enqueue<T>(\n requestId: string,\n fn: () => Promise<T>,\n options?: EnqueueOptions\n ): Promise<TurnResult<T>> {\n const previousTurn = this._queue;\n let releaseTurn!: () => void;\n const capturedGeneration = options?.generation ?? this._generation;\n\n this._countsByGeneration.set(\n capturedGeneration,\n (this._countsByGeneration.get(capturedGeneration) ?? 0) + 1\n );\n\n this._queue = new Promise<void>((resolve) => {\n releaseTurn = resolve;\n });\n\n await previousTurn;\n\n if (this._generation !== capturedGeneration) {\n this._decrementCount(capturedGeneration);\n releaseTurn();\n return { status: \"stale\" };\n }\n\n this._activeRequestId = requestId;\n try {\n const value = await fn();\n return { status: \"completed\", value };\n } finally {\n this._activeRequestId = null;\n this._decrementCount(capturedGeneration);\n releaseTurn();\n }\n }\n\n /**\n * Advance the generation counter. All turns enqueued under older\n * generations will be skipped when they reach the front of the queue.\n */\n reset(): void {\n this._generation++;\n }\n\n /**\n * Wait until the queue is fully drained (no pending or active turns).\n */\n async waitForIdle(): Promise<void> {\n let queue: Promise<void>;\n do {\n queue = this._queue;\n await queue;\n } while (this._queue !== queue);\n }\n\n /**\n * Number of active + queued turns for a given generation.\n * Defaults to the current generation.\n */\n queuedCount(generation?: number): number {\n return this._countsByGeneration.get(generation ?? this._generation) ?? 0;\n }\n\n private _decrementCount(generation: number): void {\n const count = (this._countsByGeneration.get(generation) ?? 1) - 1;\n if (count <= 0) {\n this._countsByGeneration.delete(generation);\n } else {\n this._countsByGeneration.set(generation, count);\n }\n }\n}\n","import type { MessageConcurrency } from \"./lifecycle\";\n\nexport type NormalizedMessageConcurrency =\n | \"queue\"\n | \"latest\"\n | \"merge\"\n | \"drop\"\n | {\n strategy: \"debounce\";\n debounceMs: number;\n };\n\nexport type SubmitConcurrencyDecision = {\n action: \"execute\" | \"drop\";\n strategy: NormalizedMessageConcurrency | null;\n submitSequence: number | null;\n debounceUntilMs: number | null;\n};\n\nexport class SubmitConcurrencyController {\n private _submitSequence = 0;\n private _latestOverlappingSubmitSequence = 0;\n private _pendingEnqueueCount = 0;\n private _resetEpoch = 0;\n private _activeDebounceTimers = new Set<ReturnType<typeof setTimeout>>();\n private _activeDebounceResolves = new Set<() => void>();\n\n constructor(private readonly options: { defaultDebounceMs: number }) {}\n\n get pendingEnqueueCount(): number {\n return this._pendingEnqueueCount;\n }\n\n get overlappingSubmitCount(): number {\n return this._latestOverlappingSubmitSequence;\n }\n\n decide(options: {\n concurrency: MessageConcurrency;\n isSubmitMessage: boolean;\n queuedTurns: number;\n }): SubmitConcurrencyDecision {\n const queuedTurnsInCurrentEpoch =\n options.queuedTurns + this._pendingEnqueueCount;\n\n if (!options.isSubmitMessage || queuedTurnsInCurrentEpoch === 0) {\n return {\n action: \"execute\",\n strategy: null,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n const concurrency = this.normalize(options.concurrency);\n if (concurrency === \"drop\") {\n return {\n action: \"drop\",\n strategy: concurrency,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n if (concurrency === \"queue\") {\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n const submitSequence = ++this._submitSequence;\n this._latestOverlappingSubmitSequence = submitSequence;\n\n if (concurrency === \"latest\" || concurrency === \"merge\") {\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence,\n debounceUntilMs: null\n };\n }\n\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence,\n debounceUntilMs: Date.now() + concurrency.debounceMs\n };\n }\n\n /**\n * Mark a submit as accepted and in-flight between admission and turn\n * queue registration. Returns an idempotent `release()` function that\n * must be called when the submit either reaches the turn queue or is\n * abandoned. The returned function is bound to the controller's reset\n * epoch — releases from before the most recent `reset()` are no-ops,\n * so post-reset submits keep an accurate count.\n */\n beginEnqueue(): () => void {\n this._pendingEnqueueCount++;\n const epoch = this._resetEpoch;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n if (this._resetEpoch !== epoch) return;\n this._pendingEnqueueCount = Math.max(0, this._pendingEnqueueCount - 1);\n };\n }\n\n isSuperseded(submitSequence: number | null): boolean {\n return (\n submitSequence !== null &&\n submitSequence < this._latestOverlappingSubmitSequence\n );\n }\n\n async waitForTimestamp(timestampMs: number): Promise<void> {\n const remainingMs = timestampMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n const wrappedResolve = () => {\n this._activeDebounceResolves.delete(wrappedResolve);\n resolve();\n };\n const timer = setTimeout(() => {\n this._activeDebounceTimers.delete(timer);\n wrappedResolve();\n }, remainingMs);\n\n this._activeDebounceTimers.add(timer);\n this._activeDebounceResolves.add(wrappedResolve);\n });\n }\n\n cancelActiveDebounce(): void {\n for (const timer of this._activeDebounceTimers) {\n clearTimeout(timer);\n }\n this._activeDebounceTimers.clear();\n\n const resolves = [...this._activeDebounceResolves];\n this._activeDebounceResolves.clear();\n for (const resolve of resolves) {\n resolve();\n }\n }\n\n reset(): void {\n this._resetEpoch++;\n this._pendingEnqueueCount = 0;\n this.cancelActiveDebounce();\n }\n\n async waitForIdle(waitForQueueIdle: () => Promise<void>): Promise<void> {\n while (true) {\n await waitForQueueIdle();\n if (this._pendingEnqueueCount === 0) return;\n await new Promise<void>((resolve) => setTimeout(resolve, 5));\n }\n }\n\n private normalize(\n concurrency: MessageConcurrency\n ): NormalizedMessageConcurrency {\n if (typeof concurrency === \"string\") {\n return concurrency;\n }\n\n const debounceMs = concurrency.debounceMs;\n\n return {\n strategy: \"debounce\",\n debounceMs:\n typeof debounceMs === \"number\" &&\n Number.isFinite(debounceMs) &&\n debounceMs >= 0\n ? debounceMs\n : this.options.defaultDebounceMs\n };\n }\n}\n","/**\n * Broadcast stream state machine.\n *\n * Manages the lifecycle of a StreamAccumulator for broadcast/resume\n * streams — the path where this client is *observing* a stream owned\n * by another tab or resumed after reconnect, rather than the transport-\n * owned path that feeds directly into useChat.\n *\n * The transition function is pure (no React, no WebSocket, no side\n * effects). Callers dispatch events and apply the returned state +\n * messagesUpdate. Side effects (sending ACKs, calling onData) stay\n * in the caller.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { StreamAccumulator } from \"./stream-accumulator\";\nimport type { StreamChunkData } from \"./message-builder\";\n\n// ── State ──────────────────────────────────────────────────────────\n\nexport type BroadcastStreamState =\n | { status: \"idle\" }\n | {\n status: \"observing\";\n streamId: string;\n accumulator: StreamAccumulator;\n };\n\n// ── Events ─────────────────────────────────────────────────────────\n\nexport type BroadcastStreamEvent =\n | {\n type: \"response\";\n streamId: string;\n /** Fallback message ID for a new accumulator (ignored if one exists for this stream). */\n messageId: string;\n chunkData?: unknown;\n done?: boolean;\n error?: boolean;\n replay?: boolean;\n replayComplete?: boolean;\n continuation?: boolean;\n /** Required when continuation=true so the accumulator can pick up existing parts. */\n currentMessages?: UIMessage[];\n }\n | {\n type: \"resume-fallback\";\n streamId: string;\n messageId: string;\n }\n | { type: \"clear\" };\n\n// ── Result ─────────────────────────────────────────────────────────\n\nexport interface TransitionResult {\n state: BroadcastStreamState;\n messagesUpdate?: (prev: UIMessage[]) => UIMessage[];\n isStreaming: boolean;\n}\n\n// ── Transition ─────────────────────────────────────────────────────\n\nexport function transition(\n state: BroadcastStreamState,\n event: BroadcastStreamEvent\n): TransitionResult {\n switch (event.type) {\n case \"clear\":\n return { state: { status: \"idle\" }, isStreaming: false };\n\n case \"resume-fallback\": {\n const accumulator = new StreamAccumulator({\n messageId: event.messageId\n });\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n isStreaming: true\n };\n }\n\n case \"response\": {\n let accumulator: StreamAccumulator;\n\n // A replayed `start` chunk means the server is re-sending the stream\n // buffer from chunk 0 (resume replay). Re-initialize the accumulator\n // instead of appending into an existing one: replaying into an\n // accumulator that already holds this stream's parts would duplicate\n // them (a second `text-start` unconditionally opens a second text\n // part — #1733). Re-initializing makes replay idempotent under any\n // number of replays, including a second replay triggered by a\n // duplicate STREAM_RESUMING → ACK cycle or a reconnect.\n const isReplayedStart =\n event.replay === true &&\n (event.chunkData as { type?: string } | null | undefined)?.type ===\n \"start\";\n\n if (\n state.status === \"idle\" ||\n state.streamId !== event.streamId ||\n isReplayedStart\n ) {\n let messageId = event.messageId;\n let existingParts: UIMessage[\"parts\"] | undefined;\n let existingMetadata: Record<string, unknown> | undefined;\n\n if (event.continuation && event.currentMessages) {\n for (let i = event.currentMessages.length - 1; i >= 0; i--) {\n if (event.currentMessages[i].role === \"assistant\") {\n messageId = event.currentMessages[i].id;\n existingParts = [...event.currentMessages[i].parts];\n if (event.currentMessages[i].metadata != null) {\n existingMetadata = {\n ...(event.currentMessages[i].metadata as Record<\n string,\n unknown\n >)\n };\n }\n break;\n }\n }\n }\n\n accumulator = new StreamAccumulator({\n messageId,\n continuation: event.continuation,\n existingParts,\n existingMetadata\n });\n } else {\n accumulator = state.accumulator;\n }\n\n if (event.chunkData) {\n accumulator.applyChunk(event.chunkData as StreamChunkData);\n }\n\n let messagesUpdate: ((prev: UIMessage[]) => UIMessage[]) | undefined;\n\n if (event.done) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n return {\n state: { status: \"idle\" },\n messagesUpdate,\n isStreaming: false\n };\n }\n\n if (event.chunkData && !event.replay) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n } else if (event.replayComplete) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n }\n\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n messagesUpdate,\n isStreaming: true\n };\n }\n }\n}\n","/**\n * Wire protocol message type constants for the cf_agent_chat_* protocol.\n *\n * These are the string values used on the wire between agent servers and\n * clients. Both @cloudflare/ai-chat (via its MessageType enum) and\n * @cloudflare/think use these values.\n */\nexport const CHAT_MESSAGE_TYPES = {\n CHAT_MESSAGES: \"cf_agent_chat_messages\",\n USE_CHAT_REQUEST: \"cf_agent_use_chat_request\",\n USE_CHAT_RESPONSE: \"cf_agent_use_chat_response\",\n CHAT_CLEAR: \"cf_agent_chat_clear\",\n CHAT_REQUEST_CANCEL: \"cf_agent_chat_request_cancel\",\n STREAM_RESUMING: \"cf_agent_stream_resuming\",\n STREAM_RESUME_ACK: \"cf_agent_stream_resume_ack\",\n STREAM_RESUME_REQUEST: \"cf_agent_stream_resume_request\",\n STREAM_RESUME_NONE: \"cf_agent_stream_resume_none\",\n TOOL_RESULT: \"cf_agent_tool_result\",\n TOOL_APPROVAL: \"cf_agent_tool_approval\",\n MESSAGE_UPDATED: \"cf_agent_message_updated\",\n // Server→client: a durable chat turn is being recovered (interrupted by a\n // deploy/eviction or a stream-stall watchdog abort and now resuming). Sent\n // when a recovery continuation is scheduled and cleared on every terminal\n // outcome; `@cloudflare/think` also replays it on connect so a client that\n // joins mid-recovery learns it. Purely a progress hint — backward-compatible\n // (clients that don't understand it ignore it). See issue #1620.\n CHAT_RECOVERING: \"cf_agent_chat_recovering\"\n} as const;\n","/**\n * ResumableStream: Standalone class for buffering, persisting, and replaying\n * stream chunks in SQLite. Extracted from AIChatAgent to separate concerns.\n *\n * Handles:\n * - Chunk buffering (batched writes to SQLite for performance)\n * - Stream lifecycle (start, complete, error)\n * - Chunk replay for reconnecting clients\n * - Stale stream cleanup\n * - Active stream restoration after agent restart\n */\n\nimport { nanoid } from \"nanoid\";\nimport type { Connection } from \"agents\";\nimport { CHAT_MESSAGE_TYPES } from \"./protocol\";\n\n/** Number of chunks to pack into a single SQLite row before flushing */\nconst CHUNK_BUFFER_SIZE = 10;\n/** Maximum buffer size to prevent memory issues on rapid reconnections */\nconst CHUNK_BUFFER_MAX_SIZE = 100;\n/**\n * Max accumulated raw chunk bytes packed into one row before forcing a flush.\n * The SQLite row limit is 2 MB; packing serializes bodies into a JSON array,\n * which re-escapes their contents (quotes/backslashes), so we keep the raw\n * total well under the limit to leave generous headroom for escaping overhead.\n * A chunk larger than this is flushed as its own (unwrapped) row.\n */\nconst SEGMENT_MAX_BYTES = 512_000;\n/** Default cleanup interval for old streams (ms) - every 10 minutes */\nconst CLEANUP_INTERVAL_MS = 10 * 60 * 1000;\n/**\n * Retention for completed/errored stream buffers, measured from completion.\n *\n * The assistant message is persisted separately (`cf_ai_chat_agent_messages`),\n * so once a stream completes its buffer is no longer the source of truth — it\n * is only a brief reconnect-and-replay grace window: long enough to cover a\n * client that dropped at the completion boundary and reconnects to replay the\n * just-finished stream, and to deliver a pending terminal error frame on a\n * resumed stream (#1645). It is deliberately short (not the chat's lifetime)\n * so idle/one-off chat DOs don't accumulate stale buffers (#1706).\n */\nconst COMPLETED_RETENTION_MS = 10 * 60 * 1000;\n/**\n * Retention for abandoned `streaming` rows, measured from LAST chunk activity.\n *\n * Generous relative to {@link COMPLETED_RETENTION_MS}: an interrupted turn must\n * have ample time to be resumed by a reconnecting client or healed by fiber\n * recovery before its buffer is reaped. Only a stream that has produced no\n * chunk for this long is treated as truly dead. Keyed off last activity (not\n * start time) so a long but still-active stream is never swept mid-flight.\n */\nconst ABANDONED_STREAM_RETENTION_MS = 60 * 60 * 1000;\n/** Shared encoder for UTF-8 byte length measurement */\nconst textEncoder = new TextEncoder();\n\n/**\n * A stored row body is either a single chunk body (a JSON object string —\n * legacy per-chunk rows and single-chunk segments) or a packed segment (a JSON\n * array of chunk body strings). Unpack to the individual chunk bodies in order.\n *\n * Stored chunk bodies are always serialized JSON *objects*, never arrays, so\n * `Array.isArray` reliably distinguishes a packed segment from a single body.\n */\nfunction unpackSegmentBody(rowBody: string): string[] {\n try {\n const parsed = JSON.parse(rowBody);\n if (Array.isArray(parsed)) {\n return parsed as string[];\n }\n } catch {\n // Not valid JSON — treat as a single opaque body.\n }\n return [rowBody];\n}\n\nfunction sendIfOpen(connection: Connection, message: string): boolean {\n try {\n connection.send(message);\n return true;\n } catch (error) {\n if (isWebSocketClosedSendError(error)) return false;\n throw error;\n }\n}\n\nfunction isWebSocketClosedSendError(error: unknown): boolean {\n return (\n error instanceof TypeError &&\n error.message.includes(\"WebSocket send() after close\")\n );\n}\n\n/**\n * Stored stream chunk for resumable streaming\n */\ntype StreamChunk = {\n id: string;\n stream_id: string;\n body: string;\n chunk_index: number;\n created_at: number;\n};\n\n/**\n * Stream metadata for tracking active streams\n */\ntype StreamMetadata = {\n id: string;\n request_id: string;\n status: \"streaming\" | \"completed\" | \"error\";\n created_at: number;\n completed_at: number | null;\n /**\n * The assistant message id this stream is producing, captured when the\n * stream starts. This is the SAME id the live path persists under, so orphan\n * recovery (#1691) can re-associate reconstructed chunks with the correct\n * message even when the provider stream carries no `start.messageId`. Null on\n * legacy rows written before this column existed.\n */\n message_id: string | null;\n /**\n * Whether this stream is a continuation (appends to the last assistant\n * message rather than starting a new one). Live broadcast frames carry\n * `continuation: true`, and replay frames must too (#1733): without it a\n * reconnecting client treats a replayed continuation as a fresh message\n * and drops the parts streamed before the continuation. SQLite has no\n * boolean type — 1/0/null (legacy rows predating the column).\n */\n is_continuation: number | null;\n};\n\n/**\n * Minimal SQL interface matching Agent's this.sql tagged template.\n * Allows ResumableStream to work with the Agent's SQLite without\n * depending on the full Agent class.\n */\nexport type SqlTaggedTemplate = {\n <T = Record<string, unknown>>(\n strings: TemplateStringsArray,\n ...values: (string | number | boolean | null)[]\n ): T[];\n};\n\nexport class ResumableStream {\n private _activeStreamId: string | null = null;\n private _activeRequestId: string | null = null;\n /** Monotonic row-ordering index; one increment per flushed segment row. */\n private _segmentIndex = 0;\n\n /**\n * Whether the active stream was started in this instance (true) or\n * restored from SQLite after hibernation/restart (false). An orphaned\n * stream has no live LLM reader — the ReadableStream was lost when the\n * DO was evicted.\n */\n private _isLive = false;\n\n /**\n * Whether the active stream is a continuation. Mirrors the durable\n * `is_continuation` column so replay frames can carry the flag without a\n * per-replay query; restored from SQLite after hibernation in restore().\n */\n private _activeIsContinuation = false;\n\n private _chunkBuffer: Array<{ streamId: string; body: string }> = [];\n private _chunkBufferBytes = 0;\n private _isFlushingChunks = false;\n private _lastCleanupTime = 0;\n\n constructor(private sql: SqlTaggedTemplate) {\n // Create tables for stream chunks and metadata\n this.sql`create table if not exists cf_ai_chat_stream_chunks (\n id text primary key,\n stream_id text not null,\n body text not null,\n chunk_index integer not null,\n created_at integer not null\n )`;\n\n this.sql`create table if not exists cf_ai_chat_stream_metadata (\n id text primary key,\n request_id text not null,\n status text not null,\n created_at integer not null,\n completed_at integer\n )`;\n\n // Backward-compatible migration (#1691): add the column that durably links a\n // stream to its assistant message. Tables created before this release lack\n // it, so `alter table add column` is idempotent — the duplicate-column\n // error (and ONLY that error) is swallowed on an already-migrated table.\n this._migrateMetadataColumns();\n\n this.sql`create index if not exists idx_stream_chunks_stream_id \n on cf_ai_chat_stream_chunks(stream_id, chunk_index)`;\n\n // Restore any active stream from a previous session\n this.restore();\n }\n\n /**\n * Add the #1691 recovery column to the metadata table for rows created before\n * it existed. Inspects the current schema and only runs `alter table` when the\n * column is absent — idempotent across Durable Object restarts, with no\n * error-driven control flow.\n */\n private _migrateMetadataColumns() {\n const columns =\n this.sql<{ name: string }>`\n select name from pragma_table_info('cf_ai_chat_stream_metadata')\n ` ?? [];\n const hasMessageId = columns.some((column) => column.name === \"message_id\");\n if (!hasMessageId) {\n this\n .sql`alter table cf_ai_chat_stream_metadata add column message_id text`;\n }\n const hasIsContinuation = columns.some(\n (column) => column.name === \"is_continuation\"\n );\n if (!hasIsContinuation) {\n this\n .sql`alter table cf_ai_chat_stream_metadata add column is_continuation integer`;\n }\n }\n\n // ── State accessors ────────────────────────────────────────────────\n\n get activeStreamId(): string | null {\n return this._activeStreamId;\n }\n\n get activeRequestId(): string | null {\n return this._activeRequestId;\n }\n\n hasActiveStream(): boolean {\n return this._activeStreamId !== null;\n }\n\n /**\n * Whether the active stream has a live LLM reader (started in this\n * instance) vs being restored from SQLite after hibernation (orphaned).\n */\n get isLive(): boolean {\n return this._isLive;\n }\n\n // ── Stream lifecycle ───────────────────────────────────────────────\n\n /**\n * Start tracking a new stream for resumable streaming.\n * Creates metadata entry in SQLite and sets up tracking state.\n * @param requestId - The unique ID of the chat request\n * @returns The generated stream ID\n */\n start(\n requestId: string,\n options: { messageId?: string; continuation?: boolean } = {}\n ): string {\n // Flush any pending chunks from previous streams to prevent mixing\n this.flushBuffer();\n\n const streamId = nanoid();\n this._activeStreamId = streamId;\n this._activeRequestId = requestId;\n this._segmentIndex = 0;\n this._isLive = true;\n this._activeIsContinuation = options.continuation ?? false;\n\n const messageId = options.messageId ?? null;\n\n this.sql`\n insert into cf_ai_chat_stream_metadata (id, request_id, status, created_at, message_id, is_continuation)\n values (${streamId}, ${requestId}, 'streaming', ${Date.now()}, ${messageId}, ${this._activeIsContinuation ? 1 : 0})\n `;\n\n return streamId;\n }\n\n /**\n * The assistant message id an orphaned stream was producing — the same id the\n * live path persists under, so recovery re-associates reconstructed chunks\n * with the correct message (#1691). Returns null when the row is missing or\n * is a legacy row written before the `message_id` column existed.\n */\n getStreamMessageId(streamId: string): string | null {\n const rows = this.sql<{ message_id: string | null }>`\n select message_id from cf_ai_chat_stream_metadata\n where id = ${streamId}\n `;\n if (!rows || rows.length === 0) return null;\n return rows[0].message_id ?? null;\n }\n\n /**\n * Mark a stream as completed and flush any pending chunks.\n * @param streamId - The stream to mark as completed\n */\n complete(streamId: string) {\n this.flushBuffer();\n\n this.sql`\n update cf_ai_chat_stream_metadata \n set status = 'completed', completed_at = ${Date.now()} \n where id = ${streamId}\n `;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._segmentIndex = 0;\n this._isLive = false;\n this._activeIsContinuation = false;\n\n // Periodically clean up old streams\n this._maybeCleanupOldStreams();\n }\n\n /**\n * Mark a stream as errored and clean up state.\n * @param streamId - The stream to mark as errored\n */\n markError(streamId: string) {\n this.flushBuffer();\n\n this.sql`\n update cf_ai_chat_stream_metadata \n set status = 'error', completed_at = ${Date.now()} \n where id = ${streamId}\n `;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._segmentIndex = 0;\n this._isLive = false;\n this._activeIsContinuation = false;\n }\n\n // ── Chunk storage ──────────────────────────────────────────────────\n\n /** Maximum chunk body size before skipping storage (bytes). Prevents SQLite row limit crash. */\n private static CHUNK_MAX_BYTES = 1_800_000;\n\n /**\n * Buffer a stream chunk for batch write to SQLite.\n * Chunks exceeding the row size limit are skipped to prevent crashes.\n * The chunk is still broadcast to live clients (caller handles that),\n * but will be missing from replay on reconnection.\n * @param streamId - The stream this chunk belongs to\n * @param body - The serialized chunk body\n */\n storeChunk(streamId: string, body: string) {\n // Guard against chunks that would exceed SQLite row limit.\n // The chunk is still broadcast to live clients; only replay storage is skipped.\n const bodyBytes = textEncoder.encode(body).byteLength;\n if (bodyBytes > ResumableStream.CHUNK_MAX_BYTES) {\n console.warn(\n `[ResumableStream] Skipping oversized chunk (${bodyBytes} bytes) ` +\n `to prevent SQLite row limit crash. Live clients still receive it.`\n );\n return;\n }\n\n // Force flush if buffer is at max to prevent memory issues\n if (this._chunkBuffer.length >= CHUNK_BUFFER_MAX_SIZE) {\n this.flushBuffer();\n }\n\n // Byte guard: keep a packed segment safely under the SQLite row limit. If\n // the buffer already holds chunks and adding this body would push the\n // segment past the threshold, flush first so this chunk starts a fresh\n // segment. A single large chunk therefore ends up alone and is written\n // unwrapped by flushBuffer (no array-escaping inflation).\n if (\n this._chunkBuffer.length > 0 &&\n this._chunkBufferBytes + bodyBytes > SEGMENT_MAX_BYTES\n ) {\n this.flushBuffer();\n }\n\n this._chunkBuffer.push({ streamId, body });\n this._chunkBufferBytes += bodyBytes;\n\n // Flush when buffer reaches the per-segment chunk threshold\n if (this._chunkBuffer.length >= CHUNK_BUFFER_SIZE) {\n this.flushBuffer();\n }\n }\n\n /**\n * Flush the buffered chunks to SQLite as a single packed row.\n * Uses a lock to prevent concurrent flush operations.\n *\n * The whole buffer becomes one row: a single-chunk segment is stored\n * unwrapped (legacy object format) so a large chunk avoids array-escaping\n * inflation, while a multi-chunk segment stores a JSON array of bodies. This\n * collapses N chunk rows into one, cutting rows written / stored / scanned.\n */\n flushBuffer() {\n if (this._isFlushingChunks || this._chunkBuffer.length === 0) {\n return;\n }\n\n this._isFlushingChunks = true;\n try {\n const chunks = this._chunkBuffer;\n this._chunkBuffer = [];\n this._chunkBufferBytes = 0;\n\n // All chunks in a buffer belong to the same stream: start() flushes\n // before switching streams, so the buffer is never cross-stream.\n const streamId = chunks[0].streamId;\n const segmentBody =\n chunks.length === 1\n ? chunks[0].body\n : JSON.stringify(chunks.map((chunk) => chunk.body));\n\n this.sql`\n insert into cf_ai_chat_stream_chunks (id, stream_id, body, chunk_index, created_at)\n values (${nanoid()}, ${streamId}, ${segmentBody}, ${this._segmentIndex}, ${Date.now()})\n `;\n this._segmentIndex++;\n } finally {\n this._isFlushingChunks = false;\n }\n }\n\n // ── Chunk replay ───────────────────────────────────────────────────\n\n /**\n * Send stored stream chunks to a connection for replay.\n * Chunks are marked with replay: true so the client can batch-apply them.\n *\n * Three outcomes:\n * - **Live stream**: sends chunks + `replayComplete` — client flushes and\n * continues receiving live chunks from the LLM reader.\n * - **Orphaned stream** (restored from SQLite after hibernation, no reader):\n * sends chunks + `done` and completes the stream. The caller should\n * reconstruct and persist the partial message from the stored chunks.\n * - **Completed during replay** (defensive): sends chunks + `done`.\n *\n * All sends use {@link sendIfOpen}, so a WebSocket closing mid-replay\n * does not throw. If the connection drops while iterating chunks the\n * stream is left active so the next reconnect can retry.\n *\n * @param connection - The WebSocket connection\n * @param requestId - The original request ID\n * @returns The stream ID if the stream was orphaned and finalized, null otherwise.\n * When non-null the caller should reconstruct the message from chunks.\n */\n replayChunks(connection: Connection, requestId: string): string | null {\n const streamId = this._activeStreamId;\n if (!streamId) return null;\n\n this.flushBuffer();\n\n // Replay frames must mirror what a live client observed — including the\n // continuation flag (#1733): a replayed continuation `start` that lacks\n // it would be treated as a fresh message by the client and drop the\n // parts streamed before the continuation.\n const continuation = this._activeIsContinuation;\n\n const chunks = this.sql<StreamChunk>`\n select * from cf_ai_chat_stream_chunks \n where stream_id = ${streamId} \n order by chunk_index asc\n `;\n\n for (const chunk of chunks || []) {\n for (const body of unpackSegmentBody(chunk.body)) {\n if (\n !sendIfOpen(\n connection,\n JSON.stringify({\n body,\n done: false,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true,\n ...(continuation && { continuation: true })\n })\n )\n ) {\n // Connection closed mid-replay — leave the stream active so the\n // next reconnect can retry from the start.\n return null;\n }\n }\n }\n\n if (this._activeStreamId !== streamId) {\n // Stream completed between our check above and now — send done.\n // In practice this cannot happen (DO is single-threaded and replay is\n // synchronous), but we guard defensively in case the flow changes.\n sendIfOpen(\n connection,\n JSON.stringify({\n body: \"\",\n done: true,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true,\n ...(continuation && { continuation: true })\n })\n );\n return null;\n }\n\n if (!this._isLive) {\n // Orphaned stream — restored from SQLite after hibernation but the\n // LLM ReadableStream reader was lost. No more live chunks will ever\n // arrive, so finalize it: best-effort send done, then mark completed\n // in SQLite. The orphan-cleanup decision is committed regardless of\n // whether this particular connection received the done frame, so the\n // caller can persist the reconstructed message.\n sendIfOpen(\n connection,\n JSON.stringify({\n body: \"\",\n done: true,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true,\n ...(continuation && { continuation: true })\n })\n );\n this.complete(streamId);\n return streamId;\n }\n\n // Stream is still active with a live reader — signal that replay is\n // complete so the client can flush accumulated parts to React state.\n // Without this, replayed chunks sit in activeStreamRef unflushed\n // until the next live chunk arrives.\n sendIfOpen(\n connection,\n JSON.stringify({\n body: \"\",\n done: false,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true,\n replayComplete: true,\n ...(continuation && { continuation: true })\n })\n );\n return null;\n }\n\n replayCompletedChunksByRequestId(\n connection: Connection,\n requestId: string\n ): boolean {\n const stream = this._latestStreamForRequest(requestId, \"completed\");\n if (!stream) return false;\n\n const continuation = stream.is_continuation === 1;\n if (\n !this._replayStoredChunks(connection, stream.id, requestId, continuation)\n ) {\n return false;\n }\n\n return sendIfOpen(\n connection,\n JSON.stringify({\n body: \"\",\n done: true,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true,\n ...(continuation && { continuation: true })\n })\n );\n }\n\n /**\n * Replay the stored chunks of an errored stream for a request, WITHOUT a\n * terminal frame — the caller follows up with the `done: true, error: true`\n * frame carrying the durable terminal record's error text, mirroring what a\n * live client observed (content chunks, then the error). Without this, a\n * client that missed broadcast frames while disconnected has no other\n * channel to the pre-error partial content: the server does not push\n * messages on connect, and {@link replayCompletedChunksByRequestId} only\n * serves `completed` streams (#1575).\n *\n * Returns true when the caller should proceed to send its terminal frame:\n * either no errored stream existed (nothing to replay) or its chunks were\n * replayed successfully. Returns false only when a send failed mid-replay,\n * signalling the caller to skip the terminal frame — the connection is gone\n * and the next reconnect retries the whole sequence.\n */\n replayErroredChunksByRequestId(\n connection: Connection,\n requestId: string\n ): boolean {\n const stream = this._latestStreamForRequest(requestId, \"error\");\n if (!stream) return true;\n\n return this._replayStoredChunks(\n connection,\n stream.id,\n requestId,\n stream.is_continuation === 1\n );\n }\n\n /** Latest stream row for a request with the given terminal status. */\n private _latestStreamForRequest(\n requestId: string,\n status: \"completed\" | \"error\"\n ): StreamMetadata | undefined {\n this.flushBuffer();\n\n const streams = this.sql<StreamMetadata>`\n select * from cf_ai_chat_stream_metadata\n where request_id = ${requestId}\n and status = ${status}\n order by created_at desc\n limit 1\n `;\n return streams[0];\n }\n\n /**\n * Send a finished stream's stored chunks to a connection as replay frames.\n * Returns false if the connection closed mid-replay.\n */\n private _replayStoredChunks(\n connection: Connection,\n streamId: string,\n requestId: string,\n continuation = false\n ): boolean {\n const chunks = this.sql<StreamChunk>`\n select * from cf_ai_chat_stream_chunks\n where stream_id = ${streamId}\n order by chunk_index asc\n `;\n\n for (const chunk of chunks || []) {\n for (const body of unpackSegmentBody(chunk.body)) {\n if (\n !sendIfOpen(\n connection,\n JSON.stringify({\n body,\n done: false,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true,\n ...(continuation && { continuation: true })\n })\n )\n ) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n // ── Restore / cleanup ──────────────────────────────────────────────\n\n /**\n * Restore active stream state if the agent was restarted during streaming.\n * All streams are restored regardless of age — stale cleanup happens\n * lazily in _maybeCleanupOldStreams after recovery has had its chance.\n */\n restore() {\n const activeStreams = this.sql<StreamMetadata>`\n select * from cf_ai_chat_stream_metadata \n where status = 'streaming' \n order by created_at desc \n limit 1\n `;\n\n if (activeStreams && activeStreams.length > 0) {\n const stream = activeStreams[0];\n this._activeStreamId = stream.id;\n this._activeRequestId = stream.request_id;\n // Rehydrate the continuation flag so an orphaned continuation stream\n // replayed after hibernation still carries `continuation: true` on\n // its frames (#1733). Legacy rows predate the column → null → false.\n this._activeIsContinuation = stream.is_continuation === 1;\n\n // Resume the segment row-ordering index past the highest stored value.\n const lastChunk = this.sql<{ max_index: number }>`\n select max(chunk_index) as max_index \n from cf_ai_chat_stream_chunks \n where stream_id = ${this._activeStreamId}\n `;\n this._segmentIndex =\n lastChunk && lastChunk[0]?.max_index != null\n ? lastChunk[0].max_index + 1\n : 0;\n }\n }\n\n /**\n * Clear all stream data (called on chat history clear).\n */\n clearAll() {\n this._chunkBuffer = [];\n this._chunkBufferBytes = 0;\n this.sql`delete from cf_ai_chat_stream_chunks`;\n this.sql`delete from cf_ai_chat_stream_metadata`;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._segmentIndex = 0;\n this._activeIsContinuation = false;\n }\n\n /**\n * Drop all stream tables (called on destroy).\n */\n destroy() {\n this.flushBuffer();\n this.sql`drop table if exists cf_ai_chat_stream_chunks`;\n this.sql`drop table if exists cf_ai_chat_stream_metadata`;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._activeIsContinuation = false;\n }\n\n /**\n * Force a sweep of aged stream buffers now, bypassing the lazy interval\n * gate used by {@link _maybeCleanupOldStreams}. Intended to be driven by an\n * alarm so idle/hibernated chat DOs still reclaim buffers even when no\n * further stream ever completes to trigger the lazy path.\n */\n cleanup(now: number = Date.now()): void {\n this._lastCleanupTime = now;\n this._sweepOldStreams(now);\n }\n\n /**\n * True if any stream rows remain at all. Used by alarm-driven cleanup to\n * decide whether to re-arm: once no rows remain there is nothing left to\n * sweep, so the DO can stop waking itself.\n */\n hasReclaimableStreams(): boolean {\n const rows = this.sql<{ n: number }>`\n select count(*) as n from cf_ai_chat_stream_metadata\n `;\n return (rows?.[0]?.n ?? 0) > 0;\n }\n\n // ── Internal ───────────────────────────────────────────────────────\n\n private _maybeCleanupOldStreams() {\n const now = Date.now();\n if (now - this._lastCleanupTime < CLEANUP_INTERVAL_MS) {\n return;\n }\n this._lastCleanupTime = now;\n this._sweepOldStreams(now);\n }\n\n /** Delete completed/errored buffers past the completion grace window, plus\n * abandoned \"streaming\" rows past the stale-in-flight window. The two use\n * different retentions: a completed buffer is redundant with the persisted\n * message and needs only a brief replay grace, whereas an in-flight buffer\n * must outlive resume/recovery before it is presumed dead. */\n private _sweepOldStreams(now: number) {\n const completedCutoff = now - COMPLETED_RETENTION_MS;\n this.sql`\n delete from cf_ai_chat_stream_chunks \n where stream_id in (\n select id from cf_ai_chat_stream_metadata \n where status in ('completed', 'error') and completed_at < ${completedCutoff}\n )\n `;\n this.sql`\n delete from cf_ai_chat_stream_metadata \n where status in ('completed', 'error') and completed_at < ${completedCutoff}\n `;\n\n // Clean up abandoned \"streaming\" rows. These are orphaned streams that\n // were never completed or recovered (e.g. non-durable agents that never\n // reconnected). By this point, fiber recovery has already had its chance\n // to claim them — safe to delete.\n //\n // \"Abandoned\" is keyed off LAST ACTIVITY (the most recent chunk write),\n // not the stream's start time: a long-running stream that is still\n // actively emitting chunks must never be swept mid-flight just because it\n // started long ago. A row with no chunks falls back to its start time.\n // Note `created_at <= max(chunk.created_at)` always (the row is inserted\n // before any chunk), so this set is stable across the two deletes even\n // though the first removes the chunks the second's subquery reads.\n const abandonedCutoff = now - ABANDONED_STREAM_RETENTION_MS;\n this.sql`\n delete from cf_ai_chat_stream_chunks\n where stream_id in (\n select m.id from cf_ai_chat_stream_metadata m\n where m.status = 'streaming'\n and coalesce(\n (select max(c.created_at) from cf_ai_chat_stream_chunks c\n where c.stream_id = m.id),\n m.created_at\n ) < ${abandonedCutoff}\n )\n `;\n this.sql`\n delete from cf_ai_chat_stream_metadata\n where id in (\n select m.id from cf_ai_chat_stream_metadata m\n where m.status = 'streaming'\n and coalesce(\n (select max(c.created_at) from cf_ai_chat_stream_chunks c\n where c.stream_id = m.id),\n m.created_at\n ) < ${abandonedCutoff}\n )\n `;\n }\n\n // ── Test helpers (matching old AIChatAgent test API) ────────────────\n\n /**\n * Return the stored chunks for a stream as individual chunk bodies in order,\n * unpacking packed segment rows. The returned `chunk_index` is a running\n * per-chunk sequence (0, 1, 2, …) — stable across calls because rows are\n * append-only — so callers can use it as a monotonic chunk sequence.\n */\n getStreamChunks(\n streamId: string\n ): Array<{ body: string; chunk_index: number }> {\n const rows =\n this.sql<{ body: string }>`\n select body from cf_ai_chat_stream_chunks \n where stream_id = ${streamId} \n order by chunk_index asc\n ` || [];\n const out: Array<{ body: string; chunk_index: number }> = [];\n let index = 0;\n for (const row of rows) {\n for (const body of unpackSegmentBody(row.body)) {\n out.push({ body, chunk_index: index });\n index++;\n }\n }\n return out;\n }\n\n /** @internal For testing only */\n getStreamMetadata(\n streamId: string\n ): { status: string; request_id: string } | null {\n const result = this.sql<{ status: string; request_id: string }>`\n select status, request_id from cf_ai_chat_stream_metadata \n where id = ${streamId}\n `;\n return result && result.length > 0 ? result[0] : null;\n }\n\n /** @internal For testing only */\n getAllStreamMetadata(): Array<{\n id: string;\n status: string;\n request_id: string;\n created_at: number;\n }> {\n return (\n this.sql<{\n id: string;\n status: string;\n request_id: string;\n created_at: number;\n }>`select id, status, request_id, created_at from cf_ai_chat_stream_metadata` ||\n []\n );\n }\n\n /** @internal For testing only */\n insertStaleStream(streamId: string, requestId: string, ageMs: number): void {\n const createdAt = Date.now() - ageMs;\n this.sql`\n insert into cf_ai_chat_stream_metadata (id, request_id, status, created_at)\n values (${streamId}, ${requestId}, 'streaming', ${createdAt})\n `;\n }\n\n /**\n * Append a chunk to a stream dated `ageMs` in the past. Used to exercise the\n * last-activity sweep threshold: a long-running streaming row with a *recent*\n * chunk must survive even when its start time is older than the cutoff.\n * @internal For testing only\n */\n insertChunkAt(streamId: string, body: string, ageMs: number): void {\n const createdAt = Date.now() - ageMs;\n this.sql`\n insert into cf_ai_chat_stream_chunks (id, stream_id, body, chunk_index, created_at)\n values (${nanoid()}, ${streamId}, ${body}, 0, ${createdAt})\n `;\n }\n}\n","/**\n * Helpers for building batched SQLite statements that run through the Agent's\n * `sql` tagged template (which interleaves a `?` placeholder between every\n * string fragment). Used to collapse per-row INSERT/DELETE loops into a small\n * number of multi-row statements.\n *\n * SQLite (Durable Object / D1) caps bound parameters at 100 per query, so\n * callers must chunk their inputs to stay within {@link MAX_BOUND_PARAMS}.\n * See https://developers.cloudflare.com/d1/platform/limits/\n */\n\n/** Maximum bound parameters allowed in a single SQLite (DO / D1) query. */\nexport const MAX_BOUND_PARAMS = 100;\n\n/**\n * Attach a self-referential `raw` property so a plain string[] satisfies the\n * TemplateStringsArray shape. `sql` only reads indexed string fragments, so\n * `raw` is never consumed — this just keeps the type system happy.\n */\nfunction asTemplateStringsArray(parts: string[]): TemplateStringsArray {\n (parts as unknown as { raw: readonly string[] }).raw = parts;\n return parts as unknown as TemplateStringsArray;\n}\n\n/**\n * Build a TemplateStringsArray for a single-column `IN (...)` clause. Produces\n * fragments for:\n * `${prefix}(?, ?, ...)`\n *\n * @throws if `count` is less than 1.\n */\nexport function buildInClauseStrings(\n prefix: string,\n count: number\n): TemplateStringsArray {\n if (count < 1) {\n throw new Error(`buildInClauseStrings requires count >= 1 (got ${count})`);\n }\n const parts = new Array<string>(count + 1);\n parts[0] = `${prefix}(`;\n for (let i = 1; i < count; i++) {\n parts[i] = \", \";\n }\n parts[count] = \")\";\n return asTemplateStringsArray(parts);\n}\n","/**\n * Client tool schema handling for the cf_agent_chat protocol.\n *\n * Converts client-provided tool schemas (JSON wire format) into AI SDK\n * tool definitions. By default these tools have no `execute` function —\n * when the model calls them, the tool call is sent back to the client.\n *\n * When an `execute` delegate is supplied (e.g. a parent agent that drives a\n * Think sub-agent over RPC and can run the client tools itself), the tools are\n * built WITH an `execute` so the model's call is resolved inline within the\n * same turn instead of being surfaced as a dangling tool call.\n *\n * Used by both @cloudflare/ai-chat and @cloudflare/think.\n */\n\nimport type { JSONSchema7, Tool, ToolSet } from \"ai\";\nimport { tool, jsonSchema } from \"ai\";\n\n/**\n * Wire-format tool schema sent from the client.\n * Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema`\n * because Zod schemas cannot be serialized over the wire.\n */\nexport type ClientToolSchema = {\n /** Unique name for the tool */\n name: string;\n /** Human-readable description of what the tool does */\n description?: Tool[\"description\"];\n /** JSON Schema defining the tool's input parameters */\n parameters?: JSONSchema7;\n};\n\n/**\n * Executes a client-defined tool and returns its output.\n *\n * Used for the RPC path (e.g. a parent agent delegating to a Think sub-agent)\n * where the caller can run the client tools itself, rather than the\n * browser/WebSocket path where results are sent back asynchronously.\n */\nexport type ClientToolExecutor = (call: {\n /** The name of the client tool the model called. */\n toolName: string;\n /** The model-generated input for the tool call. */\n input: unknown;\n /** The AI SDK tool-call id for the invocation. */\n toolCallId: string;\n}) => unknown | Promise<unknown>;\n\n/**\n * Converts client tool schemas to AI SDK tool format.\n *\n * By default these tools have no `execute` function — when the AI model calls\n * them, the tool call is sent back to the client for execution.\n *\n * When `options.execute` is provided, each tool is built WITH an `execute` that\n * delegates to it. This is used by the RPC path (e.g. a parent agent driving a\n * Think sub-agent) so the model's client-tool call is resolved inline within\n * the same turn.\n *\n * @param clientTools - Array of tool schemas from the client\n * @param options - Optional `execute` delegate to run the tools inline\n * @returns Record of AI SDK tools that can be spread into your tools object\n */\nexport function createToolsFromClientSchemas(\n clientTools?: ClientToolSchema[],\n options?: { execute?: ClientToolExecutor }\n): ToolSet {\n if (!clientTools || clientTools.length === 0) {\n return {};\n }\n\n const seenNames = new Set<string>();\n for (const t of clientTools) {\n if (seenNames.has(t.name)) {\n console.warn(\n `[createToolsFromClientSchemas] Duplicate tool name \"${t.name}\" found. Later definitions will override earlier ones.`\n );\n }\n seenNames.add(t.name);\n }\n\n const execute = options?.execute;\n // The AI SDK's `tool()` overloads infer the input type as `never` when an\n // `execute` is combined with a runtime `jsonSchema(...)`. Cast to a\n // permissive signature (same approach as `agentTool()`), since the wire\n // schema is untyped JSON.\n const createTool = tool as unknown as (config: {\n description: string;\n inputSchema: ReturnType<typeof jsonSchema>;\n execute?: (\n input: unknown,\n executeOptions?: { toolCallId?: string }\n ) => unknown | Promise<unknown>;\n }) => Tool;\n\n return Object.fromEntries(\n clientTools.map((t) => [\n t.name,\n createTool({\n description: t.description ?? \"\",\n inputSchema: jsonSchema(t.parameters ?? { type: \"object\" }),\n ...(execute\n ? {\n execute: (\n input: unknown,\n executeOptions?: { toolCallId?: string }\n ) =>\n execute({\n toolName: t.name,\n input,\n toolCallId: executeOptions?.toolCallId ?? \"\"\n })\n }\n : {})\n })\n ])\n );\n}\n","/**\n * ContinuationState — shared state container for auto-continuation lifecycle.\n *\n * Tracks pending, deferred, and active continuation state for the\n * tool-result → auto-continue flow. Both AIChatAgent and Think use this\n * to manage which connection/tools/body a continuation turn should use\n * and to coordinate with clients requesting stream resume.\n *\n * The scheduling algorithm (prerequisite chaining, debounce, TurnQueue\n * enrollment) stays in the host — this class only manages the data.\n */\n\nimport { CHAT_MESSAGE_TYPES } from \"./protocol\";\nimport type { ClientToolSchema } from \"./client-tools\";\n\nconst MSG_STREAM_RESUME_NONE = CHAT_MESSAGE_TYPES.STREAM_RESUME_NONE;\n\nfunction sendIfOpen(\n connection: ContinuationConnection,\n message: string\n): boolean {\n try {\n connection.send(message);\n return true;\n } catch (error) {\n if (isWebSocketClosedSendError(error)) return false;\n throw error;\n }\n}\n\nfunction isWebSocketClosedSendError(error: unknown): boolean {\n return (\n error instanceof TypeError &&\n error.message.includes(\"WebSocket send() after close\")\n );\n}\n\n/**\n * Minimal connection interface for sending WebSocket messages.\n * Matches the Connection type from agents without importing it.\n * Uses a permissive send signature so Connection (which extends\n * WebSocket with its own send overload) is structurally assignable.\n */\nexport interface ContinuationConnection {\n readonly id: string;\n send(message: string): void;\n}\n\nexport interface ContinuationPending<\n TConnection extends ContinuationConnection = ContinuationConnection\n> {\n connection: TConnection;\n connectionId: string | null;\n requestId: string;\n clientTools?: ClientToolSchema[];\n body?: Record<string, unknown>;\n errorPrefix: string | null;\n prerequisite: Promise<boolean> | null;\n pastCoalesce: boolean;\n}\n\nexport interface ContinuationDeferred<\n TConnection extends ContinuationConnection = ContinuationConnection\n> {\n connection: TConnection;\n connectionId: string | null;\n clientTools?: ClientToolSchema[];\n body?: Record<string, unknown>;\n errorPrefix: string;\n prerequisite: Promise<boolean> | null;\n}\n\nexport class ContinuationState<\n TConnection extends ContinuationConnection = ContinuationConnection\n> {\n pending: ContinuationPending<TConnection> | null = null;\n deferred: ContinuationDeferred<TConnection> | null = null;\n activeRequestId: string | null = null;\n activeConnectionId: string | null = null;\n awaitingConnections: Map<string, TConnection> = new Map();\n\n /** Clear pending state and awaiting connections (without sending RESUME_NONE). */\n clearPending(): void {\n this.pending = null;\n this.awaitingConnections.clear();\n }\n\n clearDeferred(): void {\n this.deferred = null;\n }\n\n clearAll(): void {\n this.clearPending();\n this.clearDeferred();\n this.activeRequestId = null;\n this.activeConnectionId = null;\n }\n\n /**\n * Mark a connection as no longer available without canceling the\n * continuation it initiated.\n */\n releaseConnection(connectionId: string): void {\n this.awaitingConnections.delete(connectionId);\n if (this.pending?.connectionId === connectionId) {\n this.pending = { ...this.pending, connectionId: null };\n }\n if (this.deferred?.connectionId === connectionId) {\n this.deferred = { ...this.deferred, connectionId: null };\n }\n if (this.activeConnectionId === connectionId) {\n this.activeConnectionId = null;\n }\n }\n\n /**\n * Send STREAM_RESUME_NONE to all connections waiting for a\n * continuation stream to start, then clear the map.\n */\n sendResumeNone(): void {\n const msg = JSON.stringify({ type: MSG_STREAM_RESUME_NONE });\n for (const connection of this.awaitingConnections.values()) {\n sendIfOpen(connection, msg);\n }\n this.awaitingConnections.clear();\n }\n\n /**\n * Flush awaiting connections by notifying each one via the provided\n * callback (typically sends STREAM_RESUMING), then clear.\n */\n flushAwaitingConnections(notify: (conn: TConnection) => void): void {\n for (const connection of this.awaitingConnections.values()) {\n notify(connection);\n }\n this.awaitingConnections.clear();\n }\n\n /**\n * Transition pending → active. Called when the continuation stream\n * actually starts. Moves request/connection IDs to active slots,\n * clears pending fields.\n */\n activatePending(): void {\n if (!this.pending) return;\n this.activeRequestId = this.pending.requestId;\n this.activeConnectionId = this.pending.connectionId;\n this.pending = null;\n }\n\n /**\n * Transition deferred → pending. Called when a continuation turn\n * completes and there's a deferred follow-up waiting.\n *\n * Returns the new pending state (so the host can enqueue the turn),\n * or null if there was nothing deferred.\n */\n activateDeferred(\n generateRequestId: () => string\n ): ContinuationPending<TConnection> | null {\n if (this.pending || !this.deferred) return null;\n\n const d = this.deferred;\n this.deferred = null;\n this.activeRequestId = null;\n this.activeConnectionId = null;\n\n this.pending = {\n connection: d.connection,\n connectionId: d.connectionId,\n requestId: generateRequestId(),\n clientTools: d.clientTools,\n body: d.body,\n errorPrefix: d.errorPrefix,\n prerequisite: d.prerequisite,\n pastCoalesce: false\n };\n\n if (d.connectionId !== null) {\n this.awaitingConnections.set(d.connectionId, d.connection);\n }\n return this.pending;\n }\n}\n","/**\n * AbortRegistry — manages per-request AbortControllers.\n *\n * Shared between AIChatAgent and Think for chat turn cancellation.\n * Each request gets its own AbortController keyed by request ID.\n * Controllers are created lazily on first signal access.\n */\n\nconst NOOP = () => {};\n\nexport class AbortRegistry {\n private controllers = new Map<string, AbortController>();\n\n /**\n * Get or create an AbortController for the given ID and return its signal.\n * Creates the controller lazily on first access.\n */\n getSignal(id: string): AbortSignal | undefined {\n if (typeof id !== \"string\") {\n return undefined;\n }\n\n if (!this.controllers.has(id)) {\n this.controllers.set(id, new AbortController());\n }\n\n return this.controllers.get(id)!.signal;\n }\n\n /**\n * Get the signal for an existing controller without creating one.\n * Returns undefined if no controller exists for this ID.\n */\n getExistingSignal(id: string): AbortSignal | undefined {\n return this.controllers.get(id)?.signal;\n }\n\n /**\n * Cancel a specific request by aborting its controller. Optionally\n * propagate a reason — surfaces as `signal.reason` on the registry's\n * controller and through any `AbortError` it produces downstream.\n */\n cancel(id: string, reason?: unknown): void {\n this.controllers.get(id)?.abort(reason);\n }\n\n /** Remove a controller after the request completes. */\n remove(id: string): void {\n this.controllers.delete(id);\n }\n\n /**\n * Abort all pending requests and clear the registry. Optionally propagate a\n * reason — surfaces as `signal.reason` on each controller and through any\n * `AbortError` it produces downstream, exactly like {@link cancel}.\n */\n destroyAll(reason?: unknown): void {\n for (const controller of this.controllers.values()) {\n controller.abort(reason);\n }\n this.controllers.clear();\n }\n\n /** Check if a controller exists for the given ID. */\n has(id: string): boolean {\n return this.controllers.has(id);\n }\n\n /** Number of tracked controllers. */\n get size(): number {\n return this.controllers.size;\n }\n\n /**\n * Link an external `AbortSignal` to the controller for `id`. When the\n * external signal aborts, the registry's controller is cancelled —\n * propagating the abort reason — exactly the same way an internal\n * cancel would (e.g. via a `chat-request-cancel` WebSocket message).\n *\n * This is the integration point for callers that drive a chat turn\n * programmatically and want to cancel it from outside without knowing\n * the internally-generated request id (e.g. the helper-as-sub-agent\n * pattern, where a parent's `AbortSignal` from the AI SDK tool\n * `execute` needs to land inside a `Think.saveMessages` call running\n * on a child DO).\n *\n * Behavior:\n *\n * - Passing `undefined` is a no-op and returns a no-op detacher, so\n * callers can unconditionally call this with `options?.signal`.\n * - If the external signal is already aborted, the registry's\n * controller is created (if needed) and cancelled synchronously.\n * - Otherwise a one-shot `abort` listener is attached. The returned\n * function detaches it.\n *\n * **Always call the returned detacher in a `finally` block** — the\n * external signal may outlive the request (a parent chat turn that\n * drives many helper turns reuses one signal across all of them) and\n * leaving listeners attached pins closures and grows the listener\n * list on each turn.\n *\n * @returns A detacher function. Call it after the request finishes\n * (success or failure) to remove the abort listener from `signal`.\n */\n linkExternal(id: string, signal: AbortSignal | undefined): () => void {\n if (!signal) return NOOP;\n\n if (signal.aborted) {\n // Ensure the registry controller for `id` exists, then cancel it.\n // Calling getSignal first means an early external abort still\n // produces a controller for downstream observers (`getExistingSignal`)\n // rather than a silently-empty registry.\n this.getSignal(id);\n this.cancel(id, signal.reason);\n return NOOP;\n }\n\n const listener = () => this.cancel(id, signal.reason);\n signal.addEventListener(\"abort\", listener, { once: true });\n return () => signal.removeEventListener(\"abort\", listener);\n }\n}\n","/**\n * Tool State — shared update builders and applicator for tool part state changes.\n *\n * Used by both AIChatAgent and Think to apply tool results and approvals\n * to message parts. Each agent handles find-message, persist, and broadcast\n * in their own way; this module provides the state matching and update logic.\n */\n\n/**\n * Describes an update to apply to a tool part.\n */\nexport type ToolPartUpdate = {\n toolCallId: string;\n matchStates: string[];\n apply: (part: Record<string, unknown>) => Record<string, unknown>;\n};\n\n/**\n * Apply a tool part update to a parts array.\n * Finds the first part matching `update.toolCallId` in one of `update.matchStates`,\n * applies the update immutably, and returns the new parts array with the index.\n *\n * Returns `null` if no matching part was found.\n */\nexport function applyToolUpdate(\n parts: Array<Record<string, unknown>>,\n update: ToolPartUpdate\n): { parts: Array<Record<string, unknown>>; index: number } | null {\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n if (\n \"toolCallId\" in part &&\n part.toolCallId === update.toolCallId &&\n \"state\" in part &&\n update.matchStates.includes(part.state as string)\n ) {\n const updatedParts = [...parts];\n updatedParts[i] = update.apply(part);\n return { parts: updatedParts, index: i };\n }\n }\n return null;\n}\n\n/**\n * Build an update descriptor for applying a tool result.\n *\n * Matches parts in `input-available`, `approval-requested`, or `approval-responded` state.\n * Sets state to `output-available` (with output) or `output-error` (with errorText).\n */\nexport function toolResultUpdate(\n toolCallId: string,\n output: unknown,\n overrideState?: \"output-error\",\n errorText?: string\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\n \"input-available\",\n \"approval-requested\",\n \"approval-responded\"\n ],\n apply: (part) => ({\n ...part,\n ...(overrideState === \"output-error\"\n ? {\n state: \"output-error\",\n errorText: errorText ?? \"Tool execution denied by user\"\n }\n : { state: \"output-available\", output, preliminary: false })\n })\n };\n}\n\n/**\n * Build an update descriptor for a terminal tool result that belongs to a\n * tool part in a *different* (earlier) assistant message than the one\n * currently being streamed.\n *\n * This is the \"cross-message\" case: an approved server tool executes during a\n * continuation stream, but its tool part lives in the assistant message that\n * originally requested it. `StreamAccumulator` surfaces this as a\n * `cross-message-tool-update` action because the accumulator only owns the\n * current turn's new content and cannot mutate a part from a prior message.\n *\n * Compared to {@link toolResultUpdate} this builder is deliberately more\n * defensive, mirroring the equivalent fallback in `@cloudflare/ai-chat`:\n *\n * - It matches the broad set of pre-terminal **and** terminal states, so a\n * provider that replays the entire prior tool round-trip during a\n * continuation (notably the OpenAI Responses API — issue #1404) still\n * resolves to the same part instead of silently missing it.\n * - It is **first-write-wins**: a chunk arriving for a tool that already holds\n * a terminal result is treated as a replay and the existing output is never\n * overwritten. In that case `apply` returns the *same part reference*, which\n * callers use as an idempotent-no-op signal to skip the durable write and a\n * redundant `MESSAGE_UPDATED` broadcast.\n * - It preserves a streamed `preliminary` flag when one is present, otherwise\n * marks the result final (`preliminary: false`).\n */\nexport function crossMessageToolResultUpdate(\n toolCallId: string,\n updateType: \"output-available\" | \"output-error\",\n output?: unknown,\n errorText?: string,\n preliminary?: boolean\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\n \"input-streaming\",\n \"input-available\",\n \"approval-requested\",\n \"approval-responded\",\n \"output-available\",\n \"output-error\",\n \"output-denied\"\n ],\n apply: (part) => {\n if (\n part.state === \"output-available\" ||\n part.state === \"output-error\" ||\n part.state === \"output-denied\"\n ) {\n return part;\n }\n if (updateType === \"output-error\") {\n return {\n ...part,\n state: \"output-error\",\n errorText: errorText ?? \"Tool execution failed\"\n };\n }\n return {\n ...part,\n state: \"output-available\",\n output,\n preliminary: preliminary ?? false\n };\n }\n };\n}\n\n/**\n * Build an update descriptor that replaces the output of a *paused durable\n * execution* tool part (e.g. a codemode runtime tool that paused for\n * approval).\n *\n * A paused execution completes its tool call normally — the part is already\n * `output-available` with an output of `{ status: \"paused\", executionId }`.\n * When the host later approves/rejects the execution, the new outcome\n * (completed / rejected / paused-again) must replace that output in place.\n *\n * Matching is deliberately narrow and idempotent:\n *\n * - only `output-available` parts are considered;\n * - the existing output must be a paused-execution object carrying the same\n * `executionId` — anything else (already replaced, different execution)\n * returns the *same part reference*, which callers treat as a no-op signal\n * (skip persist + broadcast), mirroring {@link crossMessageToolResultUpdate}.\n */\nexport function pausedExecutionUpdate(\n toolCallId: string,\n executionId: string,\n output: unknown\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\"output-available\"],\n apply: (part) => {\n const current = part.output as\n | { status?: unknown; executionId?: unknown }\n | null\n | undefined;\n if (\n current == null ||\n typeof current !== \"object\" ||\n current.status !== \"paused\" ||\n current.executionId !== executionId\n ) {\n return part;\n }\n return { ...part, output, preliminary: false };\n }\n };\n}\n\n/**\n * Build an update descriptor for applying a tool approval.\n *\n * Matches parts in `input-available` or `approval-requested` state.\n * Sets state to `approval-responded` (if approved) or `output-denied` (if denied).\n */\nexport function toolApprovalUpdate(\n toolCallId: string,\n approved: boolean\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\"input-available\", \"approval-requested\"],\n apply: (part) => ({\n ...part,\n state: approved ? \"approval-responded\" : \"output-denied\",\n approval: {\n ...(part.approval as Record<string, unknown> | undefined),\n approved\n }\n })\n };\n}\n","/**\n * Protocol Message Parser — typed parsing of cf_agent_chat_* WebSocket messages.\n *\n * Parses raw WebSocket messages into a discriminated union of protocol events.\n * Both AIChatAgent and Think can use this instead of manual JSON.parse + type checking.\n */\n\nimport { CHAT_MESSAGE_TYPES } from \"./protocol\";\n\n/**\n * Discriminated union of all incoming chat protocol events.\n *\n * Each agent handles the events it cares about and ignores the rest.\n * Returns `null` for non-JSON messages or unrecognized types.\n */\nexport type ChatProtocolEvent =\n | {\n type: \"chat-request\";\n id: string;\n init: { method?: string; body?: string; [key: string]: unknown };\n }\n | { type: \"clear\" }\n | { type: \"cancel\"; id: string }\n | {\n type: \"tool-result\";\n toolCallId: string;\n toolName: string;\n output: unknown;\n state?: string;\n errorText?: string;\n autoContinue?: boolean;\n clientTools?: Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>;\n }\n | {\n type: \"tool-approval\";\n toolCallId: string;\n approved: boolean;\n autoContinue?: boolean;\n }\n | { type: \"stream-resume-request\" }\n | { type: \"stream-resume-ack\"; id: string }\n | { type: \"messages\"; messages: unknown[] };\n\n/**\n * Parse a raw WebSocket message string into a typed protocol event.\n *\n * Returns `null` if the message is not valid JSON or not a recognized\n * protocol message type. Callers should fall through to the user's\n * `onMessage` handler when `null` is returned.\n *\n * @example\n * ```typescript\n * const event = parseProtocolMessage(rawMessage);\n * if (!event) return userOnMessage(connection, rawMessage);\n *\n * switch (event.type) {\n * case \"chat-request\": { ... }\n * case \"clear\": { ... }\n * case \"tool-result\": { ... }\n * }\n * ```\n */\nexport function parseProtocolMessage(raw: string): ChatProtocolEvent | null {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n\n const wireType = data.type as string | undefined;\n if (!wireType) return null;\n\n switch (wireType) {\n case CHAT_MESSAGE_TYPES.USE_CHAT_REQUEST:\n return {\n type: \"chat-request\",\n id: data.id as string,\n init: (data.init as { method?: string; body?: string }) ?? {}\n };\n\n case CHAT_MESSAGE_TYPES.CHAT_CLEAR:\n return { type: \"clear\" };\n\n case CHAT_MESSAGE_TYPES.CHAT_REQUEST_CANCEL:\n return { type: \"cancel\", id: data.id as string };\n\n case CHAT_MESSAGE_TYPES.TOOL_RESULT:\n return {\n type: \"tool-result\",\n toolCallId: data.toolCallId as string,\n toolName: (data.toolName as string) ?? \"\",\n output: data.output,\n state: data.state as string | undefined,\n errorText: data.errorText as string | undefined,\n autoContinue: data.autoContinue as boolean | undefined,\n clientTools: data.clientTools as\n | Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>\n | undefined\n };\n\n case CHAT_MESSAGE_TYPES.TOOL_APPROVAL:\n return {\n type: \"tool-approval\",\n toolCallId: data.toolCallId as string,\n approved: data.approved as boolean,\n autoContinue: data.autoContinue as boolean | undefined\n };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_REQUEST:\n return { type: \"stream-resume-request\" };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_ACK:\n return { type: \"stream-resume-ack\", id: data.id as string };\n\n case CHAT_MESSAGE_TYPES.CHAT_MESSAGES:\n return {\n type: \"messages\",\n messages: (data.messages as unknown[]) ?? []\n };\n\n default:\n return null;\n }\n}\n","/**\n * Message reconciliation — pure functions for aligning client messages\n * with server state during persistence.\n *\n * Three strategies applied in order:\n * 1. Merge server-known tool outputs into stale client messages\n * 2. Reconcile assistant IDs (exact match → content-key → toolCallId)\n * 3. Per-message toolCallId dedup for persistence\n */\n\nimport type { UIMessage } from \"ai\";\n\n/**\n * Reconcile incoming client messages against server state.\n *\n * 1. Merges server-known tool outputs into incoming messages that still\n * show stale states (input-available, approval-requested, approval-responded)\n * 2. Reconciles assistant IDs: exact match → content-key match → toolCallId match\n *\n * @param incoming - Messages from the client\n * @param serverMessages - Current server-side messages (source of truth)\n * @param sanitizeForContentKey - Function to sanitize a message before computing\n * its content key (typically strips ephemeral provider metadata)\n * @returns Reconciled messages ready for persistence\n */\nexport function reconcileMessages(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[],\n sanitizeForContentKey?: (message: UIMessage) => UIMessage\n): UIMessage[] {\n const withMergedToolOutputs = mergeServerToolOutputs(\n incoming,\n serverMessages\n );\n return reconcileAssistantIds(\n withMergedToolOutputs,\n serverMessages,\n sanitizeForContentKey\n );\n}\n\n/**\n * For a single message, resolve its ID by matching toolCallId against server state.\n * Prevents duplicate DB rows when client IDs differ from server IDs.\n * Tool call IDs are unique per conversation, so matching is safe regardless of state.\n */\nexport function resolveToolMergeId(\n message: UIMessage,\n serverMessages: readonly UIMessage[]\n): UIMessage {\n if (message.role !== \"assistant\") {\n return message;\n }\n\n for (const part of message.parts) {\n if (\"toolCallId\" in part && part.toolCallId) {\n const toolCallId = part.toolCallId as string;\n const existing = findMessageByToolCallId(serverMessages, toolCallId);\n if (existing && existing.id !== message.id) {\n return { ...message, id: existing.id };\n }\n }\n }\n\n return message;\n}\n\n/**\n * Content key for assistant messages used for dedup of identical short replies.\n * Returns JSON of sanitized parts, or undefined for non-assistant messages.\n */\nexport function assistantContentKey(\n message: UIMessage,\n sanitize?: (message: UIMessage) => UIMessage\n): string | undefined {\n if (message.role !== \"assistant\") {\n return undefined;\n }\n const sanitized = sanitize ? sanitize(message) : message;\n return JSON.stringify(sanitized.parts);\n}\n\nfunction mergeServerToolOutputs(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[]\n): UIMessage[] {\n // Index the server's RESOLVED tool parts so a stale client part (still in a\n // pre-output state) can't clobber the server's terminal state on persist.\n // All three terminal states must be protected, not just `output-available`:\n // otherwise a client that hasn't seen the server's `output-error` /\n // `output-denied` yet would persist its stale `input-available` over the\n // resolved result, losing the error/denial.\n const serverResolvedParts = new Map<string, Record<string, unknown>>();\n for (const msg of serverMessages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n const record = part as Record<string, unknown>;\n if (\n \"toolCallId\" in record &&\n \"state\" in record &&\n (record.state === \"output-available\" ||\n record.state === \"output-error\" ||\n record.state === \"output-denied\")\n ) {\n serverResolvedParts.set(record.toolCallId as string, record);\n }\n }\n }\n\n if (serverResolvedParts.size === 0) return incoming;\n\n return incoming.map((msg) => {\n if (msg.role !== \"assistant\") return msg;\n\n let hasChanges = false;\n const updatedParts = msg.parts.map((part) => {\n const record = part as Record<string, unknown>;\n if (\n \"toolCallId\" in record &&\n \"state\" in record &&\n (record.state === \"input-available\" ||\n record.state === \"approval-requested\" ||\n record.state === \"approval-responded\") &&\n serverResolvedParts.has(record.toolCallId as string)\n ) {\n hasChanges = true;\n const server = serverResolvedParts.get(record.toolCallId as string)!;\n // Overlay the server's resolved state, keeping the client part's\n // identity/input. Carry ONLY the result field that belongs to the\n // server's terminal state — so a stray `output` left on an\n // `output-error` part can't ride along and be misread as a result.\n const merged: Record<string, unknown> = {\n ...part,\n state: server.state\n };\n if (server.state === \"output-available\") {\n if (\"output\" in server) merged.output = server.output;\n } else if (server.state === \"output-error\") {\n if (\"errorText\" in server) merged.errorText = server.errorText;\n } else if (server.state === \"output-denied\") {\n if (\"approval\" in server) merged.approval = server.approval;\n }\n return merged;\n }\n return part;\n }) as UIMessage[\"parts\"];\n\n return hasChanges ? { ...msg, parts: updatedParts } : msg;\n });\n}\n\nfunction reconcileAssistantIds(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[],\n sanitize?: (message: UIMessage) => UIMessage\n): UIMessage[] {\n if (serverMessages.length === 0) return incoming;\n\n const claimedServerIndices = new Set<number>();\n const exactMatchMap = new Map<number, number>();\n\n for (let i = 0; i < incoming.length; i++) {\n const serverIdx = serverMessages.findIndex(\n (sm, si) => !claimedServerIndices.has(si) && sm.id === incoming[i].id\n );\n if (serverIdx !== -1) {\n claimedServerIndices.add(serverIdx);\n exactMatchMap.set(i, serverIdx);\n }\n }\n\n return incoming.map((incomingMessage, incomingIdx) => {\n if (exactMatchMap.has(incomingIdx)) {\n return incomingMessage;\n }\n\n if (\n incomingMessage.role !== \"assistant\" ||\n hasToolCallPart(incomingMessage)\n ) {\n return incomingMessage;\n }\n\n const incomingKey = assistantContentKey(incomingMessage, sanitize);\n if (!incomingKey) {\n return incomingMessage;\n }\n\n for (let i = 0; i < serverMessages.length; i++) {\n if (claimedServerIndices.has(i)) continue;\n\n const serverMessage = serverMessages[i];\n if (\n serverMessage.role !== \"assistant\" ||\n hasToolCallPart(serverMessage)\n ) {\n continue;\n }\n\n if (assistantContentKey(serverMessage, sanitize) === incomingKey) {\n claimedServerIndices.add(i);\n return { ...incomingMessage, id: serverMessage.id };\n }\n }\n\n return incomingMessage;\n });\n}\n\nfunction hasToolCallPart(message: UIMessage): boolean {\n return message.parts.some((part) => \"toolCallId\" in part);\n}\n\nfunction findMessageByToolCallId(\n messages: readonly UIMessage[],\n toolCallId: string\n): UIMessage | undefined {\n for (const msg of messages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n if (\"toolCallId\" in part && part.toolCallId === toolCallId) {\n return msg;\n }\n }\n }\n return undefined;\n}\n","import type { UIMessage } from \"ai\";\nimport type { ClientToolSchema } from \"./client-tools\";\n\nexport type ChatFiberSnapshot<Kind extends string = string> = {\n kind: Kind;\n version: 1;\n requestId: string;\n recoveryRootRequestId?: string;\n continuation: boolean;\n latestMessageId?: string;\n latestMessageRole?: string;\n latestUserMessageId?: string;\n startedAt: number;\n lastBody?: Record<string, unknown>;\n lastClientTools?: ClientToolSchema[];\n};\n\nexport function createChatFiberSnapshot<Kind extends string>({\n kind,\n requestId,\n recoveryRootRequestId,\n continuation,\n messages,\n lastBody,\n lastClientTools\n}: {\n kind: Kind;\n requestId: string;\n recoveryRootRequestId?: string;\n continuation: boolean;\n messages: UIMessage[];\n lastBody?: Record<string, unknown>;\n lastClientTools?: ClientToolSchema[];\n}): ChatFiberSnapshot<Kind> {\n const latestMessage =\n messages.length > 0 ? messages[messages.length - 1] : undefined;\n let latestUser: UIMessage | undefined;\n\n for (let index = messages.length - 1; index >= 0; index--) {\n if (messages[index].role === \"user\") {\n latestUser = messages[index];\n break;\n }\n }\n\n return {\n kind,\n version: 1,\n requestId,\n recoveryRootRequestId,\n continuation,\n latestMessageId: latestMessage?.id,\n latestMessageRole: latestMessage?.role,\n latestUserMessageId: latestUser?.id,\n startedAt: Date.now(),\n lastBody,\n lastClientTools\n };\n}\n\nexport function wrapChatFiberSnapshot<Kind extends string>(\n key: string,\n snapshot: ChatFiberSnapshot<Kind>,\n user: unknown | null\n): Record<string, unknown> {\n return { [key]: snapshot, user };\n}\n\nexport function unwrapChatFiberSnapshot<Kind extends string>(\n key: string,\n value: unknown,\n expectedKind?: Kind\n): {\n snapshot: ChatFiberSnapshot<Kind> | null;\n user: unknown | null;\n} {\n if (typeof value !== \"object\" || value === null || !(key in value)) {\n return { snapshot: null, user: value };\n }\n\n const envelope = value as Record<string, unknown>;\n const snapshot = envelope[key];\n if (typeof snapshot !== \"object\" || snapshot === null) {\n return { snapshot: null, user: value };\n }\n const candidate = snapshot as Record<string, unknown>;\n if (\n candidate.version !== 1 ||\n (expectedKind !== undefined && candidate.kind !== expectedKind) ||\n typeof candidate.requestId !== \"string\" ||\n typeof candidate.continuation !== \"boolean\"\n ) {\n return { snapshot: null, user: value };\n }\n\n return {\n snapshot: snapshot as ChatFiberSnapshot<Kind>,\n user: envelope.user ?? null\n };\n}\n"],"mappings":"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"}
|
package/dist/chat-sdk/index.d.ts
CHANGED