agents 0.11.8 → 0.11.9

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.
@@ -58,6 +58,30 @@ type StreamChunkData = {
58
58
  * @returns true if handled, false if the chunk type is not recognized
59
59
  */
60
60
  declare function applyChunkToParts(parts: MessagePart[], chunk: StreamChunkData): boolean;
61
+ /**
62
+ * Returns true if `chunk` would be a no-op replay against the already-known
63
+ * `parts` — i.e. some upstream is re-emitting events for a tool call that
64
+ * the message has already advanced past.
65
+ *
66
+ * Used by stream broadcasters to suppress re-broadcasting these chunks to
67
+ * connected clients. AI SDK v6's `updateToolPart` mutates an existing tool
68
+ * part in place when a chunk arrives with a matching `toolCallId`, so a
69
+ * replayed `tool-input-start` would clobber an `output-available` part back
70
+ * to `input-streaming` on the client (issue #1404).
71
+ *
72
+ * Only returns true when re-broadcasting would *visibly regress* state on
73
+ * a v6 client. Safe-by-construction chunk types (e.g. `tool-output-available`
74
+ * carrying the same output the part already has) return false.
75
+ *
76
+ * Conditions:
77
+ * - `tool-input-start` for a `toolCallId` that already exists in `parts`.
78
+ * - `tool-input-delta` for a `toolCallId` whose existing part is no longer
79
+ * `input-streaming`.
80
+ * - `tool-input-available` for a `toolCallId` whose existing part is no
81
+ * longer `input-streaming` (i.e. has already advanced to `input-available`
82
+ * or any terminal state).
83
+ */
84
+ declare function isReplayChunk(parts: MessagePart[], chunk: StreamChunkData): boolean;
61
85
  //#endregion
62
86
  //#region src/chat/sanitize.d.ts
63
87
  /** Maximum serialized message size before compaction (bytes). 1.8MB with headroom below SQLite's 2MB limit. */
@@ -812,5 +836,5 @@ declare function resolveToolMergeId(message: UIMessage, serverMessages: readonly
812
836
  */
813
837
  declare function assistantContentKey(message: UIMessage, sanitize?: (message: UIMessage) => UIMessage): string | undefined;
814
838
  //#endregion
815
- export { AbortRegistry, type BroadcastStreamEvent, type BroadcastStreamState, type TransitionResult as BroadcastTransitionResult, CHAT_MESSAGE_TYPES, type ChatProtocolEvent, type ChatRecoveryContext, type ChatRecoveryOptions, type ChatResponseResult, type ChunkAction, type ChunkResult, type ClientToolSchema, type ContinuationConnection, type ContinuationDeferred, type ContinuationPending, ContinuationState, type EnqueueOptions, type MessageConcurrency, type MessagePart, type MessageParts, type NormalizedMessageConcurrency, ROW_MAX_BYTES, ResumableStream, type SaveMessagesOptions, type SaveMessagesResult, type SqlTaggedTemplate, StreamAccumulator, type StreamAccumulatorOptions, type StreamChunkData, SubmitConcurrencyController, type SubmitConcurrencyDecision, type ToolPartUpdate, TurnQueue, type TurnResult, applyChunkToParts, applyToolUpdate, assistantContentKey, transition as broadcastTransition, byteLength, createToolsFromClientSchemas, enforceRowSizeLimit, parseProtocolMessage, reconcileMessages, resolveToolMergeId, sanitizeMessage, toolApprovalUpdate, toolResultUpdate };
839
+ export { AbortRegistry, type BroadcastStreamEvent, type BroadcastStreamState, type TransitionResult as BroadcastTransitionResult, CHAT_MESSAGE_TYPES, type ChatProtocolEvent, type ChatRecoveryContext, type ChatRecoveryOptions, type ChatResponseResult, type ChunkAction, type ChunkResult, type ClientToolSchema, type ContinuationConnection, type ContinuationDeferred, type ContinuationPending, ContinuationState, type EnqueueOptions, type MessageConcurrency, type MessagePart, type MessageParts, type NormalizedMessageConcurrency, ROW_MAX_BYTES, ResumableStream, type SaveMessagesOptions, type SaveMessagesResult, type SqlTaggedTemplate, StreamAccumulator, type StreamAccumulatorOptions, type StreamChunkData, SubmitConcurrencyController, type SubmitConcurrencyDecision, type ToolPartUpdate, TurnQueue, type TurnResult, applyChunkToParts, applyToolUpdate, assistantContentKey, transition as broadcastTransition, byteLength, createToolsFromClientSchemas, enforceRowSizeLimit, isReplayChunk, parseProtocolMessage, reconcileMessages, resolveToolMergeId, sanitizeMessage, toolApprovalUpdate, toolResultUpdate };
816
840
  //# sourceMappingURL=index.d.ts.map
@@ -99,6 +99,7 @@ function applyChunkToParts(parts, chunk) {
99
99
  });
100
100
  return true;
101
101
  case "tool-input-start":
102
+ if (findToolPartByCallId(parts, chunk.toolCallId)) return true;
102
103
  parts.push({
103
104
  type: `tool-${chunk.toolName}`,
104
105
  toolCallId: chunk.toolCallId,
@@ -112,19 +113,23 @@ function applyChunkToParts(parts, chunk) {
112
113
  return true;
113
114
  case "tool-input-delta": {
114
115
  const toolPart = findToolPartByCallId(parts, chunk.toolCallId);
115
- if (toolPart) toolPart.input = chunk.input;
116
+ if (toolPart && toolPart.state === "input-streaming") toolPart.input = chunk.input;
116
117
  return true;
117
118
  }
118
119
  case "tool-input-available": {
119
120
  const existing = findToolPartByCallId(parts, chunk.toolCallId);
120
121
  if (existing) {
121
122
  const p = existing;
122
- p.state = "input-available";
123
- p.input = chunk.input;
124
- if (chunk.providerExecuted != null) p.providerExecuted = chunk.providerExecuted;
125
- if (chunk.providerMetadata != null) p.callProviderMetadata = chunk.providerMetadata;
126
- if (chunk.title != null) p.title = chunk.title;
127
- } else parts.push({
123
+ if (p.state === "input-streaming") {
124
+ p.state = "input-available";
125
+ p.input = chunk.input;
126
+ if (chunk.providerExecuted != null) p.providerExecuted = chunk.providerExecuted;
127
+ if (chunk.providerMetadata != null) p.callProviderMetadata = chunk.providerMetadata;
128
+ if (chunk.title != null) p.title = chunk.title;
129
+ }
130
+ return true;
131
+ }
132
+ parts.push({
128
133
  type: `tool-${chunk.toolName}`,
129
134
  toolCallId: chunk.toolCallId,
130
135
  toolName: chunk.toolName,
@@ -140,6 +145,7 @@ function applyChunkToParts(parts, chunk) {
140
145
  const existing = findToolPartByCallId(parts, chunk.toolCallId);
141
146
  if (existing) {
142
147
  const p = existing;
148
+ if (p.state === "output-available" || p.state === "output-error" || p.state === "output-denied") return true;
143
149
  p.state = "output-error";
144
150
  p.errorText = chunk.errorText;
145
151
  p.input = chunk.input;
@@ -218,6 +224,37 @@ function applyChunkToParts(parts, chunk) {
218
224
  }
219
225
  }
220
226
  /**
227
+ * Returns true if `chunk` would be a no-op replay against the already-known
228
+ * `parts` — i.e. some upstream is re-emitting events for a tool call that
229
+ * the message has already advanced past.
230
+ *
231
+ * Used by stream broadcasters to suppress re-broadcasting these chunks to
232
+ * connected clients. AI SDK v6's `updateToolPart` mutates an existing tool
233
+ * part in place when a chunk arrives with a matching `toolCallId`, so a
234
+ * replayed `tool-input-start` would clobber an `output-available` part back
235
+ * to `input-streaming` on the client (issue #1404).
236
+ *
237
+ * Only returns true when re-broadcasting would *visibly regress* state on
238
+ * a v6 client. Safe-by-construction chunk types (e.g. `tool-output-available`
239
+ * carrying the same output the part already has) return false.
240
+ *
241
+ * Conditions:
242
+ * - `tool-input-start` for a `toolCallId` that already exists in `parts`.
243
+ * - `tool-input-delta` for a `toolCallId` whose existing part is no longer
244
+ * `input-streaming`.
245
+ * - `tool-input-available` for a `toolCallId` whose existing part is no
246
+ * longer `input-streaming` (i.e. has already advanced to `input-available`
247
+ * or any terminal state).
248
+ */
249
+ function isReplayChunk(parts, chunk) {
250
+ if (chunk.type !== "tool-input-start" && chunk.type !== "tool-input-delta" && chunk.type !== "tool-input-available") return false;
251
+ if (!chunk.toolCallId) return false;
252
+ const existing = findToolPartByCallId(parts, chunk.toolCallId);
253
+ if (!existing) return false;
254
+ if (chunk.type === "tool-input-start") return true;
255
+ return existing.state !== "input-streaming";
256
+ }
257
+ /**
221
258
  * Finds the last part in the array matching the given type.
222
259
  * Searches from the end for efficiency (the part we want is usually recent).
223
260
  */
@@ -1578,6 +1615,6 @@ function findMessageByToolCallId(messages, toolCallId) {
1578
1615
  }
1579
1616
  }
1580
1617
  //#endregion
1581
- export { AbortRegistry, CHAT_MESSAGE_TYPES, ContinuationState, ROW_MAX_BYTES, ResumableStream, StreamAccumulator, SubmitConcurrencyController, TurnQueue, applyChunkToParts, applyToolUpdate, assistantContentKey, transition as broadcastTransition, byteLength, createToolsFromClientSchemas, enforceRowSizeLimit, parseProtocolMessage, reconcileMessages, resolveToolMergeId, sanitizeMessage, toolApprovalUpdate, toolResultUpdate };
1618
+ export { AbortRegistry, CHAT_MESSAGE_TYPES, ContinuationState, ROW_MAX_BYTES, ResumableStream, StreamAccumulator, SubmitConcurrencyController, TurnQueue, applyChunkToParts, applyToolUpdate, assistantContentKey, transition as broadcastTransition, byteLength, createToolsFromClientSchemas, enforceRowSizeLimit, isReplayChunk, parseProtocolMessage, reconcileMessages, resolveToolMergeId, sanitizeMessage, toolApprovalUpdate, toolResultUpdate };
1582
1619
 
1583
1620
  //# sourceMappingURL=index.js.map
@@ -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/submit-concurrency.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","../../src/chat/message-reconciler.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 mergeProviderMetadata(lastReasoningPart, chunk.providerMetadata);\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 ...(chunk.providerMetadata != null\n ? { providerMetadata: chunk.providerMetadata }\n : {})\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 mergeProviderMetadata(lastReasoningPart, chunk.providerMetadata);\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 * Shallow-merges providerMetadata from a chunk onto an existing part.\n * Preserves any metadata already on the part (e.g. from earlier deltas)\n * while adding new keys from the chunk. This is critical for providers\n * like Anthropic that emit the thinking block signature on reasoning-end.\n */\nfunction mergeProviderMetadata(\n part: MessagePart,\n metadata: Record<string, unknown> | undefined\n): void {\n if (metadata == null) return;\n const p = part as Record<string, unknown>;\n p.providerMetadata = {\n ...(p.providerMetadata as Record<string, unknown> | undefined),\n ...metadata\n };\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","import type { MessageConcurrency } from \"./lifecycle\";\n\nexport type NormalizedMessageConcurrency =\n | \"queue\"\n | \"latest\"\n | \"merge\"\n | \"drop\"\n | {\n strategy: \"debounce\";\n debounceMs: number;\n };\n\nexport type SubmitConcurrencyDecision = {\n action: \"execute\" | \"drop\";\n strategy: NormalizedMessageConcurrency | null;\n submitSequence: number | null;\n debounceUntilMs: number | null;\n};\n\nexport class SubmitConcurrencyController {\n private _submitSequence = 0;\n private _latestOverlappingSubmitSequence = 0;\n private _pendingEnqueueCount = 0;\n private _resetEpoch = 0;\n private _activeDebounceTimers = new Set<ReturnType<typeof setTimeout>>();\n private _activeDebounceResolves = new Set<() => void>();\n\n constructor(private readonly options: { defaultDebounceMs: number }) {}\n\n get pendingEnqueueCount(): number {\n return this._pendingEnqueueCount;\n }\n\n get overlappingSubmitCount(): number {\n return this._latestOverlappingSubmitSequence;\n }\n\n decide(options: {\n concurrency: MessageConcurrency;\n isSubmitMessage: boolean;\n queuedTurns: number;\n }): SubmitConcurrencyDecision {\n const queuedTurnsInCurrentEpoch =\n options.queuedTurns + this._pendingEnqueueCount;\n\n if (!options.isSubmitMessage || queuedTurnsInCurrentEpoch === 0) {\n return {\n action: \"execute\",\n strategy: null,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n const concurrency = this.normalize(options.concurrency);\n if (concurrency === \"drop\") {\n return {\n action: \"drop\",\n strategy: concurrency,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n if (concurrency === \"queue\") {\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n const submitSequence = ++this._submitSequence;\n this._latestOverlappingSubmitSequence = submitSequence;\n\n if (concurrency === \"latest\" || concurrency === \"merge\") {\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence,\n debounceUntilMs: null\n };\n }\n\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence,\n debounceUntilMs: Date.now() + concurrency.debounceMs\n };\n }\n\n /**\n * Mark a submit as accepted and in-flight between admission and turn\n * queue registration. Returns an idempotent `release()` function that\n * must be called when the submit either reaches the turn queue or is\n * abandoned. The returned function is bound to the controller's reset\n * epoch — releases from before the most recent `reset()` are no-ops,\n * so post-reset submits keep an accurate count.\n */\n beginEnqueue(): () => void {\n this._pendingEnqueueCount++;\n const epoch = this._resetEpoch;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n if (this._resetEpoch !== epoch) return;\n this._pendingEnqueueCount = Math.max(0, this._pendingEnqueueCount - 1);\n };\n }\n\n isSuperseded(submitSequence: number | null): boolean {\n return (\n submitSequence !== null &&\n submitSequence < this._latestOverlappingSubmitSequence\n );\n }\n\n async waitForTimestamp(timestampMs: number): Promise<void> {\n const remainingMs = timestampMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n const wrappedResolve = () => {\n this._activeDebounceResolves.delete(wrappedResolve);\n resolve();\n };\n const timer = setTimeout(() => {\n this._activeDebounceTimers.delete(timer);\n wrappedResolve();\n }, remainingMs);\n\n this._activeDebounceTimers.add(timer);\n this._activeDebounceResolves.add(wrappedResolve);\n });\n }\n\n cancelActiveDebounce(): void {\n for (const timer of this._activeDebounceTimers) {\n clearTimeout(timer);\n }\n this._activeDebounceTimers.clear();\n\n const resolves = [...this._activeDebounceResolves];\n this._activeDebounceResolves.clear();\n for (const resolve of resolves) {\n resolve();\n }\n }\n\n reset(): void {\n this._resetEpoch++;\n this._pendingEnqueueCount = 0;\n this.cancelActiveDebounce();\n }\n\n async waitForIdle(waitForQueueIdle: () => Promise<void>): Promise<void> {\n while (true) {\n await waitForQueueIdle();\n if (this._pendingEnqueueCount === 0) return;\n await new Promise<void>((resolve) => setTimeout(resolve, 5));\n }\n }\n\n private normalize(\n concurrency: MessageConcurrency\n ): NormalizedMessageConcurrency {\n if (typeof concurrency === \"string\") {\n return concurrency;\n }\n\n const debounceMs = concurrency.debounceMs;\n\n return {\n strategy: \"debounce\",\n debounceMs:\n typeof debounceMs === \"number\" &&\n Number.isFinite(debounceMs) &&\n debounceMs >= 0\n ? debounceMs\n : this.options.defaultDebounceMs\n };\n }\n}\n","/**\n * Broadcast stream state machine.\n *\n * Manages the lifecycle of a StreamAccumulator for broadcast/resume\n * streams — the path where this client is *observing* a stream owned\n * by another tab or resumed after reconnect, rather than the transport-\n * owned path that feeds directly into useChat.\n *\n * The transition function is pure (no React, no WebSocket, no side\n * effects). Callers dispatch events and apply the returned state +\n * messagesUpdate. Side effects (sending ACKs, calling onData) stay\n * in the caller.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { StreamAccumulator } from \"./stream-accumulator\";\nimport type { StreamChunkData } from \"./message-builder\";\n\n// ── State ──────────────────────────────────────────────────────────\n\nexport type BroadcastStreamState =\n | { status: \"idle\" }\n | {\n status: \"observing\";\n streamId: string;\n accumulator: StreamAccumulator;\n };\n\n// ── Events ─────────────────────────────────────────────────────────\n\nexport type BroadcastStreamEvent =\n | {\n type: \"response\";\n streamId: string;\n /** Fallback message ID for a new accumulator (ignored if one exists for this stream). */\n messageId: string;\n chunkData?: unknown;\n done?: boolean;\n error?: boolean;\n replay?: boolean;\n replayComplete?: boolean;\n continuation?: boolean;\n /** Required when continuation=true so the accumulator can pick up existing parts. */\n currentMessages?: UIMessage[];\n }\n | {\n type: \"resume-fallback\";\n streamId: string;\n messageId: string;\n }\n | { type: \"clear\" };\n\n// ── Result ─────────────────────────────────────────────────────────\n\nexport interface TransitionResult {\n state: BroadcastStreamState;\n messagesUpdate?: (prev: UIMessage[]) => UIMessage[];\n isStreaming: boolean;\n}\n\n// ── Transition ─────────────────────────────────────────────────────\n\nexport function transition(\n state: BroadcastStreamState,\n event: BroadcastStreamEvent\n): TransitionResult {\n switch (event.type) {\n case \"clear\":\n return { state: { status: \"idle\" }, isStreaming: false };\n\n case \"resume-fallback\": {\n const accumulator = new StreamAccumulator({\n messageId: event.messageId\n });\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n isStreaming: true\n };\n }\n\n case \"response\": {\n let accumulator: StreamAccumulator;\n\n if (state.status === \"idle\" || state.streamId !== event.streamId) {\n let messageId = event.messageId;\n let existingParts: UIMessage[\"parts\"] | undefined;\n let existingMetadata: Record<string, unknown> | undefined;\n\n if (event.continuation && event.currentMessages) {\n for (let i = event.currentMessages.length - 1; i >= 0; i--) {\n if (event.currentMessages[i].role === \"assistant\") {\n messageId = event.currentMessages[i].id;\n existingParts = [...event.currentMessages[i].parts];\n if (event.currentMessages[i].metadata != null) {\n existingMetadata = {\n ...(event.currentMessages[i].metadata as Record<\n string,\n unknown\n >)\n };\n }\n break;\n }\n }\n }\n\n accumulator = new StreamAccumulator({\n messageId,\n continuation: event.continuation,\n existingParts,\n existingMetadata\n });\n } else {\n accumulator = state.accumulator;\n }\n\n if (event.chunkData) {\n accumulator.applyChunk(event.chunkData as StreamChunkData);\n }\n\n let messagesUpdate: ((prev: UIMessage[]) => UIMessage[]) | undefined;\n\n if (event.done) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n return {\n state: { status: \"idle\" },\n messagesUpdate,\n isStreaming: false\n };\n }\n\n if (event.chunkData && !event.replay) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n } else if (event.replayComplete) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n }\n\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n messagesUpdate,\n isStreaming: true\n };\n }\n }\n}\n","/**\n * Wire protocol message type constants for the cf_agent_chat_* protocol.\n *\n * These are the string values used on the wire between agent servers and\n * clients. Both @cloudflare/ai-chat (via its MessageType enum) and\n * @cloudflare/think use these values.\n */\nexport const CHAT_MESSAGE_TYPES = {\n CHAT_MESSAGES: \"cf_agent_chat_messages\",\n USE_CHAT_REQUEST: \"cf_agent_use_chat_request\",\n USE_CHAT_RESPONSE: \"cf_agent_use_chat_response\",\n CHAT_CLEAR: \"cf_agent_chat_clear\",\n CHAT_REQUEST_CANCEL: \"cf_agent_chat_request_cancel\",\n STREAM_RESUMING: \"cf_agent_stream_resuming\",\n STREAM_RESUME_ACK: \"cf_agent_stream_resume_ack\",\n STREAM_RESUME_REQUEST: \"cf_agent_stream_resume_request\",\n STREAM_RESUME_NONE: \"cf_agent_stream_resume_none\",\n TOOL_RESULT: \"cf_agent_tool_result\",\n TOOL_APPROVAL: \"cf_agent_tool_approval\",\n MESSAGE_UPDATED: \"cf_agent_message_updated\"\n} 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\nconst NOOP = () => {};\n\nexport class AbortRegistry {\n private controllers = new Map<string, AbortController>();\n\n /**\n * Get or create an AbortController for the given ID and return its signal.\n * Creates the controller lazily on first access.\n */\n getSignal(id: string): AbortSignal | undefined {\n if (typeof id !== \"string\") {\n return undefined;\n }\n\n if (!this.controllers.has(id)) {\n this.controllers.set(id, new AbortController());\n }\n\n return this.controllers.get(id)!.signal;\n }\n\n /**\n * Get the signal for an existing controller without creating one.\n * Returns undefined if no controller exists for this ID.\n */\n getExistingSignal(id: string): AbortSignal | undefined {\n return this.controllers.get(id)?.signal;\n }\n\n /**\n * Cancel a specific request by aborting its controller. Optionally\n * propagate a reason — surfaces as `signal.reason` on the registry's\n * controller and through any `AbortError` it produces downstream.\n */\n cancel(id: string, reason?: unknown): void {\n this.controllers.get(id)?.abort(reason);\n }\n\n /** Remove a controller after the request completes. */\n remove(id: string): void {\n this.controllers.delete(id);\n }\n\n /** 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 * Link an external `AbortSignal` to the controller for `id`. When the\n * external signal aborts, the registry's controller is cancelled —\n * propagating the abort reason — exactly the same way an internal\n * cancel would (e.g. via a `chat-request-cancel` WebSocket message).\n *\n * This is the integration point for callers that drive a chat turn\n * programmatically and want to cancel it from outside without knowing\n * the internally-generated request id (e.g. the helper-as-sub-agent\n * pattern, where a parent's `AbortSignal` from the AI SDK tool\n * `execute` needs to land inside a `Think.saveMessages` call running\n * on a child DO).\n *\n * Behavior:\n *\n * - Passing `undefined` is a no-op and returns a no-op detacher, so\n * callers can unconditionally call this with `options?.signal`.\n * - If the external signal is already aborted, the registry's\n * controller is created (if needed) and cancelled synchronously.\n * - Otherwise a one-shot `abort` listener is attached. The returned\n * function detaches it.\n *\n * **Always call the returned detacher in a `finally` block** — the\n * external signal may outlive the request (a parent chat turn that\n * drives many helper turns reuses one signal across all of them) and\n * leaving listeners attached pins closures and grows the listener\n * list on each turn.\n *\n * @returns A detacher function. Call it after the request finishes\n * (success or failure) to remove the abort listener from `signal`.\n */\n linkExternal(id: string, signal: AbortSignal | undefined): () => void {\n if (!signal) return NOOP;\n\n if (signal.aborted) {\n // Ensure the registry controller for `id` exists, then cancel it.\n // Calling getSignal first means an early external abort still\n // produces a controller for downstream observers (`getExistingSignal`)\n // rather than a silently-empty registry.\n this.getSignal(id);\n this.cancel(id, signal.reason);\n return NOOP;\n }\n\n const listener = () => this.cancel(id, signal.reason);\n signal.addEventListener(\"abort\", listener, { once: true });\n return () => signal.removeEventListener(\"abort\", listener);\n }\n}\n","/**\n * Tool State — shared update builders and applicator for tool part state changes.\n *\n * Used by both AIChatAgent and Think to apply tool results and approvals\n * to message parts. Each agent handles find-message, persist, and broadcast\n * in their own way; this module provides the state matching and update logic.\n */\n\n/**\n * Describes an update to apply to a tool part.\n */\nexport type ToolPartUpdate = {\n toolCallId: string;\n matchStates: string[];\n apply: (part: Record<string, unknown>) => Record<string, unknown>;\n};\n\n/**\n * Apply a tool part update to a parts array.\n * Finds the first part matching `update.toolCallId` in one of `update.matchStates`,\n * applies the update immutably, and returns the new parts array with the index.\n *\n * Returns `null` if no matching part was found.\n */\nexport function applyToolUpdate(\n parts: Array<Record<string, unknown>>,\n update: ToolPartUpdate\n): { parts: Array<Record<string, unknown>>; index: number } | null {\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n if (\n \"toolCallId\" in part &&\n part.toolCallId === update.toolCallId &&\n \"state\" in part &&\n update.matchStates.includes(part.state as string)\n ) {\n const updatedParts = [...parts];\n updatedParts[i] = update.apply(part);\n return { parts: updatedParts, index: i };\n }\n }\n return null;\n}\n\n/**\n * Build an update descriptor for applying a tool result.\n *\n * Matches parts in `input-available`, `approval-requested`, or `approval-responded` state.\n * Sets state to `output-available` (with output) or `output-error` (with errorText).\n */\nexport function toolResultUpdate(\n toolCallId: string,\n output: unknown,\n overrideState?: \"output-error\",\n errorText?: string\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\n \"input-available\",\n \"approval-requested\",\n \"approval-responded\"\n ],\n apply: (part) => ({\n ...part,\n ...(overrideState === \"output-error\"\n ? {\n state: \"output-error\",\n errorText: errorText ?? \"Tool execution denied by user\"\n }\n : { state: \"output-available\", output, preliminary: false })\n })\n };\n}\n\n/**\n * Build an update descriptor for applying a tool approval.\n *\n * Matches parts in `input-available` or `approval-requested` state.\n * Sets state to `approval-responded` (if approved) or `output-denied` (if denied).\n */\nexport function toolApprovalUpdate(\n toolCallId: string,\n approved: boolean\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\"input-available\", \"approval-requested\"],\n apply: (part) => ({\n ...part,\n state: approved ? \"approval-responded\" : \"output-denied\",\n approval: {\n ...(part.approval as Record<string, unknown> | undefined),\n approved\n }\n })\n };\n}\n","/**\n * Protocol Message Parser — typed parsing of cf_agent_chat_* WebSocket messages.\n *\n * Parses raw WebSocket messages into a discriminated union of protocol events.\n * Both AIChatAgent and Think can use this instead of manual JSON.parse + type checking.\n */\n\nimport { CHAT_MESSAGE_TYPES } from \"./protocol\";\n\n/**\n * Discriminated union of all incoming chat protocol events.\n *\n * Each agent handles the events it cares about and ignores the rest.\n * Returns `null` for non-JSON messages or unrecognized types.\n */\nexport type ChatProtocolEvent =\n | {\n type: \"chat-request\";\n id: string;\n init: { method?: string; body?: string; [key: string]: unknown };\n }\n | { type: \"clear\" }\n | { type: \"cancel\"; id: string }\n | {\n type: \"tool-result\";\n toolCallId: string;\n toolName: string;\n output: unknown;\n state?: string;\n errorText?: string;\n autoContinue?: boolean;\n clientTools?: Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>;\n }\n | {\n type: \"tool-approval\";\n toolCallId: string;\n approved: boolean;\n autoContinue?: boolean;\n }\n | { type: \"stream-resume-request\" }\n | { type: \"stream-resume-ack\"; id: string }\n | { type: \"messages\"; messages: unknown[] };\n\n/**\n * Parse a raw WebSocket message string into a typed protocol event.\n *\n * Returns `null` if the message is not valid JSON or not a recognized\n * protocol message type. Callers should fall through to the user's\n * `onMessage` handler when `null` is returned.\n *\n * @example\n * ```typescript\n * const event = parseProtocolMessage(rawMessage);\n * if (!event) return userOnMessage(connection, rawMessage);\n *\n * switch (event.type) {\n * case \"chat-request\": { ... }\n * case \"clear\": { ... }\n * case \"tool-result\": { ... }\n * }\n * ```\n */\nexport function parseProtocolMessage(raw: string): ChatProtocolEvent | null {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n\n const wireType = data.type as string | undefined;\n if (!wireType) return null;\n\n switch (wireType) {\n case CHAT_MESSAGE_TYPES.USE_CHAT_REQUEST:\n return {\n type: \"chat-request\",\n id: data.id as string,\n init: (data.init as { method?: string; body?: string }) ?? {}\n };\n\n case CHAT_MESSAGE_TYPES.CHAT_CLEAR:\n return { type: \"clear\" };\n\n case CHAT_MESSAGE_TYPES.CHAT_REQUEST_CANCEL:\n return { type: \"cancel\", id: data.id as string };\n\n case CHAT_MESSAGE_TYPES.TOOL_RESULT:\n return {\n type: \"tool-result\",\n toolCallId: data.toolCallId as string,\n toolName: (data.toolName as string) ?? \"\",\n output: data.output,\n state: data.state as string | undefined,\n errorText: data.errorText as string | undefined,\n autoContinue: data.autoContinue as boolean | undefined,\n clientTools: data.clientTools as\n | Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>\n | undefined\n };\n\n case CHAT_MESSAGE_TYPES.TOOL_APPROVAL:\n return {\n type: \"tool-approval\",\n toolCallId: data.toolCallId as string,\n approved: data.approved as boolean,\n autoContinue: data.autoContinue as boolean | undefined\n };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_REQUEST:\n return { type: \"stream-resume-request\" };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_ACK:\n return { type: \"stream-resume-ack\", id: data.id as string };\n\n case CHAT_MESSAGE_TYPES.CHAT_MESSAGES:\n return {\n type: \"messages\",\n messages: (data.messages as unknown[]) ?? []\n };\n\n default:\n return null;\n }\n}\n","/**\n * Message reconciliation — pure functions for aligning client messages\n * with server state during persistence.\n *\n * Three strategies applied in order:\n * 1. Merge server-known tool outputs into stale client messages\n * 2. Reconcile assistant IDs (exact match → content-key → toolCallId)\n * 3. Per-message toolCallId dedup for persistence\n */\n\nimport type { UIMessage } from \"ai\";\n\n/**\n * Reconcile incoming client messages against server state.\n *\n * 1. Merges server-known tool outputs into incoming messages that still\n * show stale states (input-available, approval-requested, approval-responded)\n * 2. Reconciles assistant IDs: exact match → content-key match → toolCallId match\n *\n * @param incoming - Messages from the client\n * @param serverMessages - Current server-side messages (source of truth)\n * @param sanitizeForContentKey - Function to sanitize a message before computing\n * its content key (typically strips ephemeral provider metadata)\n * @returns Reconciled messages ready for persistence\n */\nexport function reconcileMessages(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[],\n sanitizeForContentKey?: (message: UIMessage) => UIMessage\n): UIMessage[] {\n const withMergedToolOutputs = mergeServerToolOutputs(\n incoming,\n serverMessages\n );\n return reconcileAssistantIds(\n withMergedToolOutputs,\n serverMessages,\n sanitizeForContentKey\n );\n}\n\n/**\n * For a single message, resolve its ID by matching toolCallId against server state.\n * Prevents duplicate DB rows when client IDs differ from server IDs.\n * Tool call IDs are unique per conversation, so matching is safe regardless of state.\n */\nexport function resolveToolMergeId(\n message: UIMessage,\n serverMessages: readonly UIMessage[]\n): UIMessage {\n if (message.role !== \"assistant\") {\n return message;\n }\n\n for (const part of message.parts) {\n if (\"toolCallId\" in part && part.toolCallId) {\n const toolCallId = part.toolCallId as string;\n const existing = findMessageByToolCallId(serverMessages, toolCallId);\n if (existing && existing.id !== message.id) {\n return { ...message, id: existing.id };\n }\n }\n }\n\n return message;\n}\n\n/**\n * Content key for assistant messages used for dedup of identical short replies.\n * Returns JSON of sanitized parts, or undefined for non-assistant messages.\n */\nexport function assistantContentKey(\n message: UIMessage,\n sanitize?: (message: UIMessage) => UIMessage\n): string | undefined {\n if (message.role !== \"assistant\") {\n return undefined;\n }\n const sanitized = sanitize ? sanitize(message) : message;\n return JSON.stringify(sanitized.parts);\n}\n\nfunction mergeServerToolOutputs(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[]\n): UIMessage[] {\n const serverToolOutputs = new Map<string, unknown>();\n for (const msg of serverMessages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"output-available\" &&\n \"output\" in part\n ) {\n serverToolOutputs.set(\n part.toolCallId as string,\n (part as { output: unknown }).output\n );\n }\n }\n }\n\n if (serverToolOutputs.size === 0) return incoming;\n\n return incoming.map((msg) => {\n if (msg.role !== \"assistant\") return msg;\n\n let hasChanges = false;\n const updatedParts = msg.parts.map((part) => {\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n (part.state === \"input-available\" ||\n part.state === \"approval-requested\" ||\n part.state === \"approval-responded\") &&\n serverToolOutputs.has(part.toolCallId as string)\n ) {\n hasChanges = true;\n return {\n ...part,\n state: \"output-available\" as const,\n output: serverToolOutputs.get(part.toolCallId as string)\n };\n }\n return part;\n }) as UIMessage[\"parts\"];\n\n return hasChanges ? { ...msg, parts: updatedParts } : msg;\n });\n}\n\nfunction reconcileAssistantIds(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[],\n sanitize?: (message: UIMessage) => UIMessage\n): UIMessage[] {\n if (serverMessages.length === 0) return incoming;\n\n const claimedServerIndices = new Set<number>();\n const exactMatchMap = new Map<number, number>();\n\n for (let i = 0; i < incoming.length; i++) {\n const serverIdx = serverMessages.findIndex(\n (sm, si) => !claimedServerIndices.has(si) && sm.id === incoming[i].id\n );\n if (serverIdx !== -1) {\n claimedServerIndices.add(serverIdx);\n exactMatchMap.set(i, serverIdx);\n }\n }\n\n return incoming.map((incomingMessage, incomingIdx) => {\n if (exactMatchMap.has(incomingIdx)) {\n return incomingMessage;\n }\n\n if (\n incomingMessage.role !== \"assistant\" ||\n hasToolCallPart(incomingMessage)\n ) {\n return incomingMessage;\n }\n\n const incomingKey = assistantContentKey(incomingMessage, sanitize);\n if (!incomingKey) {\n return incomingMessage;\n }\n\n for (let i = 0; i < serverMessages.length; i++) {\n if (claimedServerIndices.has(i)) continue;\n\n const serverMessage = serverMessages[i];\n if (\n serverMessage.role !== \"assistant\" ||\n hasToolCallPart(serverMessage)\n ) {\n continue;\n }\n\n if (assistantContentKey(serverMessage, sanitize) === incomingKey) {\n claimedServerIndices.add(i);\n return { ...incomingMessage, id: serverMessage.id };\n }\n }\n\n return incomingMessage;\n });\n}\n\nfunction hasToolCallPart(message: UIMessage): boolean {\n return message.parts.some((part) => \"toolCallId\" in part);\n}\n\nfunction findMessageByToolCallId(\n messages: readonly UIMessage[],\n toolCallId: string\n): UIMessage | undefined {\n for (const msg of messages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n if (\"toolCallId\" in part && part.toolCallId === toolCallId) {\n return msg;\n }\n }\n }\n return undefined;\n}\n"],"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,aAAa;AAC9D,sBAAuC,QAAQ,MAAM,SAAS;AAC/D,0BAAsB,mBAAmB,MAAM,iBAAiB;SAGhE,OAAM,KAAK;IACT,MAAM;IACN,MAAM,MAAM,SAAS;IACrB,OAAO;IACP,GAAI,MAAM,oBAAoB,OAC1B,EAAE,kBAAkB,MAAM,kBAAkB,GAC5C,EAAE;IACP,CAAgB;AAEnB,UAAO;;EAGT,KAAK,iBAAiB;GACpB,MAAM,oBAAoB,mBAAmB,OAAO,YAAY;AAChE,OAAI,qBAAqB,WAAW,mBAAmB;AACpD,sBAAwC,QAAQ;AACjD,0BAAsB,mBAAmB,MAAM,iBAAiB;;AAElE,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;;;;;;;;;AAYb,SAAS,sBACP,MACA,UACM;AACN,KAAI,YAAY,KAAM;CACtB,MAAM,IAAI;AACV,GAAE,mBAAmB;EACnB,GAAI,EAAE;EACN,GAAG;EACJ;;;;;;;AAQH,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;;;;;ACnab,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;GAG2B,CAAC,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,WAEpC,CACf,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,OAAA,MADV,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;;;;;AC9FrD,IAAa,8BAAb,MAAyC;CAQvC,YAAY,SAAyD;AAAxC,OAAA,UAAA;AAP7B,OAAQ,kBAAkB;AAC1B,OAAQ,mCAAmC;AAC3C,OAAQ,uBAAuB;AAC/B,OAAQ,cAAc;AACtB,OAAQ,wCAAwB,IAAI,KAAoC;AACxE,OAAQ,0CAA0B,IAAI,KAAiB;;CAIvD,IAAI,sBAA8B;AAChC,SAAO,KAAK;;CAGd,IAAI,yBAAiC;AACnC,SAAO,KAAK;;CAGd,OAAO,SAIuB;EAC5B,MAAM,4BACJ,QAAQ,cAAc,KAAK;AAE7B,MAAI,CAAC,QAAQ,mBAAmB,8BAA8B,EAC5D,QAAO;GACL,QAAQ;GACR,UAAU;GACV,gBAAgB;GAChB,iBAAiB;GAClB;EAGH,MAAM,cAAc,KAAK,UAAU,QAAQ,YAAY;AACvD,MAAI,gBAAgB,OAClB,QAAO;GACL,QAAQ;GACR,UAAU;GACV,gBAAgB;GAChB,iBAAiB;GAClB;AAGH,MAAI,gBAAgB,QAClB,QAAO;GACL,QAAQ;GACR,UAAU;GACV,gBAAgB;GAChB,iBAAiB;GAClB;EAGH,MAAM,iBAAiB,EAAE,KAAK;AAC9B,OAAK,mCAAmC;AAExC,MAAI,gBAAgB,YAAY,gBAAgB,QAC9C,QAAO;GACL,QAAQ;GACR,UAAU;GACV;GACA,iBAAiB;GAClB;AAGH,SAAO;GACL,QAAQ;GACR,UAAU;GACV;GACA,iBAAiB,KAAK,KAAK,GAAG,YAAY;GAC3C;;;;;;;;;;CAWH,eAA2B;AACzB,OAAK;EACL,MAAM,QAAQ,KAAK;EACnB,IAAI,WAAW;AACf,eAAa;AACX,OAAI,SAAU;AACd,cAAW;AACX,OAAI,KAAK,gBAAgB,MAAO;AAChC,QAAK,uBAAuB,KAAK,IAAI,GAAG,KAAK,uBAAuB,EAAE;;;CAI1E,aAAa,gBAAwC;AACnD,SACE,mBAAmB,QACnB,iBAAiB,KAAK;;CAI1B,MAAM,iBAAiB,aAAoC;EACzD,MAAM,cAAc,cAAc,KAAK,KAAK;AAC5C,MAAI,eAAe,EACjB;AAGF,QAAM,IAAI,SAAe,YAAY;GACnC,MAAM,uBAAuB;AAC3B,SAAK,wBAAwB,OAAO,eAAe;AACnD,aAAS;;GAEX,MAAM,QAAQ,iBAAiB;AAC7B,SAAK,sBAAsB,OAAO,MAAM;AACxC,oBAAgB;MACf,YAAY;AAEf,QAAK,sBAAsB,IAAI,MAAM;AACrC,QAAK,wBAAwB,IAAI,eAAe;IAChD;;CAGJ,uBAA6B;AAC3B,OAAK,MAAM,SAAS,KAAK,sBACvB,cAAa,MAAM;AAErB,OAAK,sBAAsB,OAAO;EAElC,MAAM,WAAW,CAAC,GAAG,KAAK,wBAAwB;AAClD,OAAK,wBAAwB,OAAO;AACpC,OAAK,MAAM,WAAW,SACpB,UAAS;;CAIb,QAAc;AACZ,OAAK;AACL,OAAK,uBAAuB;AAC5B,OAAK,sBAAsB;;CAG7B,MAAM,YAAY,kBAAsD;AACtE,SAAO,MAAM;AACX,SAAM,kBAAkB;AACxB,OAAI,KAAK,yBAAyB,EAAG;AACrC,SAAM,IAAI,SAAe,YAAY,WAAW,SAAS,EAAE,CAAC;;;CAIhE,UACE,aAC8B;AAC9B,MAAI,OAAO,gBAAgB,SACzB,QAAO;EAGT,MAAM,aAAa,YAAY;AAE/B,SAAO;GACL,UAAU;GACV,YACE,OAAO,eAAe,YACtB,OAAO,SAAS,WAAW,IAC3B,cAAc,IACV,aACA,KAAK,QAAQ;GACpB;;;;;AC3HL,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,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;;;;;;;CAUT,UAAU;EACR,MAAM,gBAAgB,KAAK,GAAmB;;;;;;AAO9C,MAAI,iBAAiB,cAAc,SAAS,GAAG;GAC7C,MAAM,SAAS,cAAc;AAC7B,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;;AAOrE,OAAK,GAAG;;;;sDAI0C,OAAO;;;AAGzD,OAAK,GAAG;;oDAEwC,OAAO;;;;CAOzD,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;;;;AAhThE,gBAAe,kBAAkB;;;;;;;;;;;;AClKnC,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;;;;;;;;;;;;AClIhB,MAAM,aAAa;AAEnB,IAAa,gBAAb,MAA2B;;AACzB,OAAQ,8BAAc,IAAI,KAA8B;;;;;;CAMxD,UAAU,IAAqC;AAC7C,MAAI,OAAO,OAAO,SAChB;AAGF,MAAI,CAAC,KAAK,YAAY,IAAI,GAAG,CAC3B,MAAK,YAAY,IAAI,IAAI,IAAI,iBAAiB,CAAC;AAGjD,SAAO,KAAK,YAAY,IAAI,GAAG,CAAE;;;;;;CAOnC,kBAAkB,IAAqC;AACrD,SAAO,KAAK,YAAY,IAAI,GAAG,EAAE;;;;;;;CAQnC,OAAO,IAAY,QAAwB;AACzC,OAAK,YAAY,IAAI,GAAG,EAAE,MAAM,OAAO;;;CAIzC,OAAO,IAAkB;AACvB,OAAK,YAAY,OAAO,GAAG;;;CAI7B,aAAmB;AACjB,OAAK,MAAM,cAAc,KAAK,YAAY,QAAQ,CAChD,YAAW,OAAO;AAEpB,OAAK,YAAY,OAAO;;;CAI1B,IAAI,IAAqB;AACvB,SAAO,KAAK,YAAY,IAAI,GAAG;;;CAIjC,IAAI,OAAe;AACjB,SAAO,KAAK,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkC1B,aAAa,IAAY,QAA6C;AACpE,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI,OAAO,SAAS;AAKlB,QAAK,UAAU,GAAG;AAClB,QAAK,OAAO,IAAI,OAAO,OAAO;AAC9B,UAAO;;EAGT,MAAM,iBAAiB,KAAK,OAAO,IAAI,OAAO,OAAO;AACrD,SAAO,iBAAiB,SAAS,UAAU,EAAE,MAAM,MAAM,CAAC;AAC1D,eAAa,OAAO,oBAAoB,SAAS,SAAS;;;;;;;;;;;;AC3F9D,SAAgB,gBACd,OACA,QACiE;AACjE,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;AACnB,MACE,gBAAgB,QAChB,KAAK,eAAe,OAAO,cAC3B,WAAW,QACX,OAAO,YAAY,SAAS,KAAK,MAAgB,EACjD;GACA,MAAM,eAAe,CAAC,GAAG,MAAM;AAC/B,gBAAa,KAAK,OAAO,MAAM,KAAK;AACpC,UAAO;IAAE,OAAO;IAAc,OAAO;IAAG;;;AAG5C,QAAO;;;;;;;;AAST,SAAgB,iBACd,YACA,QACA,eACA,WACgB;AAChB,QAAO;EACL;EACA,aAAa;GACX;GACA;GACA;GACD;EACD,QAAQ,UAAU;GAChB,GAAG;GACH,GAAI,kBAAkB,iBAClB;IACE,OAAO;IACP,WAAW,aAAa;IACzB,GACD;IAAE,OAAO;IAAoB;IAAQ,aAAa;IAAO;GAC9D;EACF;;;;;;;;AASH,SAAgB,mBACd,YACA,UACgB;AAChB,QAAO;EACL;EACA,aAAa,CAAC,mBAAmB,qBAAqB;EACtD,QAAQ,UAAU;GAChB,GAAG;GACH,OAAO,WAAW,uBAAuB;GACzC,UAAU;IACR,GAAI,KAAK;IACT;IACD;GACF;EACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9BH,SAAgB,qBAAqB,KAAuC;CAC1E,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;SAChB;AACN,SAAO;;CAGT,MAAM,WAAW,KAAK;AACtB,KAAI,CAAC,SAAU,QAAO;AAEtB,SAAQ,UAAR;EACE,KAAK,mBAAmB,iBACtB,QAAO;GACL,MAAM;GACN,IAAI,KAAK;GACT,MAAO,KAAK,QAA+C,EAAE;GAC9D;EAEH,KAAK,mBAAmB,WACtB,QAAO,EAAE,MAAM,SAAS;EAE1B,KAAK,mBAAmB,oBACtB,QAAO;GAAE,MAAM;GAAU,IAAI,KAAK;GAAc;EAElD,KAAK,mBAAmB,YACtB,QAAO;GACL,MAAM;GACN,YAAY,KAAK;GACjB,UAAW,KAAK,YAAuB;GACvC,QAAQ,KAAK;GACb,OAAO,KAAK;GACZ,WAAW,KAAK;GAChB,cAAc,KAAK;GACnB,aAAa,KAAK;GAOnB;EAEH,KAAK,mBAAmB,cACtB,QAAO;GACL,MAAM;GACN,YAAY,KAAK;GACjB,UAAU,KAAK;GACf,cAAc,KAAK;GACpB;EAEH,KAAK,mBAAmB,sBACtB,QAAO,EAAE,MAAM,yBAAyB;EAE1C,KAAK,mBAAmB,kBACtB,QAAO;GAAE,MAAM;GAAqB,IAAI,KAAK;GAAc;EAE7D,KAAK,mBAAmB,cACtB,QAAO;GACL,MAAM;GACN,UAAW,KAAK,YAA0B,EAAE;GAC7C;EAEH,QACE,QAAO;;;;;;;;;;;;;;;;;;ACzGb,SAAgB,kBACd,UACA,gBACA,uBACa;AAKb,QAAO,sBAJuB,uBAC5B,UACA,eAGqB,EACrB,gBACA,sBACD;;;;;;;AAQH,SAAgB,mBACd,SACA,gBACW;AACX,KAAI,QAAQ,SAAS,YACnB,QAAO;AAGT,MAAK,MAAM,QAAQ,QAAQ,MACzB,KAAI,gBAAgB,QAAQ,KAAK,YAAY;EAC3C,MAAM,aAAa,KAAK;EACxB,MAAM,WAAW,wBAAwB,gBAAgB,WAAW;AACpE,MAAI,YAAY,SAAS,OAAO,QAAQ,GACtC,QAAO;GAAE,GAAG;GAAS,IAAI,SAAS;GAAI;;AAK5C,QAAO;;;;;;AAOT,SAAgB,oBACd,SACA,UACoB;AACpB,KAAI,QAAQ,SAAS,YACnB;CAEF,MAAM,YAAY,WAAW,SAAS,QAAQ,GAAG;AACjD,QAAO,KAAK,UAAU,UAAU,MAAM;;AAGxC,SAAS,uBACP,UACA,gBACa;CACb,MAAM,oCAAoB,IAAI,KAAsB;AACpD,MAAK,MAAM,OAAO,gBAAgB;AAChC,MAAI,IAAI,SAAS,YAAa;AAC9B,OAAK,MAAM,QAAQ,IAAI,MACrB,KACE,gBAAgB,QAChB,WAAW,QACX,KAAK,UAAU,sBACf,YAAY,KAEZ,mBAAkB,IAChB,KAAK,YACJ,KAA6B,OAC/B;;AAKP,KAAI,kBAAkB,SAAS,EAAG,QAAO;AAEzC,QAAO,SAAS,KAAK,QAAQ;AAC3B,MAAI,IAAI,SAAS,YAAa,QAAO;EAErC,IAAI,aAAa;EACjB,MAAM,eAAe,IAAI,MAAM,KAAK,SAAS;AAC3C,OACE,gBAAgB,QAChB,WAAW,SACV,KAAK,UAAU,qBACd,KAAK,UAAU,wBACf,KAAK,UAAU,yBACjB,kBAAkB,IAAI,KAAK,WAAqB,EAChD;AACA,iBAAa;AACb,WAAO;KACL,GAAG;KACH,OAAO;KACP,QAAQ,kBAAkB,IAAI,KAAK,WAAqB;KACzD;;AAEH,UAAO;IACP;AAEF,SAAO,aAAa;GAAE,GAAG;GAAK,OAAO;GAAc,GAAG;GACtD;;AAGJ,SAAS,sBACP,UACA,gBACA,UACa;AACb,KAAI,eAAe,WAAW,EAAG,QAAO;CAExC,MAAM,uCAAuB,IAAI,KAAa;CAC9C,MAAM,gCAAgB,IAAI,KAAqB;AAE/C,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,YAAY,eAAe,WAC9B,IAAI,OAAO,CAAC,qBAAqB,IAAI,GAAG,IAAI,GAAG,OAAO,SAAS,GAAG,GACpE;AACD,MAAI,cAAc,IAAI;AACpB,wBAAqB,IAAI,UAAU;AACnC,iBAAc,IAAI,GAAG,UAAU;;;AAInC,QAAO,SAAS,KAAK,iBAAiB,gBAAgB;AACpD,MAAI,cAAc,IAAI,YAAY,CAChC,QAAO;AAGT,MACE,gBAAgB,SAAS,eACzB,gBAAgB,gBAAgB,CAEhC,QAAO;EAGT,MAAM,cAAc,oBAAoB,iBAAiB,SAAS;AAClE,MAAI,CAAC,YACH,QAAO;AAGT,OAAK,IAAI,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK;AAC9C,OAAI,qBAAqB,IAAI,EAAE,CAAE;GAEjC,MAAM,gBAAgB,eAAe;AACrC,OACE,cAAc,SAAS,eACvB,gBAAgB,cAAc,CAE9B;AAGF,OAAI,oBAAoB,eAAe,SAAS,KAAK,aAAa;AAChE,yBAAqB,IAAI,EAAE;AAC3B,WAAO;KAAE,GAAG;KAAiB,IAAI,cAAc;KAAI;;;AAIvD,SAAO;GACP;;AAGJ,SAAS,gBAAgB,SAA6B;AACpD,QAAO,QAAQ,MAAM,MAAM,SAAS,gBAAgB,KAAK;;AAG3D,SAAS,wBACP,UACA,YACuB;AACvB,MAAK,MAAM,OAAO,UAAU;AAC1B,MAAI,IAAI,SAAS,YAAa;AAC9B,OAAK,MAAM,QAAQ,IAAI,MACrB,KAAI,gBAAgB,QAAQ,KAAK,eAAe,WAC9C,QAAO"}
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/submit-concurrency.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","../../src/chat/message-reconciler.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 mergeProviderMetadata(lastReasoningPart, chunk.providerMetadata);\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 ...(chunk.providerMetadata != null\n ? { providerMetadata: chunk.providerMetadata }\n : {})\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 mergeProviderMetadata(lastReasoningPart, chunk.providerMetadata);\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 // Idempotent against an existing tool part with the same toolCallId.\n // Some providers (notably the OpenAI Responses API) replay prior\n // tool calls in continuation streams as a fresh `tool-input-start`\n // → `tool-input-delta` → `tool-input-available` →\n // `tool-output-available` sequence carrying the original toolCallId\n // and original output. Without this guard a replay would push a\n // duplicate part into the streaming message *and* clobber the\n // original part's state when the AI SDK's mutate-in-place\n // `updateToolPart` processes the replay on the client (issue #1404).\n // A model that genuinely wants a fresh tool call always emits a\n // new toolCallId, so an existing match is never a legitimate\n // \"start over\".\n const existing = findToolPartByCallId(parts, chunk.toolCallId);\n if (existing) {\n return true;\n }\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 // Only mutate input while the tool is still actively input-streaming.\n // Deltas arriving after the tool has already advanced (input-available\n // or any terminal state) are provider replay and must not regress\n // a fully-formed input back to a partial one.\n const toolPart = findToolPartByCallId(parts, chunk.toolCallId);\n if (\n toolPart &&\n (toolPart as Record<string, unknown>).state === \"input-streaming\"\n ) {\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 // Only advance from the streaming-input phase. Once the tool is\n // already at input-available or any terminal state\n // (output-available, output-error, output-denied,\n // approval-requested, approval-responded), this chunk is a\n // provider replay and must not regress state or overwrite a\n // resolved input/output. See the comment on tool-input-start.\n if (p.state === \"input-streaming\") {\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 }\n return true;\n }\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 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 // First-write-wins: a tool that's already terminal must not be\n // regressed (or re-decided as an error) by a later chunk. A\n // tool-input-error here is either provider replay or a confused\n // upstream — preserve the existing terminal state.\n if (\n p.state === \"output-available\" ||\n p.state === \"output-error\" ||\n p.state === \"output-denied\"\n ) {\n return true;\n }\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 * Returns true if `chunk` would be a no-op replay against the already-known\n * `parts` — i.e. some upstream is re-emitting events for a tool call that\n * the message has already advanced past.\n *\n * Used by stream broadcasters to suppress re-broadcasting these chunks to\n * connected clients. AI SDK v6's `updateToolPart` mutates an existing tool\n * part in place when a chunk arrives with a matching `toolCallId`, so a\n * replayed `tool-input-start` would clobber an `output-available` part back\n * to `input-streaming` on the client (issue #1404).\n *\n * Only returns true when re-broadcasting would *visibly regress* state on\n * a v6 client. Safe-by-construction chunk types (e.g. `tool-output-available`\n * carrying the same output the part already has) return false.\n *\n * Conditions:\n * - `tool-input-start` for a `toolCallId` that already exists in `parts`.\n * - `tool-input-delta` for a `toolCallId` whose existing part is no longer\n * `input-streaming`.\n * - `tool-input-available` for a `toolCallId` whose existing part is no\n * longer `input-streaming` (i.e. has already advanced to `input-available`\n * or any terminal state).\n */\nexport function isReplayChunk(\n parts: MessagePart[],\n chunk: StreamChunkData\n): boolean {\n if (\n chunk.type !== \"tool-input-start\" &&\n chunk.type !== \"tool-input-delta\" &&\n chunk.type !== \"tool-input-available\"\n ) {\n return false;\n }\n if (!chunk.toolCallId) return false;\n const existing = findToolPartByCallId(parts, chunk.toolCallId);\n if (!existing) return false;\n if (chunk.type === \"tool-input-start\") return true;\n const state = (existing as Record<string, unknown>).state;\n return state !== \"input-streaming\";\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 * Shallow-merges providerMetadata from a chunk onto an existing part.\n * Preserves any metadata already on the part (e.g. from earlier deltas)\n * while adding new keys from the chunk. This is critical for providers\n * like Anthropic that emit the thinking block signature on reasoning-end.\n */\nfunction mergeProviderMetadata(\n part: MessagePart,\n metadata: Record<string, unknown> | undefined\n): void {\n if (metadata == null) return;\n const p = part as Record<string, unknown>;\n p.providerMetadata = {\n ...(p.providerMetadata as Record<string, unknown> | undefined),\n ...metadata\n };\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","import type { MessageConcurrency } from \"./lifecycle\";\n\nexport type NormalizedMessageConcurrency =\n | \"queue\"\n | \"latest\"\n | \"merge\"\n | \"drop\"\n | {\n strategy: \"debounce\";\n debounceMs: number;\n };\n\nexport type SubmitConcurrencyDecision = {\n action: \"execute\" | \"drop\";\n strategy: NormalizedMessageConcurrency | null;\n submitSequence: number | null;\n debounceUntilMs: number | null;\n};\n\nexport class SubmitConcurrencyController {\n private _submitSequence = 0;\n private _latestOverlappingSubmitSequence = 0;\n private _pendingEnqueueCount = 0;\n private _resetEpoch = 0;\n private _activeDebounceTimers = new Set<ReturnType<typeof setTimeout>>();\n private _activeDebounceResolves = new Set<() => void>();\n\n constructor(private readonly options: { defaultDebounceMs: number }) {}\n\n get pendingEnqueueCount(): number {\n return this._pendingEnqueueCount;\n }\n\n get overlappingSubmitCount(): number {\n return this._latestOverlappingSubmitSequence;\n }\n\n decide(options: {\n concurrency: MessageConcurrency;\n isSubmitMessage: boolean;\n queuedTurns: number;\n }): SubmitConcurrencyDecision {\n const queuedTurnsInCurrentEpoch =\n options.queuedTurns + this._pendingEnqueueCount;\n\n if (!options.isSubmitMessage || queuedTurnsInCurrentEpoch === 0) {\n return {\n action: \"execute\",\n strategy: null,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n const concurrency = this.normalize(options.concurrency);\n if (concurrency === \"drop\") {\n return {\n action: \"drop\",\n strategy: concurrency,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n if (concurrency === \"queue\") {\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence: null,\n debounceUntilMs: null\n };\n }\n\n const submitSequence = ++this._submitSequence;\n this._latestOverlappingSubmitSequence = submitSequence;\n\n if (concurrency === \"latest\" || concurrency === \"merge\") {\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence,\n debounceUntilMs: null\n };\n }\n\n return {\n action: \"execute\",\n strategy: concurrency,\n submitSequence,\n debounceUntilMs: Date.now() + concurrency.debounceMs\n };\n }\n\n /**\n * Mark a submit as accepted and in-flight between admission and turn\n * queue registration. Returns an idempotent `release()` function that\n * must be called when the submit either reaches the turn queue or is\n * abandoned. The returned function is bound to the controller's reset\n * epoch — releases from before the most recent `reset()` are no-ops,\n * so post-reset submits keep an accurate count.\n */\n beginEnqueue(): () => void {\n this._pendingEnqueueCount++;\n const epoch = this._resetEpoch;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n if (this._resetEpoch !== epoch) return;\n this._pendingEnqueueCount = Math.max(0, this._pendingEnqueueCount - 1);\n };\n }\n\n isSuperseded(submitSequence: number | null): boolean {\n return (\n submitSequence !== null &&\n submitSequence < this._latestOverlappingSubmitSequence\n );\n }\n\n async waitForTimestamp(timestampMs: number): Promise<void> {\n const remainingMs = timestampMs - Date.now();\n if (remainingMs <= 0) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n const wrappedResolve = () => {\n this._activeDebounceResolves.delete(wrappedResolve);\n resolve();\n };\n const timer = setTimeout(() => {\n this._activeDebounceTimers.delete(timer);\n wrappedResolve();\n }, remainingMs);\n\n this._activeDebounceTimers.add(timer);\n this._activeDebounceResolves.add(wrappedResolve);\n });\n }\n\n cancelActiveDebounce(): void {\n for (const timer of this._activeDebounceTimers) {\n clearTimeout(timer);\n }\n this._activeDebounceTimers.clear();\n\n const resolves = [...this._activeDebounceResolves];\n this._activeDebounceResolves.clear();\n for (const resolve of resolves) {\n resolve();\n }\n }\n\n reset(): void {\n this._resetEpoch++;\n this._pendingEnqueueCount = 0;\n this.cancelActiveDebounce();\n }\n\n async waitForIdle(waitForQueueIdle: () => Promise<void>): Promise<void> {\n while (true) {\n await waitForQueueIdle();\n if (this._pendingEnqueueCount === 0) return;\n await new Promise<void>((resolve) => setTimeout(resolve, 5));\n }\n }\n\n private normalize(\n concurrency: MessageConcurrency\n ): NormalizedMessageConcurrency {\n if (typeof concurrency === \"string\") {\n return concurrency;\n }\n\n const debounceMs = concurrency.debounceMs;\n\n return {\n strategy: \"debounce\",\n debounceMs:\n typeof debounceMs === \"number\" &&\n Number.isFinite(debounceMs) &&\n debounceMs >= 0\n ? debounceMs\n : this.options.defaultDebounceMs\n };\n }\n}\n","/**\n * Broadcast stream state machine.\n *\n * Manages the lifecycle of a StreamAccumulator for broadcast/resume\n * streams — the path where this client is *observing* a stream owned\n * by another tab or resumed after reconnect, rather than the transport-\n * owned path that feeds directly into useChat.\n *\n * The transition function is pure (no React, no WebSocket, no side\n * effects). Callers dispatch events and apply the returned state +\n * messagesUpdate. Side effects (sending ACKs, calling onData) stay\n * in the caller.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { StreamAccumulator } from \"./stream-accumulator\";\nimport type { StreamChunkData } from \"./message-builder\";\n\n// ── State ──────────────────────────────────────────────────────────\n\nexport type BroadcastStreamState =\n | { status: \"idle\" }\n | {\n status: \"observing\";\n streamId: string;\n accumulator: StreamAccumulator;\n };\n\n// ── Events ─────────────────────────────────────────────────────────\n\nexport type BroadcastStreamEvent =\n | {\n type: \"response\";\n streamId: string;\n /** Fallback message ID for a new accumulator (ignored if one exists for this stream). */\n messageId: string;\n chunkData?: unknown;\n done?: boolean;\n error?: boolean;\n replay?: boolean;\n replayComplete?: boolean;\n continuation?: boolean;\n /** Required when continuation=true so the accumulator can pick up existing parts. */\n currentMessages?: UIMessage[];\n }\n | {\n type: \"resume-fallback\";\n streamId: string;\n messageId: string;\n }\n | { type: \"clear\" };\n\n// ── Result ─────────────────────────────────────────────────────────\n\nexport interface TransitionResult {\n state: BroadcastStreamState;\n messagesUpdate?: (prev: UIMessage[]) => UIMessage[];\n isStreaming: boolean;\n}\n\n// ── Transition ─────────────────────────────────────────────────────\n\nexport function transition(\n state: BroadcastStreamState,\n event: BroadcastStreamEvent\n): TransitionResult {\n switch (event.type) {\n case \"clear\":\n return { state: { status: \"idle\" }, isStreaming: false };\n\n case \"resume-fallback\": {\n const accumulator = new StreamAccumulator({\n messageId: event.messageId\n });\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n isStreaming: true\n };\n }\n\n case \"response\": {\n let accumulator: StreamAccumulator;\n\n if (state.status === \"idle\" || state.streamId !== event.streamId) {\n let messageId = event.messageId;\n let existingParts: UIMessage[\"parts\"] | undefined;\n let existingMetadata: Record<string, unknown> | undefined;\n\n if (event.continuation && event.currentMessages) {\n for (let i = event.currentMessages.length - 1; i >= 0; i--) {\n if (event.currentMessages[i].role === \"assistant\") {\n messageId = event.currentMessages[i].id;\n existingParts = [...event.currentMessages[i].parts];\n if (event.currentMessages[i].metadata != null) {\n existingMetadata = {\n ...(event.currentMessages[i].metadata as Record<\n string,\n unknown\n >)\n };\n }\n break;\n }\n }\n }\n\n accumulator = new StreamAccumulator({\n messageId,\n continuation: event.continuation,\n existingParts,\n existingMetadata\n });\n } else {\n accumulator = state.accumulator;\n }\n\n if (event.chunkData) {\n accumulator.applyChunk(event.chunkData as StreamChunkData);\n }\n\n let messagesUpdate: ((prev: UIMessage[]) => UIMessage[]) | undefined;\n\n if (event.done) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n return {\n state: { status: \"idle\" },\n messagesUpdate,\n isStreaming: false\n };\n }\n\n if (event.chunkData && !event.replay) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n } else if (event.replayComplete) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n }\n\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n messagesUpdate,\n isStreaming: true\n };\n }\n }\n}\n","/**\n * Wire protocol message type constants for the cf_agent_chat_* protocol.\n *\n * These are the string values used on the wire between agent servers and\n * clients. Both @cloudflare/ai-chat (via its MessageType enum) and\n * @cloudflare/think use these values.\n */\nexport const CHAT_MESSAGE_TYPES = {\n CHAT_MESSAGES: \"cf_agent_chat_messages\",\n USE_CHAT_REQUEST: \"cf_agent_use_chat_request\",\n USE_CHAT_RESPONSE: \"cf_agent_use_chat_response\",\n CHAT_CLEAR: \"cf_agent_chat_clear\",\n CHAT_REQUEST_CANCEL: \"cf_agent_chat_request_cancel\",\n STREAM_RESUMING: \"cf_agent_stream_resuming\",\n STREAM_RESUME_ACK: \"cf_agent_stream_resume_ack\",\n STREAM_RESUME_REQUEST: \"cf_agent_stream_resume_request\",\n STREAM_RESUME_NONE: \"cf_agent_stream_resume_none\",\n TOOL_RESULT: \"cf_agent_tool_result\",\n TOOL_APPROVAL: \"cf_agent_tool_approval\",\n MESSAGE_UPDATED: \"cf_agent_message_updated\"\n} 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\nconst NOOP = () => {};\n\nexport class AbortRegistry {\n private controllers = new Map<string, AbortController>();\n\n /**\n * Get or create an AbortController for the given ID and return its signal.\n * Creates the controller lazily on first access.\n */\n getSignal(id: string): AbortSignal | undefined {\n if (typeof id !== \"string\") {\n return undefined;\n }\n\n if (!this.controllers.has(id)) {\n this.controllers.set(id, new AbortController());\n }\n\n return this.controllers.get(id)!.signal;\n }\n\n /**\n * Get the signal for an existing controller without creating one.\n * Returns undefined if no controller exists for this ID.\n */\n getExistingSignal(id: string): AbortSignal | undefined {\n return this.controllers.get(id)?.signal;\n }\n\n /**\n * Cancel a specific request by aborting its controller. Optionally\n * propagate a reason — surfaces as `signal.reason` on the registry's\n * controller and through any `AbortError` it produces downstream.\n */\n cancel(id: string, reason?: unknown): void {\n this.controllers.get(id)?.abort(reason);\n }\n\n /** Remove a controller after the request completes. */\n remove(id: string): void {\n this.controllers.delete(id);\n }\n\n /** 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 * Link an external `AbortSignal` to the controller for `id`. When the\n * external signal aborts, the registry's controller is cancelled —\n * propagating the abort reason — exactly the same way an internal\n * cancel would (e.g. via a `chat-request-cancel` WebSocket message).\n *\n * This is the integration point for callers that drive a chat turn\n * programmatically and want to cancel it from outside without knowing\n * the internally-generated request id (e.g. the helper-as-sub-agent\n * pattern, where a parent's `AbortSignal` from the AI SDK tool\n * `execute` needs to land inside a `Think.saveMessages` call running\n * on a child DO).\n *\n * Behavior:\n *\n * - Passing `undefined` is a no-op and returns a no-op detacher, so\n * callers can unconditionally call this with `options?.signal`.\n * - If the external signal is already aborted, the registry's\n * controller is created (if needed) and cancelled synchronously.\n * - Otherwise a one-shot `abort` listener is attached. The returned\n * function detaches it.\n *\n * **Always call the returned detacher in a `finally` block** — the\n * external signal may outlive the request (a parent chat turn that\n * drives many helper turns reuses one signal across all of them) and\n * leaving listeners attached pins closures and grows the listener\n * list on each turn.\n *\n * @returns A detacher function. Call it after the request finishes\n * (success or failure) to remove the abort listener from `signal`.\n */\n linkExternal(id: string, signal: AbortSignal | undefined): () => void {\n if (!signal) return NOOP;\n\n if (signal.aborted) {\n // Ensure the registry controller for `id` exists, then cancel it.\n // Calling getSignal first means an early external abort still\n // produces a controller for downstream observers (`getExistingSignal`)\n // rather than a silently-empty registry.\n this.getSignal(id);\n this.cancel(id, signal.reason);\n return NOOP;\n }\n\n const listener = () => this.cancel(id, signal.reason);\n signal.addEventListener(\"abort\", listener, { once: true });\n return () => signal.removeEventListener(\"abort\", listener);\n }\n}\n","/**\n * Tool State — shared update builders and applicator for tool part state changes.\n *\n * Used by both AIChatAgent and Think to apply tool results and approvals\n * to message parts. Each agent handles find-message, persist, and broadcast\n * in their own way; this module provides the state matching and update logic.\n */\n\n/**\n * Describes an update to apply to a tool part.\n */\nexport type ToolPartUpdate = {\n toolCallId: string;\n matchStates: string[];\n apply: (part: Record<string, unknown>) => Record<string, unknown>;\n};\n\n/**\n * Apply a tool part update to a parts array.\n * Finds the first part matching `update.toolCallId` in one of `update.matchStates`,\n * applies the update immutably, and returns the new parts array with the index.\n *\n * Returns `null` if no matching part was found.\n */\nexport function applyToolUpdate(\n parts: Array<Record<string, unknown>>,\n update: ToolPartUpdate\n): { parts: Array<Record<string, unknown>>; index: number } | null {\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n if (\n \"toolCallId\" in part &&\n part.toolCallId === update.toolCallId &&\n \"state\" in part &&\n update.matchStates.includes(part.state as string)\n ) {\n const updatedParts = [...parts];\n updatedParts[i] = update.apply(part);\n return { parts: updatedParts, index: i };\n }\n }\n return null;\n}\n\n/**\n * Build an update descriptor for applying a tool result.\n *\n * Matches parts in `input-available`, `approval-requested`, or `approval-responded` state.\n * Sets state to `output-available` (with output) or `output-error` (with errorText).\n */\nexport function toolResultUpdate(\n toolCallId: string,\n output: unknown,\n overrideState?: \"output-error\",\n errorText?: string\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\n \"input-available\",\n \"approval-requested\",\n \"approval-responded\"\n ],\n apply: (part) => ({\n ...part,\n ...(overrideState === \"output-error\"\n ? {\n state: \"output-error\",\n errorText: errorText ?? \"Tool execution denied by user\"\n }\n : { state: \"output-available\", output, preliminary: false })\n })\n };\n}\n\n/**\n * Build an update descriptor for applying a tool approval.\n *\n * Matches parts in `input-available` or `approval-requested` state.\n * Sets state to `approval-responded` (if approved) or `output-denied` (if denied).\n */\nexport function toolApprovalUpdate(\n toolCallId: string,\n approved: boolean\n): ToolPartUpdate {\n return {\n toolCallId,\n matchStates: [\"input-available\", \"approval-requested\"],\n apply: (part) => ({\n ...part,\n state: approved ? \"approval-responded\" : \"output-denied\",\n approval: {\n ...(part.approval as Record<string, unknown> | undefined),\n approved\n }\n })\n };\n}\n","/**\n * Protocol Message Parser — typed parsing of cf_agent_chat_* WebSocket messages.\n *\n * Parses raw WebSocket messages into a discriminated union of protocol events.\n * Both AIChatAgent and Think can use this instead of manual JSON.parse + type checking.\n */\n\nimport { CHAT_MESSAGE_TYPES } from \"./protocol\";\n\n/**\n * Discriminated union of all incoming chat protocol events.\n *\n * Each agent handles the events it cares about and ignores the rest.\n * Returns `null` for non-JSON messages or unrecognized types.\n */\nexport type ChatProtocolEvent =\n | {\n type: \"chat-request\";\n id: string;\n init: { method?: string; body?: string; [key: string]: unknown };\n }\n | { type: \"clear\" }\n | { type: \"cancel\"; id: string }\n | {\n type: \"tool-result\";\n toolCallId: string;\n toolName: string;\n output: unknown;\n state?: string;\n errorText?: string;\n autoContinue?: boolean;\n clientTools?: Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>;\n }\n | {\n type: \"tool-approval\";\n toolCallId: string;\n approved: boolean;\n autoContinue?: boolean;\n }\n | { type: \"stream-resume-request\" }\n | { type: \"stream-resume-ack\"; id: string }\n | { type: \"messages\"; messages: unknown[] };\n\n/**\n * Parse a raw WebSocket message string into a typed protocol event.\n *\n * Returns `null` if the message is not valid JSON or not a recognized\n * protocol message type. Callers should fall through to the user's\n * `onMessage` handler when `null` is returned.\n *\n * @example\n * ```typescript\n * const event = parseProtocolMessage(rawMessage);\n * if (!event) return userOnMessage(connection, rawMessage);\n *\n * switch (event.type) {\n * case \"chat-request\": { ... }\n * case \"clear\": { ... }\n * case \"tool-result\": { ... }\n * }\n * ```\n */\nexport function parseProtocolMessage(raw: string): ChatProtocolEvent | null {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n\n const wireType = data.type as string | undefined;\n if (!wireType) return null;\n\n switch (wireType) {\n case CHAT_MESSAGE_TYPES.USE_CHAT_REQUEST:\n return {\n type: \"chat-request\",\n id: data.id as string,\n init: (data.init as { method?: string; body?: string }) ?? {}\n };\n\n case CHAT_MESSAGE_TYPES.CHAT_CLEAR:\n return { type: \"clear\" };\n\n case CHAT_MESSAGE_TYPES.CHAT_REQUEST_CANCEL:\n return { type: \"cancel\", id: data.id as string };\n\n case CHAT_MESSAGE_TYPES.TOOL_RESULT:\n return {\n type: \"tool-result\",\n toolCallId: data.toolCallId as string,\n toolName: (data.toolName as string) ?? \"\",\n output: data.output,\n state: data.state as string | undefined,\n errorText: data.errorText as string | undefined,\n autoContinue: data.autoContinue as boolean | undefined,\n clientTools: data.clientTools as\n | Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>\n | undefined\n };\n\n case CHAT_MESSAGE_TYPES.TOOL_APPROVAL:\n return {\n type: \"tool-approval\",\n toolCallId: data.toolCallId as string,\n approved: data.approved as boolean,\n autoContinue: data.autoContinue as boolean | undefined\n };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_REQUEST:\n return { type: \"stream-resume-request\" };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_ACK:\n return { type: \"stream-resume-ack\", id: data.id as string };\n\n case CHAT_MESSAGE_TYPES.CHAT_MESSAGES:\n return {\n type: \"messages\",\n messages: (data.messages as unknown[]) ?? []\n };\n\n default:\n return null;\n }\n}\n","/**\n * Message reconciliation — pure functions for aligning client messages\n * with server state during persistence.\n *\n * Three strategies applied in order:\n * 1. Merge server-known tool outputs into stale client messages\n * 2. Reconcile assistant IDs (exact match → content-key → toolCallId)\n * 3. Per-message toolCallId dedup for persistence\n */\n\nimport type { UIMessage } from \"ai\";\n\n/**\n * Reconcile incoming client messages against server state.\n *\n * 1. Merges server-known tool outputs into incoming messages that still\n * show stale states (input-available, approval-requested, approval-responded)\n * 2. Reconciles assistant IDs: exact match → content-key match → toolCallId match\n *\n * @param incoming - Messages from the client\n * @param serverMessages - Current server-side messages (source of truth)\n * @param sanitizeForContentKey - Function to sanitize a message before computing\n * its content key (typically strips ephemeral provider metadata)\n * @returns Reconciled messages ready for persistence\n */\nexport function reconcileMessages(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[],\n sanitizeForContentKey?: (message: UIMessage) => UIMessage\n): UIMessage[] {\n const withMergedToolOutputs = mergeServerToolOutputs(\n incoming,\n serverMessages\n );\n return reconcileAssistantIds(\n withMergedToolOutputs,\n serverMessages,\n sanitizeForContentKey\n );\n}\n\n/**\n * For a single message, resolve its ID by matching toolCallId against server state.\n * Prevents duplicate DB rows when client IDs differ from server IDs.\n * Tool call IDs are unique per conversation, so matching is safe regardless of state.\n */\nexport function resolveToolMergeId(\n message: UIMessage,\n serverMessages: readonly UIMessage[]\n): UIMessage {\n if (message.role !== \"assistant\") {\n return message;\n }\n\n for (const part of message.parts) {\n if (\"toolCallId\" in part && part.toolCallId) {\n const toolCallId = part.toolCallId as string;\n const existing = findMessageByToolCallId(serverMessages, toolCallId);\n if (existing && existing.id !== message.id) {\n return { ...message, id: existing.id };\n }\n }\n }\n\n return message;\n}\n\n/**\n * Content key for assistant messages used for dedup of identical short replies.\n * Returns JSON of sanitized parts, or undefined for non-assistant messages.\n */\nexport function assistantContentKey(\n message: UIMessage,\n sanitize?: (message: UIMessage) => UIMessage\n): string | undefined {\n if (message.role !== \"assistant\") {\n return undefined;\n }\n const sanitized = sanitize ? sanitize(message) : message;\n return JSON.stringify(sanitized.parts);\n}\n\nfunction mergeServerToolOutputs(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[]\n): UIMessage[] {\n const serverToolOutputs = new Map<string, unknown>();\n for (const msg of serverMessages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"output-available\" &&\n \"output\" in part\n ) {\n serverToolOutputs.set(\n part.toolCallId as string,\n (part as { output: unknown }).output\n );\n }\n }\n }\n\n if (serverToolOutputs.size === 0) return incoming;\n\n return incoming.map((msg) => {\n if (msg.role !== \"assistant\") return msg;\n\n let hasChanges = false;\n const updatedParts = msg.parts.map((part) => {\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n (part.state === \"input-available\" ||\n part.state === \"approval-requested\" ||\n part.state === \"approval-responded\") &&\n serverToolOutputs.has(part.toolCallId as string)\n ) {\n hasChanges = true;\n return {\n ...part,\n state: \"output-available\" as const,\n output: serverToolOutputs.get(part.toolCallId as string)\n };\n }\n return part;\n }) as UIMessage[\"parts\"];\n\n return hasChanges ? { ...msg, parts: updatedParts } : msg;\n });\n}\n\nfunction reconcileAssistantIds(\n incoming: UIMessage[],\n serverMessages: readonly UIMessage[],\n sanitize?: (message: UIMessage) => UIMessage\n): UIMessage[] {\n if (serverMessages.length === 0) return incoming;\n\n const claimedServerIndices = new Set<number>();\n const exactMatchMap = new Map<number, number>();\n\n for (let i = 0; i < incoming.length; i++) {\n const serverIdx = serverMessages.findIndex(\n (sm, si) => !claimedServerIndices.has(si) && sm.id === incoming[i].id\n );\n if (serverIdx !== -1) {\n claimedServerIndices.add(serverIdx);\n exactMatchMap.set(i, serverIdx);\n }\n }\n\n return incoming.map((incomingMessage, incomingIdx) => {\n if (exactMatchMap.has(incomingIdx)) {\n return incomingMessage;\n }\n\n if (\n incomingMessage.role !== \"assistant\" ||\n hasToolCallPart(incomingMessage)\n ) {\n return incomingMessage;\n }\n\n const incomingKey = assistantContentKey(incomingMessage, sanitize);\n if (!incomingKey) {\n return incomingMessage;\n }\n\n for (let i = 0; i < serverMessages.length; i++) {\n if (claimedServerIndices.has(i)) continue;\n\n const serverMessage = serverMessages[i];\n if (\n serverMessage.role !== \"assistant\" ||\n hasToolCallPart(serverMessage)\n ) {\n continue;\n }\n\n if (assistantContentKey(serverMessage, sanitize) === incomingKey) {\n claimedServerIndices.add(i);\n return { ...incomingMessage, id: serverMessage.id };\n }\n }\n\n return incomingMessage;\n });\n}\n\nfunction hasToolCallPart(message: UIMessage): boolean {\n return message.parts.some((part) => \"toolCallId\" in part);\n}\n\nfunction findMessageByToolCallId(\n messages: readonly UIMessage[],\n toolCallId: string\n): UIMessage | undefined {\n for (const msg of messages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n if (\"toolCallId\" in part && part.toolCallId === toolCallId) {\n return msg;\n }\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "durable objects"
10
10
  ],
11
11
  "type": "module",
12
- "version": "0.11.8",
12
+ "version": "0.11.9",
13
13
  "license": "MIT",
14
14
  "repository": {
15
15
  "directory": "packages/agents",