agents 0.24.0 → 0.26.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 (170) hide show
  1. package/dist/{agent-routing-B2XLNMxq.d.ts → agent-routing-0DR0Tk2v.d.ts} +205 -132
  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 +28 -22
  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-Dj5Mzlw7.d.ts → agent-tools-YrATZJu2.d.ts} +34 -20
  10. package/dist/agent-tools.d.ts +20 -18
  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-B4WbF81e.js.map +1 -1
  32. package/dist/{capability-runner-BUBa6Ake.d.ts → capability-runner-DBAnGr6L.d.ts} +7 -2
  33. package/dist/chat/index.d.ts +340 -234
  34. package/dist/chat/index.js +592 -66
  35. package/dist/chat/index.js.map +1 -1
  36. package/dist/chat/react.d.ts +42 -2
  37. package/dist/chat/react.js +191 -35
  38. package/dist/chat/react.js.map +1 -1
  39. package/dist/chat/transport.d.ts +1 -1
  40. package/dist/chat/transport.js +1 -1
  41. package/dist/chat-sdk/index.d.ts +17 -3
  42. package/dist/chat-sdk/index.js +28 -2
  43. package/dist/chat-sdk/index.js.map +1 -1
  44. package/dist/{client-C9DAjZIY.js → client-4OlMIJx-.js} +102 -29
  45. package/dist/client-4OlMIJx-.js.map +1 -0
  46. package/dist/{client-BxdkCuVy.d.ts → client-je3koD9Z.d.ts} +45 -10
  47. package/dist/client.d.ts +7 -5
  48. package/dist/client.js +2 -2
  49. package/dist/{connector-CptFKzRh.js → connector-58XDGvc3.js} +251 -120
  50. package/dist/connector-58XDGvc3.js.map +1 -0
  51. package/dist/{connector-CkQD4MK3.d.ts → connector-Cl-VqPmA.d.ts} +244 -146
  52. package/dist/context/index.d.ts +29 -0
  53. package/dist/context/index.js +64 -17
  54. package/dist/context/index.js.map +1 -1
  55. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  56. package/dist/{current-agent-C4w86kcN.d.ts → current-agent-vAk_5JMt.d.ts} +4 -4
  57. package/dist/{durable-object-lifecycle-ei7M7lqB.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +15 -7
  58. package/dist/harness/pi/index.d.ts +344 -0
  59. package/dist/harness/pi/index.js +873 -0
  60. package/dist/harness/pi/index.js.map +1 -0
  61. package/dist/images-iiTDSyRu.js +1631 -0
  62. package/dist/images-iiTDSyRu.js.map +1 -0
  63. package/dist/{index-Dlgtd3Mj.d.ts → index-BreL0YrQ.d.ts} +2 -2
  64. package/dist/index-kbSUIer3.d.ts +343 -0
  65. package/dist/index.d.ts +46 -44
  66. package/dist/index.js +1 -1
  67. package/dist/internal_context.d.ts +1 -1
  68. package/dist/lifecycle/index.d.ts +3 -3
  69. package/dist/lifecycle/index.js +1 -1
  70. package/dist/{lifecycle-Mm_jQh7r.js → lifecycle-CFu6OV03.js} +39 -10
  71. package/dist/{lifecycle-Mm_jQh7r.js.map → lifecycle-CFu6OV03.js.map} +1 -1
  72. package/dist/mcp/client/index.d.ts +20 -20
  73. package/dist/mcp/client/x402.js +21 -19
  74. package/dist/mcp/client/x402.js.map +1 -1
  75. package/dist/mcp/index.d.ts +35 -35
  76. package/dist/mcp/index.js +1 -1
  77. package/dist/{message-builder-BymO4N_D.js → message-builder-DAl9AhHH.js} +89 -4
  78. package/dist/message-builder-DAl9AhHH.js.map +1 -0
  79. package/dist/models/ai-sdk/index.d.ts +353 -0
  80. package/dist/models/ai-sdk/index.js +2443 -0
  81. package/dist/models/ai-sdk/index.js.map +1 -0
  82. package/dist/models/pi-ai/index.d.ts +313 -0
  83. package/dist/models/pi-ai/index.js +1795 -0
  84. package/dist/models/pi-ai/index.js.map +1 -0
  85. package/dist/observability/ai/index.js +20 -4
  86. package/dist/observability/ai/index.js.map +1 -1
  87. package/dist/{protocol-B0nh6KNf.js → protocol-kHsr-f0X.js} +26 -12
  88. package/dist/{protocol-B0nh6KNf.js.map → protocol-kHsr-f0X.js.map} +1 -1
  89. package/dist/queue/index.d.ts +3 -3
  90. package/dist/queue/index.js +1 -1
  91. package/dist/{queue-BC9F5cUo.js → queue-BB-zfIYF.js} +2 -2
  92. package/dist/{queue-BC9F5cUo.js.map → queue-BB-zfIYF.js.map} +1 -1
  93. package/dist/react.d.ts +6 -6
  94. package/dist/react.js +160 -64
  95. package/dist/react.js.map +1 -1
  96. package/dist/{retries-D9B2UCq3.d.ts → retries-CV6AkXmQ.d.ts} +18 -9
  97. package/dist/retries.d.ts +11 -9
  98. package/dist/retries.js +11 -1
  99. package/dist/retries.js.map +1 -1
  100. package/dist/routing/index.d.ts +2 -2
  101. package/dist/routing/index.js +1 -1
  102. package/dist/{scheduler-D_KHqxBa.js → scheduler-DZ3xQfeK.js} +2 -2
  103. package/dist/{scheduler-D_KHqxBa.js.map → scheduler-DZ3xQfeK.js.map} +1 -1
  104. package/dist/{scheduler-BGq6M5Kd.d.ts → scheduler-RdACNXDP.d.ts} +3 -3
  105. package/dist/schedules/index.d.ts +1 -1
  106. package/dist/schedules/index.js +1 -1
  107. package/dist/serializable.d.ts +1 -1
  108. package/dist/sessions/index.d.ts +70 -3
  109. package/dist/sessions/index.js +276 -67
  110. package/dist/sessions/index.js.map +1 -1
  111. package/dist/skills/index.d.ts +29 -314
  112. package/dist/socket-address.d.ts +16 -0
  113. package/dist/socket-address.js +19 -0
  114. package/dist/socket-address.js.map +1 -0
  115. package/dist/{src-BNU3ZiJM.js → src-BZeu-SCD.js} +423 -115
  116. package/dist/src-BZeu-SCD.js.map +1 -0
  117. package/dist/state/index.d.ts +1 -1
  118. package/dist/state/index.js +1 -1
  119. package/dist/streams/index.d.ts +1 -1
  120. package/dist/streams/index.js +1 -1
  121. package/dist/{streams-BcE8JP2X.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
  122. package/dist/{streams-CCPRV6dt.js → streams-DW-UCOUc.js} +9 -2
  123. package/dist/streams-DW-UCOUc.js.map +1 -0
  124. package/dist/sub-routing.d.ts +12 -12
  125. package/dist/tasks/index.d.ts +1 -1
  126. package/dist/tasks/index.js +1 -1
  127. package/dist/{tasks-D4nLqVSI.js → tasks-68DESxf0.js} +2 -2
  128. package/dist/{tasks-D4nLqVSI.js.map → tasks-68DESxf0.js.map} +1 -1
  129. package/dist/{tasks-V2lCT8ZV.d.ts → tasks-9YxYtByh.d.ts} +2 -2
  130. package/dist/transport-PfFnt-Ld.d.ts +525 -0
  131. package/dist/{types-DCD6BNZH.d.ts → types-BA4anrjI.d.ts} +2 -2
  132. package/dist/voice/index.d.ts +2 -2
  133. package/dist/websockets/index.d.ts +23 -6
  134. package/dist/websockets/index.js +1 -1
  135. package/dist/{websockets-D7IzWbZb.js → websockets-Ci-LZFHH.js} +45 -20
  136. package/dist/{websockets-D7IzWbZb.js.map → websockets-Ci-LZFHH.js.map} +1 -1
  137. package/dist/wire-types-CnMt6_HR.js.map +1 -1
  138. package/dist/wire-types-CrjKvDyi.d.ts +300 -0
  139. package/dist/workflow-types.d.ts +25 -25
  140. package/dist/workflows.d.ts +21 -21
  141. package/dist/workflows.js +1 -1
  142. package/dist/{ws-chat-transport-UNRIS2xl.d.ts → ws-chat-transport-BbT1A5Lc.d.ts} +51 -2
  143. package/dist/{ws-chat-transport-rWwta645.js → ws-chat-transport-cqMKq3IY.js} +143 -16
  144. package/dist/ws-chat-transport-cqMKq3IY.js.map +1 -0
  145. package/docs/agent-tools.md +29 -4
  146. package/docs/browse-the-web.md +87 -5
  147. package/docs/chat-agents.md +124 -16
  148. package/docs/context.md +27 -0
  149. package/docs/durable-execution.md +2 -0
  150. package/docs/harnesses/pi.md +173 -0
  151. package/docs/human-in-the-loop.md +4 -0
  152. package/docs/index.md +3 -0
  153. package/docs/lifecycle.md +20 -4
  154. package/docs/models-pi-ai.md +325 -0
  155. package/docs/models.md +504 -0
  156. package/docs/resumable-streaming.md +2 -0
  157. package/docs/routing.md +13 -13
  158. package/docs/sessions.md +36 -1
  159. package/docs/state.md +1 -0
  160. package/docs/sub-agents.md +9 -0
  161. package/package.json +40 -2
  162. package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
  163. package/dist/browser/ai.js.map +0 -1
  164. package/dist/client-C9DAjZIY.js.map +0 -1
  165. package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
  166. package/dist/connector-CptFKzRh.js.map +0 -1
  167. package/dist/message-builder-BymO4N_D.js.map +0 -1
  168. package/dist/src-BNU3ZiJM.js.map +0 -1
  169. package/dist/streams-CCPRV6dt.js.map +0 -1
  170. 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-CCPRV6dt.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
@@ -496,16 +531,14 @@ const REPLAY_PAGE_SEGMENTS = 10;
496
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. */
@@ -856,6 +965,7 @@ var ResumableStream = class {
856
965
  this.flushBuffer();
857
966
  this.ops.settle(streamId, "errored", null);
858
967
  if (this._pendingCutover === streamId) this._pendingCutover = null;
968
+ this._lastClosedStreamId = streamId;
859
969
  this._clearActive();
860
970
  }
861
971
  /**
@@ -865,13 +975,17 @@ var ResumableStream = class {
865
975
  * but will be missing from replay on reconnection.
866
976
  * @param streamId - The stream this chunk belongs to
867
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.
868
981
  */
869
982
  storeChunk(streamId, body) {
870
- const bodyBytes = textEncoder.encode(JSON.stringify(body)).byteLength;
871
- if (bodyBytes > CHUNK_MAX_BYTES) {
983
+ const bodyBytes = storedChunkBytes(body);
984
+ if (bodyBytes > 18e5) {
872
985
  console.warn(`[ResumableStream] Skipping oversized chunk (${bodyBytes} bytes) to prevent SQLite row limit crash. Live clients still receive it.`);
873
986
  return;
874
987
  }
988
+ const seq = streamId === this._activeStreamId && this._nextChunkSeq !== null ? this._nextChunkSeq++ : void 0;
875
989
  if (this._chunkBuffer.length >= CHUNK_BUFFER_MAX_SIZE) this.flushBuffer();
876
990
  if (this._chunkBuffer.length > 0 && this._chunkBufferBytes + bodyBytes > SEGMENT_MAX_BYTES) this.flushBuffer();
877
991
  this._chunkBuffer.push({
@@ -880,6 +994,7 @@ var ResumableStream = class {
880
994
  });
881
995
  this._chunkBufferBytes += bodyBytes;
882
996
  if (this._chunkBuffer.length >= CHUNK_BUFFER_SIZE) this.flushBuffer();
997
+ return seq;
883
998
  }
884
999
  /**
885
1000
  * Flush the buffered chunks to storage as a single packed segment.
@@ -950,13 +1065,16 @@ var ResumableStream = class {
950
1065
  if (!streamId) return null;
951
1066
  this.flushBuffer();
952
1067
  const continuation = this._activeIsContinuation;
953
- if (!sendReplayBodies(connection, requestId, this._storedBodies(streamId), continuation)) return null;
1068
+ if (!sendReplayBodies(connection, requestId, this._storedBodies(streamId), continuation, this._seqBase(streamId))) return null;
954
1069
  if (!this._isLive) {
1070
+ const row = this.ops.getStream(streamId);
955
1071
  sendReplayControl(connection, requestId, {
956
1072
  done: true,
957
- continuation
1073
+ continuation,
1074
+ messageIds: row ? parseChatMetadata(row)?.originMessageIds : void 0,
1075
+ outcome: "aborted"
958
1076
  });
959
- this.complete(streamId);
1077
+ this.complete(streamId, "aborted");
960
1078
  return streamId;
961
1079
  }
962
1080
  sendReplayControl(connection, requestId, {
@@ -979,10 +1097,33 @@ var ResumableStream = class {
979
1097
  this.flushBuffer();
980
1098
  const row = this._latestChatRowByTag(requestId, "completed");
981
1099
  if (!row) return false;
982
- const continuation = parseChatMetadata(row)?.isContinuation === 1;
983
- 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;
984
1103
  return sendReplayControl(connection, requestId, {
985
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,
986
1127
  continuation
987
1128
  });
988
1129
  }
@@ -1004,7 +1145,8 @@ var ResumableStream = class {
1004
1145
  this.flushBuffer();
1005
1146
  const row = this._latestChatRowByTag(requestId, "errored");
1006
1147
  if (!row) return true;
1007
- 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);
1008
1150
  }
1009
1151
  /**
1010
1152
  * Latest chat stream row for a request regardless of status — the recovery
@@ -1053,6 +1195,7 @@ var ResumableStream = class {
1053
1195
  this._activeStreamId = row.stream_id;
1054
1196
  this._activeRequestId = row.tag;
1055
1197
  this._activeIsContinuation = row.chat.isContinuation === 1;
1198
+ this._nextChunkSeq = null;
1056
1199
  }
1057
1200
  }
1058
1201
  /**
@@ -1063,6 +1206,8 @@ var ResumableStream = class {
1063
1206
  this._chunkBuffer = [];
1064
1207
  this._chunkBufferBytes = 0;
1065
1208
  this._deleteRetiring(this._chatRows());
1209
+ this._deletedTerminals.clear();
1210
+ this._lastClosedStreamId = null;
1066
1211
  this._activeStreamId = null;
1067
1212
  this._activeRequestId = null;
1068
1213
  this._activeIsContinuation = false;
@@ -2211,23 +2356,83 @@ function parseProtocolMessage(raw) {
2211
2356
  /**
2212
2357
  * Reconcile incoming client messages against server state.
2213
2358
  *
2214
- * 1. Merges server-known tool outputs into incoming messages that still
2215
- * show stale states (input-available, approval-requested, approval-responded)
2216
- * 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}).
2217
2368
  *
2218
2369
  * @param incoming - Messages from the client
2219
2370
  * @param serverMessages - Current server-side messages (source of truth)
2220
2371
  * @param sanitizeForContentKey - Function to sanitize a message before computing
2221
- * 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)
2222
2375
  * @returns Reconciled messages ready for persistence
2223
2376
  */
2224
2377
  function reconcileMessages(incoming, serverMessages, sanitizeForContentKey) {
2225
- 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;
2226
2425
  }
2227
2426
  /**
2228
2427
  * For a single message, resolve its ID by matching toolCallId against server state.
2229
2428
  * Prevents duplicate DB rows when client IDs differ from server IDs.
2230
- * 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`.
2231
2436
  */
2232
2437
  function resolveToolMergeId(message, serverMessages) {
2233
2438
  if (message.role !== "assistant") return message;
@@ -2293,23 +2498,28 @@ function assistantContentKey(message, sanitize) {
2293
2498
  return JSON.stringify(sanitized.parts);
2294
2499
  }
2295
2500
  function mergeServerToolOutputs(incoming, serverMessages) {
2296
- const serverResolvedParts = /* @__PURE__ */ new Map();
2501
+ const serverResolvedPartsByMessage = /* @__PURE__ */ new Map();
2297
2502
  for (const msg of serverMessages) {
2298
2503
  if (msg.role !== "assistant") continue;
2504
+ const resolvedParts = /* @__PURE__ */ new Map();
2299
2505
  for (const part of msg.parts) {
2300
2506
  const record = part;
2301
- 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);
2302
2508
  }
2509
+ if (resolvedParts.size > 0) serverResolvedPartsByMessage.set(msg.id, resolvedParts);
2303
2510
  }
2304
- if (serverResolvedParts.size === 0) return incoming;
2511
+ if (serverResolvedPartsByMessage.size === 0) return incoming;
2305
2512
  return incoming.map((msg) => {
2306
2513
  if (msg.role !== "assistant") return msg;
2514
+ const ownResolvedParts = serverResolvedPartsByMessage.get(msg.id);
2515
+ if (!ownResolvedParts) return msg;
2307
2516
  let hasChanges = false;
2308
2517
  const updatedParts = msg.parts.map((part) => {
2309
2518
  const record = part;
2310
- 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) {
2311
2522
  hasChanges = true;
2312
- const server = serverResolvedParts.get(record.toolCallId);
2313
2523
  const merged = {
2314
2524
  ...part,
2315
2525
  state: server.state
@@ -2344,7 +2554,22 @@ function reconcileAssistantIds(incoming, serverMessages, sanitize) {
2344
2554
  }
2345
2555
  return incoming.map((incomingMessage, incomingIdx) => {
2346
2556
  if (exactMatchMap.has(incomingIdx)) return incomingMessage;
2347
- 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
+ }
2348
2573
  const incomingKey = assistantContentKey(incomingMessage, sanitize);
2349
2574
  if (!incomingKey) return incomingMessage;
2350
2575
  for (let i = 0; i < serverMessages.length; i++) {
@@ -2365,6 +2590,53 @@ function reconcileAssistantIds(incoming, serverMessages, sanitize) {
2365
2590
  function hasToolCallPart(message) {
2366
2591
  return message.parts.some((part) => "toolCallId" in part);
2367
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
+ }
2368
2640
  function findMessageByToolCallId(messages, toolCallId) {
2369
2641
  for (const msg of messages) {
2370
2642
  if (msg.role !== "assistant") continue;
@@ -2396,7 +2668,7 @@ function toolPartHasSettledResult(record) {
2396
2668
  *
2397
2669
  * - a tool part with NO settled result and state `approval-responded` is kept
2398
2670
  * verbatim (an approved server tool waiting for its continuation to run
2399
- * `execute()` — not abandoned);
2671
+ * `execute()` — not abandoned), unless `repairApprovalResponded` is set;
2400
2672
  * - a tool part with NO settled result for which `shouldRepair` returns false
2401
2673
  * is kept verbatim (a part still awaiting a CLIENT interaction; see option);
2402
2674
  * - any other tool part with no settled result is normalized then handed to
@@ -2415,7 +2687,8 @@ function repairInterruptedToolParts(messages, options) {
2415
2687
  let normalizedInputs = 0;
2416
2688
  const toolCallIds = [];
2417
2689
  const repaired = [];
2418
- for (const message of messages) {
2690
+ for (const [messageIndex, message] of messages.entries()) {
2691
+ const isLastMessage = messageIndex === messages.length - 1;
2419
2692
  const parts = [];
2420
2693
  let messageChanged = false;
2421
2694
  for (const part of message.parts) {
@@ -2427,8 +2700,23 @@ function repairInterruptedToolParts(messages, options) {
2427
2700
  }
2428
2701
  if (!isSettled(record)) {
2429
2702
  if ((typeof record.state === "string" ? record.state : "") === "approval-responded") {
2430
- parts.push(part);
2431
- 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
+ }
2432
2720
  }
2433
2721
  if (!shouldRepair(part)) {
2434
2722
  parts.push(part);
@@ -2493,7 +2781,7 @@ async function persistReconstructedOrphan(chunks, options) {
2493
2781
  }
2494
2782
  //#endregion
2495
2783
  //#region src/chat/recovery.ts
2496
- function createChatFiberSnapshot({ kind, requestId, recoveryRootRequestId, continuation, messages, lastBody, lastClientTools }) {
2784
+ function createChatFiberSnapshot({ kind, requestId, recoveryRootRequestId, continuation, messages, lastBody, lastClientTools, originMessageIds }) {
2497
2785
  const latestMessage = messages.length > 0 ? messages[messages.length - 1] : void 0;
2498
2786
  let latestUser;
2499
2787
  for (let index = messages.length - 1; index >= 0; index--) if (messages[index].role === "user") {
@@ -2511,7 +2799,8 @@ function createChatFiberSnapshot({ kind, requestId, recoveryRootRequestId, conti
2511
2799
  latestUserMessageId: latestUser?.id,
2512
2800
  startedAt: Date.now(),
2513
2801
  lastBody,
2514
- lastClientTools
2802
+ lastClientTools,
2803
+ ...originMessageIds ? { originMessageIds } : {}
2515
2804
  };
2516
2805
  }
2517
2806
  function wrapChatFiberSnapshot(key, snapshot, user) {
@@ -2703,13 +2992,20 @@ var ResumeHandshake = class {
2703
2992
  if (resumableStream.hasActiveStream() && resumableStream.activeRequestId === requestId) {
2704
2993
  const orphanedStreamId = resumableStream.replayChunks(connection, resumableStream.activeRequestId);
2705
2994
  if (orphanedStreamId) await this.host.persistOrphanedStream(orphanedStreamId);
2706
- } else if (resumableStream.hasActiveStream()) {} else if (await this._replayTerminalOnAck(connection, requestId)) {} else if (!resumableStream.replayCompletedChunksByRequestId(connection, requestId)) sendIfOpen(connection, JSON.stringify({
2707
- body: "",
2708
- done: true,
2709
- id: requestId,
2710
- type: responseMessageType,
2711
- replay: true
2712
- }));
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
+ }
2713
3009
  }
2714
3010
  /**
2715
3011
  * Replay a pending terminal outcome (#1645) over the resume handshake so a
@@ -2741,12 +3037,14 @@ var ResumeHandshake = class {
2741
3037
  const pending = await this.host.pendingChatTerminal();
2742
3038
  if (!pending || pending.requestId !== requestId) return false;
2743
3039
  if (!resumableStream.replayErroredChunksByRequestId(connection, pending.requestId)) return true;
3040
+ const messageIds = pending.messageIds ?? resumableStream.getOriginMessageIds(pending.requestId);
2744
3041
  sendIfOpen(connection, JSON.stringify({
2745
3042
  body: pending.body,
2746
3043
  done: true,
2747
3044
  error: true,
2748
3045
  id: pending.requestId,
2749
- type: responseMessageType
3046
+ type: responseMessageType,
3047
+ ...messageIds && { messageIds }
2750
3048
  }));
2751
3049
  return true;
2752
3050
  }
@@ -2786,7 +3084,7 @@ const CHAT_LAST_TERMINAL_KEY = "cf:chat:last-terminal";
2786
3084
  const DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS = 10;
2787
3085
  /**
2788
3086
  * Runaway-loop guard default — the framework-imposed backstop on cumulative
2789
- * recovery WORK (produced content/tool units) since an incident opened.
3087
+ * recovery WORK (durable stream segments, see below) since an incident opened.
2790
3088
  *
2791
3089
  * Originally `Infinity` (rfc-chat-recovery-work-budget): the SDK shipped the
2792
3090
  * *mechanism* but no default cap, so a progressing turn was never terminated on
@@ -3026,11 +3324,13 @@ var StreamProgressCreditThrottle = class {
3026
3324
  * until a later turn supersedes it ({@link clearChatTerminal}); a single record
3027
3325
  * is sufficient because only the most recent terminal is relevant.
3028
3326
  */
3029
- async function recordChatTerminal(storage, requestId, body) {
3030
- await storage.put(CHAT_LAST_TERMINAL_KEY, {
3327
+ async function recordChatTerminal(storage, requestId, body, messageIds) {
3328
+ const record = {
3031
3329
  requestId,
3032
3330
  body
3033
- });
3331
+ };
3332
+ if (messageIds?.length) record.messageIds = messageIds;
3333
+ await storage.put(CHAT_LAST_TERMINAL_KEY, record);
3034
3334
  }
3035
3335
  /** Clear the durable terminal record once a later turn supersedes it (#1645). */
3036
3336
  async function clearChatTerminal(storage) {
@@ -3127,10 +3427,12 @@ async function evaluateChatRecoveryIncident(input) {
3127
3427
  const workBudgetExceeded = existing != null && Number.isFinite(config.maxRecoveryWork) && work > config.maxRecoveryWork;
3128
3428
  const oomAttempts = existing?.oomAttempts ?? 0;
3129
3429
  const oomBudgetExceeded = existing != null && !awaitingClientInteraction && oomAttempts > config.maxOomRetries;
3430
+ const transientRetries = existing?.transientRetries ?? 0;
3431
+ const transientBudgetExceeded = existing != null && !awaitingClientInteraction && transientRetries >= config.maxAttempts;
3130
3432
  const debounced = existing != null && !madeProgress && now - existing.lastAttemptAt < 3e4;
3131
3433
  const attempt = madeProgress ? 1 : debounced ? existing?.attempt ?? 1 : (existing?.attempt ?? 0) + 1;
3132
3434
  let abortedByCaller = false;
3133
- 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 {
3134
3436
  const ctx = {
3135
3437
  incidentId,
3136
3438
  requestId: identity.requestId,
@@ -3145,7 +3447,7 @@ async function evaluateChatRecoveryIncident(input) {
3145
3447
  } catch (error) {
3146
3448
  input.onShouldKeepRecoveringError?.(error);
3147
3449
  }
3148
- const exhausted = !awaitingClientInteraction && (oomBudgetExceeded || noProgressExceeded || workBudgetExceeded || abortedByCaller || attempt > config.maxAttempts);
3450
+ const exhausted = !awaitingClientInteraction && (oomBudgetExceeded || noProgressExceeded || workBudgetExceeded || abortedByCaller || transientBudgetExceeded || attempt > config.maxAttempts);
3149
3451
  const incident = {
3150
3452
  incidentId,
3151
3453
  requestId: identity.requestId,
@@ -3160,6 +3462,7 @@ async function evaluateChatRecoveryIncident(input) {
3160
3462
  progress,
3161
3463
  workBaseline,
3162
3464
  ...oomAttempts > 0 ? { oomAttempts } : {},
3465
+ ...transientRetries > 0 ? { transientRetries } : {},
3163
3466
  ...exhausted ? { reason: oomBudgetExceeded ? "out_of_memory" : workBudgetExceeded ? "work_budget_exceeded" : noProgressExceeded ? "no_progress_timeout" : abortedByCaller ? "recovery_aborted" : "max_attempts_exceeded" } : {}
3164
3467
  };
3165
3468
  const events = [];
@@ -3272,7 +3575,7 @@ var ChatRecoveryEngine = class {
3272
3575
  options: void 0,
3273
3576
  partial
3274
3577
  })) await wake.persistOrphanedStream(streamId);
3275
- await adapter.exhaustChatRecovery(incident, config, partial, streamId, ctx.createdAt);
3578
+ await adapter.exhaustChatRecovery(incident, config, partial, streamId, ctx.createdAt, snapshot?.originMessageIds);
3276
3579
  return true;
3277
3580
  }
3278
3581
  try {
@@ -3386,7 +3689,7 @@ var ChatRecoveryEngine = class {
3386
3689
  maxAttempts: incident.maxAttempts,
3387
3690
  recoveryKind: input.recoveryKind
3388
3691
  });
3389
- 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);
3390
3693
  }
3391
3694
  /**
3392
3695
  * Reschedule a recovery continuation/retry that timed out waiting for stable
@@ -3425,6 +3728,47 @@ var ChatRecoveryEngine = class {
3425
3728
  return true;
3426
3729
  }
3427
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
+ /**
3428
3772
  * Record that a recovery callback observed a Durable Object memory-limit reset
3429
3773
  * (the isolate exceeded its 128 MB limit — `isDurableObjectMemoryLimitReset`)
3430
3774
  * and decide what to do next (#1825).
@@ -3591,6 +3935,47 @@ var ChatRecoveryEngine = class {
3591
3935
  else if (status === "completed" || status === "skipped" || status === "failed") await adapter.setRecovering(false);
3592
3936
  }
3593
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
+ }
3594
3979
  /**
3595
3980
  * Build the `ChatRecoveryExhaustedContext` delivered to `onExhausted` and the
3596
3981
  * `chat:recovery:exhausted` event. Pure field-mapping shared by both packages;
@@ -3750,20 +4135,14 @@ async function* iterateWithStallWatchdog(source, timeoutMs, onStall) {
3750
4135
  //#endregion
3751
4136
  //#region src/chat/truncate-older-messages.ts
3752
4137
  /**
3753
- * Read-time context truncation.
3754
- *
3755
- * Truncates older tool outputs and long text before sending to the LLM.
3756
- * Structured tool outputs keep their container shape so tool-specific
3757
- * `toModelOutput` handlers can safely replay older results.
3758
- * Does NOT mutate stored messages — operates on a copy.
3759
- */
3760
- /**
3761
4138
  * Truncate tool outputs and long text in older messages.
3762
4139
  * Returns a new array — input messages are not mutated.
3763
4140
  *
3764
4141
  * Recent messages (last `keepRecent`) are left intact.
3765
4142
  * Older messages get tool outputs and long text truncated. Structured tool
3766
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.
3767
4146
  *
3768
4147
  * Use in assembleContext() before sending to the LLM:
3769
4148
  * ```typescript
@@ -3778,6 +4157,7 @@ function truncateOlderMessages(messages, options) {
3778
4157
  const keepRecent = options?.keepRecent ?? 4;
3779
4158
  const maxToolOutput = options?.maxToolOutputChars ?? 500;
3780
4159
  const maxText = options?.maxTextChars ?? 1e4;
4160
+ const truncateToolOutputs = options?.toolOutputs ?? true;
3781
4161
  if (messages.length <= keepRecent) return messages;
3782
4162
  const cutoff = messages.length - keepRecent;
3783
4163
  const result = [];
@@ -3789,7 +4169,7 @@ function truncateOlderMessages(messages, options) {
3789
4169
  const msg = messages[i];
3790
4170
  let changed = false;
3791
4171
  const truncatedParts = msg.parts.map((part) => {
3792
- if ((part.type.startsWith("tool-") || part.type === "dynamic-tool") && "output" in part) {
4172
+ if (truncateToolOutputs && isToolPart(part) && "output" in part && !isProviderExecuted(part)) {
3793
4173
  const output = part.output;
3794
4174
  if (output !== void 0) {
3795
4175
  const truncated = truncateToolOutput(output, maxToolOutput);
@@ -3821,7 +4201,153 @@ function truncateOlderMessages(messages, options) {
3821
4201
  }
3822
4202
  return result;
3823
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
+ }
3824
4350
  //#endregion
3825
- 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 };
3826
4352
 
3827
4353
  //# sourceMappingURL=index.js.map