agents 0.9.0 → 0.10.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.
Files changed (50) hide show
  1. package/dist/chat/index.d.ts +151 -2
  2. package/dist/chat/index.js +210 -11
  3. package/dist/chat/index.js.map +1 -1
  4. package/dist/{client-BwgM3cRz.js → client-QBjFV5de.js} +161 -49
  5. package/dist/client-QBjFV5de.js.map +1 -0
  6. package/dist/client.d.ts +2 -2
  7. package/dist/{compaction-helpers-BFTBIzpK.js → compaction-helpers-BPE1_ziA.js} +1 -1
  8. package/dist/{compaction-helpers-BFTBIzpK.js.map → compaction-helpers-BPE1_ziA.js.map} +1 -1
  9. package/dist/{compaction-helpers-DkJreaDR.d.ts → compaction-helpers-CHNQeyRm.d.ts} +1 -1
  10. package/dist/{do-oauth-client-provider-C2jurFjW.d.ts → do-oauth-client-provider-31gqR33H.d.ts} +1 -1
  11. package/dist/{email-DwPlM0bQ.d.ts → email-Cql45SKP.d.ts} +1 -1
  12. package/dist/email.d.ts +2 -2
  13. package/dist/experimental/memory/session/index.d.ts +153 -82
  14. package/dist/experimental/memory/session/index.js +257 -24
  15. package/dist/experimental/memory/session/index.js.map +1 -1
  16. package/dist/experimental/memory/utils/index.d.ts +1 -1
  17. package/dist/experimental/memory/utils/index.js +1 -1
  18. package/dist/{index-BtHngIIG.d.ts → index-BPkkIqMn.d.ts} +204 -74
  19. package/dist/{index-Ua2Nfvbm.d.ts → index-DDSX-g7W.d.ts} +11 -1
  20. package/dist/index.d.ts +30 -26
  21. package/dist/index.js +2 -3049
  22. package/dist/{internal_context-DT8RxmAN.d.ts → internal_context-DuQZFvWI.d.ts} +1 -1
  23. package/dist/internal_context.d.ts +1 -1
  24. package/dist/mcp/client.d.ts +2 -2
  25. package/dist/mcp/client.js +1 -1
  26. package/dist/mcp/do-oauth-client-provider.d.ts +1 -1
  27. package/dist/mcp/index.d.ts +1 -1
  28. package/dist/mcp/index.js +2 -2
  29. package/dist/observability/index.d.ts +1 -1
  30. package/dist/react.d.ts +3 -1
  31. package/dist/react.js +3 -0
  32. package/dist/react.js.map +1 -1
  33. package/dist/{retries-DXMQGhG3.d.ts → retries-B_CN5KM9.d.ts} +1 -1
  34. package/dist/retries.d.ts +1 -1
  35. package/dist/{serializable-8Jt1B04R.d.ts → serializable-DGdO8CDh.d.ts} +1 -1
  36. package/dist/serializable.d.ts +1 -1
  37. package/dist/src-B8NZxxsO.js +3217 -0
  38. package/dist/src-B8NZxxsO.js.map +1 -0
  39. package/dist/{types-C-m0II8i.d.ts → types-B9A8AU7B.d.ts} +1 -1
  40. package/dist/types.d.ts +1 -1
  41. package/dist/{workflow-types-CZNXKj_D.d.ts → workflow-types-XmOkuI7A.d.ts} +1 -1
  42. package/dist/workflow-types.d.ts +1 -1
  43. package/dist/workflows.d.ts +2 -2
  44. package/dist/workflows.js +1 -1
  45. package/package.json +12 -16
  46. package/dist/client-BwgM3cRz.js.map +0 -1
  47. package/dist/experimental/forever.d.ts +0 -64
  48. package/dist/experimental/forever.js +0 -338
  49. package/dist/experimental/forever.js.map +0 -1
  50. package/dist/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["textEncoder"],"sources":["../../src/chat/message-builder.ts","../../src/chat/sanitize.ts","../../src/chat/stream-accumulator.ts","../../src/chat/turn-queue.ts","../../src/chat/broadcast-state.ts","../../src/chat/protocol.ts","../../src/chat/resumable-stream.ts","../../src/chat/client-tools.ts","../../src/chat/continuation-state.ts"],"sourcesContent":["/**\n * Shared message builder for reconstructing UIMessage parts from stream chunks.\n *\n * Used by both @cloudflare/ai-chat (server + client) and @cloudflare/think\n * to avoid duplicating the chunk-type switch/case logic.\n *\n * Operates on a mutable parts array for performance (avoids allocating new arrays\n * on every chunk during streaming).\n */\n\nimport type { UIMessage } from \"ai\";\n\n/** The parts array type from UIMessage */\nexport type MessageParts = UIMessage[\"parts\"];\n\n/** A single part from the UIMessage parts array */\nexport type MessagePart = MessageParts[number];\n\n/**\n * Parsed chunk data from an AI SDK stream event.\n * This is the JSON-parsed body of a CF_AGENT_USE_CHAT_RESPONSE message,\n * or the `data:` payload of an SSE line.\n */\nexport type StreamChunkData = {\n type: string;\n id?: string;\n delta?: string;\n text?: string;\n mediaType?: string;\n url?: string;\n sourceId?: string;\n title?: string;\n filename?: string;\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n inputTextDelta?: string;\n output?: unknown;\n state?: string;\n errorText?: string;\n /** When true, the output is preliminary (may be updated by a later chunk) */\n preliminary?: boolean;\n /** Approval ID for tools with needsApproval */\n approvalId?: string;\n providerMetadata?: Record<string, unknown>;\n /** Whether the tool was executed by the provider (e.g. Gemini code execution) */\n providerExecuted?: boolean;\n /** Payload for data-* parts (developer-defined typed JSON) */\n data?: unknown;\n /** When true, data parts are ephemeral and not persisted to message.parts */\n transient?: boolean;\n /** Message ID assigned by the server at stream start */\n messageId?: string;\n /** Per-message metadata attached by start/finish/message-metadata chunks */\n messageMetadata?: unknown;\n [key: string]: unknown;\n};\n\n/**\n * Applies a stream chunk to a mutable parts array, building up the message\n * incrementally. Returns true if the chunk was handled, false if it was\n * an unrecognized type (caller may handle it with additional logic).\n *\n * Handles all common chunk types that both server and client need:\n * - text-start / text-delta / text-end\n * - reasoning-start / reasoning-delta / reasoning-end\n * - file\n * - source-url / source-document\n * - tool-input-start / tool-input-delta / tool-input-available / tool-input-error\n * - tool-output-available / tool-output-error\n * - step-start (aliased from start-step)\n * - data-* (developer-defined typed JSON blobs)\n *\n * @param parts - The mutable parts array to update\n * @param chunk - The parsed stream chunk data\n * @returns true if handled, false if the chunk type is not recognized\n */\nexport function applyChunkToParts(\n parts: MessagePart[],\n chunk: StreamChunkData\n): boolean {\n switch (chunk.type) {\n case \"text-start\": {\n parts.push({\n type: \"text\",\n text: \"\",\n state: \"streaming\"\n } as MessagePart);\n return true;\n }\n\n case \"text-delta\": {\n const lastTextPart = findLastPartByType(parts, \"text\");\n if (lastTextPart && lastTextPart.type === \"text\") {\n (lastTextPart as { text: string }).text += chunk.delta ?? \"\";\n } else {\n // No text-start received — create a new text part (stream resumption fallback)\n parts.push({\n type: \"text\",\n text: chunk.delta ?? \"\",\n state: \"streaming\"\n } as MessagePart);\n }\n return true;\n }\n\n case \"text-end\": {\n const lastTextPart = findLastPartByType(parts, \"text\");\n if (lastTextPart && \"state\" in lastTextPart) {\n (lastTextPart as { state: string }).state = \"done\";\n }\n return true;\n }\n\n case \"reasoning-start\": {\n parts.push({\n type: \"reasoning\",\n text: \"\",\n state: \"streaming\"\n } as MessagePart);\n return true;\n }\n\n case \"reasoning-delta\": {\n const lastReasoningPart = findLastPartByType(parts, \"reasoning\");\n if (lastReasoningPart && lastReasoningPart.type === \"reasoning\") {\n (lastReasoningPart as { text: string }).text += chunk.delta ?? \"\";\n } else {\n // No reasoning-start received — create a new reasoning part (stream resumption fallback)\n parts.push({\n type: \"reasoning\",\n text: chunk.delta ?? \"\",\n state: \"streaming\"\n } as MessagePart);\n }\n return true;\n }\n\n case \"reasoning-end\": {\n const lastReasoningPart = findLastPartByType(parts, \"reasoning\");\n if (lastReasoningPart && \"state\" in lastReasoningPart) {\n (lastReasoningPart as { state: string }).state = \"done\";\n }\n return true;\n }\n\n case \"file\": {\n parts.push({\n type: \"file\",\n mediaType: chunk.mediaType,\n url: chunk.url\n } as MessagePart);\n return true;\n }\n\n case \"source-url\": {\n parts.push({\n type: \"source-url\",\n sourceId: chunk.sourceId,\n url: chunk.url,\n title: chunk.title,\n providerMetadata: chunk.providerMetadata\n } as MessagePart);\n return true;\n }\n\n case \"source-document\": {\n parts.push({\n type: \"source-document\",\n sourceId: chunk.sourceId,\n mediaType: chunk.mediaType,\n title: chunk.title,\n filename: chunk.filename,\n providerMetadata: chunk.providerMetadata\n } as MessagePart);\n return true;\n }\n\n case \"tool-input-start\": {\n parts.push({\n type: `tool-${chunk.toolName}`,\n toolCallId: chunk.toolCallId,\n toolName: chunk.toolName,\n state: \"input-streaming\",\n input: undefined,\n ...(chunk.providerExecuted != null\n ? { providerExecuted: chunk.providerExecuted }\n : {}),\n ...(chunk.providerMetadata != null\n ? { callProviderMetadata: chunk.providerMetadata }\n : {}),\n ...(chunk.title != null ? { title: chunk.title } : {})\n } as MessagePart);\n return true;\n }\n\n case \"tool-input-delta\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n (toolPart as Record<string, unknown>).input = chunk.input;\n }\n return true;\n }\n\n case \"tool-input-available\": {\n const existing = findToolPartByCallId(parts, chunk.toolCallId);\n if (existing) {\n const p = existing as Record<string, unknown>;\n p.state = \"input-available\";\n p.input = chunk.input;\n if (chunk.providerExecuted != null) {\n p.providerExecuted = chunk.providerExecuted;\n }\n if (chunk.providerMetadata != null) {\n p.callProviderMetadata = chunk.providerMetadata;\n }\n if (chunk.title != null) {\n p.title = chunk.title;\n }\n } else {\n parts.push({\n type: `tool-${chunk.toolName}`,\n toolCallId: chunk.toolCallId,\n toolName: chunk.toolName,\n state: \"input-available\",\n input: chunk.input,\n ...(chunk.providerExecuted != null\n ? { providerExecuted: chunk.providerExecuted }\n : {}),\n ...(chunk.providerMetadata != null\n ? { callProviderMetadata: chunk.providerMetadata }\n : {}),\n ...(chunk.title != null ? { title: chunk.title } : {})\n } as MessagePart);\n }\n return true;\n }\n\n case \"tool-input-error\": {\n const existing = findToolPartByCallId(parts, chunk.toolCallId);\n if (existing) {\n const p = existing as Record<string, unknown>;\n p.state = \"output-error\";\n p.errorText = chunk.errorText;\n p.input = chunk.input;\n if (chunk.providerExecuted != null) {\n p.providerExecuted = chunk.providerExecuted;\n }\n if (chunk.providerMetadata != null) {\n p.callProviderMetadata = chunk.providerMetadata;\n }\n } else {\n parts.push({\n type: `tool-${chunk.toolName}`,\n toolCallId: chunk.toolCallId,\n toolName: chunk.toolName,\n state: \"output-error\",\n input: chunk.input,\n errorText: chunk.errorText,\n ...(chunk.providerExecuted != null\n ? { providerExecuted: chunk.providerExecuted }\n : {}),\n ...(chunk.providerMetadata != null\n ? { callProviderMetadata: chunk.providerMetadata }\n : {})\n } as MessagePart);\n }\n return true;\n }\n\n case \"tool-approval-request\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n const p = toolPart as Record<string, unknown>;\n p.state = \"approval-requested\";\n p.approval = { id: chunk.approvalId };\n }\n return true;\n }\n\n case \"tool-output-denied\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n const p = toolPart as Record<string, unknown>;\n p.state = \"output-denied\";\n }\n return true;\n }\n\n case \"tool-output-available\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n const p = toolPart as Record<string, unknown>;\n p.state = \"output-available\";\n p.output = chunk.output;\n if (chunk.preliminary !== undefined) {\n p.preliminary = chunk.preliminary;\n }\n }\n return true;\n }\n\n case \"tool-output-error\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n const p = toolPart as Record<string, unknown>;\n p.state = \"output-error\";\n p.errorText = chunk.errorText;\n }\n return true;\n }\n\n // Both \"step-start\" (client convention) and \"start-step\" (server convention)\n case \"step-start\":\n case \"start-step\": {\n parts.push({ type: \"step-start\" } as MessagePart);\n return true;\n }\n\n default: {\n // https://ai-sdk.dev/docs/ai-sdk-ui/streaming-data\n if (chunk.type.startsWith(\"data-\")) {\n // Transient parts are ephemeral — the AI SDK client fires an onData\n // callback instead of adding them to message.parts. On the server we\n // still broadcast them (so connected clients see them in real time)\n // but skip persisting them into the stored message parts.\n if (chunk.transient) {\n return true;\n }\n\n // Reconciliation: if a part with the same type AND id already exists,\n // update its data in-place instead of appending a duplicate.\n if (chunk.id != null) {\n const existing = findDataPartByTypeAndId(parts, chunk.type, chunk.id);\n if (existing) {\n (existing as Record<string, unknown>).data = chunk.data;\n return true;\n }\n }\n\n // Append new data parts to the array directly.\n // Note: `chunk.data` should always be provided — if omitted, the\n // persisted part will have `data: undefined` which JSON.stringify\n // drops, so the part will have no `data` field on reload.\n // The cast is needed because UIMessage[\"parts\"] doesn't include\n // data-* types in its union because they're an open extension point.\n parts.push({\n type: chunk.type,\n ...(chunk.id != null && { id: chunk.id }),\n data: chunk.data\n } as MessagePart);\n return true;\n }\n\n return false;\n }\n }\n}\n\n/**\n * Finds the last part in the array matching the given type.\n * Searches from the end for efficiency (the part we want is usually recent).\n */\nfunction findLastPartByType(\n parts: MessagePart[],\n type: string\n): MessagePart | undefined {\n for (let i = parts.length - 1; i >= 0; i--) {\n if (parts[i].type === type) {\n return parts[i];\n }\n }\n return undefined;\n}\n\n/**\n * Finds a tool part by its toolCallId.\n * Searches from the end since the tool part is usually recent.\n */\nfunction findToolPartByCallId(\n parts: MessagePart[],\n toolCallId: string | undefined\n): MessagePart | undefined {\n if (!toolCallId) return undefined;\n for (let i = parts.length - 1; i >= 0; i--) {\n const p = parts[i];\n if (\"toolCallId\" in p && p.toolCallId === toolCallId) {\n return p;\n }\n }\n return undefined;\n}\n\n/**\n * Finds a data part by its type and id for reconciliation.\n * Data parts use type+id as a composite key so when the same combination\n * is seen again, the existing part's data is updated in-place.\n */\nfunction findDataPartByTypeAndId(\n parts: MessagePart[],\n type: string,\n id: string\n): MessagePart | undefined {\n for (let i = parts.length - 1; i >= 0; i--) {\n const p = parts[i];\n if (p.type === type && \"id\" in p && (p as { id: string }).id === id) {\n return p;\n }\n }\n return undefined;\n}\n","/**\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\";\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 (replace with summary)\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 outputJson = JSON.stringify((part as { output: unknown }).output);\n if (outputJson.length > 1000) {\n return {\n ...part,\n output:\n \"This tool output was too large to persist in storage \" +\n `(${outputJson.length} bytes). ` +\n \"If the user asks about this data, suggest re-running the tool. \" +\n `Preview: ${outputJson.slice(0, 500)}...`\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","/**\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} 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 buffer before flushing to SQLite */\nconst CHUNK_BUFFER_SIZE = 10;\n/** Maximum buffer size to prevent memory issues on rapid reconnections */\nconst CHUNK_BUFFER_MAX_SIZE = 100;\n/** Maximum age for a \"streaming\" stream before considering it stale (ms) - 5 minutes */\nconst STREAM_STALE_THRESHOLD_MS = 5 * 60 * 1000;\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 * 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\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 private _streamChunkIndex = 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<{\n id: string;\n streamId: string;\n body: string;\n index: number;\n }> = [];\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 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 // ── 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): 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._streamChunkIndex = 0;\n this._isLive = true;\n\n this.sql`\n insert into cf_ai_chat_stream_metadata (id, request_id, status, created_at)\n values (${streamId}, ${requestId}, 'streaming', ${Date.now()})\n `;\n\n return streamId;\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._streamChunkIndex = 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._streamChunkIndex = 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 this._chunkBuffer.push({\n id: nanoid(),\n streamId,\n body,\n index: this._streamChunkIndex\n });\n this._streamChunkIndex++;\n\n // Flush when buffer reaches threshold\n if (this._chunkBuffer.length >= CHUNK_BUFFER_SIZE) {\n this.flushBuffer();\n }\n }\n\n /**\n * Flush buffered chunks to SQLite in a single batch.\n * Uses a lock to prevent concurrent flush operations.\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\n const now = Date.now();\n for (const chunk of chunks) {\n this.sql`\n insert into cf_ai_chat_stream_chunks (id, stream_id, body, chunk_index, created_at)\n values (${chunk.id}, ${chunk.streamId}, ${chunk.body}, ${chunk.index}, ${now})\n `;\n }\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 * @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 connection.send(\n JSON.stringify({\n body: chunk.body,\n done: false,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true\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 connection.send(\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: send done and mark completed in SQLite.\n connection.send(\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 connection.send(\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 // ── Restore / cleanup ──────────────────────────────────────────────\n\n /**\n * Restore active stream state if the agent was restarted during streaming.\n * Validates stream freshness to avoid sending stale resume notifications.\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 const streamAge = Date.now() - stream.created_at;\n\n // Check if stream is stale; delete to free storage\n if (streamAge > STREAM_STALE_THRESHOLD_MS) {\n this\n .sql`delete from cf_ai_chat_stream_chunks where stream_id = ${stream.id}`;\n this\n .sql`delete from cf_ai_chat_stream_metadata where id = ${stream.id}`;\n console.warn(\n `[ResumableStream] Deleted stale stream ${stream.id} (age: ${Math.round(streamAge / 1000)}s)`\n );\n return;\n }\n\n this._activeStreamId = stream.id;\n this._activeRequestId = stream.request_id;\n\n // Get the last chunk index\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._streamChunkIndex =\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.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._streamChunkIndex = 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\n // ── Test helpers (matching old AIChatAgent test API) ────────────────\n\n /** @internal For testing only */\n getStreamChunks(\n streamId: string\n ): Array<{ body: string; chunk_index: number }> {\n return (\n this.sql<{ body: string; chunk_index: number }>`\n select body, chunk_index from cf_ai_chat_stream_chunks \n where stream_id = ${streamId} \n order by chunk_index asc\n ` || []\n );\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 * 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\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 connection: ContinuationConnection;\n connectionId: string;\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 connection: ContinuationConnection;\n connectionId: string;\n clientTools?: ClientToolSchema[];\n body?: Record<string, unknown>;\n errorPrefix: string;\n prerequisite: Promise<boolean> | null;\n}\n\nexport class ContinuationState {\n pending: ContinuationPending | null = null;\n deferred: ContinuationDeferred | null = null;\n activeRequestId: string | null = null;\n activeConnectionId: string | null = null;\n awaitingConnections: Map<string, ContinuationConnection> = 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 * 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 connection.send(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(\n notify: (conn: ContinuationConnection) => void\n ): 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 | 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 this.awaitingConnections.set(d.connectionId, d.connection);\n return this.pending;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA6EA,SAAgB,kBACd,OACA,OACS;AACT,SAAQ,MAAM,MAAd;EACE,KAAK;AACH,SAAM,KAAK;IACT,MAAM;IACN,MAAM;IACN,OAAO;IACR,CAAgB;AACjB,UAAO;EAGT,KAAK,cAAc;GACjB,MAAM,eAAe,mBAAmB,OAAO,OAAO;AACtD,OAAI,gBAAgB,aAAa,SAAS,OACvC,cAAkC,QAAQ,MAAM,SAAS;OAG1D,OAAM,KAAK;IACT,MAAM;IACN,MAAM,MAAM,SAAS;IACrB,OAAO;IACR,CAAgB;AAEnB,UAAO;;EAGT,KAAK,YAAY;GACf,MAAM,eAAe,mBAAmB,OAAO,OAAO;AACtD,OAAI,gBAAgB,WAAW,aAC5B,cAAmC,QAAQ;AAE9C,UAAO;;EAGT,KAAK;AACH,SAAM,KAAK;IACT,MAAM;IACN,MAAM;IACN,OAAO;IACR,CAAgB;AACjB,UAAO;EAGT,KAAK,mBAAmB;GACtB,MAAM,oBAAoB,mBAAmB,OAAO,YAAY;AAChE,OAAI,qBAAqB,kBAAkB,SAAS,YACjD,mBAAuC,QAAQ,MAAM,SAAS;OAG/D,OAAM,KAAK;IACT,MAAM;IACN,MAAM,MAAM,SAAS;IACrB,OAAO;IACR,CAAgB;AAEnB,UAAO;;EAGT,KAAK,iBAAiB;GACpB,MAAM,oBAAoB,mBAAmB,OAAO,YAAY;AAChE,OAAI,qBAAqB,WAAW,kBACjC,mBAAwC,QAAQ;AAEnD,UAAO;;EAGT,KAAK;AACH,SAAM,KAAK;IACT,MAAM;IACN,WAAW,MAAM;IACjB,KAAK,MAAM;IACZ,CAAgB;AACjB,UAAO;EAGT,KAAK;AACH,SAAM,KAAK;IACT,MAAM;IACN,UAAU,MAAM;IAChB,KAAK,MAAM;IACX,OAAO,MAAM;IACb,kBAAkB,MAAM;IACzB,CAAgB;AACjB,UAAO;EAGT,KAAK;AACH,SAAM,KAAK;IACT,MAAM;IACN,UAAU,MAAM;IAChB,WAAW,MAAM;IACjB,OAAO,MAAM;IACb,UAAU,MAAM;IAChB,kBAAkB,MAAM;IACzB,CAAgB;AACjB,UAAO;EAGT,KAAK;AACH,SAAM,KAAK;IACT,MAAM,QAAQ,MAAM;IACpB,YAAY,MAAM;IAClB,UAAU,MAAM;IAChB,OAAO;IACP,OAAO,KAAA;IACP,GAAI,MAAM,oBAAoB,OAC1B,EAAE,kBAAkB,MAAM,kBAAkB,GAC5C,EAAE;IACN,GAAI,MAAM,oBAAoB,OAC1B,EAAE,sBAAsB,MAAM,kBAAkB,GAChD,EAAE;IACN,GAAI,MAAM,SAAS,OAAO,EAAE,OAAO,MAAM,OAAO,GAAG,EAAE;IACtD,CAAgB;AACjB,UAAO;EAGT,KAAK,oBAAoB;GACvB,MAAM,WAAW,qBAAqB,OAAO,MAAM,WAAW;AAC9D,OAAI,SACD,UAAqC,QAAQ,MAAM;AAEtD,UAAO;;EAGT,KAAK,wBAAwB;GAC3B,MAAM,WAAW,qBAAqB,OAAO,MAAM,WAAW;AAC9D,OAAI,UAAU;IACZ,MAAM,IAAI;AACV,MAAE,QAAQ;AACV,MAAE,QAAQ,MAAM;AAChB,QAAI,MAAM,oBAAoB,KAC5B,GAAE,mBAAmB,MAAM;AAE7B,QAAI,MAAM,oBAAoB,KAC5B,GAAE,uBAAuB,MAAM;AAEjC,QAAI,MAAM,SAAS,KACjB,GAAE,QAAQ,MAAM;SAGlB,OAAM,KAAK;IACT,MAAM,QAAQ,MAAM;IACpB,YAAY,MAAM;IAClB,UAAU,MAAM;IAChB,OAAO;IACP,OAAO,MAAM;IACb,GAAI,MAAM,oBAAoB,OAC1B,EAAE,kBAAkB,MAAM,kBAAkB,GAC5C,EAAE;IACN,GAAI,MAAM,oBAAoB,OAC1B,EAAE,sBAAsB,MAAM,kBAAkB,GAChD,EAAE;IACN,GAAI,MAAM,SAAS,OAAO,EAAE,OAAO,MAAM,OAAO,GAAG,EAAE;IACtD,CAAgB;AAEnB,UAAO;;EAGT,KAAK,oBAAoB;GACvB,MAAM,WAAW,qBAAqB,OAAO,MAAM,WAAW;AAC9D,OAAI,UAAU;IACZ,MAAM,IAAI;AACV,MAAE,QAAQ;AACV,MAAE,YAAY,MAAM;AACpB,MAAE,QAAQ,MAAM;AAChB,QAAI,MAAM,oBAAoB,KAC5B,GAAE,mBAAmB,MAAM;AAE7B,QAAI,MAAM,oBAAoB,KAC5B,GAAE,uBAAuB,MAAM;SAGjC,OAAM,KAAK;IACT,MAAM,QAAQ,MAAM;IACpB,YAAY,MAAM;IAClB,UAAU,MAAM;IAChB,OAAO;IACP,OAAO,MAAM;IACb,WAAW,MAAM;IACjB,GAAI,MAAM,oBAAoB,OAC1B,EAAE,kBAAkB,MAAM,kBAAkB,GAC5C,EAAE;IACN,GAAI,MAAM,oBAAoB,OAC1B,EAAE,sBAAsB,MAAM,kBAAkB,GAChD,EAAE;IACP,CAAgB;AAEnB,UAAO;;EAGT,KAAK,yBAAyB;GAC5B,MAAM,WAAW,qBAAqB,OAAO,MAAM,WAAW;AAC9D,OAAI,UAAU;IACZ,MAAM,IAAI;AACV,MAAE,QAAQ;AACV,MAAE,WAAW,EAAE,IAAI,MAAM,YAAY;;AAEvC,UAAO;;EAGT,KAAK,sBAAsB;GACzB,MAAM,WAAW,qBAAqB,OAAO,MAAM,WAAW;AAC9D,OAAI,UAAU;IACZ,MAAM,IAAI;AACV,MAAE,QAAQ;;AAEZ,UAAO;;EAGT,KAAK,yBAAyB;GAC5B,MAAM,WAAW,qBAAqB,OAAO,MAAM,WAAW;AAC9D,OAAI,UAAU;IACZ,MAAM,IAAI;AACV,MAAE,QAAQ;AACV,MAAE,SAAS,MAAM;AACjB,QAAI,MAAM,gBAAgB,KAAA,EACxB,GAAE,cAAc,MAAM;;AAG1B,UAAO;;EAGT,KAAK,qBAAqB;GACxB,MAAM,WAAW,qBAAqB,OAAO,MAAM,WAAW;AAC9D,OAAI,UAAU;IACZ,MAAM,IAAI;AACV,MAAE,QAAQ;AACV,MAAE,YAAY,MAAM;;AAEtB,UAAO;;EAIT,KAAK;EACL,KAAK;AACH,SAAM,KAAK,EAAE,MAAM,cAAc,CAAgB;AACjD,UAAO;EAGT;AAEE,OAAI,MAAM,KAAK,WAAW,QAAQ,EAAE;AAKlC,QAAI,MAAM,UACR,QAAO;AAKT,QAAI,MAAM,MAAM,MAAM;KACpB,MAAM,WAAW,wBAAwB,OAAO,MAAM,MAAM,MAAM,GAAG;AACrE,SAAI,UAAU;AACX,eAAqC,OAAO,MAAM;AACnD,aAAO;;;AAUX,UAAM,KAAK;KACT,MAAM,MAAM;KACZ,GAAI,MAAM,MAAM,QAAQ,EAAE,IAAI,MAAM,IAAI;KACxC,MAAM,MAAM;KACb,CAAgB;AACjB,WAAO;;AAGT,UAAO;;;;;;;AASb,SAAS,mBACP,OACA,MACyB;AACzB,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,IACrC,KAAI,MAAM,GAAG,SAAS,KACpB,QAAO,MAAM;;;;;;AAUnB,SAAS,qBACP,OACA,YACyB;AACzB,KAAI,CAAC,WAAY,QAAO,KAAA;AACxB,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,IAAI,MAAM;AAChB,MAAI,gBAAgB,KAAK,EAAE,eAAe,WACxC,QAAO;;;;;;;;AAWb,SAAS,wBACP,OACA,MACA,IACyB;AACzB,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,IAAI,MAAM;AAChB,MAAI,EAAE,SAAS,QAAQ,QAAQ,KAAM,EAAqB,OAAO,GAC/D,QAAO;;;;;AC5Yb,MAAMA,gBAAc,IAAI,aAAa;;AAGrC,MAAa,gBAAgB;;AAG7B,SAAgB,WAAW,GAAmB;AAC5C,QAAOA,cAAY,OAAO,EAAE,CAAC;;;;;;;;;AAU/B,SAAgB,gBAAgB,SAA+B;CA4B7D,MAAM,iBA3BgB,QAAQ,MAAM,KAAK,SAAS;EAChD,IAAI,gBAAgB;AAEpB,MACE,sBAAsB,iBACtB,cAAc,oBACd,OAAO,cAAc,qBAAqB,YAC1C,YAAY,cAAc,iBAE1B,iBAAgB,oBAAoB,eAAe,mBAAmB;AAGxE,MACE,0BAA0B,iBAC1B,cAAc,wBACd,OAAO,cAAc,yBAAyB,YAC9C,YAAY,cAAc,qBAE1B,iBAAgB,oBACd,eACA,uBACD;AAGH,SAAO;GACP,CAEmC,QAAQ,SAAS;AACpD,MAAI,KAAK,SAAS,aAAa;GAC7B,MAAM,gBAAgB;AACtB,OAAI,CAAC,cAAc,QAAQ,cAAc,KAAK,MAAM,KAAK,IAAI;AAC3D,QACE,sBAAsB,iBACtB,cAAc,oBACd,OAAO,cAAc,qBAAqB,YAC1C,OAAO,KAAK,cAAc,iBAAiB,CAAC,SAAS,EAErD,QAAO;AAET,WAAO;;;AAGX,SAAO;GACP;AAEF,QAAO;EAAE,GAAG;EAAS,OAAO;EAAgB;;AAG9C,SAAS,oBACP,MACA,aACG;CACH,MAAM,WAAY,KAAiC;AAKnD,KAAI,CAAC,UAAU,OAAQ,QAAO;CAE9B,MAAM,EACJ,QAAQ,SACR,2BAA2B,MAC3B,GAAG,eACD,SAAS;CAEb,MAAM,uBAAuB,OAAO,KAAK,WAAW,CAAC,SAAS;CAC9D,MAAM,EAAE,QAAQ,SAAS,GAAG,iBAAiB;CAE7C,IAAI;AACJ,KAAI,qBACF,eAAc;EAAE,GAAG;EAAc,QAAQ;EAAY;UAC5C,OAAO,KAAK,aAAa,CAAC,SAAS,EAC5C,eAAc;CAGhB,MAAM,GAAG,cAAc,UAAU,GAAG,aAAa;AAKjD,KAAI,YACF,QAAO;EAAE,GAAG;GAAW,cAAc;EAAa;AAEpD,QAAO;;;;;;;;;;AAWT,SAAgB,oBAAoB,SAA+B;CACjE,IAAI,OAAO,KAAK,UAAU,QAAQ;CAClC,IAAI,OAAO,WAAW,KAAK;AAC3B,KAAI,QAAA,KAAuB,QAAO;AAElC,KAAI,QAAQ,SAAS,YACnB,QAAO,kBAAkB,QAAQ;CAGnC,MAAM,iBAAiB,QAAQ,MAAM,KAAK,SAAS;AACjD,MACE,YAAY,QACZ,gBAAgB,QAChB,WAAW,QACX,KAAK,UAAU,oBACf;GACA,MAAM,aAAa,KAAK,UAAW,KAA6B,OAAO;AACvE,OAAI,WAAW,SAAS,IACtB,QAAO;IACL,GAAG;IACH,QACE,yDACI,WAAW,OAAO,mFAEV,WAAW,MAAM,GAAG,IAAI,CAAC;IACxC;;AAGL,SAAO;GACP;CAEF,IAAI,SAAoB;EAAE,GAAG;EAAS,OAAO;EAAgB;AAE7D,QAAO,KAAK,UAAU,OAAO;AAC7B,QAAO,WAAW,KAAK;AACvB,KAAI,QAAA,KAAuB,QAAO;AAElC,QAAO,kBAAkB,OAAO;;AAGlC,SAAS,kBAAkB,SAA+B;CACxD,MAAM,QAAQ,CAAC,GAAG,QAAQ,MAAM;AAEhC,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;AACnB,MAAI,KAAK,SAAS,UAAU,UAAU,MAAM;GAC1C,MAAM,OAAQ,KAA0B;AACxC,OAAI,KAAK,SAAS,KAAM;AACtB,UAAM,KAAK;KACT,GAAG;KACH,MACE,gCAAgC,KAAK,OAAO,4BACxB,KAAK,MAAM,GAAG,IAAI,CAAC;KAC1C;IAED,MAAM,YAAY;KAAE,GAAG;KAAS;KAAO;AACvC,QAAI,WAAW,KAAK,UAAU,UAAU,CAAC,IAAA,KACvC;;;;AAMR,QAAO;EAAE,GAAG;EAAS;EAAO;;;;ACzK9B,SAAS,WAAW,OAAqD;AACvE,KAAI,SAAS,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,CACrE,QAAO;;AAwCX,IAAa,oBAAb,MAA+B;CAM7B,YAAY,SAAmC;AAC7C,OAAK,YAAY,QAAQ;AACzB,OAAK,kBAAkB,QAAQ,gBAAgB;AAC/C,OAAK,QAAQ,QAAQ,gBAAgB,CAAC,GAAG,QAAQ,cAAc,GAAG,EAAE;AACpE,OAAK,WAAW,QAAQ,mBACpB,EAAE,GAAG,QAAQ,kBAAkB,GAC/B,KAAA;;CAGN,WAAW,OAAqC;EAC9C,MAAM,UAAU,kBAAkB,KAAK,OAAO,MAAM;AAGpD,MAAI,MAAM,SAAS,2BAA2B,MAAM,WAClD,QAAO;GACL;GACA,QAAQ;IAAE,MAAM;IAAyB,YAAY,MAAM;IAAY;GACxE;AAMH,OACG,MAAM,SAAS,2BACd,MAAM,SAAS,wBACjB,MAAM;OAKF,CAHiB,KAAK,MAAM,MAC7B,MAAM,gBAAgB,KAAK,EAAE,eAAe,MAAM,WACpD,CAEC,QAAO;IACL;IACA,QAAQ;KACN,MAAM;KACN,YACE,MAAM,SAAS,0BACX,qBACA;KACN,YAAY,MAAM;KAClB,QAAQ,MAAM;KACd,WAAW,MAAM;KACjB,aAAa,MAAM;KACpB;IACF;;AAIL,MAAI,CAAC,QACH,SAAQ,MAAM,MAAd;GACE,KAAK,SAAS;AACZ,QAAI,MAAM,aAAa,QAAQ,CAAC,KAAK,gBACnC,MAAK,YAAY,MAAM;IAEzB,MAAM,YAAY,WAAW,MAAM,gBAAgB;AACnD,QAAI,UACF,MAAK,WAAW,KAAK,WACjB;KAAE,GAAG,KAAK;KAAU,GAAG;KAAW,GAClC,EAAE,GAAG,WAAW;AAEtB,WAAO;KACL,SAAS;KACT,QAAQ;MACN,MAAM;MACN,WAAW,MAAM;MACjB,UAAU;MACX;KACF;;GAEH,KAAK,UAAU;IACb,MAAM,aAAa,WAAW,MAAM,gBAAgB;AACpD,QAAI,WACF,MAAK,WAAW,KAAK,WACjB;KAAE,GAAG,KAAK;KAAU,GAAG;KAAY,GACnC,EAAE,GAAG,YAAY;AAMvB,WAAO;KACL,SAAS;KACT,QAAQ;MACN,MAAM;MACN,cAPF,kBAAkB,QACb,MAAM,eACP,KAAA;MAMF,UAAU;MACX;KACF;;GAEH,KAAK,oBAAoB;IACvB,MAAM,UAAU,WAAW,MAAM,gBAAgB;AACjD,QAAI,QACF,MAAK,WAAW,KAAK,WACjB;KAAE,GAAG,KAAK;KAAU,GAAG;KAAS,GAChC,EAAE,GAAG,SAAS;AAEpB,WAAO;KACL,SAAS;KACT,QAAQ;MACN,MAAM;MACN,UAAU,WAAW,EAAE;MACxB;KACF;;GAEH,KAAK,cACH,QAAO,EAAE,SAAS,MAAM;GAE1B,KAAK,QACH,QAAO;IACL,SAAS;IACT,QAAQ;KACN,MAAM;KACN,OAAO,MAAM,aAAa,KAAK,UAAU,MAAM;KAChD;IACF;;AAKP,SAAO,EAAE,SAAS;;;CAIpB,YAAuB;AACrB,SAAO;GACL,IAAI,KAAK;GACT,MAAM;GACN,OAAO,CAAC,GAAG,KAAK,MAAM;GACtB,GAAI,KAAK,YAAY,QAAQ,EAAE,UAAU,KAAK,UAAU;GACzD;;;;;;;CAQH,UAAU,UAAoC;EAC5C,IAAI,cAAc,SAAS,WAAW,MAAM,EAAE,OAAO,KAAK,UAAU;AAEpE,MAAI,cAAc,KAAK,KAAK;QACrB,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,IACxC,KAAI,SAAS,GAAG,SAAS,aAAa;AACpC,kBAAc;AACd;;;EAQN,MAAM,iBAA4B;GAChC,IAHA,eAAe,IAAI,SAAS,aAAa,KAAK,KAAK;GAInD,MAAM;GACN,OAAO,CAAC,GAAG,KAAK,MAAM;GACtB,GAAI,KAAK,YAAY,QAAQ,EAAE,UAAU,KAAK,UAAU;GACzD;AAED,MAAI,eAAe,GAAG;GACpB,MAAM,UAAU,CAAC,GAAG,SAAS;AAC7B,WAAQ,eAAe;AACvB,UAAO;;AAET,SAAO,CAAC,GAAG,UAAU,eAAe;;;;;AC3MxC,IAAa,YAAb,MAAuB;;AACrB,OAAQ,SAAwB,QAAQ,SAAS;AACjD,OAAQ,cAAc;AACtB,OAAQ,mBAAkC;AAC1C,OAAQ,sCAAsB,IAAI,KAAqB;;CAEvD,IAAI,aAAqB;AACvB,SAAO,KAAK;;CAGd,IAAI,kBAAiC;AACnC,SAAO,KAAK;;CAGd,IAAI,WAAoB;AACtB,SAAO,KAAK,qBAAqB;;CAGnC,MAAM,QACJ,WACA,IACA,SACwB;EACxB,MAAM,eAAe,KAAK;EAC1B,IAAI;EACJ,MAAM,qBAAqB,SAAS,cAAc,KAAK;AAEvD,OAAK,oBAAoB,IACvB,qBACC,KAAK,oBAAoB,IAAI,mBAAmB,IAAI,KAAK,EAC3D;AAED,OAAK,SAAS,IAAI,SAAe,YAAY;AAC3C,iBAAc;IACd;AAEF,QAAM;AAEN,MAAI,KAAK,gBAAgB,oBAAoB;AAC3C,QAAK,gBAAgB,mBAAmB;AACxC,gBAAa;AACb,UAAO,EAAE,QAAQ,SAAS;;AAG5B,OAAK,mBAAmB;AACxB,MAAI;AAEF,UAAO;IAAE,QAAQ;IAAa,OADhB,MAAM,IAAI;IACa;YAC7B;AACR,QAAK,mBAAmB;AACxB,QAAK,gBAAgB,mBAAmB;AACxC,gBAAa;;;;;;;CAQjB,QAAc;AACZ,OAAK;;;;;CAMP,MAAM,cAA6B;EACjC,IAAI;AACJ,KAAG;AACD,WAAQ,KAAK;AACb,SAAM;WACC,KAAK,WAAW;;;;;;CAO3B,YAAY,YAA6B;AACvC,SAAO,KAAK,oBAAoB,IAAI,cAAc,KAAK,YAAY,IAAI;;CAGzE,gBAAwB,YAA0B;EAChD,MAAM,SAAS,KAAK,oBAAoB,IAAI,WAAW,IAAI,KAAK;AAChE,MAAI,SAAS,EACX,MAAK,oBAAoB,OAAO,WAAW;MAE3C,MAAK,oBAAoB,IAAI,YAAY,MAAM;;;;;ACnDrD,SAAgB,WACd,OACA,OACkB;AAClB,SAAQ,MAAM,MAAd;EACE,KAAK,QACH,QAAO;GAAE,OAAO,EAAE,QAAQ,QAAQ;GAAE,aAAa;GAAO;EAE1D,KAAK,mBAAmB;GACtB,MAAM,cAAc,IAAI,kBAAkB,EACxC,WAAW,MAAM,WAClB,CAAC;AACF,UAAO;IACL,OAAO;KACL,QAAQ;KACR,UAAU,MAAM;KAChB;KACD;IACD,aAAa;IACd;;EAGH,KAAK,YAAY;GACf,IAAI;AAEJ,OAAI,MAAM,WAAW,UAAU,MAAM,aAAa,MAAM,UAAU;IAChE,IAAI,YAAY,MAAM;IACtB,IAAI;IACJ,IAAI;AAEJ,QAAI,MAAM,gBAAgB,MAAM;UACzB,IAAI,IAAI,MAAM,gBAAgB,SAAS,GAAG,KAAK,GAAG,IACrD,KAAI,MAAM,gBAAgB,GAAG,SAAS,aAAa;AACjD,kBAAY,MAAM,gBAAgB,GAAG;AACrC,sBAAgB,CAAC,GAAG,MAAM,gBAAgB,GAAG,MAAM;AACnD,UAAI,MAAM,gBAAgB,GAAG,YAAY,KACvC,oBAAmB,EACjB,GAAI,MAAM,gBAAgB,GAAG,UAI9B;AAEH;;;AAKN,kBAAc,IAAI,kBAAkB;KAClC;KACA,cAAc,MAAM;KACpB;KACA;KACD,CAAC;SAEF,eAAc,MAAM;AAGtB,OAAI,MAAM,UACR,aAAY,WAAW,MAAM,UAA6B;GAG5D,IAAI;AAEJ,OAAI,MAAM,MAAM;AACd,sBAAkB,SAAS,YAAY,UAAU,KAAK;AACtD,WAAO;KACL,OAAO,EAAE,QAAQ,QAAQ;KACzB;KACA,aAAa;KACd;;AAGH,OAAI,MAAM,aAAa,CAAC,MAAM,OAC5B,mBAAkB,SAAS,YAAY,UAAU,KAAK;YAC7C,MAAM,eACf,mBAAkB,SAAS,YAAY,UAAU,KAAK;AAGxD,UAAO;IACL,OAAO;KACL,QAAQ;KACR,UAAU,MAAM;KAChB;KACD;IACD;IACA,aAAa;IACd;;;;;;;;;;;;;AC9IP,MAAa,qBAAqB;CAChC,eAAe;CACf,kBAAkB;CAClB,mBAAmB;CACnB,YAAY;CACZ,qBAAqB;CACrB,iBAAiB;CACjB,mBAAmB;CACnB,uBAAuB;CACvB,oBAAoB;CACpB,aAAa;CACb,eAAe;CACf,iBAAiB;CAClB;;;;;;;;;;;;;;;ACHD,MAAM,oBAAoB;;AAE1B,MAAM,wBAAwB;;AAE9B,MAAM,4BAA4B,MAAS;;AAE3C,MAAM,sBAAsB,MAAU;;AAEtC,MAAM,2BAA2B,OAAU,KAAK;;AAEhD,MAAM,cAAc,IAAI,aAAa;AAoCrC,IAAa,kBAAb,MAAa,gBAAgB;CAsB3B,YAAY,KAAgC;AAAxB,OAAA,MAAA;AArBpB,OAAQ,kBAAiC;AACzC,OAAQ,mBAAkC;AAC1C,OAAQ,oBAAoB;AAQ5B,OAAQ,UAAU;AAElB,OAAQ,eAKH,EAAE;AACP,OAAQ,oBAAoB;AAC5B,OAAQ,mBAAmB;AAIzB,OAAK,GAAG;;;;;;;AAQR,OAAK,GAAG;;;;;;;AAQR,OAAK,GAAG;;AAIR,OAAK,SAAS;;CAKhB,IAAI,iBAAgC;AAClC,SAAO,KAAK;;CAGd,IAAI,kBAAiC;AACnC,SAAO,KAAK;;CAGd,kBAA2B;AACzB,SAAO,KAAK,oBAAoB;;;;;;CAOlC,IAAI,SAAkB;AACpB,SAAO,KAAK;;;;;;;;CAWd,MAAM,WAA2B;AAE/B,OAAK,aAAa;EAElB,MAAM,WAAW,QAAQ;AACzB,OAAK,kBAAkB;AACvB,OAAK,mBAAmB;AACxB,OAAK,oBAAoB;AACzB,OAAK,UAAU;AAEf,OAAK,GAAG;;gBAEI,SAAS,IAAI,UAAU,iBAAiB,KAAK,KAAK,CAAC;;AAG/D,SAAO;;;;;;CAOT,SAAS,UAAkB;AACzB,OAAK,aAAa;AAElB,OAAK,GAAG;;iDAEqC,KAAK,KAAK,CAAC;mBACzC,SAAS;;AAExB,OAAK,kBAAkB;AACvB,OAAK,mBAAmB;AACxB,OAAK,oBAAoB;AACzB,OAAK,UAAU;AAGf,OAAK,yBAAyB;;;;;;CAOhC,UAAU,UAAkB;AAC1B,OAAK,aAAa;AAElB,OAAK,GAAG;;6CAEiC,KAAK,KAAK,CAAC;mBACrC,SAAS;;AAExB,OAAK,kBAAkB;AACvB,OAAK,mBAAmB;AACxB,OAAK,oBAAoB;AACzB,OAAK,UAAU;;;;;;;;;;CAgBjB,WAAW,UAAkB,MAAc;EAGzC,MAAM,YAAY,YAAY,OAAO,KAAK,CAAC;AAC3C,MAAI,YAAY,gBAAgB,iBAAiB;AAC/C,WAAQ,KACN,+CAA+C,UAAU,2EAE1D;AACD;;AAIF,MAAI,KAAK,aAAa,UAAU,sBAC9B,MAAK,aAAa;AAGpB,OAAK,aAAa,KAAK;GACrB,IAAI,QAAQ;GACZ;GACA;GACA,OAAO,KAAK;GACb,CAAC;AACF,OAAK;AAGL,MAAI,KAAK,aAAa,UAAU,kBAC9B,MAAK,aAAa;;;;;;CAQtB,cAAc;AACZ,MAAI,KAAK,qBAAqB,KAAK,aAAa,WAAW,EACzD;AAGF,OAAK,oBAAoB;AACzB,MAAI;GACF,MAAM,SAAS,KAAK;AACpB,QAAK,eAAe,EAAE;GAEtB,MAAM,MAAM,KAAK,KAAK;AACtB,QAAK,MAAM,SAAS,OAClB,MAAK,GAAG;;oBAEI,MAAM,GAAG,IAAI,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,MAAM,MAAM,IAAI,IAAI;;YAGzE;AACR,QAAK,oBAAoB;;;;;;;;;;;;;;;;;;;;CAuB7B,aAAa,YAAwB,WAAkC;EACrE,MAAM,WAAW,KAAK;AACtB,MAAI,CAAC,SAAU,QAAO;AAEtB,OAAK,aAAa;EAElB,MAAM,SAAS,KAAK,GAAgB;;0BAEd,SAAS;;;AAI/B,OAAK,MAAM,SAAS,UAAU,EAAE,CAC9B,YAAW,KACT,KAAK,UAAU;GACb,MAAM,MAAM;GACZ,MAAM;GACN,IAAI;GACJ,MAAM,mBAAmB;GACzB,QAAQ;GACT,CAAC,CACH;AAGH,MAAI,KAAK,oBAAoB,UAAU;AAIrC,cAAW,KACT,KAAK,UAAU;IACb,MAAM;IACN,MAAM;IACN,IAAI;IACJ,MAAM,mBAAmB;IACzB,QAAQ;IACT,CAAC,CACH;AACD,UAAO;;AAGT,MAAI,CAAC,KAAK,SAAS;AAIjB,cAAW,KACT,KAAK,UAAU;IACb,MAAM;IACN,MAAM;IACN,IAAI;IACJ,MAAM,mBAAmB;IACzB,QAAQ;IACT,CAAC,CACH;AACD,QAAK,SAAS,SAAS;AACvB,UAAO;;AAOT,aAAW,KACT,KAAK,UAAU;GACb,MAAM;GACN,MAAM;GACN,IAAI;GACJ,MAAM,mBAAmB;GACzB,QAAQ;GACR,gBAAgB;GACjB,CAAC,CACH;AACD,SAAO;;;;;;CAST,UAAU;EACR,MAAM,gBAAgB,KAAK,GAAmB;;;;;;AAO9C,MAAI,iBAAiB,cAAc,SAAS,GAAG;GAC7C,MAAM,SAAS,cAAc;GAC7B,MAAM,YAAY,KAAK,KAAK,GAAG,OAAO;AAGtC,OAAI,YAAY,2BAA2B;AACzC,SACG,GAAG,0DAA0D,OAAO;AACvE,SACG,GAAG,qDAAqD,OAAO;AAClE,YAAQ,KACN,0CAA0C,OAAO,GAAG,SAAS,KAAK,MAAM,YAAY,IAAK,CAAC,IAC3F;AACD;;AAGF,QAAK,kBAAkB,OAAO;AAC9B,QAAK,mBAAmB,OAAO;GAG/B,MAAM,YAAY,KAAK,GAA0B;;;4BAG3B,KAAK,gBAAgB;;AAE3C,QAAK,oBACH,aAAa,UAAU,IAAI,aAAa,OACpC,UAAU,GAAG,YAAY,IACzB;;;;;;CAOV,WAAW;AACT,OAAK,eAAe,EAAE;AACtB,OAAK,GAAG;AACR,OAAK,GAAG;AACR,OAAK,kBAAkB;AACvB,OAAK,mBAAmB;AACxB,OAAK,oBAAoB;;;;;CAM3B,UAAU;AACR,OAAK,aAAa;AAClB,OAAK,GAAG;AACR,OAAK,GAAG;AACR,OAAK,kBAAkB;AACvB,OAAK,mBAAmB;;CAK1B,0BAAkC;EAChC,MAAM,MAAM,KAAK,KAAK;AACtB,MAAI,MAAM,KAAK,mBAAmB,oBAChC;AAEF,OAAK,mBAAmB;EAExB,MAAM,SAAS,MAAM;AACrB,OAAK,GAAG;;;;oEAIwD,OAAO;;;AAGvE,OAAK,GAAG;;kEAEsD,OAAO;;;;CAOvE,gBACE,UAC8C;AAC9C,SACE,KAAK,GAA0C;;4BAEzB,SAAS;;WAE1B,EAAE;;;CAKX,kBACE,UAC+C;EAC/C,MAAM,SAAS,KAAK,GAA2C;;mBAEhD,SAAS;;AAExB,SAAO,UAAU,OAAO,SAAS,IAAI,OAAO,KAAK;;;CAInD,uBAKG;AACD,SACE,KAAK,GAKH,+EACF,EAAE;;;CAKN,kBAAkB,UAAkB,WAAmB,OAAqB;EAC1E,MAAM,YAAY,KAAK,KAAK,GAAG;AAC/B,OAAK,GAAG;;gBAEI,SAAS,IAAI,UAAU,iBAAiB,UAAU;;;;AA7ShE,gBAAe,kBAAkB;;;;;;;;;;;;ACpKnC,SAAgB,6BACd,aACS;AACT,KAAI,CAAC,eAAe,YAAY,WAAW,EACzC,QAAO,EAAE;CAGX,MAAM,4BAAY,IAAI,KAAa;AACnC,MAAK,MAAM,KAAK,aAAa;AAC3B,MAAI,UAAU,IAAI,EAAE,KAAK,CACvB,SAAQ,KACN,uDAAuD,EAAE,KAAK,wDAC/D;AAEH,YAAU,IAAI,EAAE,KAAK;;AAGvB,QAAO,OAAO,YACZ,YAAY,KAAK,MAAM,CACrB,EAAE,MACF,KAAK;EACH,aAAa,EAAE,eAAe;EAC9B,aAAa,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;EAC5D,CAAC,CACH,CAAC,CACH;;;;;;;;;;;;;;;AC9CH,MAAM,yBAAyB,mBAAmB;AAiClD,IAAa,oBAAb,MAA+B;;AAC7B,OAAA,UAAsC;AACtC,OAAA,WAAwC;AACxC,OAAA,kBAAiC;AACjC,OAAA,qBAAoC;AACpC,OAAA,sCAA2D,IAAI,KAAK;;;CAGpE,eAAqB;AACnB,OAAK,UAAU;AACf,OAAK,oBAAoB,OAAO;;CAGlC,gBAAsB;AACpB,OAAK,WAAW;;CAGlB,WAAiB;AACf,OAAK,cAAc;AACnB,OAAK,eAAe;AACpB,OAAK,kBAAkB;AACvB,OAAK,qBAAqB;;;;;;CAO5B,iBAAuB;EACrB,MAAM,MAAM,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAK,MAAM,cAAc,KAAK,oBAAoB,QAAQ,CACxD,YAAW,KAAK,IAAI;AAEtB,OAAK,oBAAoB,OAAO;;;;;;CAOlC,yBACE,QACM;AACN,OAAK,MAAM,cAAc,KAAK,oBAAoB,QAAQ,CACxD,QAAO,WAAW;AAEpB,OAAK,oBAAoB,OAAO;;;;;;;CAQlC,kBAAwB;AACtB,MAAI,CAAC,KAAK,QAAS;AACnB,OAAK,kBAAkB,KAAK,QAAQ;AACpC,OAAK,qBAAqB,KAAK,QAAQ;AACvC,OAAK,UAAU;;;;;;;;;CAUjB,iBACE,mBAC4B;AAC5B,MAAI,KAAK,WAAW,CAAC,KAAK,SAAU,QAAO;EAE3C,MAAM,IAAI,KAAK;AACf,OAAK,WAAW;AAChB,OAAK,kBAAkB;AACvB,OAAK,qBAAqB;AAE1B,OAAK,UAAU;GACb,YAAY,EAAE;GACd,cAAc,EAAE;GAChB,WAAW,mBAAmB;GAC9B,aAAa,EAAE;GACf,MAAM,EAAE;GACR,aAAa,EAAE;GACf,cAAc,EAAE;GAChB,cAAc;GACf;AAED,OAAK,oBAAoB,IAAI,EAAE,cAAc,EAAE,WAAW;AAC1D,SAAO,KAAK"}
1
+ {"version":3,"file":"index.js","names":["textEncoder"],"sources":["../../src/chat/message-builder.ts","../../src/chat/sanitize.ts","../../src/chat/stream-accumulator.ts","../../src/chat/turn-queue.ts","../../src/chat/broadcast-state.ts","../../src/chat/protocol.ts","../../src/chat/resumable-stream.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"],"sourcesContent":["/**\n * Shared message builder for reconstructing UIMessage parts from stream chunks.\n *\n * Used by both @cloudflare/ai-chat (server + client) and @cloudflare/think\n * to avoid duplicating the chunk-type switch/case logic.\n *\n * Operates on a mutable parts array for performance (avoids allocating new arrays\n * on every chunk during streaming).\n */\n\nimport type { UIMessage } from \"ai\";\n\n/** The parts array type from UIMessage */\nexport type MessageParts = UIMessage[\"parts\"];\n\n/** A single part from the UIMessage parts array */\nexport type MessagePart = MessageParts[number];\n\n/**\n * Parsed chunk data from an AI SDK stream event.\n * This is the JSON-parsed body of a CF_AGENT_USE_CHAT_RESPONSE message,\n * or the `data:` payload of an SSE line.\n */\nexport type StreamChunkData = {\n type: string;\n id?: string;\n delta?: string;\n text?: string;\n mediaType?: string;\n url?: string;\n sourceId?: string;\n title?: string;\n filename?: string;\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n inputTextDelta?: string;\n output?: unknown;\n state?: string;\n errorText?: string;\n /** When true, the output is preliminary (may be updated by a later chunk) */\n preliminary?: boolean;\n /** Approval ID for tools with needsApproval */\n approvalId?: string;\n providerMetadata?: Record<string, unknown>;\n /** Whether the tool was executed by the provider (e.g. Gemini code execution) */\n providerExecuted?: boolean;\n /** Payload for data-* parts (developer-defined typed JSON) */\n data?: unknown;\n /** When true, data parts are ephemeral and not persisted to message.parts */\n transient?: boolean;\n /** Message ID assigned by the server at stream start */\n messageId?: string;\n /** Per-message metadata attached by start/finish/message-metadata chunks */\n messageMetadata?: unknown;\n [key: string]: unknown;\n};\n\n/**\n * Applies a stream chunk to a mutable parts array, building up the message\n * incrementally. Returns true if the chunk was handled, false if it was\n * an unrecognized type (caller may handle it with additional logic).\n *\n * Handles all common chunk types that both server and client need:\n * - text-start / text-delta / text-end\n * - reasoning-start / reasoning-delta / reasoning-end\n * - file\n * - source-url / source-document\n * - tool-input-start / tool-input-delta / tool-input-available / tool-input-error\n * - tool-output-available / tool-output-error\n * - step-start (aliased from start-step)\n * - data-* (developer-defined typed JSON blobs)\n *\n * @param parts - The mutable parts array to update\n * @param chunk - The parsed stream chunk data\n * @returns true if handled, false if the chunk type is not recognized\n */\nexport function applyChunkToParts(\n parts: MessagePart[],\n chunk: StreamChunkData\n): boolean {\n switch (chunk.type) {\n case \"text-start\": {\n parts.push({\n type: \"text\",\n text: \"\",\n state: \"streaming\"\n } as MessagePart);\n return true;\n }\n\n case \"text-delta\": {\n const lastTextPart = findLastPartByType(parts, \"text\");\n if (lastTextPart && lastTextPart.type === \"text\") {\n (lastTextPart as { text: string }).text += chunk.delta ?? \"\";\n } else {\n // No text-start received — create a new text part (stream resumption fallback)\n parts.push({\n type: \"text\",\n text: chunk.delta ?? \"\",\n state: \"streaming\"\n } as MessagePart);\n }\n return true;\n }\n\n case \"text-end\": {\n const lastTextPart = findLastPartByType(parts, \"text\");\n if (lastTextPart && \"state\" in lastTextPart) {\n (lastTextPart as { state: string }).state = \"done\";\n }\n return true;\n }\n\n case \"reasoning-start\": {\n parts.push({\n type: \"reasoning\",\n text: \"\",\n state: \"streaming\"\n } as MessagePart);\n return true;\n }\n\n case \"reasoning-delta\": {\n const lastReasoningPart = findLastPartByType(parts, \"reasoning\");\n if (lastReasoningPart && lastReasoningPart.type === \"reasoning\") {\n (lastReasoningPart as { text: string }).text += chunk.delta ?? \"\";\n } else {\n // No reasoning-start received — create a new reasoning part (stream resumption fallback)\n parts.push({\n type: \"reasoning\",\n text: chunk.delta ?? \"\",\n state: \"streaming\"\n } as MessagePart);\n }\n return true;\n }\n\n case \"reasoning-end\": {\n const lastReasoningPart = findLastPartByType(parts, \"reasoning\");\n if (lastReasoningPart && \"state\" in lastReasoningPart) {\n (lastReasoningPart as { state: string }).state = \"done\";\n }\n return true;\n }\n\n case \"file\": {\n parts.push({\n type: \"file\",\n mediaType: chunk.mediaType,\n url: chunk.url\n } as MessagePart);\n return true;\n }\n\n case \"source-url\": {\n parts.push({\n type: \"source-url\",\n sourceId: chunk.sourceId,\n url: chunk.url,\n title: chunk.title,\n providerMetadata: chunk.providerMetadata\n } as MessagePart);\n return true;\n }\n\n case \"source-document\": {\n parts.push({\n type: \"source-document\",\n sourceId: chunk.sourceId,\n mediaType: chunk.mediaType,\n title: chunk.title,\n filename: chunk.filename,\n providerMetadata: chunk.providerMetadata\n } as MessagePart);\n return true;\n }\n\n case \"tool-input-start\": {\n parts.push({\n type: `tool-${chunk.toolName}`,\n toolCallId: chunk.toolCallId,\n toolName: chunk.toolName,\n state: \"input-streaming\",\n input: undefined,\n ...(chunk.providerExecuted != null\n ? { providerExecuted: chunk.providerExecuted }\n : {}),\n ...(chunk.providerMetadata != null\n ? { callProviderMetadata: chunk.providerMetadata }\n : {}),\n ...(chunk.title != null ? { title: chunk.title } : {})\n } as MessagePart);\n return true;\n }\n\n case \"tool-input-delta\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n (toolPart as Record<string, unknown>).input = chunk.input;\n }\n return true;\n }\n\n case \"tool-input-available\": {\n const existing = findToolPartByCallId(parts, chunk.toolCallId);\n if (existing) {\n const p = existing as Record<string, unknown>;\n p.state = \"input-available\";\n p.input = chunk.input;\n if (chunk.providerExecuted != null) {\n p.providerExecuted = chunk.providerExecuted;\n }\n if (chunk.providerMetadata != null) {\n p.callProviderMetadata = chunk.providerMetadata;\n }\n if (chunk.title != null) {\n p.title = chunk.title;\n }\n } else {\n parts.push({\n type: `tool-${chunk.toolName}`,\n toolCallId: chunk.toolCallId,\n toolName: chunk.toolName,\n state: \"input-available\",\n input: chunk.input,\n ...(chunk.providerExecuted != null\n ? { providerExecuted: chunk.providerExecuted }\n : {}),\n ...(chunk.providerMetadata != null\n ? { callProviderMetadata: chunk.providerMetadata }\n : {}),\n ...(chunk.title != null ? { title: chunk.title } : {})\n } as MessagePart);\n }\n return true;\n }\n\n case \"tool-input-error\": {\n const existing = findToolPartByCallId(parts, chunk.toolCallId);\n if (existing) {\n const p = existing as Record<string, unknown>;\n p.state = \"output-error\";\n p.errorText = chunk.errorText;\n p.input = chunk.input;\n if (chunk.providerExecuted != null) {\n p.providerExecuted = chunk.providerExecuted;\n }\n if (chunk.providerMetadata != null) {\n p.callProviderMetadata = chunk.providerMetadata;\n }\n } else {\n parts.push({\n type: `tool-${chunk.toolName}`,\n toolCallId: chunk.toolCallId,\n toolName: chunk.toolName,\n state: \"output-error\",\n input: chunk.input,\n errorText: chunk.errorText,\n ...(chunk.providerExecuted != null\n ? { providerExecuted: chunk.providerExecuted }\n : {}),\n ...(chunk.providerMetadata != null\n ? { callProviderMetadata: chunk.providerMetadata }\n : {})\n } as MessagePart);\n }\n return true;\n }\n\n case \"tool-approval-request\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n const p = toolPart as Record<string, unknown>;\n p.state = \"approval-requested\";\n p.approval = { id: chunk.approvalId };\n }\n return true;\n }\n\n case \"tool-output-denied\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n const p = toolPart as Record<string, unknown>;\n p.state = \"output-denied\";\n }\n return true;\n }\n\n case \"tool-output-available\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n const p = toolPart as Record<string, unknown>;\n p.state = \"output-available\";\n p.output = chunk.output;\n if (chunk.preliminary !== undefined) {\n p.preliminary = chunk.preliminary;\n }\n }\n return true;\n }\n\n case \"tool-output-error\": {\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (toolPart) {\n const p = toolPart as Record<string, unknown>;\n p.state = \"output-error\";\n p.errorText = chunk.errorText;\n }\n return true;\n }\n\n // Both \"step-start\" (client convention) and \"start-step\" (server convention)\n case \"step-start\":\n case \"start-step\": {\n parts.push({ type: \"step-start\" } as MessagePart);\n return true;\n }\n\n default: {\n // https://ai-sdk.dev/docs/ai-sdk-ui/streaming-data\n if (chunk.type.startsWith(\"data-\")) {\n // Transient parts are ephemeral — the AI SDK client fires an onData\n // callback instead of adding them to message.parts. On the server we\n // still broadcast them (so connected clients see them in real time)\n // but skip persisting them into the stored message parts.\n if (chunk.transient) {\n return true;\n }\n\n // Reconciliation: if a part with the same type AND id already exists,\n // update its data in-place instead of appending a duplicate.\n if (chunk.id != null) {\n const existing = findDataPartByTypeAndId(parts, chunk.type, chunk.id);\n if (existing) {\n (existing as Record<string, unknown>).data = chunk.data;\n return true;\n }\n }\n\n // Append new data parts to the array directly.\n // Note: `chunk.data` should always be provided — if omitted, the\n // persisted part will have `data: undefined` which JSON.stringify\n // drops, so the part will have no `data` field on reload.\n // The cast is needed because UIMessage[\"parts\"] doesn't include\n // data-* types in its union because they're an open extension point.\n parts.push({\n type: chunk.type,\n ...(chunk.id != null && { id: chunk.id }),\n data: chunk.data\n } as MessagePart);\n return true;\n }\n\n return false;\n }\n }\n}\n\n/**\n * Finds the last part in the array matching the given type.\n * Searches from the end for efficiency (the part we want is usually recent).\n */\nfunction findLastPartByType(\n parts: MessagePart[],\n type: string\n): MessagePart | undefined {\n for (let i = parts.length - 1; i >= 0; i--) {\n if (parts[i].type === type) {\n return parts[i];\n }\n }\n return undefined;\n}\n\n/**\n * Finds a tool part by its toolCallId.\n * Searches from the end since the tool part is usually recent.\n */\nfunction findToolPartByCallId(\n parts: MessagePart[],\n toolCallId: string | undefined\n): MessagePart | undefined {\n if (!toolCallId) return undefined;\n for (let i = parts.length - 1; i >= 0; i--) {\n const p = parts[i];\n if (\"toolCallId\" in p && p.toolCallId === toolCallId) {\n return p;\n }\n }\n return undefined;\n}\n\n/**\n * Finds a data part by its type and id for reconciliation.\n * Data parts use type+id as a composite key so when the same combination\n * is seen again, the existing part's data is updated in-place.\n */\nfunction findDataPartByTypeAndId(\n parts: MessagePart[],\n type: string,\n id: string\n): MessagePart | undefined {\n for (let i = parts.length - 1; i >= 0; i--) {\n const p = parts[i];\n if (p.type === type && \"id\" in p && (p as { id: string }).id === id) {\n return p;\n }\n }\n return undefined;\n}\n","/**\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\";\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 (replace with summary)\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 outputJson = JSON.stringify((part as { output: unknown }).output);\n if (outputJson.length > 1000) {\n return {\n ...part,\n output:\n \"This tool output was too large to persist in storage \" +\n `(${outputJson.length} bytes). ` +\n \"If the user asks about this data, suggest re-running the tool. \" +\n `Preview: ${outputJson.slice(0, 500)}...`\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","/**\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} 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 buffer before flushing to SQLite */\nconst CHUNK_BUFFER_SIZE = 10;\n/** Maximum buffer size to prevent memory issues on rapid reconnections */\nconst CHUNK_BUFFER_MAX_SIZE = 100;\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 * 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\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 private _streamChunkIndex = 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<{\n id: string;\n streamId: string;\n body: string;\n index: number;\n }> = [];\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 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 // ── 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): 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._streamChunkIndex = 0;\n this._isLive = true;\n\n this.sql`\n insert into cf_ai_chat_stream_metadata (id, request_id, status, created_at)\n values (${streamId}, ${requestId}, 'streaming', ${Date.now()})\n `;\n\n return streamId;\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._streamChunkIndex = 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._streamChunkIndex = 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 this._chunkBuffer.push({\n id: nanoid(),\n streamId,\n body,\n index: this._streamChunkIndex\n });\n this._streamChunkIndex++;\n\n // Flush when buffer reaches threshold\n if (this._chunkBuffer.length >= CHUNK_BUFFER_SIZE) {\n this.flushBuffer();\n }\n }\n\n /**\n * Flush buffered chunks to SQLite in a single batch.\n * Uses a lock to prevent concurrent flush operations.\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\n const now = Date.now();\n for (const chunk of chunks) {\n this.sql`\n insert into cf_ai_chat_stream_chunks (id, stream_id, body, chunk_index, created_at)\n values (${chunk.id}, ${chunk.streamId}, ${chunk.body}, ${chunk.index}, ${now})\n `;\n }\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 * @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 connection.send(\n JSON.stringify({\n body: chunk.body,\n done: false,\n id: requestId,\n type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,\n replay: true\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 connection.send(\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: send done and mark completed in SQLite.\n connection.send(\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 connection.send(\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 // ── 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 // Get the last chunk index\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._streamChunkIndex =\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.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._streamChunkIndex = 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 /** @internal For testing only */\n getStreamChunks(\n streamId: string\n ): Array<{ body: string; chunk_index: number }> {\n return (\n this.sql<{ body: string; chunk_index: number }>`\n select body, chunk_index from cf_ai_chat_stream_chunks \n where stream_id = ${streamId} \n order by chunk_index asc\n ` || []\n );\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 * 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\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 connection: ContinuationConnection;\n connectionId: string;\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 connection: ContinuationConnection;\n connectionId: string;\n clientTools?: ClientToolSchema[];\n body?: Record<string, unknown>;\n errorPrefix: string;\n prerequisite: Promise<boolean> | null;\n}\n\nexport class ContinuationState {\n pending: ContinuationPending | null = null;\n deferred: ContinuationDeferred | null = null;\n activeRequestId: string | null = null;\n activeConnectionId: string | null = null;\n awaitingConnections: Map<string, ContinuationConnection> = 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 * 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 connection.send(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(\n notify: (conn: ContinuationConnection) => void\n ): 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 | 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 this.awaitingConnections.set(d.connectionId, d.connection);\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\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 /** Cancel a specific request by aborting its controller. */\n cancel(id: string): void {\n this.controllers.get(id)?.abort();\n }\n\n /** Remove a controller after the request completes. */\n remove(id: string): void {\n this.controllers.delete(id);\n }\n\n /** Abort all pending requests and clear the registry. */\n destroyAll(): void {\n for (const controller of this.controllers.values()) {\n controller.abort();\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 * 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 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"],"mappings":"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"}