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,10 +1,11 @@
1
- import { isPlatformFailure, tryN } from "../retries.js";
2
- import { i as normalizeToolInput, n as getPartialStreamText, r as isReplayChunk, t as applyChunkToParts } from "../message-builder-BymO4N_D.js";
3
- import { i as interceptAgentToolBroadcast, n as applyAgentToolEvent, r as createAgentToolEventState, t as AgentToolProgressEmitter } from "../agent-tools-y7zLfw4Q.js";
1
+ import { isPositionlessAgentToolChunk } from "../agent-tool-types.js";
2
+ import { isDurableObjectResetError, isPlatformFailure, tryN } from "../retries.js";
3
+ import { a as isReplayChunk, i as isLateToolInputChunk, n as applyLateToolInput, o as lateToolInputForwardChunks, r as getPartialStreamText, s as normalizeToolInput, t as applyChunkToParts } from "../message-builder-DAl9AhHH.js";
4
+ import { a as interceptAgentToolBroadcast, i as createAgentToolEventState, r as applyAgentToolEvent, s as storedChunkBytes, t as AgentToolProgressEmitter } from "../agent-tools-CdJC-8Dc.js";
4
5
  import { t as TextSegmentJoiner } from "../text-segment-joiner-BtAFQSA_.js";
5
6
  import { n as sanitizeMessage, t as byteLength } from "../sanitize-D9TujEK8.js";
6
- import { i as StreamAccumulator, n as STREAM_RESUME_NONE_REASONS, r as transition, t as CHAT_MESSAGE_TYPES } from "../protocol-B0nh6KNf.js";
7
- import { n as Streams } from "../streams-DZKgAj9b.js";
7
+ import { a as StreamAccumulator, i as transition, n as STREAM_RESUME_NONE_REASONS, t as CHAT_MESSAGE_TYPES } from "../protocol-kHsr-f0X.js";
8
+ import { n as Streams } from "../streams-DW-UCOUc.js";
8
9
  import { t as MessageType } from "../wire-types-CnMt6_HR.js";
9
10
  import { jsonSchema, tool } from "ai";
10
11
  import { nanoid } from "nanoid";
@@ -262,6 +263,36 @@ var TurnQueue = class {
262
263
  }
263
264
  };
264
265
  //#endregion
266
+ //#region src/chat/origin-message-ids.ts
267
+ /**
268
+ * IDs of the user messages a chat request originated from: the trailing run of
269
+ * user messages in its `messages` (one for a plain send or a regenerate,
270
+ * several when queued sends arrive together). Terminal response frames echo
271
+ * them as `messageIds` (#2280).
272
+ */
273
+ function originMessageIds(messages) {
274
+ if (!Array.isArray(messages)) return void 0;
275
+ const ids = [];
276
+ for (let i = messages.length - 1; i >= 0; i--) {
277
+ const message = messages[i];
278
+ if (message?.role !== "user") break;
279
+ if (typeof message.id === "string" && message.id) ids.unshift(message.id);
280
+ }
281
+ return ids.length > 0 ? ids : void 0;
282
+ }
283
+ /**
284
+ * Return `frame` with `messageIds` added when it is a terminal
285
+ * (`done` or `error`) frame that does not already carry them.
286
+ */
287
+ function withOriginMessageIds(frame, messageIds) {
288
+ const fields = frame;
289
+ if (!messageIds || fields.messageIds !== void 0 || !(fields.done || fields.error)) return frame;
290
+ return {
291
+ ...frame,
292
+ messageIds
293
+ };
294
+ }
295
+ //#endregion
265
296
  //#region src/chat/submit-concurrency.ts
266
297
  var SubmitConcurrencyController = class {
267
298
  constructor(options) {
@@ -410,13 +441,15 @@ function isWebSocketClosedSendError(error) {
410
441
  * @returns False when the connection closed mid-replay — the caller leaves
411
442
  * its stream state untouched so the next reconnect can retry.
412
443
  */
413
- function sendReplayBodies(connection, requestId, bodies, continuation) {
444
+ function sendReplayBodies(connection, requestId, bodies, continuation, firstSeq = 0) {
445
+ let seq = firstSeq;
414
446
  for (const body of bodies) if (!sendIfOpen(connection, JSON.stringify({
415
447
  body,
416
448
  done: false,
417
449
  id: requestId,
418
450
  type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,
419
451
  replay: true,
452
+ seq: seq++,
420
453
  ...continuation && { continuation: true }
421
454
  }))) return false;
422
455
  return true;
@@ -435,7 +468,9 @@ function sendReplayControl(connection, requestId, options) {
435
468
  type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,
436
469
  replay: true,
437
470
  ...options.replayComplete && { replayComplete: true },
438
- ...options.continuation && { continuation: true }
471
+ ...options.continuation && { continuation: true },
472
+ ...options.done && options.messageIds && { messageIds: options.messageIds },
473
+ ...options.done && options.outcome && { outcome: options.outcome }
439
474
  }));
440
475
  }
441
476
  //#endregion
@@ -492,20 +527,18 @@ const REPLAY_PAGE_SEGMENTS = 10;
492
527
  * the newest chunk's timestamp for rows past it — so a long but still-active
493
528
  * stream is never reclaimed mid-flight. Terminal rows carry no such window:
494
529
  * a stream that finished is redundant with its persisted message, and the
495
- * cutover deletes it in the same transaction; anything a crash left behind
496
- * is reclaimed by the next {@link ResumableStream.start}.
530
+ * cutover deletes it in the same transaction; leftovers are reclaimed by
531
+ * the next {@link ResumableStream.start}.
497
532
  */
498
533
  const ABANDONED_STREAM_RETENTION_MS = 3600 * 1e3;
499
- /** Shared encoder for UTF-8 byte length measurement */
500
- const textEncoder = new TextEncoder();
534
+ /** Deleted streams whose terminal details a late resume ACK can still read. */
535
+ const MAX_REMEMBERED_DELETED_TERMINALS = 32;
501
536
  /**
502
537
  * Ceiling for one stored chat segment after JSON serialization, and the
503
538
  * `maxChunkBytes` the backing Streams capability must be constructed with.
504
539
  * Kept under the 2 MB SQLite row limit with headroom for escaping.
505
540
  */
506
541
  const CHAT_STREAM_MAX_CHUNK_BYTES = 19e5;
507
- /** Maximum serialized chunk body size before skipping storage (bytes). */
508
- const CHUNK_MAX_BYTES = 18e5;
509
542
  /**
510
543
  * Construct the Streams capability instance a chat host must install to back
511
544
  * its `ResumableStream`: identical to `new Streams()` except for the raised
@@ -548,10 +581,13 @@ var ResumableStream = class {
548
581
  this._activeRequestId = null;
549
582
  this._isLive = false;
550
583
  this._activeIsContinuation = false;
584
+ this._nextChunkSeq = null;
551
585
  this._chunkBuffer = [];
552
586
  this._chunkBufferBytes = 0;
553
587
  this._isFlushingChunks = false;
554
588
  this._pendingCutover = null;
589
+ this._lastClosedStreamId = null;
590
+ this._deletedTerminals = /* @__PURE__ */ new Map();
555
591
  this.ops = streams.__DO_NOT_USE_WILL_BREAK__sync();
556
592
  this.ops.ensureTables();
557
593
  this._sql = sql;
@@ -559,7 +595,10 @@ var ResumableStream = class {
559
595
  this._ensureProgressTable();
560
596
  deletionHooks.get(streams)?.();
561
597
  deletionHooks.set(streams, this.ops.onDelete((row, cursor) => {
562
- if (parseChatMetadata(row)) this._retire(cursor);
598
+ const chat = parseChatMetadata(row);
599
+ if (!chat) return;
600
+ this._retire(cursor);
601
+ this._rememberDeleted(row, chat);
563
602
  }));
564
603
  this._migrateLegacyTables(sql);
565
604
  this.restore();
@@ -759,18 +798,34 @@ var ResumableStream = class {
759
798
  */
760
799
  start(requestId, options = {}) {
761
800
  this.flushBuffer();
801
+ const seqBase = this._nextSeqForRequest(requestId);
762
802
  this.reclaim();
763
803
  const streamId = nanoid();
764
804
  this._activeStreamId = streamId;
765
805
  this._activeRequestId = requestId;
766
806
  this._isLive = true;
767
807
  this._activeIsContinuation = options.continuation ?? false;
808
+ this._nextChunkSeq = seqBase;
768
809
  const metadata = { cfChat: 1 };
769
810
  if (options.messageId != null) metadata.messageId = options.messageId;
811
+ if (options.parentMessageId != null) metadata.parentMessageId = options.parentMessageId;
770
812
  if (this._activeIsContinuation) metadata.isContinuation = 1;
813
+ if (seqBase > 0) metadata.seqBase = seqBase;
814
+ if (options.originMessageIds?.length) metadata.originMessageIds = options.originMessageIds;
771
815
  this.ops.insertStream(streamId, requestId, metadata);
772
816
  return streamId;
773
817
  }
818
+ _nextSeqForRequest(requestId) {
819
+ const prior = this._latestChatRowByTag(requestId);
820
+ if (!prior) return 0;
821
+ let count = 0;
822
+ for (const _body of this._storedBodies(prior.stream_id)) count++;
823
+ return (parseChatMetadata(prior)?.seqBase ?? 0) + count;
824
+ }
825
+ _seqBase(streamId) {
826
+ const row = this.ops.getStream(streamId);
827
+ return row && parseChatMetadata(row)?.seqBase || 0;
828
+ }
774
829
  /**
775
830
  * The assistant message id an orphaned stream was producing — the same id the
776
831
  * live path persists under, so recovery re-associates reconstructed chunks
@@ -783,13 +838,65 @@ var ResumableStream = class {
783
838
  return parseChatMetadata(row)?.messageId ?? null;
784
839
  }
785
840
  /**
841
+ * The message an orphaned stream's assistant message is a child of, or null
842
+ * when the stream appends to the latest leaf or predates parent tracking.
843
+ */
844
+ getStreamParentMessageId(streamId) {
845
+ const row = this.ops.getStream(streamId);
846
+ if (!row) return null;
847
+ return parseChatMetadata(row)?.parentMessageId ?? null;
848
+ }
849
+ /**
850
+ * The user message ids the request's latest chat stream was started for
851
+ * (#2280), or undefined when no stream recorded them.
852
+ */
853
+ getOriginMessageIds(requestId) {
854
+ const row = this._latestChatRowByTag(requestId);
855
+ return (row ? parseChatMetadata(row)?.originMessageIds : void 0) ?? this._deletedTerminals.get(requestId)?.messageIds;
856
+ }
857
+ /**
858
+ * How the request's latest chat stream ended (recorded by {@link complete}
859
+ * or {@link finish}), or undefined for a normal completion.
860
+ */
861
+ getOutcome(requestId) {
862
+ const row = this._latestChatRowByTag(requestId);
863
+ return (row ? parseChatMetadata(row)?.outcome : void 0) ?? this._deletedTerminals.get(requestId)?.outcome;
864
+ }
865
+ _rememberDeleted(row, chat) {
866
+ if (!row.tag || !chat.originMessageIds && !chat.outcome) return;
867
+ this._deletedTerminals.delete(row.tag);
868
+ this._deletedTerminals.set(row.tag, {
869
+ messageIds: chat.originMessageIds,
870
+ outcome: chat.outcome
871
+ });
872
+ if (this._deletedTerminals.size > MAX_REMEMBERED_DELETED_TERMINALS) {
873
+ const oldest = this._deletedTerminals.keys().next().value;
874
+ if (oldest !== void 0) this._deletedTerminals.delete(oldest);
875
+ }
876
+ }
877
+ _recordOutcome(streamId, outcome) {
878
+ if (outcome === void 0 || outcome === "completed") return;
879
+ const row = this.ops.getStream(streamId);
880
+ const chat = row ? parseChatMetadata(row) : null;
881
+ if (!chat) return;
882
+ this.ops.setMetadata(streamId, {
883
+ ...chat,
884
+ outcome
885
+ });
886
+ }
887
+ /**
786
888
  * Mark a stream as completed and flush any pending chunks.
787
889
  * @param streamId - The stream to mark as completed
890
+ * @param outcome - How the request ended, when not a normal completion.
891
+ * Defaults to `aborted` for a stream restored without a live reader.
788
892
  */
789
- complete(streamId) {
893
+ complete(streamId, outcome) {
790
894
  this.flushBuffer();
895
+ const orphaned = streamId === this._activeStreamId && !this._isLive;
896
+ this._recordOutcome(streamId, outcome ?? (orphaned ? "aborted" : void 0));
791
897
  this.ops.settle(streamId, "completed", null);
792
898
  if (this._pendingCutover === streamId) this._pendingCutover = null;
899
+ this._lastClosedStreamId = streamId;
793
900
  this._clearActive();
794
901
  }
795
902
  /**
@@ -799,9 +906,11 @@ var ResumableStream = class {
799
906
  * live — exactly the evidence recovery rebuilds the message from. The
800
907
  * host MUST follow with {@link cutover} or {@link finalizePending}.
801
908
  */
802
- finish(streamId) {
909
+ finish(streamId, outcome) {
803
910
  this.flushBuffer();
911
+ this._recordOutcome(streamId, outcome);
804
912
  this._pendingCutover = streamId;
913
+ this._lastClosedStreamId = streamId;
805
914
  this._clearActive();
806
915
  }
807
916
  /** The stream {@link finish}ed and awaiting its cutover, if any. */
@@ -814,29 +923,33 @@ var ResumableStream = class {
814
923
  * crash leaves either the live stream or the finished message, never
815
924
  * neither; nothing is left to sweep. `discard: false` keeps the settled
816
925
  * rows (an agent-tool child whose parent still tails them); they are
817
- * reclaimed by the next {@link start}.
926
+ * reclaimed by the next {@link start}. The settlement and `persist`
927
+ * writes commit or roll back together.
818
928
  */
819
929
  cutover(streamId, persist, options = {}) {
820
930
  this.flushBuffer();
821
931
  const discard = options.discard ?? true;
932
+ const commit = persist;
822
933
  const settled = this.ops.settle(streamId, "completed", null, {
823
- commit: persist,
934
+ commit,
824
935
  discard
825
936
  });
826
937
  if (settled && discard) this._notifyProgress();
827
- if (!settled) persist();
938
+ if (!settled) commit();
828
939
  if (this._pendingCutover === streamId) this._pendingCutover = null;
829
940
  this._clearActive();
830
941
  }
831
942
  /**
832
943
  * Settle a {@link finish}ed stream that had nothing to persist (no parts,
833
944
  * a persist that threw). Idempotent; a no-op when nothing is pending.
945
+ * The pending marker clears only once settlement succeeds, so a caller
946
+ * may retry after a settlement failure — matching {@link cutover}.
834
947
  */
835
948
  finalizePending() {
836
949
  const streamId = this._pendingCutover;
837
950
  if (streamId === null) return;
838
- this._pendingCutover = null;
839
951
  this.ops.settle(streamId, "completed", null);
952
+ this._pendingCutover = null;
840
953
  }
841
954
  _clearActive() {
842
955
  this._activeStreamId = null;
@@ -852,6 +965,7 @@ var ResumableStream = class {
852
965
  this.flushBuffer();
853
966
  this.ops.settle(streamId, "errored", null);
854
967
  if (this._pendingCutover === streamId) this._pendingCutover = null;
968
+ this._lastClosedStreamId = streamId;
855
969
  this._clearActive();
856
970
  }
857
971
  /**
@@ -861,13 +975,17 @@ var ResumableStream = class {
861
975
  * but will be missing from replay on reconnection.
862
976
  * @param streamId - The stream this chunk belongs to
863
977
  * @param body - The serialized chunk body
978
+ * @returns The chunk's index in a replay of the stream, for the live
979
+ * broadcast to carry as `seq`; `undefined` when the chunk is not stored
980
+ * or the stream's count is not tracked.
864
981
  */
865
982
  storeChunk(streamId, body) {
866
- const bodyBytes = textEncoder.encode(JSON.stringify(body)).byteLength;
867
- if (bodyBytes > CHUNK_MAX_BYTES) {
983
+ const bodyBytes = storedChunkBytes(body);
984
+ if (bodyBytes > 18e5) {
868
985
  console.warn(`[ResumableStream] Skipping oversized chunk (${bodyBytes} bytes) to prevent SQLite row limit crash. Live clients still receive it.`);
869
986
  return;
870
987
  }
988
+ const seq = streamId === this._activeStreamId && this._nextChunkSeq !== null ? this._nextChunkSeq++ : void 0;
871
989
  if (this._chunkBuffer.length >= CHUNK_BUFFER_MAX_SIZE) this.flushBuffer();
872
990
  if (this._chunkBuffer.length > 0 && this._chunkBufferBytes + bodyBytes > SEGMENT_MAX_BYTES) this.flushBuffer();
873
991
  this._chunkBuffer.push({
@@ -876,6 +994,7 @@ var ResumableStream = class {
876
994
  });
877
995
  this._chunkBufferBytes += bodyBytes;
878
996
  if (this._chunkBuffer.length >= CHUNK_BUFFER_SIZE) this.flushBuffer();
997
+ return seq;
879
998
  }
880
999
  /**
881
1000
  * Flush the buffered chunks to storage as a single packed segment.
@@ -946,13 +1065,16 @@ var ResumableStream = class {
946
1065
  if (!streamId) return null;
947
1066
  this.flushBuffer();
948
1067
  const continuation = this._activeIsContinuation;
949
- if (!sendReplayBodies(connection, requestId, this._storedBodies(streamId), continuation)) return null;
1068
+ if (!sendReplayBodies(connection, requestId, this._storedBodies(streamId), continuation, this._seqBase(streamId))) return null;
950
1069
  if (!this._isLive) {
1070
+ const row = this.ops.getStream(streamId);
951
1071
  sendReplayControl(connection, requestId, {
952
1072
  done: true,
953
- continuation
1073
+ continuation,
1074
+ messageIds: row ? parseChatMetadata(row)?.originMessageIds : void 0,
1075
+ outcome: "aborted"
954
1076
  });
955
- this.complete(streamId);
1077
+ this.complete(streamId, "aborted");
956
1078
  return streamId;
957
1079
  }
958
1080
  sendReplayControl(connection, requestId, {
@@ -975,10 +1097,33 @@ var ResumableStream = class {
975
1097
  this.flushBuffer();
976
1098
  const row = this._latestChatRowByTag(requestId, "completed");
977
1099
  if (!row) return false;
978
- const continuation = parseChatMetadata(row)?.isContinuation === 1;
979
- if (!sendReplayBodies(connection, requestId, this._storedBodies(row.stream_id), continuation)) return false;
1100
+ const chat = parseChatMetadata(row);
1101
+ const continuation = chat?.isContinuation === 1;
1102
+ if (!sendReplayBodies(connection, requestId, this._storedBodies(row.stream_id), continuation, chat?.seqBase)) return false;
980
1103
  return sendReplayControl(connection, requestId, {
981
1104
  done: true,
1105
+ continuation,
1106
+ messageIds: chat?.originMessageIds,
1107
+ outcome: chat?.outcome
1108
+ });
1109
+ }
1110
+ /**
1111
+ * Replay the request's just-closed stream (finished and awaiting its
1112
+ * cutover, completed for a recovery, or errored), ending in
1113
+ * `replayComplete` rather than `done`: the host has not sent the request's
1114
+ * terminal frame yet and delivers it live once the message is persisted.
1115
+ * After the cutover deleted the rows, only the `replayComplete` is sent.
1116
+ * @returns False when the connection closed mid-replay.
1117
+ */
1118
+ replayClosedStreamChunks(connection, requestId) {
1119
+ this.flushBuffer();
1120
+ const row = this._lastClosedStreamId !== null ? this.ops.getStream(this._lastClosedStreamId) : void 0;
1121
+ const chat = row?.tag === requestId ? parseChatMetadata(row) : null;
1122
+ const continuation = chat?.isContinuation === 1;
1123
+ if (row && chat && !sendReplayBodies(connection, requestId, this._storedBodies(row.stream_id), continuation, chat.seqBase)) return false;
1124
+ return sendReplayControl(connection, requestId, {
1125
+ done: false,
1126
+ replayComplete: true,
982
1127
  continuation
983
1128
  });
984
1129
  }
@@ -1000,7 +1145,8 @@ var ResumableStream = class {
1000
1145
  this.flushBuffer();
1001
1146
  const row = this._latestChatRowByTag(requestId, "errored");
1002
1147
  if (!row) return true;
1003
- return sendReplayBodies(connection, requestId, this._storedBodies(row.stream_id), parseChatMetadata(row)?.isContinuation === 1);
1148
+ const chat = parseChatMetadata(row);
1149
+ return sendReplayBodies(connection, requestId, this._storedBodies(row.stream_id), chat?.isContinuation === 1, chat?.seqBase);
1004
1150
  }
1005
1151
  /**
1006
1152
  * Latest chat stream row for a request regardless of status — the recovery
@@ -1049,6 +1195,7 @@ var ResumableStream = class {
1049
1195
  this._activeStreamId = row.stream_id;
1050
1196
  this._activeRequestId = row.tag;
1051
1197
  this._activeIsContinuation = row.chat.isContinuation === 1;
1198
+ this._nextChunkSeq = null;
1052
1199
  }
1053
1200
  }
1054
1201
  /**
@@ -1059,6 +1206,8 @@ var ResumableStream = class {
1059
1206
  this._chunkBuffer = [];
1060
1207
  this._chunkBufferBytes = 0;
1061
1208
  this._deleteRetiring(this._chatRows());
1209
+ this._deletedTerminals.clear();
1210
+ this._lastClosedStreamId = null;
1062
1211
  this._activeStreamId = null;
1063
1212
  this._activeRequestId = null;
1064
1213
  this._activeIsContinuation = false;
@@ -2207,23 +2356,83 @@ function parseProtocolMessage(raw) {
2207
2356
  /**
2208
2357
  * Reconcile incoming client messages against server state.
2209
2358
  *
2210
- * 1. Merges server-known tool outputs into incoming messages that still
2211
- * show stale states (input-available, approval-requested, approval-responded)
2212
- * 2. Reconciles assistant IDs: exact match → content-key match → toolCallId match
2359
+ * 1. Reconciles assistant IDs: exact match → same tool call → content-key
2360
+ * match. Each server row is claimed at most once, and a tool-call match
2361
+ * requires the same toolCallId, tool and input, since providers may reuse
2362
+ * toolCallIds across turns.
2363
+ * 2. Merges server-known tool outputs into incoming messages that still
2364
+ * show stale states (input-available, approval-requested, approval-responded).
2365
+ * Outputs come only from the server row the message resolved to.
2366
+ * 3. Drops a stale copy of an assistant the same submit also echoes under its
2367
+ * stored ID (see {@link dropStaleToolCopies}).
2213
2368
  *
2214
2369
  * @param incoming - Messages from the client
2215
2370
  * @param serverMessages - Current server-side messages (source of truth)
2216
2371
  * @param sanitizeForContentKey - Function to sanitize a message before computing
2217
- * its content key (typically strips ephemeral provider metadata)
2372
+ * its content key or comparing its tool calls against stored rows
2373
+ * (typically the host's persistence sanitizer, so a tool input the host
2374
+ * truncates on write compares equal to its stored form)
2218
2375
  * @returns Reconciled messages ready for persistence
2219
2376
  */
2220
2377
  function reconcileMessages(incoming, serverMessages, sanitizeForContentKey) {
2221
- return reconcileAssistantIds(mergeServerToolOutputs(incoming, serverMessages), serverMessages, sanitizeForContentKey);
2378
+ return dropStaleToolCopies(mergeServerToolOutputs(reconcileAssistantIds(incoming, serverMessages, sanitizeForContentKey), serverMessages), serverMessages, sanitizeForContentKey);
2379
+ }
2380
+ /**
2381
+ * Drop a stale client copy of an assistant that the same submit also echoes
2382
+ * under its stored ID. Kept, the copy persists as a second row carrying the
2383
+ * same toolCallIds and reaches the next prompt as a duplicate tool call, which
2384
+ * providers that issue unique IDs reject.
2385
+ *
2386
+ * A message is dropped only when it claimed no server row, is not the last
2387
+ * submitted message (a new call awaiting its result sits there), and consists
2388
+ * solely of `step-start` parts and pending tool parts each matching the same
2389
+ * call already settled on a server row this submit claimed. Anything else is
2390
+ * kept.
2391
+ */
2392
+ function dropStaleToolCopies(reconciled, serverMessages, sanitize) {
2393
+ const reconciledIds = new Set(reconciled.map((msg) => msg.id));
2394
+ const serverIds = /* @__PURE__ */ new Set();
2395
+ const settledOnClaimed = /* @__PURE__ */ new Map();
2396
+ for (const msg of serverMessages) {
2397
+ serverIds.add(msg.id);
2398
+ if (msg.role !== "assistant" || !reconciledIds.has(msg.id)) continue;
2399
+ for (const part of msg.parts) {
2400
+ const record = part;
2401
+ if (!isResolvedToolPart(record)) continue;
2402
+ const toolCallId = record.toolCallId;
2403
+ const settled = settledOnClaimed.get(toolCallId);
2404
+ if (settled) settled.push(record);
2405
+ else settledOnClaimed.set(toolCallId, [record]);
2406
+ }
2407
+ }
2408
+ if (settledOnClaimed.size === 0) return reconciled;
2409
+ const lastIndex = reconciled.length - 1;
2410
+ const kept = reconciled.filter((msg, index) => {
2411
+ if (index === lastIndex) return true;
2412
+ if (msg.role !== "assistant" || serverIds.has(msg.id)) return true;
2413
+ const comparable = sanitize ? sanitize(msg) : msg;
2414
+ let hasToolPart = false;
2415
+ for (const part of comparable.parts) {
2416
+ const record = part;
2417
+ if (record.type === "step-start") continue;
2418
+ if (typeof record.toolCallId !== "string" || !(isPendingToolPart(record) || record.state === "input-streaming")) return true;
2419
+ if (!settledOnClaimed.get(record.toolCallId)?.some((candidate) => sameToolCall(candidate, record))) return true;
2420
+ hasToolPart = true;
2421
+ }
2422
+ return !hasToolPart;
2423
+ });
2424
+ return kept.length === reconciled.length ? reconciled : kept;
2222
2425
  }
2223
2426
  /**
2224
2427
  * For a single message, resolve its ID by matching toolCallId against server state.
2225
2428
  * Prevents duplicate DB rows when client IDs differ from server IDs.
2226
- * Tool call IDs are unique per conversation, so matching is safe regardless of state.
2429
+ *
2430
+ * @deprecated Unsafe when a provider reuses a toolCallId across turns. This
2431
+ * scans the whole conversation and claims nothing, so a later assistant can
2432
+ * adopt an earlier row's ID and overwrite it on upsert (#1992). Use
2433
+ * {@link reconcileMessages}, which claims server rows one-to-one over the
2434
+ * whole transcript. Retained only for backwards compatibility; no longer used
2435
+ * by `@cloudflare/ai-chat` or `@cloudflare/think`.
2227
2436
  */
2228
2437
  function resolveToolMergeId(message, serverMessages) {
2229
2438
  if (message.role !== "assistant") return message;
@@ -2289,23 +2498,28 @@ function assistantContentKey(message, sanitize) {
2289
2498
  return JSON.stringify(sanitized.parts);
2290
2499
  }
2291
2500
  function mergeServerToolOutputs(incoming, serverMessages) {
2292
- const serverResolvedParts = /* @__PURE__ */ new Map();
2501
+ const serverResolvedPartsByMessage = /* @__PURE__ */ new Map();
2293
2502
  for (const msg of serverMessages) {
2294
2503
  if (msg.role !== "assistant") continue;
2504
+ const resolvedParts = /* @__PURE__ */ new Map();
2295
2505
  for (const part of msg.parts) {
2296
2506
  const record = part;
2297
- if ("toolCallId" in record && "state" in record && (record.state === "output-available" || record.state === "output-error" || record.state === "output-denied")) serverResolvedParts.set(record.toolCallId, record);
2507
+ if (isResolvedToolPart(record)) resolvedParts.set(record.toolCallId, record);
2298
2508
  }
2509
+ if (resolvedParts.size > 0) serverResolvedPartsByMessage.set(msg.id, resolvedParts);
2299
2510
  }
2300
- if (serverResolvedParts.size === 0) return incoming;
2511
+ if (serverResolvedPartsByMessage.size === 0) return incoming;
2301
2512
  return incoming.map((msg) => {
2302
2513
  if (msg.role !== "assistant") return msg;
2514
+ const ownResolvedParts = serverResolvedPartsByMessage.get(msg.id);
2515
+ if (!ownResolvedParts) return msg;
2303
2516
  let hasChanges = false;
2304
2517
  const updatedParts = msg.parts.map((part) => {
2305
2518
  const record = part;
2306
- if ("toolCallId" in record && "state" in record && (record.state === "input-available" || record.state === "approval-requested" || record.state === "approval-responded") && serverResolvedParts.has(record.toolCallId)) {
2519
+ if (!isPendingToolPart(record)) return part;
2520
+ const server = ownResolvedParts.get(record.toolCallId);
2521
+ if (server) {
2307
2522
  hasChanges = true;
2308
- const server = serverResolvedParts.get(record.toolCallId);
2309
2523
  const merged = {
2310
2524
  ...part,
2311
2525
  state: server.state
@@ -2340,7 +2554,22 @@ function reconcileAssistantIds(incoming, serverMessages, sanitize) {
2340
2554
  }
2341
2555
  return incoming.map((incomingMessage, incomingIdx) => {
2342
2556
  if (exactMatchMap.has(incomingIdx)) return incomingMessage;
2343
- if (incomingMessage.role !== "assistant" || hasToolCallPart(incomingMessage)) return incomingMessage;
2557
+ if (incomingMessage.role !== "assistant") return incomingMessage;
2558
+ const incomingToolParts = toolPartsByCallId(sanitize ? sanitize(incomingMessage) : incomingMessage);
2559
+ if (incomingToolParts.size > 0) {
2560
+ for (let i = 0; i < serverMessages.length; i++) {
2561
+ if (claimedServerIndices.has(i)) continue;
2562
+ const serverMessage = serverMessages[i];
2563
+ if (serverMessage.role === "assistant" && carriesSameToolCalls(serverMessage, incomingToolParts)) {
2564
+ claimedServerIndices.add(i);
2565
+ return {
2566
+ ...incomingMessage,
2567
+ id: serverMessage.id
2568
+ };
2569
+ }
2570
+ }
2571
+ return incomingMessage;
2572
+ }
2344
2573
  const incomingKey = assistantContentKey(incomingMessage, sanitize);
2345
2574
  if (!incomingKey) return incomingMessage;
2346
2575
  for (let i = 0; i < serverMessages.length; i++) {
@@ -2361,6 +2590,53 @@ function reconcileAssistantIds(incoming, serverMessages, sanitize) {
2361
2590
  function hasToolCallPart(message) {
2362
2591
  return message.parts.some((part) => "toolCallId" in part);
2363
2592
  }
2593
+ /** A server-side tool part that has reached a terminal state. */
2594
+ function isResolvedToolPart(record) {
2595
+ return "toolCallId" in record && "state" in record && (record.state === "output-available" || record.state === "output-error" || record.state === "output-denied");
2596
+ }
2597
+ /** A client-side tool part still waiting on a result. */
2598
+ function isPendingToolPart(record) {
2599
+ return "toolCallId" in record && "state" in record && (record.state === "input-available" || record.state === "approval-requested" || record.state === "approval-responded");
2600
+ }
2601
+ /**
2602
+ * Whether `serverMessage` shares at least one toolCallId with the incoming
2603
+ * message and every shared toolCallId is the same call on both sides. A
2604
+ * provider that reuses a toolCallId for a new call carries a different tool
2605
+ * or input, so it cannot adopt the older row's ID.
2606
+ */
2607
+ function carriesSameToolCalls(serverMessage, incomingToolParts) {
2608
+ let shared = false;
2609
+ for (const part of serverMessage.parts) {
2610
+ const record = part;
2611
+ if (typeof record.toolCallId !== "string") continue;
2612
+ const incomingPart = incomingToolParts.get(record.toolCallId);
2613
+ if (!incomingPart) continue;
2614
+ if (!sameToolCall(record, incomingPart)) return false;
2615
+ shared = true;
2616
+ }
2617
+ return shared;
2618
+ }
2619
+ /**
2620
+ * Same tool and structurally equal input (object key order ignored). Static
2621
+ * tool parts may omit `toolName`, so it is compared only when both carry it.
2622
+ */
2623
+ function sameToolCall(a, b) {
2624
+ return a.type === b.type && (a.toolName === void 0 || b.toolName === void 0 || a.toolName === b.toolName) && stableStringify(a.input) === stableStringify(b.input);
2625
+ }
2626
+ function stableStringify(value) {
2627
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
2628
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item) ?? "null").join(",")}]`;
2629
+ const record = value;
2630
+ return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
2631
+ }
2632
+ function toolPartsByCallId(message) {
2633
+ const parts = /* @__PURE__ */ new Map();
2634
+ for (const part of message.parts) {
2635
+ const record = part;
2636
+ if (typeof record.toolCallId === "string" && !parts.has(record.toolCallId)) parts.set(record.toolCallId, record);
2637
+ }
2638
+ return parts;
2639
+ }
2364
2640
  function findMessageByToolCallId(messages, toolCallId) {
2365
2641
  for (const msg of messages) {
2366
2642
  if (msg.role !== "assistant") continue;
@@ -2392,7 +2668,7 @@ function toolPartHasSettledResult(record) {
2392
2668
  *
2393
2669
  * - a tool part with NO settled result and state `approval-responded` is kept
2394
2670
  * verbatim (an approved server tool waiting for its continuation to run
2395
- * `execute()` — not abandoned);
2671
+ * `execute()` — not abandoned), unless `repairApprovalResponded` is set;
2396
2672
  * - a tool part with NO settled result for which `shouldRepair` returns false
2397
2673
  * is kept verbatim (a part still awaiting a CLIENT interaction; see option);
2398
2674
  * - any other tool part with no settled result is normalized then handed to
@@ -2411,7 +2687,8 @@ function repairInterruptedToolParts(messages, options) {
2411
2687
  let normalizedInputs = 0;
2412
2688
  const toolCallIds = [];
2413
2689
  const repaired = [];
2414
- for (const message of messages) {
2690
+ for (const [messageIndex, message] of messages.entries()) {
2691
+ const isLastMessage = messageIndex === messages.length - 1;
2415
2692
  const parts = [];
2416
2693
  let messageChanged = false;
2417
2694
  for (const part of message.parts) {
@@ -2423,8 +2700,23 @@ function repairInterruptedToolParts(messages, options) {
2423
2700
  }
2424
2701
  if (!isSettled(record)) {
2425
2702
  if ((typeof record.state === "string" ? record.state : "") === "approval-responded") {
2426
- parts.push(part);
2427
- continue;
2703
+ if (!options.repairApprovalResponded || isLastMessage) {
2704
+ parts.push(part);
2705
+ continue;
2706
+ }
2707
+ if (record.approval?.approved === false) {
2708
+ const normalized = normalizeInput("input" in record ? record.input : void 0);
2709
+ parts.push({
2710
+ ...part,
2711
+ input: normalized.input,
2712
+ state: "output-denied"
2713
+ });
2714
+ if (normalized.changed) normalizedInputs++;
2715
+ removedToolCalls++;
2716
+ messageChanged = true;
2717
+ toolCallIds.push(toolCallId);
2718
+ continue;
2719
+ }
2428
2720
  }
2429
2721
  if (!shouldRepair(part)) {
2430
2722
  parts.push(part);
@@ -2489,7 +2781,7 @@ async function persistReconstructedOrphan(chunks, options) {
2489
2781
  }
2490
2782
  //#endregion
2491
2783
  //#region src/chat/recovery.ts
2492
- function createChatFiberSnapshot({ kind, requestId, recoveryRootRequestId, continuation, messages, lastBody, lastClientTools }) {
2784
+ function createChatFiberSnapshot({ kind, requestId, recoveryRootRequestId, continuation, messages, lastBody, lastClientTools, originMessageIds }) {
2493
2785
  const latestMessage = messages.length > 0 ? messages[messages.length - 1] : void 0;
2494
2786
  let latestUser;
2495
2787
  for (let index = messages.length - 1; index >= 0; index--) if (messages[index].role === "user") {
@@ -2507,7 +2799,8 @@ function createChatFiberSnapshot({ kind, requestId, recoveryRootRequestId, conti
2507
2799
  latestUserMessageId: latestUser?.id,
2508
2800
  startedAt: Date.now(),
2509
2801
  lastBody,
2510
- lastClientTools
2802
+ lastClientTools,
2803
+ ...originMessageIds ? { originMessageIds } : {}
2511
2804
  };
2512
2805
  }
2513
2806
  function wrapChatFiberSnapshot(key, snapshot, user) {
@@ -2699,13 +2992,20 @@ var ResumeHandshake = class {
2699
2992
  if (resumableStream.hasActiveStream() && resumableStream.activeRequestId === requestId) {
2700
2993
  const orphanedStreamId = resumableStream.replayChunks(connection, resumableStream.activeRequestId);
2701
2994
  if (orphanedStreamId) await this.host.persistOrphanedStream(orphanedStreamId);
2702
- } else if (resumableStream.hasActiveStream()) {} else if (await this._replayTerminalOnAck(connection, requestId)) {} else if (!resumableStream.replayCompletedChunksByRequestId(connection, requestId)) sendIfOpen(connection, JSON.stringify({
2703
- body: "",
2704
- done: true,
2705
- id: requestId,
2706
- type: responseMessageType,
2707
- replay: true
2708
- }));
2995
+ } else if (resumableStream.hasActiveStream()) {} else if (this.host.holdsTerminalFrames?.(requestId)) resumableStream.replayClosedStreamChunks(connection, requestId);
2996
+ else if (await this._replayTerminalOnAck(connection, requestId)) {} else if (!resumableStream.replayCompletedChunksByRequestId(connection, requestId)) {
2997
+ const messageIds = resumableStream.getOriginMessageIds(requestId);
2998
+ const outcome = resumableStream.getOutcome(requestId);
2999
+ sendIfOpen(connection, JSON.stringify({
3000
+ body: "",
3001
+ done: true,
3002
+ id: requestId,
3003
+ type: responseMessageType,
3004
+ replay: true,
3005
+ ...messageIds && { messageIds },
3006
+ ...outcome && { outcome }
3007
+ }));
3008
+ }
2709
3009
  }
2710
3010
  /**
2711
3011
  * Replay a pending terminal outcome (#1645) over the resume handshake so a
@@ -2737,12 +3037,14 @@ var ResumeHandshake = class {
2737
3037
  const pending = await this.host.pendingChatTerminal();
2738
3038
  if (!pending || pending.requestId !== requestId) return false;
2739
3039
  if (!resumableStream.replayErroredChunksByRequestId(connection, pending.requestId)) return true;
3040
+ const messageIds = pending.messageIds ?? resumableStream.getOriginMessageIds(pending.requestId);
2740
3041
  sendIfOpen(connection, JSON.stringify({
2741
3042
  body: pending.body,
2742
3043
  done: true,
2743
3044
  error: true,
2744
3045
  id: pending.requestId,
2745
- type: responseMessageType
3046
+ type: responseMessageType,
3047
+ ...messageIds && { messageIds }
2746
3048
  }));
2747
3049
  return true;
2748
3050
  }
@@ -2782,7 +3084,7 @@ const CHAT_LAST_TERMINAL_KEY = "cf:chat:last-terminal";
2782
3084
  const DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS = 10;
2783
3085
  /**
2784
3086
  * Runaway-loop guard default — the framework-imposed backstop on cumulative
2785
- * recovery WORK (produced content/tool units) since an incident opened.
3087
+ * recovery WORK (durable stream segments, see below) since an incident opened.
2786
3088
  *
2787
3089
  * Originally `Infinity` (rfc-chat-recovery-work-budget): the SDK shipped the
2788
3090
  * *mechanism* but no default cap, so a progressing turn was never terminated on
@@ -3022,11 +3324,13 @@ var StreamProgressCreditThrottle = class {
3022
3324
  * until a later turn supersedes it ({@link clearChatTerminal}); a single record
3023
3325
  * is sufficient because only the most recent terminal is relevant.
3024
3326
  */
3025
- async function recordChatTerminal(storage, requestId, body) {
3026
- await storage.put(CHAT_LAST_TERMINAL_KEY, {
3327
+ async function recordChatTerminal(storage, requestId, body, messageIds) {
3328
+ const record = {
3027
3329
  requestId,
3028
3330
  body
3029
- });
3331
+ };
3332
+ if (messageIds?.length) record.messageIds = messageIds;
3333
+ await storage.put(CHAT_LAST_TERMINAL_KEY, record);
3030
3334
  }
3031
3335
  /** Clear the durable terminal record once a later turn supersedes it (#1645). */
3032
3336
  async function clearChatTerminal(storage) {
@@ -3123,10 +3427,12 @@ async function evaluateChatRecoveryIncident(input) {
3123
3427
  const workBudgetExceeded = existing != null && Number.isFinite(config.maxRecoveryWork) && work > config.maxRecoveryWork;
3124
3428
  const oomAttempts = existing?.oomAttempts ?? 0;
3125
3429
  const oomBudgetExceeded = existing != null && !awaitingClientInteraction && oomAttempts > config.maxOomRetries;
3430
+ const transientRetries = existing?.transientRetries ?? 0;
3431
+ const transientBudgetExceeded = existing != null && !awaitingClientInteraction && transientRetries >= config.maxAttempts;
3126
3432
  const debounced = existing != null && !madeProgress && now - existing.lastAttemptAt < 3e4;
3127
3433
  const attempt = madeProgress ? 1 : debounced ? existing?.attempt ?? 1 : (existing?.attempt ?? 0) + 1;
3128
3434
  let abortedByCaller = false;
3129
- if (existing != null && !awaitingClientInteraction && config.shouldKeepRecovering && !noProgressExceeded && !workBudgetExceeded && !oomBudgetExceeded && attempt <= config.maxAttempts) try {
3435
+ if (existing != null && !awaitingClientInteraction && config.shouldKeepRecovering && !noProgressExceeded && !workBudgetExceeded && !oomBudgetExceeded && !transientBudgetExceeded && attempt <= config.maxAttempts) try {
3130
3436
  const ctx = {
3131
3437
  incidentId,
3132
3438
  requestId: identity.requestId,
@@ -3141,7 +3447,7 @@ async function evaluateChatRecoveryIncident(input) {
3141
3447
  } catch (error) {
3142
3448
  input.onShouldKeepRecoveringError?.(error);
3143
3449
  }
3144
- const exhausted = !awaitingClientInteraction && (oomBudgetExceeded || noProgressExceeded || workBudgetExceeded || abortedByCaller || attempt > config.maxAttempts);
3450
+ const exhausted = !awaitingClientInteraction && (oomBudgetExceeded || noProgressExceeded || workBudgetExceeded || abortedByCaller || transientBudgetExceeded || attempt > config.maxAttempts);
3145
3451
  const incident = {
3146
3452
  incidentId,
3147
3453
  requestId: identity.requestId,
@@ -3156,6 +3462,7 @@ async function evaluateChatRecoveryIncident(input) {
3156
3462
  progress,
3157
3463
  workBaseline,
3158
3464
  ...oomAttempts > 0 ? { oomAttempts } : {},
3465
+ ...transientRetries > 0 ? { transientRetries } : {},
3159
3466
  ...exhausted ? { reason: oomBudgetExceeded ? "out_of_memory" : workBudgetExceeded ? "work_budget_exceeded" : noProgressExceeded ? "no_progress_timeout" : abortedByCaller ? "recovery_aborted" : "max_attempts_exceeded" } : {}
3160
3467
  };
3161
3468
  const events = [];
@@ -3268,7 +3575,7 @@ var ChatRecoveryEngine = class {
3268
3575
  options: void 0,
3269
3576
  partial
3270
3577
  })) await wake.persistOrphanedStream(streamId);
3271
- await adapter.exhaustChatRecovery(incident, config, partial, streamId, ctx.createdAt);
3578
+ await adapter.exhaustChatRecovery(incident, config, partial, streamId, ctx.createdAt, snapshot?.originMessageIds);
3272
3579
  return true;
3273
3580
  }
3274
3581
  try {
@@ -3382,7 +3689,7 @@ var ChatRecoveryEngine = class {
3382
3689
  maxAttempts: incident.maxAttempts,
3383
3690
  recoveryKind: input.recoveryKind
3384
3691
  });
3385
- await this.adapter.scheduleRecovery(input.callback, input.data, input.reason ?? "initial", 0);
3692
+ await this.adapter.scheduleRecovery(input.callback, input.data, input.reason ?? "initial", input.delaySeconds ?? 0);
3386
3693
  }
3387
3694
  /**
3388
3695
  * Reschedule a recovery continuation/retry that timed out waiting for stable
@@ -3421,6 +3728,47 @@ var ChatRecoveryEngine = class {
3421
3728
  return true;
3422
3729
  }
3423
3730
  /**
3731
+ * Bump the incident's durable `transientRetries` counter before scheduling a
3732
+ * recovery for a transient or rate-limited stream error, and return the new
3733
+ * count so the caller can derive its backoff. Returns `1` when the incident
3734
+ * record is gone.
3735
+ */
3736
+ async recordTransientRetry(incidentId) {
3737
+ const { adapter } = this;
3738
+ const key = chatRecoveryIncidentKey(incidentId);
3739
+ const incident = await adapter.getIncident(key);
3740
+ if (!incident) return 1;
3741
+ const transientRetries = (incident.transientRetries ?? 0) + 1;
3742
+ await adapter.putIncident(key, {
3743
+ ...incident,
3744
+ transientRetries
3745
+ });
3746
+ return transientRetries;
3747
+ }
3748
+ /**
3749
+ * The user cancelled `requestId` while its recovery was scheduled but not
3750
+ * yet running (e.g. during a transient-error backoff). Marks every
3751
+ * `scheduled` incident whose recovery root or current attempt is
3752
+ * `requestId` as `skipped` with {@link CHAT_RECOVERY_CANCELLED_REASON}, so
3753
+ * the queued callback bails when it fires (see {@link isRecoveryCancelled}).
3754
+ * Returns whether any incident was cancelled.
3755
+ */
3756
+ async cancelScheduledRecovery(requestId) {
3757
+ const incidents = await this.adapter.listActiveIncidents?.() ?? [];
3758
+ let cancelled = false;
3759
+ for (const incident of incidents) if (incident.status === "scheduled" && (incident.recoveryRootRequestId === requestId || incident.requestId === requestId)) {
3760
+ await this.updateIncident(incident.incidentId, "skipped", CHAT_RECOVERY_CANCELLED_REASON);
3761
+ cancelled = true;
3762
+ }
3763
+ return cancelled;
3764
+ }
3765
+ /** Whether the user cancelled this incident's scheduled recovery. */
3766
+ async isRecoveryCancelled(incidentId) {
3767
+ if (!incidentId) return false;
3768
+ const incident = await this.adapter.getIncident(chatRecoveryIncidentKey(incidentId));
3769
+ return incident?.status === "skipped" && incident.reason === "user_cancelled";
3770
+ }
3771
+ /**
3424
3772
  * Record that a recovery callback observed a Durable Object memory-limit reset
3425
3773
  * (the isolate exceeded its 128 MB limit — `isDurableObjectMemoryLimitReset`)
3426
3774
  * and decide what to do next (#1825).
@@ -3587,6 +3935,47 @@ var ChatRecoveryEngine = class {
3587
3935
  else if (status === "completed" || status === "skipped" || status === "failed") await adapter.setRecovering(false);
3588
3936
  }
3589
3937
  };
3938
+ /** `incident.reason` for a scheduled recovery the user cancelled. */
3939
+ const CHAT_RECOVERY_CANCELLED_REASON = "user_cancelled";
3940
+ /**
3941
+ * Delay before the `retries`-th transient-error recovery: exponential backoff
3942
+ * capped at {@link CHAT_RECOVERY_MAX_BACKOFF_SECONDS}. A provider
3943
+ * `retryAfterSeconds` (rate limits) extends it, capped at
3944
+ * {@link CHAT_RECOVERY_MAX_RETRY_AFTER_SECONDS}.
3945
+ */
3946
+ function chatRecoveryBackoffSeconds(retries, retryAfterSeconds) {
3947
+ const backoff = Math.min(2 ** (retries - 1), 30);
3948
+ if (retryAfterSeconds === void 0) return backoff;
3949
+ return Math.min(Math.max(backoff, retryAfterSeconds), 60);
3950
+ }
3951
+ /**
3952
+ * The `Retry-After` an error carries, in whole seconds. Reads
3953
+ * `responseHeaders` (AI SDK `APICallError`) or `headers` on the error or its
3954
+ * `cause` chain; accepts delta-seconds or an HTTP date. `undefined` when absent
3955
+ * or unparseable.
3956
+ */
3957
+ function retryAfterSeconds(error, now = Date.now()) {
3958
+ let current = error;
3959
+ for (let depth = 0; depth < 8 && current != null; depth++) {
3960
+ if (typeof current !== "object") return void 0;
3961
+ const record = current;
3962
+ const value = readRetryAfterHeader(record.responseHeaders) ?? readRetryAfterHeader(record.headers);
3963
+ if (value !== void 0) return parseRetryAfter(value, now);
3964
+ current = record.cause;
3965
+ }
3966
+ }
3967
+ function readRetryAfterHeader(headers) {
3968
+ if (headers == null || typeof headers !== "object") return void 0;
3969
+ if (headers instanceof Headers) return headers.get("retry-after") ?? void 0;
3970
+ for (const [name, value] of Object.entries(headers)) if (name.toLowerCase() === "retry-after" && typeof value === "string") return value;
3971
+ }
3972
+ function parseRetryAfter(value, now) {
3973
+ const trimmed = value.trim();
3974
+ if (/^\d+(\.\d+)?$/.test(trimmed)) return Math.ceil(Number(trimmed));
3975
+ const date = Date.parse(trimmed);
3976
+ if (Number.isNaN(date)) return void 0;
3977
+ return Math.max(0, Math.ceil((date - now) / 1e3));
3978
+ }
3590
3979
  /**
3591
3980
  * Build the `ChatRecoveryExhaustedContext` delivered to `onExhausted` and the
3592
3981
  * `chat:recovery:exhausted` event. Pure field-mapping shared by both packages;
@@ -3746,20 +4135,14 @@ async function* iterateWithStallWatchdog(source, timeoutMs, onStall) {
3746
4135
  //#endregion
3747
4136
  //#region src/chat/truncate-older-messages.ts
3748
4137
  /**
3749
- * Read-time context truncation.
3750
- *
3751
- * Truncates older tool outputs and long text before sending to the LLM.
3752
- * Structured tool outputs keep their container shape so tool-specific
3753
- * `toModelOutput` handlers can safely replay older results.
3754
- * Does NOT mutate stored messages — operates on a copy.
3755
- */
3756
- /**
3757
4138
  * Truncate tool outputs and long text in older messages.
3758
4139
  * Returns a new array — input messages are not mutated.
3759
4140
  *
3760
4141
  * Recent messages (last `keepRecent`) are left intact.
3761
4142
  * Older messages get tool outputs and long text truncated. Structured tool
3762
4143
  * outputs are truncated in place instead of being replaced by raw strings.
4144
+ * Provider-executed tool outputs are never truncated: the provider parses
4145
+ * them against its own schema when they are replayed.
3763
4146
  *
3764
4147
  * Use in assembleContext() before sending to the LLM:
3765
4148
  * ```typescript
@@ -3774,6 +4157,7 @@ function truncateOlderMessages(messages, options) {
3774
4157
  const keepRecent = options?.keepRecent ?? 4;
3775
4158
  const maxToolOutput = options?.maxToolOutputChars ?? 500;
3776
4159
  const maxText = options?.maxTextChars ?? 1e4;
4160
+ const truncateToolOutputs = options?.toolOutputs ?? true;
3777
4161
  if (messages.length <= keepRecent) return messages;
3778
4162
  const cutoff = messages.length - keepRecent;
3779
4163
  const result = [];
@@ -3785,7 +4169,7 @@ function truncateOlderMessages(messages, options) {
3785
4169
  const msg = messages[i];
3786
4170
  let changed = false;
3787
4171
  const truncatedParts = msg.parts.map((part) => {
3788
- if ((part.type.startsWith("tool-") || part.type === "dynamic-tool") && "output" in part) {
4172
+ if (truncateToolOutputs && isToolPart(part) && "output" in part && !isProviderExecuted(part)) {
3789
4173
  const output = part.output;
3790
4174
  if (output !== void 0) {
3791
4175
  const truncated = truncateToolOutput(output, maxToolOutput);
@@ -3817,7 +4201,153 @@ function truncateOlderMessages(messages, options) {
3817
4201
  }
3818
4202
  return result;
3819
4203
  }
4204
+ /**
4205
+ * Truncate the tool results of older messages after `convertToModelMessages`.
4206
+ * `messages` are the UI messages that were converted, so "older" means the
4207
+ * same messages {@link truncateOlderMessages} treats as older.
4208
+ *
4209
+ * The converted result is what the model reads, after any `toModelOutput`, so
4210
+ * no tool schema applies to it. Provider-executed results are left intact.
4211
+ * Inline file and image bytes in an older `content` result are replaced by a
4212
+ * short text marker.
4213
+ *
4214
+ * Tool call ids are not unique across turns, so each converted result is
4215
+ * matched to the UI tool part it came from by order: the Nth result for an id
4216
+ * belongs to the Nth UI part with that id that converts to a tool result.
4217
+ */
4218
+ function truncateOlderToolResults(modelMessages, messages, options) {
4219
+ const keepRecent = options?.keepRecent ?? 4;
4220
+ const maxChars = options?.maxToolOutputChars ?? 500;
4221
+ const cutoff = messages.length - keepRecent;
4222
+ if (cutoff <= 0) return modelMessages;
4223
+ const resultsById = /* @__PURE__ */ new Map();
4224
+ let hasOlderResult = false;
4225
+ messages.forEach((message, index) => {
4226
+ for (const part of message.parts) {
4227
+ if (!isToolPart(part) || !producesToolResult(part)) continue;
4228
+ const truncatable = index < cutoff && !isProviderExecuted(part);
4229
+ const toolCallId = part.toolCallId;
4230
+ const results = resultsById.get(toolCallId) ?? [];
4231
+ results.push(truncatable);
4232
+ resultsById.set(toolCallId, results);
4233
+ hasOlderResult ||= truncatable;
4234
+ }
4235
+ });
4236
+ if (!hasOlderResult) return modelMessages;
4237
+ return modelMessages.map((message) => {
4238
+ if (message.role !== "tool") return message;
4239
+ let changed = false;
4240
+ const content = message.content.map((part) => {
4241
+ if (part.type !== "tool-result") return part;
4242
+ if (!resultsById.get(part.toolCallId)?.shift()) return part;
4243
+ const output = truncateModelOutput(part.output, maxChars);
4244
+ if (output === part.output) return part;
4245
+ changed = true;
4246
+ return {
4247
+ ...part,
4248
+ output
4249
+ };
4250
+ });
4251
+ return changed ? {
4252
+ ...message,
4253
+ content
4254
+ } : message;
4255
+ });
4256
+ }
4257
+ function truncateModelOutput(output, maxChars) {
4258
+ switch (output.type) {
4259
+ case "text":
4260
+ case "error-text":
4261
+ case "json":
4262
+ case "error-json": {
4263
+ const truncated = truncateToolOutput(output.value, maxChars);
4264
+ return truncated.truncated ? {
4265
+ ...output,
4266
+ value: truncated.output
4267
+ } : output;
4268
+ }
4269
+ case "content": {
4270
+ let changed = false;
4271
+ const withoutBytes = output.value.map((item) => {
4272
+ const mediaType = inlineBinaryMediaType(item);
4273
+ if (mediaType === void 0) return item;
4274
+ changed = true;
4275
+ return {
4276
+ type: "text",
4277
+ text: omittedMediaMarker(mediaType)
4278
+ };
4279
+ });
4280
+ const total = output.value.reduce((sum, item) => sum + (inlineText(item)?.length ?? 0), 0);
4281
+ if (total <= maxChars) return changed ? {
4282
+ ...output,
4283
+ value: withoutBytes
4284
+ } : output;
4285
+ const suffix = truncatedSuffix(total);
4286
+ let remaining = Math.max(0, maxChars - suffix.length);
4287
+ let truncated = false;
4288
+ const value = output.value.flatMap((item, index) => {
4289
+ const text = inlineText(item);
4290
+ if (text === void 0) return [withoutBytes[index]];
4291
+ if (truncated) return [];
4292
+ if (text.length <= remaining) {
4293
+ remaining -= text.length;
4294
+ return [item];
4295
+ }
4296
+ truncated = true;
4297
+ return [withInlineText(item, maxChars <= suffix.length ? suffix.slice(0, maxChars) : `${text.slice(0, remaining)}${suffix}`)];
4298
+ });
4299
+ return {
4300
+ ...output,
4301
+ value
4302
+ };
4303
+ }
4304
+ default: return output;
4305
+ }
4306
+ }
4307
+ /** Text a content item carries inline: a text item, or a text file. */
4308
+ function inlineText(item) {
4309
+ if (item.type === "text") return item.text;
4310
+ if (item.type === "file" && item.data.type === "text") return item.data.text;
4311
+ }
4312
+ function withInlineText(item, text) {
4313
+ if (item.type === "file" && item.data.type === "text") return {
4314
+ ...item,
4315
+ data: {
4316
+ ...item.data,
4317
+ text
4318
+ }
4319
+ };
4320
+ return {
4321
+ ...item,
4322
+ text
4323
+ };
4324
+ }
4325
+ /** Media type of a content item that carries file or image bytes inline. */
4326
+ function inlineBinaryMediaType(item) {
4327
+ if (item.type === "file-data" || item.type === "image-data") return item.mediaType;
4328
+ if (item.type === "file" && item.data.type === "data") return item.mediaType;
4329
+ }
4330
+ function omittedMediaMarker(mediaType) {
4331
+ return `[${mediaType} omitted from an older tool result]`;
4332
+ }
4333
+ /**
4334
+ * Whether `convertToModelMessages` turns this UI tool part into a result in a
4335
+ * `tool` message. Provider-executed results go in the assistant message
4336
+ * instead, unless the part is a denied approval.
4337
+ */
4338
+ function producesToolResult(part) {
4339
+ const { state, approval } = part;
4340
+ if (state === "approval-responded") return approval?.approved === false;
4341
+ if (isProviderExecuted(part)) return false;
4342
+ return state === "output-available" || state === "output-error" || state === "output-denied";
4343
+ }
4344
+ function isToolPart(part) {
4345
+ return part.type.startsWith("tool-") || part.type === "dynamic-tool";
4346
+ }
4347
+ function isProviderExecuted(part) {
4348
+ return part.providerExecuted === true;
4349
+ }
3820
4350
  //#endregion
3821
- export { AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS, AbortRegistry, AgentToolProgressEmitter, AgentToolStreamProgressThrottle, AutoContinuationController, CHAT_LAST_TERMINAL_KEY, CHAT_MESSAGE_TYPES, CHAT_RECOVERING_FLAG_TTL_MS, CHAT_RECOVERING_KEY, CHAT_RECOVERY_ALARM_DEBOUNCE_MS, CHAT_RECOVERY_INCIDENT_KEY_PREFIX, CHAT_RECOVERY_INCIDENT_TTL_MS, CHAT_RECOVERY_PROGRESS_KEY, CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS, CHAT_RECOVERY_TASK_NAME, CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS, ChatRecoveryEngine, ChatStreamStalledError, ContinuationState, DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS, DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES, DEFAULT_CHAT_RECOVERY_MAX_WORK, DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS, DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS, DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE, KV_DELETE_MAX_KEYS, MAX_BOUND_PARAMS, MessageType, PreStreamTurns, ROW_MAX_BYTES, ResumableStream, ResumeHandshake, STREAM_RESUME_NONE_REASONS, StreamAccumulator, StreamProgressCreditThrottle, SubmitConcurrencyController, TIMED_OUT, TextSegmentJoiner, TurnQueue, aiSdkRecoveryCodec, applyAgentToolEvent, applyChunkToParts, applyToolUpdate, awaitWithDeadline, transition as broadcastTransition, buildChatRecoveringFrame, buildInClauseStrings, bumpChatRecoveryProgress, byteLength, chatRecoveryTaskRunOptions, classifyAgentToolChildRecovery, clearChatTerminal, clientResolvableToolNames, createAgentToolEventState, createChatFiberSnapshot, createChatRecoveryTaskDefinition, createChatStreams, createChatTurnTaskDefinition, createToolsFromClientSchemas, crossMessageToolResultUpdate, dispatchChatRecoveryToHandoff, drainInteractionApplies, enforceRowSizeLimit, hasIncompleteToolBatch, interceptAgentToolBroadcast, isPlatformFailure, isReplayChunk, iterateWithStallWatchdog, listActiveChatRecoveryIncidents, normalizeToolInput, parseProtocolMessage, partAwaitsClientInteraction, pausedExecutionUpdate, pendingChatTerminal, persistReconstructedOrphan, readChatRecoveryProgress, reconcileMessages, reconcileOrphanPartial, recordChatTerminal, repairInterruptedToolParts, resolveChatRecoveryConfig, resolveToolMergeId, runChatRecoveryExhaustion, sanitizeMessage, sendIfOpen, setChatRecovering, shouldCreditStreamProgress, sweepStaleChatRecoveryIncidents, toolApprovalUpdate, toolPartHasSettledResult, toolResultUpdate, truncateOlderMessages, unwrapChatFiberSnapshot, wrapChatFiberSnapshot };
4351
+ export { AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS, AbortRegistry, AgentToolProgressEmitter, AgentToolStreamProgressThrottle, AutoContinuationController, CHAT_LAST_TERMINAL_KEY, CHAT_MESSAGE_TYPES, CHAT_RECOVERING_FLAG_TTL_MS, CHAT_RECOVERING_KEY, CHAT_RECOVERY_ALARM_DEBOUNCE_MS, CHAT_RECOVERY_CANCELLED_REASON, CHAT_RECOVERY_INCIDENT_KEY_PREFIX, CHAT_RECOVERY_INCIDENT_TTL_MS, CHAT_RECOVERY_PROGRESS_KEY, CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS, CHAT_RECOVERY_TASK_NAME, CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS, ChatRecoveryEngine, ChatStreamStalledError, ContinuationState, DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS, DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES, DEFAULT_CHAT_RECOVERY_MAX_WORK, DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS, DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS, DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE, KV_DELETE_MAX_KEYS, MAX_BOUND_PARAMS, MessageType, PreStreamTurns, ROW_MAX_BYTES, ResumableStream, ResumeHandshake, STREAM_RESUME_NONE_REASONS, StreamAccumulator, StreamProgressCreditThrottle, SubmitConcurrencyController, TIMED_OUT, TextSegmentJoiner, TurnQueue, aiSdkRecoveryCodec, applyAgentToolEvent, applyChunkToParts, applyLateToolInput, applyToolUpdate, awaitWithDeadline, transition as broadcastTransition, buildChatRecoveringFrame, buildInClauseStrings, bumpChatRecoveryProgress, byteLength, chatRecoveryBackoffSeconds, chatRecoveryTaskRunOptions, classifyAgentToolChildRecovery, clearChatTerminal, clientResolvableToolNames, createAgentToolEventState, createChatFiberSnapshot, createChatRecoveryTaskDefinition, createChatStreams, createChatTurnTaskDefinition, createToolsFromClientSchemas, crossMessageToolResultUpdate, dispatchChatRecoveryToHandoff, drainInteractionApplies, enforceRowSizeLimit, hasIncompleteToolBatch, interceptAgentToolBroadcast, isDurableObjectResetError, isLateToolInputChunk, isPlatformFailure, isPositionlessAgentToolChunk, isReplayChunk, iterateWithStallWatchdog, lateToolInputForwardChunks, listActiveChatRecoveryIncidents, normalizeToolInput, originMessageIds, parseProtocolMessage, partAwaitsClientInteraction, partialHasSettledToolResults, pausedExecutionUpdate, pendingChatTerminal, persistReconstructedOrphan, readChatRecoveryProgress, reconcileMessages, reconcileOrphanPartial, recordChatTerminal, repairInterruptedToolParts, resolveChatRecoveryConfig, resolveToolMergeId, retryAfterSeconds, runChatRecoveryExhaustion, sanitizeMessage, sendIfOpen, setChatRecovering, shouldCreditStreamProgress, sweepStaleChatRecoveryIncidents, toolApprovalUpdate, toolPartHasSettledResult, toolResultUpdate, truncateOlderMessages, truncateOlderToolResults, unwrapChatFiberSnapshot, withOriginMessageIds, wrapChatFiberSnapshot };
3822
4352
 
3823
4353
  //# sourceMappingURL=index.js.map