agents 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/dist/{agent-routing-DE5zmCQ8.d.ts → agent-routing-0DR0Tk2v.d.ts} +270 -190
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-routing.js.map +1 -1
  4. package/dist/agent-tool-types.d.ts +13 -7
  5. package/dist/agent-tool-types.js +23 -1
  6. package/dist/agent-tool-types.js.map +1 -1
  7. package/dist/{agent-tools-y7zLfw4Q.js → agent-tools-CdJC-8Dc.js} +72 -9
  8. package/dist/agent-tools-CdJC-8Dc.js.map +1 -0
  9. package/dist/{agent-tools-DtXMTDGM.d.ts → agent-tools-YrATZJu2.d.ts} +29 -15
  10. package/dist/agent-tools.d.ts +6 -4
  11. package/dist/agent-tools.js.map +1 -1
  12. package/dist/ai-fm7xx1BK.js +312 -0
  13. package/dist/ai-fm7xx1BK.js.map +1 -0
  14. package/dist/base64-redaction-C1Uu29t5.js +59 -0
  15. package/dist/base64-redaction-C1Uu29t5.js.map +1 -0
  16. package/dist/browser/ai-sdk.d.ts +90 -0
  17. package/dist/browser/ai-sdk.js +51 -0
  18. package/dist/browser/ai-sdk.js.map +1 -0
  19. package/dist/browser/ai.d.ts +4 -4
  20. package/dist/browser/ai.js +1 -347
  21. package/dist/browser/index.d.ts +81 -79
  22. package/dist/browser/index.js +361 -2
  23. package/dist/browser/index.js.map +1 -0
  24. package/dist/browser/tanstack-ai.d.ts +96 -1
  25. package/dist/browser/tanstack-ai.js +62 -2
  26. package/dist/browser/tanstack-ai.js.map +1 -1
  27. package/dist/browser-DE3d-RLk.d.ts +275 -0
  28. package/dist/browser-tool-CQ10e6Kj.js +480 -0
  29. package/dist/browser-tool-CQ10e6Kj.js.map +1 -0
  30. package/dist/browser-tool-DuNdlwZ9.d.ts +73 -0
  31. package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
  32. package/dist/capability-B4WbF81e.js.map +1 -0
  33. package/dist/{capability-runner-Be_-PLR1.d.ts → capability-runner-DBAnGr6L.d.ts} +45 -15
  34. package/dist/chat/index.d.ts +341 -232
  35. package/dist/chat/index.js +602 -72
  36. package/dist/chat/index.js.map +1 -1
  37. package/dist/chat/react.d.ts +42 -2
  38. package/dist/chat/react.js +191 -35
  39. package/dist/chat/react.js.map +1 -1
  40. package/dist/chat/transport.d.ts +1 -1
  41. package/dist/chat/transport.js +1 -1
  42. package/dist/chat-sdk/index.d.ts +17 -3
  43. package/dist/chat-sdk/index.js +28 -2
  44. package/dist/chat-sdk/index.js.map +1 -1
  45. package/dist/client-4OlMIJx-.js +591 -0
  46. package/dist/client-4OlMIJx-.js.map +1 -0
  47. package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
  48. package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
  49. package/dist/client-je3koD9Z.d.ts +446 -0
  50. package/dist/client.d.ts +29 -283
  51. package/dist/client.js +2 -274
  52. package/dist/{connector-CptFKzRh.js → connector-58XDGvc3.js} +251 -120
  53. package/dist/connector-58XDGvc3.js.map +1 -0
  54. package/dist/{connector-CkQD4MK3.d.ts → connector-Cl-VqPmA.d.ts} +244 -146
  55. package/dist/context/index.d.ts +29 -0
  56. package/dist/context/index.js +64 -17
  57. package/dist/context/index.js.map +1 -1
  58. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  59. package/dist/current-agent-vAk_5JMt.d.ts +93 -0
  60. package/dist/{current-agent-Da_C9a3b.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +26 -127
  61. package/dist/harnesses/pi/index.d.ts +348 -0
  62. package/dist/harnesses/pi/index.js +754 -0
  63. package/dist/harnesses/pi/index.js.map +1 -0
  64. package/dist/images-iiTDSyRu.js +1631 -0
  65. package/dist/images-iiTDSyRu.js.map +1 -0
  66. package/dist/index-BreL0YrQ.d.ts +78 -0
  67. package/dist/index-kbSUIer3.d.ts +343 -0
  68. package/dist/index.d.ts +43 -41
  69. package/dist/index.js +2 -2
  70. package/dist/internal_context.d.ts +1 -1
  71. package/dist/lifecycle/index.d.ts +19 -17
  72. package/dist/lifecycle/index.js +2 -2
  73. package/dist/{lifecycle-CMRGjZdw.js → lifecycle-CFu6OV03.js} +73 -29
  74. package/dist/lifecycle-CFu6OV03.js.map +1 -0
  75. package/dist/mcp/client/index.d.ts +20 -20
  76. package/dist/mcp/client/index.js +1 -1
  77. package/dist/mcp/client/x402.js +21 -19
  78. package/dist/mcp/client/x402.js.map +1 -1
  79. package/dist/mcp/index.d.ts +35 -35
  80. package/dist/mcp/index.js +2 -2
  81. package/dist/{message-builder-BymO4N_D.js → message-builder-DAl9AhHH.js} +89 -4
  82. package/dist/message-builder-DAl9AhHH.js.map +1 -0
  83. package/dist/models/ai-sdk/index.d.ts +353 -0
  84. package/dist/models/ai-sdk/index.js +2443 -0
  85. package/dist/models/ai-sdk/index.js.map +1 -0
  86. package/dist/models/pi-ai/index.d.ts +313 -0
  87. package/dist/models/pi-ai/index.js +1795 -0
  88. package/dist/models/pi-ai/index.js.map +1 -0
  89. package/dist/observability/ai/index.js +20 -4
  90. package/dist/observability/ai/index.js.map +1 -1
  91. package/dist/{protocol-B0nh6KNf.js → protocol-kHsr-f0X.js} +26 -12
  92. package/dist/{protocol-B0nh6KNf.js.map → protocol-kHsr-f0X.js.map} +1 -1
  93. package/dist/queue/index.d.ts +123 -0
  94. package/dist/queue/index.js +2 -0
  95. package/dist/queue-BB-zfIYF.js +406 -0
  96. package/dist/queue-BB-zfIYF.js.map +1 -0
  97. package/dist/react.d.ts +31 -13
  98. package/dist/react.js +190 -71
  99. package/dist/react.js.map +1 -1
  100. package/dist/{retries-D9Ds-1lz.d.ts → retries-CV6AkXmQ.d.ts} +23 -8
  101. package/dist/retries.d.ts +12 -8
  102. package/dist/retries.js +19 -1
  103. package/dist/retries.js.map +1 -1
  104. package/dist/routing/index.d.ts +2 -2
  105. package/dist/routing/index.js +1 -1
  106. package/dist/{scheduler-DD9NdYbF.js → scheduler-DZ3xQfeK.js} +4 -11
  107. package/dist/scheduler-DZ3xQfeK.js.map +1 -0
  108. package/dist/{scheduler-Dwh85ZGl.d.ts → scheduler-RdACNXDP.d.ts} +6 -6
  109. package/dist/schedules/index.d.ts +1 -1
  110. package/dist/schedules/index.js +1 -1
  111. package/dist/serializable.d.ts +1 -1
  112. package/dist/sessions/index.d.ts +70 -3
  113. package/dist/sessions/index.js +276 -67
  114. package/dist/sessions/index.js.map +1 -1
  115. package/dist/skills/index.d.ts +29 -314
  116. package/dist/socket-address.d.ts +16 -0
  117. package/dist/socket-address.js +19 -0
  118. package/dist/socket-address.js.map +1 -0
  119. package/dist/{src-DlSHshb2.js → src-BZeu-SCD.js} +541 -500
  120. package/dist/src-BZeu-SCD.js.map +1 -0
  121. package/dist/state/index.d.ts +6 -0
  122. package/dist/state/index.js +137 -0
  123. package/dist/state/index.js.map +1 -0
  124. package/dist/streams/index.d.ts +1 -1
  125. package/dist/streams/index.js +1 -1
  126. package/dist/{streams-D6tJ0NN9.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
  127. package/dist/{streams-DZKgAj9b.js → streams-DW-UCOUc.js} +9 -2
  128. package/dist/streams-DW-UCOUc.js.map +1 -0
  129. package/dist/sub-routing.d.ts +12 -12
  130. package/dist/tasks/index.d.ts +1 -1
  131. package/dist/tasks/index.js +1 -1
  132. package/dist/{tasks-ylZgBjhj.js → tasks-68DESxf0.js} +3 -3
  133. package/dist/{tasks-ylZgBjhj.js.map → tasks-68DESxf0.js.map} +1 -1
  134. package/dist/{tasks-BRJ5zgya.d.ts → tasks-9YxYtByh.d.ts} +5 -5
  135. package/dist/transport-PfFnt-Ld.d.ts +525 -0
  136. package/dist/transport-protocol-BxyngQ_R.js +25 -0
  137. package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
  138. package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
  139. package/dist/types-BA4anrjI.d.ts +70 -0
  140. package/dist/voice/index.d.ts +2 -2
  141. package/dist/websockets/index.d.ts +195 -69
  142. package/dist/websockets/index.js +3 -2
  143. package/dist/websockets-Ci-LZFHH.js +1030 -0
  144. package/dist/websockets-Ci-LZFHH.js.map +1 -0
  145. package/dist/wire-types-CnMt6_HR.js.map +1 -1
  146. package/dist/wire-types-CrjKvDyi.d.ts +300 -0
  147. package/dist/workflow-types.d.ts +25 -25
  148. package/dist/workflows.d.ts +21 -21
  149. package/dist/workflows.js +1 -1
  150. package/dist/{ws-chat-transport-UNRIS2xl.d.ts → ws-chat-transport-BbT1A5Lc.d.ts} +51 -2
  151. package/dist/{ws-chat-transport-rWwta645.js → ws-chat-transport-cqMKq3IY.js} +143 -16
  152. package/dist/ws-chat-transport-cqMKq3IY.js.map +1 -0
  153. package/docs/agent-class.md +2 -2
  154. package/docs/agent-tools.md +29 -4
  155. package/docs/browse-the-web.md +87 -5
  156. package/docs/chat-agents.md +124 -16
  157. package/docs/client-sdk.md +30 -0
  158. package/docs/context.md +27 -0
  159. package/docs/durable-execution.md +2 -0
  160. package/docs/harnesses/pi.md +91 -0
  161. package/docs/human-in-the-loop.md +4 -0
  162. package/docs/index.md +4 -1
  163. package/docs/lifecycle.md +114 -23
  164. package/docs/models-pi-ai.md +325 -0
  165. package/docs/models.md +504 -0
  166. package/docs/queue.md +135 -241
  167. package/docs/resumable-streaming.md +2 -0
  168. package/docs/retries.md +1 -1
  169. package/docs/routing.md +13 -13
  170. package/docs/sessions.md +36 -1
  171. package/docs/state.md +1 -0
  172. package/docs/sub-agents.md +12 -3
  173. package/package.json +51 -3
  174. package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
  175. package/dist/browser/ai.js.map +0 -1
  176. package/dist/capability-BjSKYpzg.js.map +0 -1
  177. package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
  178. package/dist/client.js.map +0 -1
  179. package/dist/connector-CptFKzRh.js.map +0 -1
  180. package/dist/lifecycle-CMRGjZdw.js.map +0 -1
  181. package/dist/message-builder-BymO4N_D.js.map +0 -1
  182. package/dist/scheduler-DD9NdYbF.js.map +0 -1
  183. package/dist/src-DlSHshb2.js.map +0 -1
  184. package/dist/streams-DZKgAj9b.js.map +0 -1
  185. package/dist/websockets-DUfRHPRq.js +0 -502
  186. package/dist/websockets-DUfRHPRq.js.map +0 -1
  187. package/dist/ws-chat-transport-rWwta645.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { t as applyChunkToParts } from "./message-builder-BymO4N_D.js";
1
+ import { t as applyChunkToParts } from "./message-builder-DAl9AhHH.js";
2
2
  //#region src/chat/stream-accumulator.ts
3
3
  function asMetadata(value) {
4
4
  if (value != null && typeof value === "object" && !Array.isArray(value)) return value;
@@ -139,6 +139,22 @@ var StreamAccumulator = class {
139
139
  };
140
140
  //#endregion
141
141
  //#region src/chat/broadcast-state.ts
142
+ function textOf(parts) {
143
+ let text = "";
144
+ for (const part of parts) if (part.type === "text") text += part.text;
145
+ return text;
146
+ }
147
+ /**
148
+ * Whether `messages` already holds a copy of the observed message whose text
149
+ * the accumulator does not extend. A healthy live accumulator is always the
150
+ * stored copy plus more; one that diverges holds interleaved or duplicated
151
+ * chunks (#2166), so the stored copy must win or the corruption never heals.
152
+ */
153
+ function observedDivergesFrom(accumulator, messages) {
154
+ const existing = messages.find((m) => m.id === accumulator.messageId);
155
+ if (!existing) return false;
156
+ return !textOf(accumulator.parts).startsWith(textOf(existing.parts));
157
+ }
142
158
  function transition(state, event) {
143
159
  switch (event.type) {
144
160
  case "clear": return {
@@ -166,15 +182,13 @@ function transition(state, event) {
166
182
  else accumulator = state.accumulator;
167
183
  if (event.chunkData) accumulator.applyChunk(event.chunkData);
168
184
  let messagesUpdate;
169
- if (event.done) {
170
- messagesUpdate = (prev) => accumulator.mergeInto(prev);
171
- return {
172
- state: { status: "idle" },
173
- messagesUpdate,
174
- isStreaming: false
175
- };
176
- }
177
- if (event.chunkData && !event.replay) messagesUpdate = (prev) => accumulator.mergeInto(prev);
185
+ const mergeUnlessDiverged = (prev) => observedDivergesFrom(accumulator, prev) ? prev : accumulator.mergeInto(prev);
186
+ if (event.done) return {
187
+ state: { status: "idle" },
188
+ messagesUpdate: mergeUnlessDiverged,
189
+ isStreaming: false
190
+ };
191
+ if (event.chunkData && !event.replay) messagesUpdate = mergeUnlessDiverged;
178
192
  else if (event.replayComplete) messagesUpdate = (prev) => accumulator.mergeInto(prev);
179
193
  return {
180
194
  state: {
@@ -220,6 +234,6 @@ const CHAT_MESSAGE_TYPES = {
220
234
  CHAT_RECOVERING: "cf_agent_chat_recovering"
221
235
  };
222
236
  //#endregion
223
- export { StreamAccumulator as i, STREAM_RESUME_NONE_REASONS as n, transition as r, CHAT_MESSAGE_TYPES as t };
237
+ export { StreamAccumulator as a, transition as i, STREAM_RESUME_NONE_REASONS as n, observedDivergesFrom as r, CHAT_MESSAGE_TYPES as t };
224
238
 
225
- //# sourceMappingURL=protocol-B0nh6KNf.js.map
239
+ //# sourceMappingURL=protocol-kHsr-f0X.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol-B0nh6KNf.js","names":[],"sources":["../src/chat/stream-accumulator.ts","../src/chat/broadcast-state.ts","../src/chat/protocol.ts"],"sourcesContent":["/**\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 /** Whether chunks continue the current assistant message when merged. */\n continuation?: boolean;\n /** Eager seed; omitted continuations seed from the messages passed to `mergeInto`. */\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 // Continuation chunks wait here until mergeInto can seed from current state.\n private _pendingContinuationChunks: StreamChunkData[] | null;\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 this._pendingContinuationChunks =\n this._isContinuation &&\n options.existingParts === undefined &&\n options.existingMetadata === undefined\n ? []\n : null;\n }\n\n applyChunk(chunk: StreamChunkData): ChunkResult {\n this._pendingContinuationChunks?.push(chunk);\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). An unseeded\n * continuation adopts current parts here before applying its queued chunks.\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 if (this._pendingContinuationChunks !== null) {\n const pendingChunks = this._pendingContinuationChunks;\n this._pendingContinuationChunks = null;\n this.parts.splice(\n 0,\n this.parts.length,\n ...(existingIdx >= 0 ? messages[existingIdx].parts : [])\n );\n const currentMetadata =\n existingIdx >= 0\n ? asMetadata(messages[existingIdx].metadata)\n : undefined;\n this.metadata = currentMetadata ? { ...currentMetadata } : undefined;\n if (existingIdx >= 0) {\n this.messageId = messages[existingIdx].id;\n }\n for (const chunk of pendingChunks) {\n this.applyChunk(chunk);\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 * 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 /** @deprecated Continuations now seed from current messages in `messagesUpdate`. */\n currentMessages?: UIMessage[];\n }\n | {\n type: \"resume-fallback\";\n streamId: string;\n messageId: string;\n }\n | { type: \"clear\" };\n\n// ── Result ─────────────────────────────────────────────────────────\n\nexport interface TransitionResult {\n state: BroadcastStreamState;\n messagesUpdate?: (prev: UIMessage[]) => UIMessage[];\n isStreaming: boolean;\n}\n\n// ── Transition ─────────────────────────────────────────────────────\n\nexport function transition(\n state: BroadcastStreamState,\n event: BroadcastStreamEvent\n): TransitionResult {\n switch (event.type) {\n case \"clear\":\n return { state: { status: \"idle\" }, isStreaming: false };\n\n case \"resume-fallback\": {\n const accumulator = new StreamAccumulator({\n messageId: event.messageId\n });\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n isStreaming: true\n };\n }\n\n case \"response\": {\n let accumulator: StreamAccumulator;\n\n // A replayed `start` chunk means the server is re-sending the stream\n // buffer from chunk 0 (resume replay). Re-initialize the accumulator\n // instead of appending into an existing one: replaying into an\n // accumulator that already holds this stream's parts would duplicate\n // them (a second `text-start` unconditionally opens a second text\n // part — #1733). Re-initializing makes replay idempotent under any\n // number of replays, including a second replay triggered by a\n // duplicate STREAM_RESUMING → ACK cycle or a reconnect.\n const isReplayedStart =\n event.replay === true &&\n (event.chunkData as { type?: string } | null | undefined)?.type ===\n \"start\";\n\n if (\n state.status === \"idle\" ||\n state.streamId !== event.streamId ||\n isReplayedStart\n ) {\n accumulator = new StreamAccumulator({\n messageId: event.messageId,\n continuation: event.continuation\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 STREAM_RESUME_NONE_REASONS = {\n /** No active, pending, or terminal stream exists for this agent. */\n IDLE: \"idle\",\n /** An active tool continuation is owned by another live connection. */\n CONTINUATION_OWNED: \"continuation-owned\"\n} as const;\n\nexport type StreamResumeNoneReason =\n (typeof STREAM_RESUME_NONE_REASONS)[keyof typeof STREAM_RESUME_NONE_REASONS];\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 // Server→client: a turn has been accepted but its resumable stream has not\n // started yet (queued, debouncing, waiting on MCP setup, or running async\n // work in `onChatMessage`). Sent in response to a resume request (or on\n // connect) so a reconnecting/re-mounting client keeps its expectation instead\n // of resolving its resume probe to \"no stream\" and then false-timing-out the\n // turn. Resolved by a later `STREAM_RESUMING` (stream started) or\n // `STREAM_RESUME_NONE` (turn settled without streaming). Backward-compatible —\n // clients that don't understand it ignore it. See issue #1784.\n STREAM_PENDING: \"cf_agent_stream_pending\",\n TOOL_RESULT: \"cf_agent_tool_result\",\n TOOL_APPROVAL: \"cf_agent_tool_approval\",\n MESSAGE_UPDATED: \"cf_agent_message_updated\",\n // Server→client: a durable chat turn is being recovered (interrupted by a\n // deploy/eviction or a stream-stall watchdog abort and now resuming). Sent\n // when a recovery continuation is scheduled and cleared on every terminal\n // outcome; `@cloudflare/think` also replays it on connect so a client that\n // joins mid-recovery learns it. Purely a progress hint — backward-compatible\n // (clients that don't understand it ignore it). See issue #1620.\n CHAT_RECOVERING: \"cf_agent_chat_recovering\"\n} as const;\n"],"mappings":"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"}
1
+ {"version":3,"file":"protocol-kHsr-f0X.js","names":[],"sources":["../src/chat/stream-accumulator.ts","../src/chat/broadcast-state.ts","../src/chat/protocol.ts"],"sourcesContent":["/**\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 /** Whether chunks continue the current assistant message when merged. */\n continuation?: boolean;\n /** Eager seed; omitted continuations seed from the messages passed to `mergeInto`. */\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 // Continuation chunks wait here until mergeInto can seed from current state.\n private _pendingContinuationChunks: StreamChunkData[] | null;\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 this._pendingContinuationChunks =\n this._isContinuation &&\n options.existingParts === undefined &&\n options.existingMetadata === undefined\n ? []\n : null;\n }\n\n applyChunk(chunk: StreamChunkData): ChunkResult {\n this._pendingContinuationChunks?.push(chunk);\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). An unseeded\n * continuation adopts current parts here before applying its queued chunks.\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 if (this._pendingContinuationChunks !== null) {\n const pendingChunks = this._pendingContinuationChunks;\n this._pendingContinuationChunks = null;\n this.parts.splice(\n 0,\n this.parts.length,\n ...(existingIdx >= 0 ? messages[existingIdx].parts : [])\n );\n const currentMetadata =\n existingIdx >= 0\n ? asMetadata(messages[existingIdx].metadata)\n : undefined;\n this.metadata = currentMetadata ? { ...currentMetadata } : undefined;\n if (existingIdx >= 0) {\n this.messageId = messages[existingIdx].id;\n }\n for (const chunk of pendingChunks) {\n this.applyChunk(chunk);\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 * 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 /** @deprecated Continuations now seed from current messages in `messagesUpdate`. */\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// ── Snapshot reconciliation ────────────────────────────────────────\n\nfunction textOf(parts: UIMessage[\"parts\"]): string {\n let text = \"\";\n for (const part of parts) {\n if (part.type === \"text\") text += part.text;\n }\n return text;\n}\n\n/**\n * Whether `messages` already holds a copy of the observed message whose text\n * the accumulator does not extend. A healthy live accumulator is always the\n * stored copy plus more; one that diverges holds interleaved or duplicated\n * chunks (#2166), so the stored copy must win or the corruption never heals.\n */\nexport function observedDivergesFrom(\n accumulator: StreamAccumulator,\n messages: UIMessage[]\n): boolean {\n const existing = messages.find((m) => m.id === accumulator.messageId);\n if (!existing) return false;\n return !textOf(accumulator.parts).startsWith(textOf(existing.parts));\n}\n\n// ── Transition ─────────────────────────────────────────────────────\n\nexport function transition(\n state: BroadcastStreamState,\n event: BroadcastStreamEvent\n): TransitionResult {\n switch (event.type) {\n case \"clear\":\n return { state: { status: \"idle\" }, isStreaming: false };\n\n case \"resume-fallback\": {\n const accumulator = new StreamAccumulator({\n messageId: event.messageId\n });\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n isStreaming: true\n };\n }\n\n case \"response\": {\n let accumulator: StreamAccumulator;\n\n // A replayed `start` chunk means the server is re-sending the stream\n // buffer from chunk 0 (resume replay). Re-initialize the accumulator\n // instead of appending into an existing one: replaying into an\n // accumulator that already holds this stream's parts would duplicate\n // them (a second `text-start` unconditionally opens a second text\n // part — #1733). Re-initializing makes replay idempotent under any\n // number of replays, including a second replay triggered by a\n // duplicate STREAM_RESUMING → ACK cycle or a reconnect.\n const isReplayedStart =\n event.replay === true &&\n (event.chunkData as { type?: string } | null | undefined)?.type ===\n \"start\";\n\n if (\n state.status === \"idle\" ||\n state.streamId !== event.streamId ||\n isReplayedStart\n ) {\n accumulator = new StreamAccumulator({\n messageId: event.messageId,\n continuation: event.continuation\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 const mergeUnlessDiverged = (prev: UIMessage[]) =>\n observedDivergesFrom(accumulator, prev)\n ? prev\n : accumulator.mergeInto(prev);\n\n if (event.done) {\n return {\n state: { status: \"idle\" },\n messagesUpdate: mergeUnlessDiverged,\n isStreaming: false\n };\n }\n\n if (event.chunkData && !event.replay) {\n messagesUpdate = mergeUnlessDiverged;\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 STREAM_RESUME_NONE_REASONS = {\n /** No active, pending, or terminal stream exists for this agent. */\n IDLE: \"idle\",\n /** An active tool continuation is owned by another live connection. */\n CONTINUATION_OWNED: \"continuation-owned\"\n} as const;\n\nexport type StreamResumeNoneReason =\n (typeof STREAM_RESUME_NONE_REASONS)[keyof typeof STREAM_RESUME_NONE_REASONS];\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 // Server→client: a turn has been accepted but its resumable stream has not\n // started yet (queued, debouncing, waiting on MCP setup, or running async\n // work in `onChatMessage`). Sent in response to a resume request (or on\n // connect) so a reconnecting/re-mounting client keeps its expectation instead\n // of resolving its resume probe to \"no stream\" and then false-timing-out the\n // turn. Resolved by a later `STREAM_RESUMING` (stream started) or\n // `STREAM_RESUME_NONE` (turn settled without streaming). Backward-compatible —\n // clients that don't understand it ignore it. See issue #1784.\n STREAM_PENDING: \"cf_agent_stream_pending\",\n TOOL_RESULT: \"cf_agent_tool_result\",\n TOOL_APPROVAL: \"cf_agent_tool_approval\",\n MESSAGE_UPDATED: \"cf_agent_message_updated\",\n // Server→client: a durable chat turn is being recovered (interrupted by a\n // deploy/eviction or a stream-stall watchdog abort and now resuming). Sent\n // when a recovery continuation is scheduled and cleared on every terminal\n // outcome; `@cloudflare/think` also replays it on connect so a client that\n // joins mid-recovery learns it. Purely a progress hint — backward-compatible\n // (clients that don't understand it ignore it). See issue #1620.\n CHAT_RECOVERING: \"cf_agent_chat_recovering\"\n} as const;\n"],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ import {
2
+ _ as LifecycleJobContext,
3
+ d as LifecycleRouteContext,
4
+ s as LifecycleCapability,
5
+ v as LifecycleJobOutcome
6
+ } from "../capability-runner-DBAnGr6L.js";
7
+ import { t as RetryOptions } from "../retries-CV6AkXmQ.js";
8
+ import {
9
+ a as QueuePayload,
10
+ i as QueueItem,
11
+ n as QueueCriteria,
12
+ o as QueuePushOptions,
13
+ r as QueueHandlers,
14
+ t as QueueCallbacks
15
+ } from "../types-BA4anrjI.js";
16
+
17
+ //#region src/queue/options.d.ts
18
+ /** Events emitted while a Queue creates, retries, or fails work. */
19
+ type QueueEventType = "queue:create" | "queue:retry" | "queue:error";
20
+ /**
21
+ * Optional callbacks and policy for a Queue capability.
22
+ *
23
+ * @experimental The API surface may change before stabilizing.
24
+ */
25
+ interface QueueOptions<Handlers extends QueueHandlers = QueueCallbacks> {
26
+ /**
27
+ * Named callbacks this Queue can run. Each item persists a callback name;
28
+ * registration in a field initializer re-binds the names on every Durable
29
+ * Object wake, so register unconditionally. Names outside this map are
30
+ * rejected unless a composition-root resolver supplies them — the internal
31
+ * aperture behind `Agent`'s name-based queue API.
32
+ */
33
+ readonly callbacks?: Handlers;
34
+ /** Default callback retry policy. */
35
+ readonly retry?: RetryOptions;
36
+ /** Observe terminal callback errors. Runs as capability code without host context. */
37
+ readonly onError?: (error: unknown) => void | Promise<void>;
38
+ }
39
+ //#endregion
40
+ //#region src/queue/queue.d.ts
41
+ /**
42
+ * Durable background work for a Lifecycle Object.
43
+ *
44
+ * Register callbacks in the constructor and install the instance with
45
+ * `Lifecycle.use()`. Each pushed item becomes a job due immediately in the
46
+ * Lifecycle job queue; Lifecycle owns the physical alarm and the alarm event
47
+ * loop, drives items one at a time in push order, retries a throwing
48
+ * callback per its retry policy, and Queue runs registered callbacks through
49
+ * Lifecycle's host invocation boundary. An item that still fails after its
50
+ * last attempt is dropped after `queue:error` and the `onError` hook.
51
+ *
52
+ * Items survive the Durable Object leaving memory: an isolate that dies mid
53
+ * callback wakes again on the Lifecycle deadman alarm and resumes the queue.
54
+ * Callbacks should therefore be idempotent.
55
+ *
56
+ * @experimental The API surface may change before stabilizing.
57
+ */
58
+ declare class Queue<
59
+ Handlers extends QueueHandlers = QueueCallbacks
60
+ > extends LifecycleCapability {
61
+ #private;
62
+ /**
63
+ * Create a durable Queue.
64
+ *
65
+ * @param options - Registered callbacks plus optional retry and error
66
+ * policy. Registering `callbacks` types {@link push} against the map —
67
+ * names and payloads are checked where the handlers are declared and where
68
+ * they are pushed. Names outside the map are rejected unless a
69
+ * composition-root resolver supplies them — the internal aperture behind
70
+ * `Agent`'s name-based queue API.
71
+ */
72
+ constructor(options?: QueueOptions<Handlers>);
73
+ /** Migrate legacy `cf_agents_queues` rows into the Lifecycle job queue. */
74
+ onStart(): Promise<void>;
75
+ /** Drive one due item dispatched by the Lifecycle event loop. */
76
+ onJob(context: LifecycleJobContext): Promise<LifecycleJobOutcome | void>;
77
+ /** Observe one item's terminal application failure; the item is dropped. */
78
+ onJobError(
79
+ context: LifecycleJobContext,
80
+ error: unknown
81
+ ): Promise<LifecycleJobOutcome | void>;
82
+ /** Handle Queue protocol messages routed by another Lifecycle. */
83
+ onRoute(context: LifecycleRouteContext): Promise<unknown>;
84
+ /**
85
+ * Push one item for a registered callback. The item is due immediately;
86
+ * the Lifecycle alarm event loop runs it in push order after this call
87
+ * returns.
88
+ *
89
+ * Once the Lifecycle has started (and this Queue is not routed through
90
+ * another Lifecycle), the item row is written synchronously before this
91
+ * method's promise is even returned, so a caller may pair a push with its
92
+ * own writes in one synchronous block — the item then commits atomically
93
+ * with them.
94
+ */
95
+ push<Name extends keyof Handlers & string>(
96
+ callback: Name,
97
+ payload?: QueuePayload<Handlers[Name]>,
98
+ options?: QueuePushOptions
99
+ ): Promise<QueueItem<QueuePayload<Handlers[Name]>>>;
100
+ /** Cancel one pending item. Returns false when no item matched. */
101
+ cancel(id: string): Promise<boolean>;
102
+ /** Cancel every pending item, or every item for one callback. Returns the count. */
103
+ cancelAll(callback?: string): Promise<number>;
104
+ /** Read one pending item. */
105
+ get<T = unknown>(id: string): Promise<QueueItem<T> | undefined>;
106
+ /** List pending items in push order, optionally filtered by callback. */
107
+ list<T = unknown>(criteria?: QueueCriteria): Promise<QueueItem<T>[]>;
108
+ /** @internal Remove items owned by one routed Lifecycle subtree. */
109
+ __DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix: string): Promise<void>;
110
+ }
111
+ //#endregion
112
+ export {
113
+ Queue,
114
+ type QueueCallbacks,
115
+ type QueueCriteria,
116
+ type QueueEventType,
117
+ type QueueHandlers,
118
+ type QueueItem,
119
+ type QueueOptions,
120
+ type QueuePayload,
121
+ type QueuePushOptions
122
+ };
123
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ import { t as Queue } from "../queue-BB-zfIYF.js";
2
+ export { Queue };