agents 0.16.2 → 0.17.1

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 (126) hide show
  1. package/README.md +11 -8
  2. package/dist/{agent-tool-types-CTw3UJUP.d.ts → agent-tool-types-CNyE1iz_.d.ts} +671 -138
  3. package/dist/agent-tool-types.d.ts +34 -18
  4. package/dist/agent-tool-types.js +20 -1
  5. package/dist/agent-tool-types.js.map +1 -0
  6. package/dist/agent-tools-BXlsuX0d.js +304 -0
  7. package/dist/agent-tools-BXlsuX0d.js.map +1 -0
  8. package/dist/agent-tools-CSnyGvJ2.d.ts +119 -0
  9. package/dist/agent-tools.d.ts +24 -18
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/ai-chat-agent.d.ts +1 -1
  12. package/dist/ai-chat-agent.js +1 -2
  13. package/dist/ai-chat-agent.js.map +1 -1
  14. package/dist/ai-chat-v5-migration.d.ts +1 -1
  15. package/dist/ai-chat-v5-migration.js +1 -2
  16. package/dist/ai-chat-v5-migration.js.map +1 -1
  17. package/dist/ai-react.d.ts +1 -1
  18. package/dist/ai-react.js +1 -2
  19. package/dist/ai-react.js.map +1 -1
  20. package/dist/ai-types.d.ts +1 -7
  21. package/dist/ai-types.js +2 -8
  22. package/dist/ai-types.js.map +1 -1
  23. package/dist/browser/ai.js +1 -1
  24. package/dist/browser/index.js +1 -1
  25. package/dist/chat/index.d.ts +2033 -77
  26. package/dist/chat/index.js +1828 -245
  27. package/dist/chat/index.js.map +1 -1
  28. package/dist/chat/react.d.ts +602 -0
  29. package/dist/chat/react.js +1506 -0
  30. package/dist/chat/react.js.map +1 -0
  31. package/dist/chat-sdk/index.d.ts +4 -4
  32. package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
  33. package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
  34. package/dist/{client-BXJ9n2f7.js → client-BZ-B3NhC.js} +2 -2
  35. package/dist/client-BZ-B3NhC.js.map +1 -0
  36. package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
  37. package/dist/client.d.ts +76 -57
  38. package/dist/client.js +33 -5
  39. package/dist/client.js.map +1 -1
  40. package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
  41. package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
  42. package/dist/email.js +1 -1
  43. package/dist/email.js.map +1 -1
  44. package/dist/{index-B7IbEeze.d.ts → index-BRnybD6X.d.ts} +197 -14
  45. package/dist/index.d.ts +95 -73
  46. package/dist/index.js +694 -25
  47. package/dist/index.js.map +1 -1
  48. package/dist/mcp/client.d.ts +18 -14
  49. package/dist/mcp/client.js +1 -1
  50. package/dist/mcp/index.d.ts +30 -30
  51. package/dist/mcp/index.js +7 -7
  52. package/dist/mcp/index.js.map +1 -1
  53. package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
  54. package/dist/message-builder-BymO4N_D.js.map +1 -0
  55. package/dist/observability/index.d.ts +1 -1
  56. package/dist/observability/index.js +4 -2
  57. package/dist/observability/index.js.map +1 -1
  58. package/dist/react.d.ts +123 -110
  59. package/dist/react.js +44 -11
  60. package/dist/react.js.map +1 -1
  61. package/dist/{retries-CwlpAGet.d.ts → retries-CvHJwSuh.d.ts} +15 -6
  62. package/dist/retries.d.ts +8 -6
  63. package/dist/retries.js +44 -1
  64. package/dist/retries.js.map +1 -1
  65. package/dist/serializable.d.ts +1 -1
  66. package/dist/skills/compile.js +1 -1
  67. package/dist/skills/compile.js.map +1 -1
  68. package/dist/skills/index.js +5 -5
  69. package/dist/skills/index.js.map +1 -1
  70. package/dist/sub-routing.d.ts +6 -6
  71. package/dist/utils.js +1 -1
  72. package/dist/utils.js.map +1 -1
  73. package/dist/vite.js +5 -5
  74. package/dist/vite.js.map +1 -1
  75. package/dist/wire-types-nflOzNuU.js +240 -0
  76. package/dist/wire-types-nflOzNuU.js.map +1 -0
  77. package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
  78. package/dist/workflow-types.d.ts +25 -21
  79. package/dist/workflow-types.js.map +1 -1
  80. package/dist/workflows.d.ts +22 -21
  81. package/dist/workflows.js +31 -7
  82. package/dist/workflows.js.map +1 -1
  83. package/docs/adding-to-existing-project.md +450 -0
  84. package/docs/agent-class.md +503 -0
  85. package/docs/agent-tools.md +552 -0
  86. package/docs/browse-the-web.md +430 -0
  87. package/docs/callable-methods.md +627 -0
  88. package/docs/chat-agents.md +1696 -0
  89. package/docs/chat-sdk.md +181 -0
  90. package/docs/client-sdk.md +520 -0
  91. package/docs/client-tools-continuation.md +177 -0
  92. package/docs/codemode.md +440 -0
  93. package/docs/configuration.md +775 -0
  94. package/docs/cross-domain-authentication.md +171 -0
  95. package/docs/durable-execution.md +537 -0
  96. package/docs/email.md +663 -0
  97. package/docs/get-current-agent.md +204 -0
  98. package/docs/getting-started.md +305 -0
  99. package/docs/http-websockets.md +668 -0
  100. package/docs/human-in-the-loop.md +661 -0
  101. package/docs/index.md +151 -0
  102. package/docs/long-running-agents.md +730 -0
  103. package/docs/mcp-client.md +620 -0
  104. package/docs/mcp-servers.md +526 -0
  105. package/docs/mcp-transports.md +308 -0
  106. package/docs/migration-to-ai-sdk-v5.md +96 -0
  107. package/docs/migration-to-ai-sdk-v6.md +163 -0
  108. package/docs/observability.md +261 -0
  109. package/docs/push-notifications.md +367 -0
  110. package/docs/queue.md +329 -0
  111. package/docs/readonly-connections.md +278 -0
  112. package/docs/resumable-streaming.md +127 -0
  113. package/docs/retries.md +444 -0
  114. package/docs/routing.md +749 -0
  115. package/docs/scheduling.md +898 -0
  116. package/docs/securing-mcp-servers.md +359 -0
  117. package/docs/server-driven-messages.md +477 -0
  118. package/docs/sessions.md +1024 -0
  119. package/docs/state.md +512 -0
  120. package/docs/sub-agents.md +389 -0
  121. package/docs/webhooks.md +604 -0
  122. package/docs/workflows.md +877 -0
  123. package/package.json +41 -14
  124. package/dist/agent-tools-3zLG7MgA.js.map +0 -1
  125. package/dist/agent-tools-DZhI5F6Q.d.ts +0 -14
  126. package/dist/client-BXJ9n2f7.js.map +0 -1
@@ -1,14 +1,25 @@
1
1
  import {
2
- a as AgentToolEventState,
3
- d as AgentToolRunState,
4
- i as AgentToolEventMessage,
5
- r as AgentToolEvent
6
- } from "../agent-tool-types-CTw3UJUP.js";
2
+ U as FiberRecoveryContext,
3
+ _ as AgentToolRunState,
4
+ a as AgentToolEvent,
5
+ o as AgentToolEventMessage,
6
+ s as AgentToolEventState
7
+ } from "../agent-tool-types-CNyE1iz_.js";
7
8
  import {
8
- n as createAgentToolEventState,
9
- t as applyAgentToolEvent
10
- } from "../agent-tools-DZhI5F6Q.js";
11
- import { JSONSchema7, Tool, ToolSet, UIMessage } from "ai";
9
+ n as ClientToolSchema,
10
+ r as createToolsFromClientSchemas,
11
+ t as ClientToolExecutor
12
+ } from "../client-tools-aIBO0Fk7.js";
13
+ import {
14
+ a as applyAgentToolEvent,
15
+ i as AgentToolProgressEmitter,
16
+ n as AgentToolProgressEmitHooks,
17
+ o as createAgentToolEventState,
18
+ r as AgentToolProgressEmitResult,
19
+ s as interceptAgentToolBroadcast,
20
+ t as AgentToolBroadcastHooks
21
+ } from "../agent-tools-CSnyGvJ2.js";
22
+ import { JSONSchema7, UIMessage } from "ai";
12
23
  import { Connection } from "agents";
13
24
 
14
25
  //#region src/chat/message-builder.d.ts
@@ -39,7 +50,8 @@ type StreamChunkData = {
39
50
  state?: string;
40
51
  errorText?: string /** When true, the output is preliminary (may be updated by a later chunk) */;
41
52
  preliminary?: boolean /** Approval ID for tools with needsApproval */;
42
- approvalId?: string;
53
+ approvalId?: string /** Optional framework-specific metadata for the approval request. */;
54
+ approvalDescriptor?: unknown;
43
55
  providerMetadata?: Record<
44
56
  string,
45
57
  unknown
@@ -119,6 +131,20 @@ declare function applyChunkToParts(
119
131
  * - `tool-input-available` for a `toolCallId` whose existing part is no
120
132
  * longer `input-streaming` (i.e. has already advanced to `input-available`
121
133
  * or any terminal state).
134
+ * - `tool-output-denied` for a `toolCallId` whose existing part is already
135
+ * settled (`output-available` / `output-error` / `output-denied`) or
136
+ * user-approved (`approval-responded`). A continuation that re-validates
137
+ * the transcript can re-emit a denial for an approval the SDK now deems
138
+ * unneeded; `applyChunkToParts` already drops it server-side, and this stops
139
+ * it reaching the client (where the in-place `updateToolPart` would flip the
140
+ * part to `output-denied`) and the replay buffer. Mirrors the
141
+ * first-write-wins guard in `applyChunkToParts`.
142
+ * - `tool-approval-request` for a `toolCallId` whose existing part is already
143
+ * `approval-responded` or settled. A continuation replaying a prior tool
144
+ * round-trip can re-emit the approval request; left unfiltered it would
145
+ * revert an already-approved tool back to `approval-requested` on the client
146
+ * (re-showing Approve/Reject) and replay that regression on reconnect. Same
147
+ * pattern and rationale as `tool-output-denied`.
122
148
  */
123
149
  declare function isReplayChunk(
124
150
  parts: MessagePart[],
@@ -138,15 +164,30 @@ declare function byteLength(s: string): number;
138
164
  * 2. Filters truly empty reasoning parts (no text, no remaining providerMetadata)
139
165
  */
140
166
  declare function sanitizeMessage(message: UIMessage): UIMessage;
167
+ /** Optional hooks for {@link enforceRowSizeLimit}. */
168
+ interface EnforceRowSizeLimitOptions {
169
+ /**
170
+ * Optional logger invoked when a message has to be compacted/truncated. The
171
+ * package supplies its own log prefix (log prefixes stay package-specific).
172
+ */
173
+ warn?: (message: string) => void;
174
+ }
141
175
  /**
142
176
  * Enforce SQLite row size limits by compacting tool outputs and text parts
143
- * when a serialized message exceeds the safety threshold (1.8MB).
177
+ * when a serialized message exceeds the safety threshold (1.8MB). Shared by
178
+ * `@cloudflare/ai-chat` and `@cloudflare/think` so both compact identically.
144
179
  *
145
180
  * Compaction strategy:
146
- * 1. Compact tool outputs over 1KB while preserving structured output shape
147
- * 2. If still too big, truncate text parts from oldest to newest
181
+ * 1. Compact tool outputs over 1KB with {@link truncateToolOutput}, preserving
182
+ * the structured output shape, and annotate `metadata.compactedToolOutputs`
183
+ * with the compacted tool-call IDs.
184
+ * 2. If still too big, truncate text parts from oldest to newest, annotating
185
+ * `metadata.compactedTextParts` with the truncated part indices.
148
186
  */
149
- declare function enforceRowSizeLimit(message: UIMessage): UIMessage;
187
+ declare function enforceRowSizeLimit(
188
+ message: UIMessage,
189
+ options?: EnforceRowSizeLimitOptions
190
+ ): UIMessage;
150
191
  //#endregion
151
192
  //#region src/chat/stream-accumulator.d.ts
152
193
  interface StreamAccumulatorOptions {
@@ -266,52 +307,18 @@ declare class TurnQueue {
266
307
  private _decrementCount;
267
308
  }
268
309
  //#endregion
269
- //#region src/chat/client-tools.d.ts
270
- /**
271
- * Wire-format tool schema sent from the client.
272
- * Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema`
273
- * because Zod schemas cannot be serialized over the wire.
274
- */
275
- type ClientToolSchema = {
276
- /** Unique name for the tool */ name: string /** Human-readable description of what the tool does */;
277
- description?: Tool["description"] /** JSON Schema defining the tool's input parameters */;
278
- parameters?: JSONSchema7;
279
- };
310
+ //#region src/chat/lifecycle.d.ts
280
311
  /**
281
- * Executes a client-defined tool and returns its output.
282
- *
283
- * Used for the RPC path (e.g. a parent agent delegating to a Think sub-agent)
284
- * where the caller can run the client tools itself, rather than the
285
- * browser/WebSocket path where results are sent back asynchronously.
312
+ * An advisory, open-ended hint that an action records during a turn to
313
+ * influence how the final reply is delivered without changing the model-visible
314
+ * tool output. The base type is intentionally open; channels/voice surfaces
315
+ * narrow it to the shapes they understand and ignore the rest.
316
+ * `@cloudflare/think` exports a richer named union for `ctx.attachReply`'s
317
+ * parameter; this base is what rides on `ChatResponseResult`.
286
318
  */
287
- type ClientToolExecutor = (call: {
288
- /** The name of the client tool the model called. */ toolName: string /** The model-generated input for the tool call. */;
289
- input: unknown /** The AI SDK tool-call id for the invocation. */;
290
- toolCallId: string;
291
- }) => unknown | Promise<unknown>;
292
- /**
293
- * Converts client tool schemas to AI SDK tool format.
294
- *
295
- * By default these tools have no `execute` function — when the AI model calls
296
- * them, the tool call is sent back to the client for execution.
297
- *
298
- * When `options.execute` is provided, each tool is built WITH an `execute` that
299
- * delegates to it. This is used by the RPC path (e.g. a parent agent driving a
300
- * Think sub-agent) so the model's client-tool call is resolved inline within
301
- * the same turn.
302
- *
303
- * @param clientTools - Array of tool schemas from the client
304
- * @param options - Optional `execute` delegate to run the tools inline
305
- * @returns Record of AI SDK tools that can be spread into your tools object
306
- */
307
- declare function createToolsFromClientSchemas(
308
- clientTools?: ClientToolSchema[],
309
- options?: {
310
- execute?: ClientToolExecutor;
311
- }
312
- ): ToolSet;
313
- //#endregion
314
- //#region src/chat/lifecycle.d.ts
319
+ type ReplyAttachment = {
320
+ type: string;
321
+ } & Record<string, unknown>;
315
322
  /**
316
323
  * Result passed to the `onChatResponse` lifecycle hook after a chat
317
324
  * turn completes.
@@ -325,6 +332,11 @@ type ChatResponseResult = {
325
332
  | "error"
326
333
  | "aborted" /** Error message when `status` is `"error"`. */;
327
334
  error?: string;
335
+ /**
336
+ * Advisory reply attachments recorded during the turn (best-effort,
337
+ * producing-attempt only — not re-applied on a ledger replay).
338
+ */
339
+ attachments?: ReplyAttachment[];
328
340
  };
329
341
  /**
330
342
  * Options accepted by programmatic entry points that drive a chat turn
@@ -445,6 +457,9 @@ type ChatRecoveryExhaustedContext = Pick<
445
457
  * - `work_budget_exceeded` — the turn kept producing content but exceeded the
446
458
  * configured `maxRecoveryWork` runaway-loop budget.
447
459
  * - `recovery_aborted` — the caller's `shouldKeepRecovering` hook returned `false`.
460
+ * - `out_of_memory` — recovery attempts kept hitting a Durable Object
461
+ * memory-limit reset (the isolate exceeded its 128 MB limit) until the
462
+ * tight `maxOomRetries` budget drained (#1825).
448
463
  * - `stable_timeout` — a recovery attempt kept timing out waiting for the
449
464
  * isolate to reach stable state until the budget drained (extreme churn).
450
465
  * - `max_recovery_window_exceeded` — DEPRECATED. The old absolute incident-age
@@ -482,7 +497,7 @@ type ChatRecoveryProgressContext = {
482
497
  /**
483
498
  * Configuration for durable chat recovery. `true` uses these defaults:
484
499
  * `maxAttempts: 10`, `stableTimeoutMs: 10_000`, `noProgressTimeoutMs: 300_000`
485
- * (5 min), `maxRecoveryWork: Infinity`, and a generic terminal message.
500
+ * (5 min), `maxRecoveryWork: 1000`, and a generic terminal message.
486
501
  *
487
502
  * **Apply this as a class field or in the constructor — never assign it in
488
503
  * `onStart()`.** On every wake the SDK evaluates recovery budgets (and may seal
@@ -510,12 +525,31 @@ type ChatRecoveryConfig =
510
525
  /**
511
526
  * Runaway-loop guard. Maximum recovery WORK — produced content/tool units
512
527
  * since the incident began — before a still-progressing turn is sealed
513
- * with `reason="work_budget_exceeded"`. Defaults to `Infinity` (no cap):
514
- * the SDK never terminates a progressing turn on its own. Set a finite
515
- * value (or use `shouldKeepRecovering`) to bound a loop that keeps
516
- * emitting content but never converges.
528
+ * with `reason="work_budget_exceeded"`. Defaults to `1000`: a generous
529
+ * backstop that bounds wasted re-run cost when a turn keeps emitting a
530
+ * little content but never converges (e.g. an isolate that OOMs mid-stream
531
+ * on every recovery #1825 — which otherwise resets the attempt cap and
532
+ * no-progress window forever). Work only accrues from the first
533
+ * interruption until the turn completes, so a normal interrupted turn
534
+ * never approaches it. A very long agentic turn under heavy interruption
535
+ * that legitimately needs more can raise this (or set `Infinity` to
536
+ * disable the framework cap and bound the runaway via `shouldKeepRecovering`
537
+ * instead).
517
538
  */
518
539
  maxRecoveryWork?: number;
540
+ /**
541
+ * Tight retry budget for the specific case of a Durable Object isolate
542
+ * exceeding its memory limit and being reset mid-turn. An OOM is usually
543
+ * deterministic (the turn's working set no longer fits in 128 MB), so
544
+ * re-running re-OOMs; but a single OOM can be a transient spike, so
545
+ * recovery retries this many times before sealing with
546
+ * `reason="out_of_memory"`. Counts only attempts that ended in an OOM (not
547
+ * total attempts), so a turn interrupted by deploys is unaffected.
548
+ * Defaults to `3`. Set `0` to seal on the first OOM. Much tighter than
549
+ * `maxRecoveryWork` because an OOM is attributable and each re-run is
550
+ * expensive (it re-runs the model).
551
+ */
552
+ maxOomRetries?: number;
519
553
  /**
520
554
  * Caller policy consulted on each recovery attempt from the second
521
555
  * onward — it is NOT called on the first detection (the attempt that
@@ -543,6 +577,7 @@ type ResolvedChatRecoveryConfig = {
543
577
  terminalMessage: string;
544
578
  noProgressTimeoutMs: number;
545
579
  maxRecoveryWork: number;
580
+ maxOomRetries: number;
546
581
  shouldKeepRecovering?: (
547
582
  ctx: ChatRecoveryProgressContext
548
583
  ) => boolean | Promise<boolean>;
@@ -669,6 +704,18 @@ declare function transition(
669
704
  ): TransitionResult;
670
705
  //#endregion
671
706
  //#region src/chat/resumable-stream.d.ts
707
+ /**
708
+ * How far ahead (seconds) to schedule the resumable-stream buffer cleanup
709
+ * alarm. Set to the short completion-grace window ({@link COMPLETED_RETENTION_MS},
710
+ * 10m) so a finished buffer is reclaimed promptly. The re-arm-while-reclaimable
711
+ * loop (see {@link cleanupStreamBuffers}) revisits any longer-lived rows — e.g.
712
+ * an abandoned in-flight buffer on its 1h window — by waking again each interval
713
+ * until they age out, then stops. Driving cleanup from an alarm (rather than
714
+ * only piggybacking on the next stream completion) ensures idle/one-off chat
715
+ * DOs still reclaim their buffers without waking forever (#1706). Shared by
716
+ * `AIChatAgent` and `Think`.
717
+ */
718
+ declare const STREAM_CLEANUP_DELAY_SECONDS: number;
672
719
  /**
673
720
  * Minimal SQL interface matching Agent's this.sql tagged template.
674
721
  * Allows ResumableStream to work with the Agent's SQLite without
@@ -890,6 +937,22 @@ declare class ResumableStream {
890
937
  */
891
938
  insertChunkAt(streamId: string, body: string, ageMs: number): void;
892
939
  }
940
+ /**
941
+ * The buffer-cleanup alarm body: sweep aged stream buffers, then re-arm only
942
+ * while rows remain so a fully-swept DO stops waking itself. `rearm` schedules
943
+ * the next sweep — it MUST schedule a non-idempotent alarm, because this runs
944
+ * INSIDE the currently-executing one-shot schedule row, which `alarm()` deletes
945
+ * only after it returns; an idempotent reschedule would dedup onto that row and
946
+ * be deleted with it, so the re-arm would silently never fire and buffers that
947
+ * survived this sweep (e.g. a younger turn) would go uncollected. A fresh
948
+ * delayed row survives the deletion. Shared by `AIChatAgent` and `Think`.
949
+ *
950
+ * `@internal`
951
+ */
952
+ declare function cleanupStreamBuffers(
953
+ stream: Pick<ResumableStream, "cleanup" | "hasReclaimableStreams">,
954
+ rearm: () => Promise<void>
955
+ ): Promise<void>;
893
956
  //#endregion
894
957
  //#region src/chat/sql-batch.d.ts
895
958
  /**
@@ -934,23 +997,208 @@ declare const CHAT_MESSAGE_TYPES: {
934
997
  readonly STREAM_RESUME_ACK: "cf_agent_stream_resume_ack";
935
998
  readonly STREAM_RESUME_REQUEST: "cf_agent_stream_resume_request";
936
999
  readonly STREAM_RESUME_NONE: "cf_agent_stream_resume_none";
1000
+ readonly STREAM_PENDING: "cf_agent_stream_pending";
937
1001
  readonly TOOL_RESULT: "cf_agent_tool_result";
938
1002
  readonly TOOL_APPROVAL: "cf_agent_tool_approval";
939
1003
  readonly MESSAGE_UPDATED: "cf_agent_message_updated";
940
1004
  readonly CHAT_RECOVERING: "cf_agent_chat_recovering";
941
1005
  };
942
1006
  //#endregion
943
- //#region src/chat/continuation-state.d.ts
1007
+ //#region src/chat/wire-types.d.ts
944
1008
  /**
945
- * Minimal connection interface for sending WebSocket messages.
946
- * Matches the Connection type from agents without importing it.
947
- * Uses a permissive send signature so Connection (which extends
948
- * WebSocket with its own send overload) is structurally assignable.
1009
+ * Enum for message types to improve type safety and maintainability
949
1010
  */
950
- interface ContinuationConnection {
1011
+ declare enum MessageType {
1012
+ CF_AGENT_CHAT_MESSAGES = "cf_agent_chat_messages",
1013
+ CF_AGENT_USE_CHAT_REQUEST = "cf_agent_use_chat_request",
1014
+ CF_AGENT_USE_CHAT_RESPONSE = "cf_agent_use_chat_response",
1015
+ CF_AGENT_CHAT_CLEAR = "cf_agent_chat_clear",
1016
+ CF_AGENT_CHAT_REQUEST_CANCEL = "cf_agent_chat_request_cancel",
1017
+ /** Sent by server when client connects and there's an active stream to resume */
1018
+ CF_AGENT_STREAM_RESUMING = "cf_agent_stream_resuming",
1019
+ /** Sent by client to acknowledge stream resuming notification and request chunks */
1020
+ CF_AGENT_STREAM_RESUME_ACK = "cf_agent_stream_resume_ack",
1021
+ /** Sent by client after message handler is ready, requesting stream resume check */
1022
+ CF_AGENT_STREAM_RESUME_REQUEST = "cf_agent_stream_resume_request",
1023
+ /** Sent by server when client requests resume but no active stream exists */
1024
+ CF_AGENT_STREAM_RESUME_NONE = "cf_agent_stream_resume_none",
1025
+ /**
1026
+ * Sent by server when a turn is accepted but its resumable stream has not
1027
+ * started yet (queued / debouncing / waiting on MCP / async setup). Tells a
1028
+ * reconnecting client to keep waiting rather than resolve its resume probe to
1029
+ * "no stream". Resolved by a later `CF_AGENT_STREAM_RESUMING` (stream started)
1030
+ * or `CF_AGENT_STREAM_RESUME_NONE` (settled without streaming). See #1784.
1031
+ */
1032
+ CF_AGENT_STREAM_PENDING = "cf_agent_stream_pending",
1033
+ /** Client sends tool result to server (for client-side tools) */
1034
+ CF_AGENT_TOOL_RESULT = "cf_agent_tool_result",
1035
+ /** Server notifies client that a message was updated (e.g., tool result applied) */
1036
+ CF_AGENT_MESSAGE_UPDATED = "cf_agent_message_updated",
1037
+ /** Client sends tool approval response to server (for tools with needsApproval) */
1038
+ CF_AGENT_TOOL_APPROVAL = "cf_agent_tool_approval",
1039
+ /**
1040
+ * Server→client progress hint: a durable chat turn is being recovered
1041
+ * (interrupted by a deploy/eviction or a stream-stall watchdog abort and now
1042
+ * resuming). Sent when a recovery continuation is scheduled and cleared on
1043
+ * every terminal outcome. (`@cloudflare/think` also replays it on connect;
1044
+ * `@cloudflare/ai-chat` broadcasts the live signal only — see #1645.)
1045
+ * Backward-compatible — clients that don't understand it ignore it. See #1620.
1046
+ */
1047
+ CF_AGENT_CHAT_RECOVERING = "cf_agent_chat_recovering"
1048
+ }
1049
+ /**
1050
+ * Types of messages sent from the Agent to clients
1051
+ */
1052
+ type OutgoingMessage<ChatMessage extends UIMessage = UIMessage> =
1053
+ | {
1054
+ /** Indicates this message is a command to clear chat history */ type: MessageType.CF_AGENT_CHAT_CLEAR;
1055
+ }
1056
+ | {
1057
+ /** Indicates this message contains updated chat messages */ type: MessageType.CF_AGENT_CHAT_MESSAGES /** Array of chat messages */;
1058
+ messages: readonly ChatMessage[];
1059
+ }
1060
+ | {
1061
+ /** Indicates this message is a response to a chat request */ type: MessageType.CF_AGENT_USE_CHAT_RESPONSE /** Unique ID of the request this response corresponds to */;
1062
+ id: string /** Content body of the response */;
1063
+ body: string /** Whether this is the final chunk of the response */;
1064
+ done: boolean /** Whether this response contains an error */;
1065
+ error?: boolean /** Whether this is a continuation (append to last assistant message) */;
1066
+ continuation?: boolean /** Whether this chunk is being replayed from storage (stream resumption) */;
1067
+ replay?: boolean /** Signals that replay of stored chunks is complete (stream is still active) */;
1068
+ replayComplete?: boolean;
1069
+ }
1070
+ | {
1071
+ /** Indicates the server is resuming an active stream */ type: MessageType.CF_AGENT_STREAM_RESUMING /** The request ID of the stream being resumed */;
1072
+ id: string;
1073
+ }
1074
+ | {
1075
+ /** Server notifies client that a message was updated (e.g., tool result applied) */ type: MessageType.CF_AGENT_MESSAGE_UPDATED /** The updated message */;
1076
+ message: ChatMessage;
1077
+ }
1078
+ | {
1079
+ /** Server responds to resume request when no active stream exists */ type: MessageType.CF_AGENT_STREAM_RESUME_NONE;
1080
+ }
1081
+ | {
1082
+ /**
1083
+ * Server signals an accepted turn whose resumable stream has not started
1084
+ * yet — the client should keep waiting for `STREAM_RESUMING` (or a later
1085
+ * `STREAM_RESUME_NONE`) rather than give up. See #1784.
1086
+ */
1087
+ type: MessageType.CF_AGENT_STREAM_PENDING /** The accepted request id, when known. */;
1088
+ id?: string;
1089
+ }
1090
+ | {
1091
+ /**
1092
+ * Progress hint: a durable chat turn is being recovered (`recovering:
1093
+ * true`) or recovery has resolved (`recovering: false`). Purely advisory;
1094
+ * a client renders a "recovering…" indicator while true.
1095
+ */
1096
+ type: MessageType.CF_AGENT_CHAT_RECOVERING /** Whether recovery is in progress (true) or has resolved (false). */;
1097
+ recovering: boolean /** The recovery-root request id of the turn being recovered, if known. */;
1098
+ id?: string;
1099
+ };
1100
+ /**
1101
+ * Types of messages sent from clients to the Agent
1102
+ */
1103
+ type IncomingMessage<ChatMessage extends UIMessage = UIMessage> =
1104
+ | {
1105
+ /** Indicates this message is a command to clear chat history */ type: MessageType.CF_AGENT_CHAT_CLEAR;
1106
+ }
1107
+ | {
1108
+ /** Indicates this message is a request to the chat API */ type: MessageType.CF_AGENT_USE_CHAT_REQUEST /** Unique ID for this request */;
1109
+ id: string /** Request initialization options */;
1110
+ init: Pick<
1111
+ RequestInit,
1112
+ | "method"
1113
+ | "keepalive"
1114
+ | "headers"
1115
+ | "body"
1116
+ | "redirect"
1117
+ | "integrity"
1118
+ | "credentials"
1119
+ | "mode"
1120
+ | "referrer"
1121
+ | "referrerPolicy"
1122
+ | "window"
1123
+ >;
1124
+ }
1125
+ | {
1126
+ /** Indicates this message contains updated chat messages */ type: MessageType.CF_AGENT_CHAT_MESSAGES /** Array of chat messages */;
1127
+ messages: ChatMessage[];
1128
+ }
1129
+ | {
1130
+ /** Indicates the user wants to stop generation of this message */ type: MessageType.CF_AGENT_CHAT_REQUEST_CANCEL;
1131
+ id: string;
1132
+ }
1133
+ | {
1134
+ /** Client acknowledges stream resuming notification and is ready to receive chunks */ type: MessageType.CF_AGENT_STREAM_RESUME_ACK /** The request ID of the stream being resumed */;
1135
+ id: string;
1136
+ }
1137
+ | {
1138
+ /** Client requests stream resume check after message handler is registered */ type: MessageType.CF_AGENT_STREAM_RESUME_REQUEST;
1139
+ }
1140
+ | {
1141
+ /** Client sends tool result to server (for client-side tools) */ type: MessageType.CF_AGENT_TOOL_RESULT /** The tool call ID this result is for */;
1142
+ toolCallId: string /** The name of the tool */;
1143
+ toolName: string /** The output from the tool execution */;
1144
+ output: unknown /** Override the tool part state (e.g. "output-error" for custom denial) */;
1145
+ state?:
1146
+ | "output-available"
1147
+ | "output-error" /** Error message when state is "output-error" */;
1148
+ errorText?: string /** Whether server should auto-continue the conversation after applying result */;
1149
+ autoContinue?: boolean /** Client tool schemas for continuation (client is source of truth) */;
1150
+ clientTools?: Array<{
1151
+ name: string;
1152
+ description?: string;
1153
+ parameters?: JSONSchema7;
1154
+ }>;
1155
+ }
1156
+ | {
1157
+ /** Client sends tool approval response to server (for tools with needsApproval) */ type: MessageType.CF_AGENT_TOOL_APPROVAL /** The tool call ID this approval is for */;
1158
+ toolCallId: string /** Whether the tool execution was approved */;
1159
+ approved: boolean /** Whether server should auto-continue the conversation after applying approval */;
1160
+ autoContinue?: boolean;
1161
+ };
1162
+ //#endregion
1163
+ //#region src/chat/connection.d.ts
1164
+ /**
1165
+ * Connection I/O — shared WebSocket send guard for chat agents.
1166
+ *
1167
+ * `@internal` — sibling-package support for `@cloudflare/ai-chat` and
1168
+ * `@cloudflare/think`, not a public API. See
1169
+ * `design/rfc-chat-recovery-foundation.md`.
1170
+ *
1171
+ * Both packages (and `continuation-state`) hand-maintained byte-identical
1172
+ * copies of `sendIfOpen` / `isWebSocketClosedSendError`; this is the single
1173
+ * shared implementation.
1174
+ */
1175
+ /**
1176
+ * Minimal connection interface for sending WebSocket messages. Matches the
1177
+ * `Connection` type from `agents` without importing it: `Connection` extends
1178
+ * `WebSocket` with its own `send` overload, so it is structurally assignable.
1179
+ */
1180
+ interface ChatConnection {
951
1181
  readonly id: string;
952
1182
  send(message: string): void;
953
1183
  }
1184
+ /**
1185
+ * Send a message on a connection, swallowing the specific
1186
+ * "send after close" error a racing disconnect produces. Returns `true` if the
1187
+ * send went out, `false` if the socket was already closed. Any other error
1188
+ * rethrows.
1189
+ */
1190
+ declare function sendIfOpen(
1191
+ connection: ChatConnection,
1192
+ message: string
1193
+ ): boolean;
1194
+ //#endregion
1195
+ //#region src/chat/continuation-state.d.ts
1196
+ /**
1197
+ * Minimal connection interface for sending WebSocket messages. Alias of the
1198
+ * shared {@link ChatConnection} — kept as a named export for back-compat with
1199
+ * existing `ContinuationConnection` consumers.
1200
+ */
1201
+ type ContinuationConnection = ChatConnection;
954
1202
  interface ContinuationPending<
955
1203
  TConnection extends ContinuationConnection = ContinuationConnection
956
1204
  > {
@@ -1018,6 +1266,197 @@ declare class ContinuationState<
1018
1266
  ): ContinuationPending<TConnection> | null;
1019
1267
  }
1020
1268
  //#endregion
1269
+ //#region src/chat/pre-stream-turns.d.ts
1270
+ declare class PreStreamTurns<
1271
+ TConnection extends ChatConnection = ChatConnection
1272
+ > {
1273
+ /**
1274
+ * Accepted-but-not-yet-streamed request ids. A turn enters on `begin()` and
1275
+ * leaves on `settle()`; the set being non-empty means "pre-stream work is in
1276
+ * flight", which gates parking and the eventual `resume_none` release.
1277
+ */
1278
+ private readonly _accepted;
1279
+ /** Connections parked waiting for a stream to start. */
1280
+ readonly awaitingConnections: Map<string, TConnection>;
1281
+ /** The most recently accepted pre-stream request id (for the keep-waiting frame). */
1282
+ private _latestRequestId;
1283
+ /** Mark a freshly-accepted turn as in flight (pre-stream). */
1284
+ begin(requestId: string): void;
1285
+ /**
1286
+ * Mark an accepted turn as settled. Returns `true` when no accepted turn
1287
+ * remains in flight (the caller should release parked connections if no
1288
+ * stream is active).
1289
+ */
1290
+ settle(requestId: string): boolean;
1291
+ /** Whether any accepted turn is still pre-stream. */
1292
+ hasInFlight(): boolean;
1293
+ /** The request id to advertise in the keep-waiting frame, if known. */
1294
+ get latestRequestId(): string | null;
1295
+ /**
1296
+ * Park a reconnecting connection and tell it to keep waiting (so its
1297
+ * transport does not resolve `reconnectToStream` early). No-op when nothing
1298
+ * is in flight. Parked connections are deliberately NOT added to the host's
1299
+ * `pendingResumeConnections` — they must keep receiving any live broadcast —
1300
+ * until the host flushes them through `notifyStreamResuming` on stream start.
1301
+ */
1302
+ park(connection: TConnection): boolean;
1303
+ /** Drop a single connection (e.g. on socket close) without releasing others. */
1304
+ release(connectionId: string): void;
1305
+ /**
1306
+ * A stream has started: hand every parked connection to `notify` (the host's
1307
+ * `notifyStreamResuming`, which sends `STREAM_RESUMING` and excludes the
1308
+ * connection from live broadcast until it ACKs), then clear the awaiting map.
1309
+ * The accepted set is untouched — the turn is still running.
1310
+ */
1311
+ flushOnStreamStart(notify: (connection: TConnection) => void): void;
1312
+ /**
1313
+ * Release every parked connection with `STREAM_RESUME_NONE` (the turn settled
1314
+ * without ever starting a stream) and clear the awaiting map. Safe to call
1315
+ * when the map is empty (no-op), so the host can call it liberally from a
1316
+ * turn-settle path.
1317
+ */
1318
+ releaseAwaiting(): void;
1319
+ /** Drop all state (chat clear / destroy). Does not send any frames. */
1320
+ reset(): void;
1321
+ }
1322
+ //#endregion
1323
+ //#region src/chat/auto-continuation-controller.d.ts
1324
+ /**
1325
+ * The data a host supplies to schedule (or re-target) a pending/deferred
1326
+ * auto-continuation. Mirrors the fields a host writes onto
1327
+ * {@link ContinuationState.pending} — the host owns where the values come from
1328
+ * (e.g. Think hardcodes a fixed `errorPrefix` and `body: undefined`; ai-chat
1329
+ * threads them per tool-result event).
1330
+ */
1331
+ interface ContinuationSpec<
1332
+ TConnection extends ContinuationConnection = ContinuationConnection
1333
+ > {
1334
+ connection: TConnection;
1335
+ clientTools: ClientToolSchema[] | undefined;
1336
+ body: Record<string, unknown> | undefined;
1337
+ errorPrefix: string;
1338
+ }
1339
+ /**
1340
+ * Host substrate the controller parameterizes over. Implemented by the agent
1341
+ * (typically via a small adapter object capturing `this`).
1342
+ */
1343
+ interface AutoContinuationHost<
1344
+ TConnection extends ContinuationConnection = ContinuationConnection
1345
+ > {
1346
+ /** Shared continuation state (pending/deferred/awaiting connections). */
1347
+ readonly continuation: ContinuationState<TConnection>;
1348
+ /** Generate a request id for a freshly-created continuation turn. */
1349
+ generateRequestId(): string;
1350
+ /**
1351
+ * `true` while an assistant turn is streaming — the parallel tool batch can
1352
+ * still grow with tool calls the model hasn't emitted yet, so no completeness
1353
+ * check is meaningful. (`_streamingAssistant !== null` in Think;
1354
+ * `_streamingTurnActive` in ai-chat.)
1355
+ */
1356
+ isStreamActive(): boolean;
1357
+ /** `true` while a tool-result/approval apply is in flight. */
1358
+ hasPendingInteraction(): boolean;
1359
+ /**
1360
+ * `true` when the latest assistant message is mid-batch (a settled tool
1361
+ * result beside an unanswered tool call/approval — the #1649 signature).
1362
+ */
1363
+ hasIncompleteToolBatch(): boolean;
1364
+ /**
1365
+ * Drain every in-flight tool-result/approval apply (including any enqueued
1366
+ * while draining) so the subsequent completeness re-check sees every result
1367
+ * that has already arrived. Bounded by real apply activity, never a timer.
1368
+ */
1369
+ drainInteractionApplies(): Promise<void>;
1370
+ /** Hold the isolate alive for the duration of `fn` (alarm heartbeats). */
1371
+ keepAliveWhile<T>(fn: () => Promise<T>): Promise<T>;
1372
+ /**
1373
+ * Run the continuation turn for the current {@link ContinuationState.pending}.
1374
+ * Each host's inference/reply pipeline (Think: `_turnQueue.enqueue` +
1375
+ * `_runInferenceLoop`; ai-chat: `_runExclusiveChatTurn` + `onChatMessage`).
1376
+ * Reads everything it needs from `continuation.pending`, so it takes no args.
1377
+ */
1378
+ fire(): void;
1379
+ }
1380
+ declare class AutoContinuationController<
1381
+ TConnection extends ContinuationConnection = ContinuationConnection
1382
+ > {
1383
+ private readonly host;
1384
+ /**
1385
+ * Small debounce window to batch adjacent client-side tool results/approvals
1386
+ * into a single server continuation barrier check (#1650).
1387
+ */
1388
+ static readonly COALESCE_MS = 50;
1389
+ /**
1390
+ * Coalesce/debounce timer for the event-driven barrier (#1650). Each tool
1391
+ * result/approval re-arms it; on fire it runs {@link fireWhenStable}.
1392
+ */
1393
+ private _timer;
1394
+ /**
1395
+ * Double-fire guard (#1650). Ensures only one in-flight apply-drain runs;
1396
+ * that drain re-checks completeness on completion before firing. A sibling
1397
+ * that re-arms the coalesce timer during a drain is absorbed by the
1398
+ * in-progress drain rather than starting its own.
1399
+ */
1400
+ private _barrierActive;
1401
+ constructor(host: AutoContinuationHost<TConnection>);
1402
+ /**
1403
+ * Schedule an auto-continuation for a tool result/approval that opted in with
1404
+ * `autoContinue` (#1650). Coalesces rapid sibling results into a single
1405
+ * continuation via the debounce timer; the actual fire is gated by
1406
+ * {@link fireWhenStable}. If a continuation is already running
1407
+ * (`pastCoalesce`), the new result is stored as the deferred follow-up
1408
+ * instead of re-arming.
1409
+ */
1410
+ schedule(spec: ContinuationSpec<TConnection>): void;
1411
+ /**
1412
+ * Re-arm the barrier for a result/approval that arrived WITHOUT `autoContinue`
1413
+ * (#1650). A standalone errored result declines to continue on its own, but in
1414
+ * a parallel batch a SIBLING may already have opted in — and this result can
1415
+ * be the one that completes the batch, so we must re-run the barrier check.
1416
+ * Unlike {@link schedule} this NEVER creates a pending continuation, and
1417
+ * no-ops once the continuation is running (`pastCoalesce`).
1418
+ */
1419
+ rearmForBatch(): void;
1420
+ /** (Re)arm the coalesce timer; on fire, run {@link fireWhenStable}. */
1421
+ armTimer(): void;
1422
+ /**
1423
+ * Fire an auto-continuation, but only once the model's parallel tool-call
1424
+ * batch is fully answered (#1649) and no assistant turn is mid-stream (#1650).
1425
+ * The barrier is event-driven with NO orphan timeout: when the batch is still
1426
+ * incomplete we drain the in-flight applies, re-check, and — if still
1427
+ * incomplete — return WITHOUT firing and WITHOUT holding the isolate, leaving
1428
+ * `continuation.pending` in place. The next sibling's result re-arms the
1429
+ * coalesce timer and re-runs this check; the continuation fires once the final
1430
+ * sibling lands. A true orphan (a sibling that never arrives) simply never
1431
+ * auto-continues — a later user turn / chat recovery repairs the transcript.
1432
+ */
1433
+ fireWhenStable(): void;
1434
+ /**
1435
+ * Transition the deferred follow-up (stored while a continuation was running)
1436
+ * to pending and re-run the barrier — its batch may still be incomplete (or a
1437
+ * stream active), in which case it parks and re-arms instead of firing blind.
1438
+ */
1439
+ activateDeferredAndReschedule(): void;
1440
+ /**
1441
+ * Cancel any still-armed coalesce timer. Called on the fire path so a sibling
1442
+ * result that re-armed it during a barrier wait can't fire a duplicate
1443
+ * continuation after this one starts (#1649 / #1650).
1444
+ */
1445
+ cancelTimer(): void;
1446
+ /**
1447
+ * `true` when the barrier is going to fire on its own — its coalesce timer is
1448
+ * still pending or its completeness drain is in progress. The host combines
1449
+ * this with its own pending/`pastCoalesce` checks to decide idle/stable.
1450
+ */
1451
+ isArmed(): boolean;
1452
+ /**
1453
+ * Tear down the controller-owned barrier state (timer + double-fire guard).
1454
+ * Scoped to ONLY this controller's fields — the host clears the rest of its
1455
+ * turn state (stream gate, interaction tail, continuation data) separately.
1456
+ */
1457
+ reset(): void;
1458
+ }
1459
+ //#endregion
1021
1460
  //#region src/chat/abort-registry.d.ts
1022
1461
  /**
1023
1462
  * AbortRegistry — manages per-request AbortControllers.
@@ -1090,6 +1529,45 @@ declare class AbortRegistry {
1090
1529
  linkExternal(id: string, signal: AbortSignal | undefined): () => void;
1091
1530
  }
1092
1531
  //#endregion
1532
+ //#region src/chat/async-helpers.d.ts
1533
+ /**
1534
+ * @internal Small async control-flow helpers shared by the chat hosts
1535
+ * (`@cloudflare/ai-chat` and `@cloudflare/think`) — not a public API. Extracted
1536
+ * so the host idle/stable waits and the interaction-apply completeness drain
1537
+ * stay byte-identical across both. See `design/chat-shared-layer.md`.
1538
+ */
1539
+ /**
1540
+ * Sentinel returned by {@link awaitWithDeadline} when the deadline elapses
1541
+ * before the awaited promise settles. A single shared symbol so both hosts
1542
+ * compare against the same identity.
1543
+ */
1544
+ declare const TIMED_OUT: unique symbol;
1545
+ /**
1546
+ * Await `promise`, but give up and resolve to {@link TIMED_OUT} once `deadline`
1547
+ * (an absolute `Date.now()` ms timestamp) passes. A `null` deadline waits
1548
+ * indefinitely (the promise is returned unchanged). The timeout timer is always
1549
+ * cleared so it can't pin the isolate awake past resolution.
1550
+ */
1551
+ declare function awaitWithDeadline<T>(
1552
+ promise: Promise<T>,
1553
+ deadline: number | null
1554
+ ): Promise<T | typeof TIMED_OUT>;
1555
+ /**
1556
+ * Drain the host's interaction-apply chain so a subsequent completeness check
1557
+ * (e.g. `hasIncompleteToolBatch`) sees every tool result that has ALREADY
1558
+ * arrived.
1559
+ *
1560
+ * Bounded by real apply activity (a storage write each), never a fixed timer:
1561
+ * `getTail` is re-read after every await because a sibling can extend the tail
1562
+ * mid-drain, and the loop stops once the tail stops advancing. Bails early when
1563
+ * `hasPending()` goes false (the pending continuation was cleared by a chat
1564
+ * clear / turn reset) so a stale drain can't hold the isolate awake.
1565
+ */
1566
+ declare function drainInteractionApplies(
1567
+ hasPending: () => boolean,
1568
+ getTail: () => Promise<unknown>
1569
+ ): Promise<void>;
1570
+ //#endregion
1093
1571
  //#region src/chat/tool-state.d.ts
1094
1572
  /**
1095
1573
  * Tool State — shared update builders and applicator for tool part state changes.
@@ -1198,6 +1676,51 @@ declare function toolApprovalUpdate(
1198
1676
  toolCallId: string,
1199
1677
  approved: boolean
1200
1678
  ): ToolPartUpdate;
1679
+ /** A minimal message shape for the leaf tool/interaction scans. */
1680
+ type ToolBatchMessage = {
1681
+ role: string;
1682
+ parts: ReadonlyArray<unknown>;
1683
+ };
1684
+ /** Extract a tool part's name from its `tool-<name>` / `dynamic-tool` shape. */
1685
+ /**
1686
+ * Whether a part is still awaiting a CLIENT interaction that can genuinely
1687
+ * arrive after a restart: an `approval-requested` part (a reconnecting client
1688
+ * replays the approval) or an `input-available` part for a CLIENT tool (the SPA
1689
+ * replays the `tool-result`). A SERVER tool's `input-available` is NOT pending —
1690
+ * its `execute()` died with the isolate.
1691
+ */
1692
+ declare function partAwaitsClientInteraction(
1693
+ part: unknown,
1694
+ clientResolvable: Set<string>
1695
+ ): boolean;
1696
+ /**
1697
+ * Names of the CLIENT-resolvable tools — the client-provided schemas from the
1698
+ * last request, which have no server `execute`. An interrupted `input-available`
1699
+ * part for one of these can still be resolved by the client replaying a
1700
+ * `tool-result`; a server tool's cannot.
1701
+ */
1702
+ declare function clientResolvableToolNames(
1703
+ tools:
1704
+ | ReadonlyArray<
1705
+ | {
1706
+ name?: string;
1707
+ }
1708
+ | null
1709
+ | undefined
1710
+ >
1711
+ | undefined
1712
+ ): Set<string>;
1713
+ /**
1714
+ * `true` when the latest assistant message is mid-batch: it carries at least
1715
+ * one settled tool result AND at least one tool call/approval still awaiting a
1716
+ * client result. That is the #1649 signature — the model fanned out parallel
1717
+ * tool calls and only some have been answered. Scoped to the leaf (the step the
1718
+ * continuation answers) so an unrelated dangling tool in an earlier message
1719
+ * doesn't block a legitimate follow-up continuation.
1720
+ */
1721
+ declare function hasIncompleteToolBatch(
1722
+ messages: ReadonlyArray<ToolBatchMessage>
1723
+ ): boolean;
1201
1724
  //#endregion
1202
1725
  //#region src/chat/parse-protocol.d.ts
1203
1726
  /**
@@ -1310,15 +1833,190 @@ declare function resolveToolMergeId(
1310
1833
  serverMessages: readonly UIMessage[]
1311
1834
  ): UIMessage;
1312
1835
  /**
1313
- * Content key for assistant messages used for dedup of identical short replies.
1314
- * Returns JSON of sanitized parts, or undefined for non-assistant messages.
1836
+ * Merge a freshly-reconstructed orphaned partial onto the assistant message
1837
+ * that already owns its target id (the orphan-persist **(c)** step).
1838
+ *
1839
+ * Used by hosts whose store can hold an assistant row for the SAME id BEFORE
1840
+ * the stream finalizes — e.g. an early persist at tool-approval time, or a
1841
+ * continuation resuming the prior assistant message. On recovery the engine
1842
+ * replays the same chunks, so a naive append would leave two parts per tool
1843
+ * call. The merge therefore:
1844
+ *
1845
+ * - keeps ALL existing parts (the persisted row is authoritative for tool
1846
+ * parts that had a client result applied IN PLACE — that result lives only
1847
+ * in storage, never in the chunk stream, so a whole-message replace would
1848
+ * clobber it);
1849
+ * - appends only the reconstructed parts whose `toolCallId` is NOT already
1850
+ * present (dedup by tool-call identity);
1851
+ * - overlays the incoming metadata onto the existing metadata (incoming wins
1852
+ * on conflicts), falling back to whichever side is present.
1853
+ *
1854
+ * The result carries the INCOMING message's id/role (the caller has already
1855
+ * resolved the incoming id to the existing row's id via the (b) target-id
1856
+ * step), so it is safe to write straight back through `updateMessage`.
1857
+ *
1858
+ * Hosts whose orphan persist only ever runs at stream finalize (no early/
1859
+ * mid-stream row for the same id) never hit the merge branch and don't need
1860
+ * this — a plain append/replace is already dedup-safe because the shared
1861
+ * reconstruction (`StreamAccumulator` / `applyChunkToParts`) is idempotent by
1862
+ * `toolCallId`.
1315
1863
  */
1316
- declare function assistantContentKey(
1317
- message: UIMessage,
1318
- sanitize?: (message: UIMessage) => UIMessage
1319
- ): string | undefined;
1864
+ declare function reconcileOrphanPartial(
1865
+ existing: UIMessage,
1866
+ incoming: UIMessage
1867
+ ): UIMessage;
1868
+ //#endregion
1869
+ //#region src/chat/repair-transcript.d.ts
1870
+ /**
1871
+ * Whether a tool part already has a settled result the provider accepts, so it
1872
+ * must NOT be re-repaired into an errored result.
1873
+ *
1874
+ * Single source of truth for the terminal tool states. Mirrors the AI SDK's
1875
+ * terminal states: `convertToModelMessages` emits a `tool-result` for
1876
+ * `output-available`, `output-error`, AND `output-denied` (a user-denied
1877
+ * approval — its denial reason becomes the tool-result). Omitting any of these
1878
+ * makes repair re-flip the part every turn — clobbering a real `errorText` /
1879
+ * denial with the generic "interrupted" message.
1880
+ */
1881
+ declare function toolPartHasSettledResult(
1882
+ record: Record<string, unknown>
1883
+ ): boolean;
1884
+ interface RepairInterruptedToolPartsOptions {
1885
+ /**
1886
+ * Decide the replacement for an interrupted tool part (no settled result, not
1887
+ * `approval-responded`). Its `input` has already been normalized to a valid
1888
+ * object. The default host behavior flips it to an errored tool-result; hosts
1889
+ * expose this as an overridable `repairInterruptedToolPart` hook so a subclass
1890
+ * can, e.g., convert an interrupted client-resolved tool into a text part.
1891
+ */
1892
+ repairPart: (part: UIMessage["parts"][number]) => UIMessage["parts"][number];
1893
+ /**
1894
+ * Whether a tool part already carries a settled result (defaults to
1895
+ * {@link toolPartHasSettledResult}).
1896
+ */
1897
+ isSettled?: (record: Record<string, unknown>) => boolean;
1898
+ /**
1899
+ * Normalize a tool part's `input` (defaults to the shared
1900
+ * {@link normalizeToolInput}).
1901
+ */
1902
+ normalizeInput?: (input: unknown) => {
1903
+ input: unknown;
1904
+ changed: boolean;
1905
+ };
1906
+ /**
1907
+ * Whether an interrupted tool part (no settled result, not
1908
+ * `approval-responded`) should be repaired at all. Defaults to `true` (repair
1909
+ * everything, like Think — which converts even client tools via its
1910
+ * `repairPart` override). A host whose default `repairPart` errors the part
1911
+ * (ai-chat) passes this to SKIP a part still legitimately awaiting a CLIENT
1912
+ * interaction (an `input-available` client tool or an `approval-requested`
1913
+ * part the user may still answer) so it is left verbatim rather than clobbered
1914
+ * with an error. Skipped parts are not counted in `removedToolCalls`.
1915
+ */
1916
+ shouldRepair?: (part: UIMessage["parts"][number]) => boolean;
1917
+ }
1918
+ interface RepairInterruptedToolPartsResult {
1919
+ /** A new messages array; unchanged messages keep their original reference. */
1920
+ messages: UIMessage[];
1921
+ /** Count of interrupted tool calls flipped to a repaired shape. */
1922
+ removedToolCalls: number;
1923
+ /** Count of tool parts whose malformed `input` was normalized. */
1924
+ normalizedInputs: number;
1925
+ /** The tool-call ids that were repaired. */
1926
+ toolCallIds: string[];
1927
+ }
1928
+ /**
1929
+ * Repair interrupted tool calls and normalize malformed tool inputs across a
1930
+ * transcript. Behavior mirrors `@cloudflare/think`'s original
1931
+ * `_repairToolTranscriptParts`:
1932
+ *
1933
+ * - a tool part with NO settled result and state `approval-responded` is kept
1934
+ * verbatim (an approved server tool waiting for its continuation to run
1935
+ * `execute()` — not abandoned);
1936
+ * - a tool part with NO settled result for which `shouldRepair` returns false
1937
+ * is kept verbatim (a part still awaiting a CLIENT interaction; see option);
1938
+ * - any other tool part with no settled result is normalized then handed to
1939
+ * `repairPart` (default: flipped to an errored result);
1940
+ * - a tool part WITH a settled result only has its `input` normalized.
1941
+ *
1942
+ * Messages with no changed part keep their original object reference so callers
1943
+ * can cheaply detect what to persist.
1944
+ */
1945
+ declare function repairInterruptedToolParts(
1946
+ messages: UIMessage[],
1947
+ options: RepairInterruptedToolPartsOptions
1948
+ ): RepairInterruptedToolPartsResult;
1949
+ //#endregion
1950
+ //#region src/chat/orphan-store.d.ts
1951
+ interface OrphanPersistStore<
1952
+ M extends {
1953
+ id: string;
1954
+ } = UIMessage
1955
+ > {
1956
+ /** Read the stored message with this id, or `null` if none exists. */
1957
+ getMessage(id: string): M | null | Promise<M | null>;
1958
+ /**
1959
+ * Append a new message. `parentId` is honored by tree-structured stores
1960
+ * (`undefined` → attach to the latest leaf); flat-array stores ignore it.
1961
+ */
1962
+ appendMessage(message: M, parentId?: string | null): void | Promise<void>;
1963
+ /** Replace the stored message that owns `message.id`. */
1964
+ updateMessage(message: M): void | Promise<void>;
1965
+ }
1966
+ //#endregion
1967
+ //#region src/chat/orphan-persist.d.ts
1968
+ interface PersistReconstructedOrphanOptions<
1969
+ TMessage extends UIMessage = UIMessage
1970
+ > {
1971
+ /** The store seam to upsert through (a `SessionProvider` write-subset). */
1972
+ store: OrphanPersistStore<TMessage>;
1973
+ /**
1974
+ * Id for the reconstructed message when the stream carried no provider
1975
+ * `start.messageId` to adopt. The accumulator still adopts a provider id when
1976
+ * present.
1977
+ */
1978
+ fallbackId: string;
1979
+ /**
1980
+ * Finalize the reconstructed message before upsert — e.g. strip internal
1981
+ * parts or resolve the persist-target id. Return `null` to skip persistence
1982
+ * entirely (e.g. an empty structural-only message).
1983
+ */
1984
+ prepare: (message: TMessage) => TMessage | null;
1985
+ /**
1986
+ * Combine an existing row with the reconstructed message when a row already
1987
+ * owns the id (replace, or reconcile partials).
1988
+ */
1989
+ merge: (existing: TMessage, incoming: TMessage) => TMessage;
1990
+ }
1991
+ /**
1992
+ * Reconstruct a message from `chunks` and upsert it via the store. Returns
1993
+ * `true` when a write happened (so a caller that broadcasts after — Think — can
1994
+ * gate its broadcast on it), `false` when there was nothing to persist (no
1995
+ * parts, or `prepare` returned `null`).
1996
+ */
1997
+ declare function persistReconstructedOrphan<
1998
+ TMessage extends UIMessage = UIMessage
1999
+ >(
2000
+ chunks: ReadonlyArray<{
2001
+ body: string;
2002
+ }>,
2003
+ options: PersistReconstructedOrphanOptions<TMessage>
2004
+ ): Promise<boolean>;
1320
2005
  //#endregion
1321
2006
  //#region src/chat/recovery.d.ts
2007
+ /**
2008
+ * The minimal transcript-tail shape {@link createChatFiberSnapshot} reads to
2009
+ * derive the snapshot's `latest*Id` markers. Deliberately NOT `UIMessage`: the
2010
+ * snapshot only ever needs each message's `id` + `role`, so any host transcript
2011
+ * (AI SDK `UIMessage[]`, `Think`'s session leaves, or the pi adapter's plain
2012
+ * `AgentMessage[]`) satisfies it structurally. Keeping this off `UIMessage` is
2013
+ * the Phase-5 genericity seam — the snapshot builder must not couple to the AI
2014
+ * SDK message shape.
2015
+ */
2016
+ interface SnapshotMessage {
2017
+ id?: string;
2018
+ role: string;
2019
+ }
1322
2020
  type ChatFiberSnapshot<Kind extends string = string> = {
1323
2021
  kind: Kind;
1324
2022
  version: 1;
@@ -1345,7 +2043,7 @@ declare function createChatFiberSnapshot<Kind extends string>({
1345
2043
  requestId: string;
1346
2044
  recoveryRootRequestId?: string;
1347
2045
  continuation: boolean;
1348
- messages: UIMessage[];
2046
+ messages: ReadonlyArray<SnapshotMessage>;
1349
2047
  lastBody?: Record<string, unknown>;
1350
2048
  lastClientTools?: ClientToolSchema[];
1351
2049
  }): ChatFiberSnapshot<Kind>;
@@ -1363,72 +2061,1330 @@ declare function unwrapChatFiberSnapshot<Kind extends string>(
1363
2061
  user: unknown | null;
1364
2062
  };
1365
2063
  //#endregion
2064
+ //#region src/chat/recovery-incident.d.ts
2065
+ /**
2066
+ * Whether a recovery is retrying an unanswered user turn or continuing a
2067
+ * partial assistant turn. Intentionally NOT part of the incident identity (see
2068
+ * {@link chatRecoveryIncidentId}).
2069
+ */
2070
+ type ChatRecoveryKind = "retry" | "continue";
2071
+ /**
2072
+ * Durable per-incident recovery record.
2073
+ *
2074
+ * PERSISTED CONTRACT — this shape round-trips across deploys (including the
2075
+ * deploy that ships the shared engine, which is itself a deploy-mid-recovery).
2076
+ * Fields are added as optional so older persisted incidents keep recovering.
2077
+ */
2078
+ type ChatRecoveryIncident = {
2079
+ incidentId: string;
2080
+ requestId: string /** Stable request ID for the whole continuation chain (the recovery root). */;
2081
+ recoveryRootRequestId?: string;
2082
+ recoveryKind: ChatRecoveryKind;
2083
+ attempt: number;
2084
+ maxAttempts: number;
2085
+ status:
2086
+ | "detected"
2087
+ | "scheduled"
2088
+ | "attempting"
2089
+ | "completed"
2090
+ | "skipped"
2091
+ | "exhausted"
2092
+ | "failed";
2093
+ firstSeenAt: number;
2094
+ lastAttemptAt: number;
2095
+ /**
2096
+ * Epoch ms of the last attempt that observed forward progress. The recovery
2097
+ * budget is keyed to this (`now - lastProgressAt > noProgressTimeoutMs`), so a
2098
+ * turn that keeps producing content survives churn indefinitely while a
2099
+ * genuinely stuck turn is sealed within the window (#1637). Optional for
2100
+ * backward-compat — falls back to `firstSeenAt`.
2101
+ */
2102
+ lastProgressAt?: number;
2103
+ reason?: string;
2104
+ /**
2105
+ * High-water mark of the durable, monotonic recovery-progress counter
2106
+ * observed for this incident. Distinguishes a turn making forward progress
2107
+ * but repeatedly interrupted by isolate resets (deploys) — which must NOT
2108
+ * exhaust the budget — from one that genuinely fails to advance. Sourced from
2109
+ * a persisted counter, never the compactable transcript (#1628).
2110
+ */
2111
+ progress?: number;
2112
+ /**
2113
+ * Value of the durable progress counter when this incident opened. The
2114
+ * runaway-loop work budget is `progress - workBaseline`, compared against
2115
+ * `maxRecoveryWork`. Optional for backward-compat — a missing baseline is
2116
+ * treated as the current marker (zero work so far), so an in-flight incident
2117
+ * from an older build is never falsely sealed.
2118
+ */
2119
+ workBaseline?: number;
2120
+ /**
2121
+ * Count of recovery attempts for this incident that ended in a Durable Object
2122
+ * memory-limit reset (the isolate exceeded its 128 MB limit — see
2123
+ * `isDurableObjectMemoryLimitReset`). An OOM is a poison signal: re-running the
2124
+ * same memory-heavy turn deterministically re-OOMs, so unlike a deploy/eviction
2125
+ * it must NOT credit forward progress and must be bounded by a tight,
2126
+ * OOM-specific budget (`maxOomRetries`) rather than the generic attempt cap
2127
+ * (which resets on progress). Bumped by `ChatRecoveryEngine.recordOomAndDecide`
2128
+ * when a recovery callback observes an OOM; exceeding `maxOomRetries` seals the
2129
+ * incident with `reason="out_of_memory"` (#1825). Optional for backward-compat.
2130
+ */
2131
+ oomAttempts?: number;
2132
+ };
2133
+ declare const CHAT_RECOVERY_INCIDENT_KEY_PREFIX = "cf:chat-recovery:incident:";
2134
+ /**
2135
+ * Durable, monotonic forward-progress counter for recovery budget resets.
2136
+ * Bumped at production time when new content is streamed, so it reflects
2137
+ * genuinely new content and is immune to reconnects/re-persists; never
2138
+ * recomputed from the (compactable) transcript.
2139
+ */
2140
+ declare const CHAT_RECOVERY_PROGRESS_KEY = "cf:chat-recovery:progress";
2141
+ /**
2142
+ * Durable record of an in-progress recovery so a "recovering…" status (#1620)
2143
+ * can be broadcast live and survive the set/clear happening in different
2144
+ * isolates (a continuation runs in a later alarm invocation).
2145
+ */
2146
+ declare const CHAT_RECOVERING_KEY = "cf:chat:recovering";
2147
+ /**
2148
+ * Durable record of the last turn that ended in a terminal error / abandoned
2149
+ * recovery (#1645). Replayed on the next reconnect via the resume handshake;
2150
+ * cleared when a later turn supersedes it.
2151
+ */
2152
+ declare const CHAT_LAST_TERMINAL_KEY = "cf:chat:last-terminal";
2153
+ /**
2154
+ * Secondary backstop only. The primary recovery bound is the no-progress wall
2155
+ * clock; with alarm debounce this cap rarely binds (it catches a pathological
2156
+ * tight alarm-loop). Kept high so the no-progress window seals first under
2157
+ * normal deploy cadence (#1637).
2158
+ */
2159
+ declare const DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS = 10;
2160
+ /**
2161
+ * Runaway-loop guard default — the framework-imposed backstop on cumulative
2162
+ * recovery WORK (produced content/tool units) since an incident opened.
2163
+ *
2164
+ * Originally `Infinity` (rfc-chat-recovery-work-budget): the SDK shipped the
2165
+ * *mechanism* but no default cap, so a progressing turn was never terminated on
2166
+ * its own. Production issue #1825 showed that this is a footgun: an isolate that
2167
+ * OOMs mid-stream still credits a little progress before it dies, which resets
2168
+ * BOTH progress-keyed bounds (the attempt cap and the no-progress window) on
2169
+ * every wake — and a fast crash loop (each attempt inside the alarm-debounce
2170
+ * window) pins the attempt counter too. With `maxRecoveryWork = Infinity` the
2171
+ * ONLY instrument whose meter still climbs across such a loop is disabled, so
2172
+ * recovery re-runs the turn (and its LLM calls) forever.
2173
+ *
2174
+ * A finite default closes that loop out of the box: work climbs regardless of
2175
+ * debounce/progress resets, so a content-emitting runaway is always sealed with
2176
+ * `reason="work_budget_exceeded"`. The value is deliberately generous — it
2177
+ * bounds wasted re-run cost without clipping a normal interrupted turn (work
2178
+ * only accrues from the first interruption until the turn completes, after which
2179
+ * the incident is deleted). A very long agentic turn under heavy interruption
2180
+ * that legitimately needs more should raise `maxRecoveryWork` (or set it to
2181
+ * `Infinity` to restore the pre-#1825 unbounded behavior).
2182
+ */
2183
+ declare const DEFAULT_CHAT_RECOVERY_MAX_WORK = 1000;
2184
+ /**
2185
+ * Tight, OOM-specific retry budget (#1825). A Durable Object memory-limit reset
2186
+ * (`isDurableObjectMemoryLimitReset`) is usually deterministic — the turn's
2187
+ * working set no longer fits in the isolate's 128 MB — so re-running it re-OOMs.
2188
+ * But a single OOM CAN be a transient spike (the isolate's 128 MB is shared
2189
+ * across the global scope / noisy neighbors), so recovery retries a small number
2190
+ * of times before sealing with `reason="out_of_memory"` rather than abandoning a
2191
+ * turn that one more attempt might have completed. Far tighter than the generic
2192
+ * `maxRecoveryWork` backstop because an OOM is attributable and re-running it is
2193
+ * expensive (it re-runs the model). Counts attempts that ended in an OOM, not
2194
+ * total attempts, so a turn interrupted by deploys (no OOM) is unaffected.
2195
+ */
2196
+ declare const DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES = 3;
2197
+ declare const DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS = 10000;
2198
+ /**
2199
+ * Delay before retrying a recovery that timed out waiting for stable state.
2200
+ * Gives an actively-churning isolate (e.g. a deploy in flight) time to settle.
2201
+ */
2202
+ declare const CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS = 3;
2203
+ declare const DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE =
2204
+ "The assistant was interrupted and could not recover. Please try again.";
2205
+ /**
2206
+ * Incidents that have not seen a new attempt within this window are assumed
2207
+ * abandoned and swept so durable storage does not grow without bound.
2208
+ */
2209
+ declare const CHAT_RECOVERY_INCIDENT_TTL_MS: number;
2210
+ /** Max keys per Durable Object KV `delete([...])` call. */
2211
+ declare const KV_DELETE_MAX_KEYS = 128;
2212
+ /**
2213
+ * PRIMARY recovery bound (#1637): seal an incident that has made no forward
2214
+ * progress for this long. Keyed to `lastProgressAt`, which resets on every
2215
+ * progress-bearing attempt — so a turn that keeps producing content survives
2216
+ * deploy churn indefinitely, while a genuinely stuck turn dies within 5 min.
2217
+ */
2218
+ declare const DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS: number;
2219
+ /**
2220
+ * Alarm debounce: recovery alarms bunched within this window collapse into a
2221
+ * single attempt. A deploy rollout drops/reconnects the socket several times
2222
+ * over ~11–22s; without this, one logical deploy would burn several attempts.
2223
+ */
2224
+ declare const CHAT_RECOVERY_ALARM_DEBOUNCE_MS: number;
2225
+ /**
2226
+ * Staleness bound for the live "recovering…" flag (#1620). A flag older than
2227
+ * this is treated as abandoned so it can neither pin the indicator on forever
2228
+ * nor suppress a genuinely-new recovering signal. NOT a recovery budget.
2229
+ */
2230
+ declare const CHAT_RECOVERING_FLAG_TTL_MS: number;
2231
+ /**
2232
+ * Resolve a raw `chatRecovery` config field into the fully-defaulted form the
2233
+ * engine reasons about. Identical defaulting in both packages today.
2234
+ */
2235
+ declare function resolveChatRecoveryConfig(
2236
+ raw: ChatRecoveryConfig | undefined
2237
+ ): ResolvedChatRecoveryConfig;
2238
+ /**
2239
+ * Sweep recovery incidents inactive past the TTL from durable storage. Lists by
2240
+ * the incident key prefix, selects stale keys (`selectStaleIncidentKeys`), and
2241
+ * batch-deletes them — the DO KV `delete([...])` accepts up to
2242
+ * `KV_DELETE_MAX_KEYS` per call, collapsing N awaited round-trips into
2243
+ * ceil(N / 128). Shared by `AIChatAgent` and `Think` so the sweep policy lives in
2244
+ * one place. See `design/rfc-chat-recovery-foundation.md`.
2245
+ */
2246
+ declare function sweepStaleChatRecoveryIncidents(
2247
+ storage: Pick<DurableObjectStorage, "list" | "delete">,
2248
+ now: number
2249
+ ): Promise<void>;
2250
+ /**
2251
+ * List the persisted recovery incidents that are still live (status
2252
+ * `detected` / `scheduled` / `attempting`) — i.e. NOT yet terminalized
2253
+ * (`exhausted` / `failed`). Used by the alarm-boundary OOM circuit breaker
2254
+ * (#1825) to find the incident(s) it must seal when the in-DO budgets could not.
2255
+ * Lists by the incident key prefix so the storage layout stays encapsulated.
2256
+ */
2257
+ declare function listActiveChatRecoveryIncidents(
2258
+ storage: Pick<DurableObjectStorage, "list">
2259
+ ): Promise<
2260
+ {
2261
+ key: string;
2262
+ incident: ChatRecoveryIncident;
2263
+ }[]
2264
+ >;
2265
+ /**
2266
+ * Summarize a child agent's persisted recovery incidents for the parent's
2267
+ * agent-tool reattach decision: `"in-progress"` if any incident is still live
2268
+ * (detected/scheduled/attempting), else `"failed"` if any terminalized
2269
+ * (exhausted/failed), else `"none"`. In-progress takes precedence so a parent
2270
+ * never gives up on a child that is still recovering. Shared by `AIChatAgent`
2271
+ * and `Think`. See `design/rfc-chat-recovery-foundation.md`.
2272
+ */
2273
+ declare function classifyAgentToolChildRecovery(
2274
+ storage: Pick<DurableObjectStorage, "list">
2275
+ ): Promise<"in-progress" | "failed" | "none">;
2276
+ /**
2277
+ * Read the durable monotonic recovery-progress counter (0 when unset). The value
2278
+ * feeds the no-progress budget decision; shared by `AIChatAgent` and `Think`.
2279
+ */
2280
+ declare function readChatRecoveryProgress(
2281
+ storage: Pick<DurableObjectStorage, "get">
2282
+ ): Promise<number>;
2283
+ /**
2284
+ * Advance the durable recovery-progress counter by one. Called when genuinely new
2285
+ * content is durably flushed (real, reconnect-immune forward progress); shared by
2286
+ * `AIChatAgent` and `Think`.
2287
+ */
2288
+ declare function bumpChatRecoveryProgress(
2289
+ storage: Pick<DurableObjectStorage, "get" | "put">
2290
+ ): Promise<void>;
2291
+ /**
2292
+ * Throttle window for crediting a parent turn's recovery progress from forwarded
2293
+ * sub-agent (agent-tool) stream chunks (N9). Forwarding a child's chunks IS
2294
+ * forward progress for the parent, but the credit must not write storage per
2295
+ * token.
2296
+ */
2297
+ declare const AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS = 5000;
2298
+ /**
2299
+ * Per-isolate throttle gate for agent-tool stream-progress crediting (N9). The
2300
+ * `_lastBumpAt` clock is in-memory, so it resets per isolate and the first
2301
+ * forwarded chunk after a restart always credits. `shouldCredit(now)` returns
2302
+ * `true` at most once per `AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS` window and
2303
+ * records the time on each credit. Shared by `AIChatAgent` and `Think`.
2304
+ */
2305
+ declare class AgentToolStreamProgressThrottle {
2306
+ private _lastBumpAt;
2307
+ shouldCredit(now: number): boolean;
2308
+ }
2309
+ /**
2310
+ * Throttle window for crediting recovery progress from mid-segment streaming
2311
+ * content (text/reasoning/tool-input deltas). A milestone chunk credits
2312
+ * unconditionally; deltas credit at most once per window so a long single
2313
+ * segment registers forward progress across crashes without writing storage per
2314
+ * token. 5s is far finer than the 300s no-progress budget, so any crash gap
2315
+ * longer than this window over an actively-streaming segment still credits.
2316
+ */
2317
+ declare const CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS = 5000;
2318
+ /**
2319
+ * Per-isolate throttle gate for crediting recovery progress from mid-segment
2320
+ * streaming-content chunks — the delta arm of {@link shouldCreditStreamProgress}.
2321
+ * The `_lastBumpAt` clock is in-memory, so it resets per isolate and the first
2322
+ * delta after a restart always credits. Shared by `AIChatAgent` and `Think`.
2323
+ */
2324
+ declare class StreamProgressCreditThrottle {
2325
+ private _lastBumpAt;
2326
+ shouldCredit(now: number): boolean;
2327
+ }
2328
+ /** Durable record of the last turn that ended in a terminal error (#1645). */
2329
+ type ChatTerminalRecord = {
2330
+ requestId: string;
2331
+ body: string;
2332
+ };
2333
+ /**
2334
+ * Persist a durable record of the last terminal turn so a client that
2335
+ * (re)connects after the turn ended still learns its outcome (#1645). Kept
2336
+ * until a later turn supersedes it ({@link clearChatTerminal}); a single record
2337
+ * is sufficient because only the most recent terminal is relevant.
2338
+ */
2339
+ declare function recordChatTerminal(
2340
+ storage: Pick<DurableObjectStorage, "put">,
2341
+ requestId: string,
2342
+ body: string
2343
+ ): Promise<void>;
2344
+ /** Clear the durable terminal record once a later turn supersedes it (#1645). */
2345
+ declare function clearChatTerminal(
2346
+ storage: Pick<DurableObjectStorage, "delete">
2347
+ ): Promise<void>;
2348
+ /** Read the pending terminal record, or `null` if none is stored (#1645). */
2349
+ declare function pendingChatTerminal(
2350
+ storage: Pick<DurableObjectStorage, "get">
2351
+ ): Promise<ChatTerminalRecord | null>;
2352
+ /**
2353
+ * Build the on-connect "recovering…" replay frame (#1620), or `null` when no
2354
+ * (non-stale) recovery is in progress. A client that connects between recovery
2355
+ * attempts (no active stream) reads the turn as working rather than frozen. A
2356
+ * record older than the flag TTL is treated as abandoned (its terminal-clear
2357
+ * never ran) and skipped, so a dead recovery can't show "recovering…" forever.
2358
+ * `messageType` is the package's recovering wire-type enum.
2359
+ */
2360
+ declare function buildChatRecoveringFrame(
2361
+ storage: Pick<DurableObjectStorage, "get">,
2362
+ messageType: string,
2363
+ now: number
2364
+ ): Promise<Record<string, unknown> | null>;
2365
+ /**
2366
+ * Set or clear the live "recovering…" status (#1620). Persists a durable record
2367
+ * (so set/clear stay consistent across the isolates a recovery spans) and
2368
+ * broadcasts a recovering frame — but only on a genuine transition, so a
2369
+ * deploy/reconnect storm (which re-detects recovery many times) doesn't spam
2370
+ * the wire. A flag older than the TTL is stale: the owning incident was
2371
+ * abandoned without a terminal (e.g. the DO went idle before recovery could
2372
+ * resolve), so it is treated as not-recovering and can neither pin the
2373
+ * indicator on forever nor suppress a genuinely-new recovering signal.
2374
+ * `messageType` is the package's recovering wire-type enum; `broadcast` is the
2375
+ * package's chat-broadcast wrapper.
2376
+ */
2377
+ declare function setChatRecovering(
2378
+ active: boolean,
2379
+ requestId: string | undefined,
2380
+ deps: {
2381
+ storage: Pick<DurableObjectStorage, "get" | "put" | "delete">;
2382
+ messageType: string;
2383
+ broadcast: (frame: Record<string, unknown>) => void;
2384
+ now: number;
2385
+ }
2386
+ ): Promise<void>;
2387
+ /**
2388
+ * Observability event produced by an incident evaluation or a status
2389
+ * transition, emitted by the caller. The `detected`/`attempt` events come from
2390
+ * the budget evaluation (begin path); the `scheduled` event comes from
2391
+ * `ChatRecoveryEngine.scheduleRecovery`; the `completed`/`skipped`/`failed`
2392
+ * events come from `ChatRecoveryEngine.updateIncident`. `reason` is carried only
2393
+ * by the `skipped`/`failed` transitions that record a cause.
2394
+ */
2395
+ type ChatRecoveryIncidentEvent = {
2396
+ type:
2397
+ | "chat:recovery:detected"
2398
+ | "chat:recovery:attempt"
2399
+ | "chat:recovery:scheduled"
2400
+ | "chat:recovery:completed"
2401
+ | "chat:recovery:skipped"
2402
+ | "chat:recovery:failed";
2403
+ incidentId: string;
2404
+ requestId: string;
2405
+ attempt: number;
2406
+ maxAttempts: number;
2407
+ recoveryKind: ChatRecoveryKind;
2408
+ reason?: string;
2409
+ };
2410
+ type EvaluateChatRecoveryIncidentInput = {
2411
+ /** Recovery identity for this turn. */ identity: {
2412
+ requestId: string;
2413
+ recoveryRootRequestId?: string | null;
2414
+ latestUserMessageId?: string | null;
2415
+ recoveryKind: ChatRecoveryKind;
2416
+ } /** Fully-resolved recovery config. */;
2417
+ config: ResolvedChatRecoveryConfig /** The existing incident for this identity, or `null` if this is fresh. */;
2418
+ existing: ChatRecoveryIncident | null /** Current value of the durable monotonic progress counter. */;
2419
+ currentProgress: number;
2420
+ /**
2421
+ * Whether the turn is parked on a pending CLIENT interaction (an
2422
+ * `input-available` client-tool part or an `approval-requested` part). Such a
2423
+ * turn is waiting on the human, not stuck, so it is budget-free.
2424
+ */
2425
+ awaitingClientInteraction: boolean /** Injected clock (epoch ms) for deterministic tests. */;
2426
+ now: number;
2427
+ /**
2428
+ * Invoked when `config.shouldKeepRecovering` throws. Lets each package keep
2429
+ * its own log prefix. A throwing predicate is treated as "keep recovering".
2430
+ */
2431
+ onShouldKeepRecoveringError?: (error: unknown) => void;
2432
+ };
2433
+ type EvaluateChatRecoveryIncidentResult = {
2434
+ /** The next incident record to persist. */ incident: ChatRecoveryIncident /** Whether this incident is now sealed as exhausted. */;
2435
+ exhausted: boolean /** Observability events to emit, in order. */;
2436
+ events: ChatRecoveryIncidentEvent[];
2437
+ };
2438
+ //#endregion
2439
+ //#region src/chat/recovery-engine.d.ts
2440
+ /** The scheduled-callback entrypoints a recovery schedule can target. */
2441
+ type ChatRecoveryScheduleCallback =
2442
+ | "_chatRecoveryContinue"
2443
+ | "_chatRecoveryRetry";
2444
+ /**
2445
+ * Why a recovery callback is being scheduled. The idempotency of the underlying
2446
+ * `schedule()` call depends ONLY on this:
2447
+ *
2448
+ * - `"initial"` — the first schedule of a continuation/retry when an interrupted
2449
+ * turn is detected on wake. A deploy rollout drops/reconnects the socket
2450
+ * several times, re-triggering detection; idempotent scheduling (dedup on
2451
+ * callback + payload) collapses that storm into a single enqueued continuation
2452
+ * instead of N duplicates.
2453
+ *
2454
+ * - `"stable_timeout_retry"` — a reschedule issued from INSIDE the currently-
2455
+ * executing one-shot schedule row (a continuation that timed out waiting for
2456
+ * stable state). `alarm()` deletes that row only AFTER the callback returns,
2457
+ * so an idempotent reschedule would dedup onto the doomed row and be deleted
2458
+ * with it — the retry would never fire. A fresh (non-idempotent) delayed row
2459
+ * survives the deletion.
2460
+ */
2461
+ type ChatRecoveryScheduleReason = "initial" | "stable_timeout_retry";
2462
+ /**
2463
+ * A reconstructed orphaned-stream partial. The engine seam is deliberately
2464
+ * **wire-vocabulary-agnostic**: `text` is the accumulated assistant text and
2465
+ * `parts` is OPAQUE to the engine (`unknown[]`) — each host casts it back to its
2466
+ * own message-part vocabulary (AI SDK `UIMessage` parts, AG-UI tool parts, …).
2467
+ * The single fact the engine needs about parts — does the partial carry settled
2468
+ * (non-idempotent) tool work that must survive a `{ persist: false }` recovery
2469
+ * (#1631)? — is precomputed by the {@link ChatRecoveryCodec} as
2470
+ * `hasSettledToolResults`. So the engine never imports a part vocabulary; the
2471
+ * codec owns it (see `partialHasSettledToolResults` in `recovery-codec.ts` for
2472
+ * the AI SDK codec's implementation of that predicate).
2473
+ */
2474
+ type RecoveryPartial = {
2475
+ text: string;
2476
+ parts: unknown[];
2477
+ hasSettledToolResults: boolean;
2478
+ };
2479
+ /** Lifecycle status of a recovered stream's metadata row. */
2480
+ type ChatStreamStatus = "streaming" | "completed" | "error";
2481
+ /**
2482
+ * Resolve the `schedule()` idempotency option for a recovery schedule. Single
2483
+ * source of truth for both packages; see {@link ChatRecoveryScheduleReason} for
2484
+ * the rationale behind each case.
2485
+ *
2486
+ * This is a cutover invariant: flipping either case silently breaks deploy-storm
2487
+ * dedup (initial) or stalls stable-timeout retries (reschedule), and neither is
2488
+ * caught by a type error — only by the recovery suites.
2489
+ */
2490
+ declare function chatRecoverySchedulePolicy(
2491
+ reason: ChatRecoveryScheduleReason
2492
+ ): {
2493
+ idempotent: boolean;
2494
+ };
2495
+ /** Identity + context for opening (or re-evaluating) a recovery incident. */
2496
+ interface BeginChatRecoveryIncidentInput {
2497
+ requestId: string;
2498
+ recoveryRootRequestId?: string | null;
2499
+ latestUserMessageId?: string | null;
2500
+ recoveryKind: ChatRecoveryKind;
2501
+ /** Test-only clock injection for deterministic debounce/window timing. */
2502
+ nowMs?: number;
2503
+ }
2504
+ interface BeginChatRecoveryIncidentResult {
2505
+ incident: ChatRecoveryIncident;
2506
+ config: ResolvedChatRecoveryConfig;
2507
+ exhausted: boolean;
2508
+ }
2509
+ /**
2510
+ * Package-specific host operations the engine drives during incident
2511
+ * orchestration. Every method is a thin pass-through to the package's existing
2512
+ * storage / clock / event / interaction primitives — the engine owns only the
2513
+ * *sequence*, not the I/O.
2514
+ */
2515
+ interface ChatRecoveryAdapter {
2516
+ /** Resolve the effective recovery config (defaults + caller overrides). */
2517
+ resolveConfig(): ResolvedChatRecoveryConfig;
2518
+ /** Wall clock; only consulted when the input carries no test `nowMs`. */
2519
+ now(): number;
2520
+ /** Evict incidents past the TTL. Runs before the existing-record read. */
2521
+ sweepStaleIncidents(now: number): Promise<void>;
2522
+ /** Read the persisted incident for `key`, or `null` if none. */
2523
+ getIncident(key: string): Promise<ChatRecoveryIncident | null>;
2524
+ /**
2525
+ * Optional: rehydrate any state the interaction predicate depends on. Invoked
2526
+ * after the existing-incident read and BEFORE `isAwaitingClientInteraction`.
2527
+ * `Think` uses this to restore client tools from durable storage on a cold
2528
+ * boot-recovery wake (so a HITL turn is not misread as stuck); `AIChatAgent`
2529
+ * has no such state and omits it.
2530
+ */
2531
+ ensureInteractionStateLoaded?(): void;
2532
+ /**
2533
+ * Optional: give the package a chance to handle a NON-chat fiber before chat
2534
+ * recovery inspects it. Returns `true` if the package fully consumed the
2535
+ * fiber, in which case the engine tells the caller to skip chat-recovery
2536
+ * processing for it. `Think` uses this for its messenger/workflow reply fibers
2537
+ * (`think:messenger-reply`); `AIChatAgent` has no non-chat fibers and omits it
2538
+ * (the engine then treats every recovered fiber as a chat-recovery candidate).
2539
+ *
2540
+ * Ordering invariant: the engine dispatches this FIRST, before the
2541
+ * chat-fiber-name gate, so a non-chat fiber is never misclassified as an
2542
+ * orphaned chat turn.
2543
+ */
2544
+ tryHandleNonChatFiberRecovery?(ctx: FiberRecoveryContext): Promise<boolean>;
2545
+ /** Monotonic forward-progress marker for the no-progress budget. */
2546
+ readProgress(): Promise<number>;
2547
+ /**
2548
+ * Whether the turn is parked on a pending CLIENT interaction (waiting on the
2549
+ * human, not stuck). When true the engine keeps the incident budget-free.
2550
+ * Optional: a host with no client-interaction/HITL substrate (e.g. the pi
2551
+ * fixture) omits it and the engine treats the turn as never parked (`false`).
2552
+ */
2553
+ isAwaitingClientInteraction?(): boolean;
2554
+ /** Persist the evaluated incident under `key`. */
2555
+ putIncident(key: string, incident: ChatRecoveryIncident): Promise<void>;
2556
+ /**
2557
+ * Delete the incident record under `key`. The engine calls this on the
2558
+ * terminal `completed` transition (a completed recovery is never retried, so
2559
+ * its record is dropped rather than left in storage forever).
2560
+ */
2561
+ deleteIncident(key: string): Promise<void>;
2562
+ /** Broadcast a lifecycle event produced by the evaluation or a transition. */
2563
+ emitRecoveryEvent(event: ChatRecoveryIncidentEvent): void;
2564
+ /**
2565
+ * Enqueue a recovery callback. A thin pass-through to the package's
2566
+ * `schedule(delaySeconds, callback, data, chatRecoverySchedulePolicy(reason))`
2567
+ * — the engine owns the surrounding orchestration (the transition + emit for
2568
+ * the initial schedule in {@link ChatRecoveryEngine.scheduleRecovery}, the
2569
+ * attempt bump for {@link ChatRecoveryEngine.rescheduleAfterStableTimeout});
2570
+ * the package owns the Durable Object alarm write and the payload shape.
2571
+ * `reason` selects the idempotency policy and `delaySeconds` the alarm delay
2572
+ * (`0` for the initial enqueue, `CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS` for
2573
+ * a stable-timeout reschedule).
2574
+ */
2575
+ scheduleRecovery(
2576
+ callback: ChatRecoveryScheduleCallback,
2577
+ data: Record<string, unknown>,
2578
+ reason: ChatRecoveryScheduleReason,
2579
+ delaySeconds: number
2580
+ ): Promise<void>;
2581
+ /**
2582
+ * Set or clear the live "recovering…" status (#1620). The engine calls this on
2583
+ * the incident transitions: `scheduled` → active (keyed by the recovery-root
2584
+ * request id, falling back to the incident's request id), and
2585
+ * `completed`/`skipped`/`failed` → cleared. The package owns the underlying
2586
+ * staleness / idempotency / broadcast I/O.
2587
+ */
2588
+ setRecovering(active: boolean, requestId?: string): Promise<void>;
2589
+ /**
2590
+ * Report a throw from the caller's `shouldKeepRecovering` hook. Optional: a
2591
+ * host that does not surface this diagnostic omits it (the engine swallows the
2592
+ * report).
2593
+ */
2594
+ onShouldKeepRecoveringError?(error: unknown): void;
2595
+ /**
2596
+ * Terminalize a given-up recovery turn: deliver the exhaustion notification
2597
+ * plus the package-owned terminal record / banner / submission writes. A thin
2598
+ * pass-through to the package's `_exhaustChatRecovery` (which composes
2599
+ * {@link runChatRecoveryExhaustion}). Driven by
2600
+ * {@link ChatRecoveryEngine.exhaustRecoveryGiveUp}; the engine owns the
2601
+ * surrounding read → re-entry-guard → build → terminalize → seal sequence, the
2602
+ * package owns the terminal writes (uniformly broadcast-first; their set
2603
+ * differs — `Think` also writes a submission row).
2604
+ */
2605
+ exhaustChatRecovery(
2606
+ incident: ChatRecoveryIncident,
2607
+ config: ResolvedChatRecoveryConfig,
2608
+ partial: RecoveryPartial,
2609
+ streamId: string,
2610
+ createdAt: number
2611
+ ): Promise<void>;
2612
+ /**
2613
+ * Resolve the orphaned stream identity for a (recovery-root) request id —
2614
+ * `streamId` is `""` when no stream metadata survives. Drives BOTH the wake
2615
+ * path (which consumes the full {@link ResolvedRecoveryStream}) and the
2616
+ * give-up path (which reads only `.streamId`). A thin pass-through to the
2617
+ * package's stream-metadata lookup: the newest row keyed by the request id,
2618
+ * else the live active stream.
2619
+ */
2620
+ resolveRecoveryStream(requestId: string): ResolvedRecoveryStream;
2621
+ /** Reconstruct the partial text/parts buffered for `streamId`. */
2622
+ getPartialStreamText(streamId: string): RecoveryPartial;
2623
+ /**
2624
+ * The in-flight recovery-root request id, consulted as a fallback in the
2625
+ * give-up root-id chain when the payload carries no `originalRequestId` /
2626
+ * `recoveredRequestId` and no incident record survives. `undefined` when no
2627
+ * recovery chain is active. (`AIChatAgent` and `Think` both back this with
2628
+ * `_activeChatRecoveryRootRequestId`.)
2629
+ */
2630
+ activeChatRecoveryRootRequestId(): string | undefined;
2631
+ /**
2632
+ * Report a tolerated best-effort bookkeeping failure during give-up: the
2633
+ * incident `"read"` (before synthesizing) or the sealing `"seal"` write
2634
+ * (after terminalization). Neither aborts terminalization — see
2635
+ * {@link ChatRecoveryEngine.exhaustRecoveryGiveUp}.
2636
+ */
2637
+ onGiveUpBookkeepingError(phase: "read" | "seal", error: unknown): void;
2638
+ }
2639
+ /** Resolved orphaned-stream identity for a recovered chat turn. */
2640
+ interface ResolvedRecoveryStream {
2641
+ /** The orphaned stream id, or `""` when no stream metadata survives. */
2642
+ streamId: string;
2643
+ /**
2644
+ * Whether the orphaned stream is still the live in-flight stream (so its
2645
+ * partial has not already been persisted + completed by an ACK-driven
2646
+ * reconnect). Gates persistence and stream completion.
2647
+ */
2648
+ streamStillActive: boolean;
2649
+ /**
2650
+ * The stream metadata row's lifecycle status, when the host tracks it
2651
+ * (`Think`). `undefined` for hosts that do not model terminal streams
2652
+ * (`AIChatAgent`) — those keep every terminal-stream branch dead, per the
2653
+ * "substrate capabilities are optional" decision in the RFC.
2654
+ */
2655
+ streamStatus?: ChatStreamStatus;
2656
+ }
2657
+ /** Input to {@link ChatFiberWakeHooks.classifyRecoveredTurn}. */
2658
+ interface ClassifyRecoveredTurnInput {
2659
+ snapshot: ChatFiberSnapshot | null;
2660
+ requestId: string;
2661
+ streamId: string;
2662
+ partial: RecoveryPartial;
2663
+ streamStillActive: boolean;
2664
+ streamStatus?: ChatStreamStatus;
2665
+ }
2666
+ /** Input to {@link ChatFiberWakeHooks.invokeOnChatRecovery}. */
2667
+ interface InvokeOnChatRecoveryInput {
2668
+ incident: ChatRecoveryIncident;
2669
+ recoveryKind: ChatRecoveryKind;
2670
+ recoveryRootRequestId: string;
2671
+ requestId: string;
2672
+ streamId: string;
2673
+ partial: RecoveryPartial;
2674
+ snapshot: ChatFiberSnapshot | null;
2675
+ recoveryData: unknown;
2676
+ createdAt: number;
2677
+ }
2678
+ /** Input to {@link ChatFiberWakeHooks.shouldPersistOrphanedPartial}. */
2679
+ interface PersistOrphanedPartialInput {
2680
+ streamId: string;
2681
+ streamStillActive: boolean;
2682
+ streamStatus?: ChatStreamStatus;
2683
+ snapshot: ChatFiberSnapshot | null;
2684
+ }
2685
+ /** Input to {@link ChatFiberWakeHooks.dispatchRecoveredTurn}. */
2686
+ interface DispatchRecoveredTurnInput<TClassify> {
2687
+ incident: ChatRecoveryIncident;
2688
+ config: ResolvedChatRecoveryConfig;
2689
+ recoveryKind: ChatRecoveryKind;
2690
+ options: ChatRecoveryOptions;
2691
+ snapshot: ChatFiberSnapshot | null;
2692
+ requestId: string;
2693
+ recoveryRootRequestId: string;
2694
+ streamId: string;
2695
+ streamStatus?: ChatStreamStatus;
2696
+ /** The package-specific classification detail produced by `classifyRecoveredTurn`. */
2697
+ detail: TClassify;
2698
+ }
2699
+ /**
2700
+ * The wake-dispatch host operations the engine drives when an interrupted CHAT
2701
+ * fiber is detected on restart — the divergent organs the frame-collapse map
2702
+ * flagged. Kept SEPARATE from {@link ChatRecoveryAdapter} (and passed per call to
2703
+ * {@link ChatRecoveryEngine.handleChatFiberRecovery}) so the incident/give-up
2704
+ * adapter stays focused, and generic over `TClassify` so the
2705
+ * `classifyRecoveredTurn` → `dispatchRecoveredTurn` handoff is type-safe without a
2706
+ * class-level generic.
2707
+ *
2708
+ * The engine owns the wake LIFECYCLE (gate → parse → unwrap → stream → partial →
2709
+ * classify → begin-incident → exhausted-branch → onChatRecovery → persist →
2710
+ * complete → dispatch → catch→failed) and the shared persist clause; these hooks
2711
+ * own the package-specific I/O and the retry/continue/skip decision.
2712
+ */
2713
+ interface ChatFiberWakeHooks<TClassify> {
2714
+ /** The chat-fiber name prefix (`CHAT_FIBER_NAME + ":"`) gating the wake path. */
2715
+ chatFiberPrefix(): string;
2716
+ /** Decode the fiber snapshot into the recovery snapshot + checkpointed user data. */
2717
+ unwrapRecoverySnapshot(ctx: FiberRecoveryContext): {
2718
+ snapshot: ChatFiberSnapshot | null;
2719
+ recoveryData: unknown;
2720
+ };
2721
+ /**
2722
+ * Classify the recovered turn as a `retry` or `continue` and return any
2723
+ * package-specific detail the dispatch decision needs (e.g. the pre-stream
2724
+ * retry target id). Runs before the incident is opened.
2725
+ */
2726
+ classifyRecoveredTurn(input: ClassifyRecoveredTurnInput):
2727
+ | {
2728
+ recoveryKind: ChatRecoveryKind;
2729
+ detail: TClassify;
2730
+ }
2731
+ | Promise<{
2732
+ recoveryKind: ChatRecoveryKind;
2733
+ detail: TClassify;
2734
+ }>;
2735
+ /**
2736
+ * Build the package's `ChatRecoveryContext` and invoke the user `onChatRecovery`
2737
+ * hook, returning its (defaulted) options. The engine wraps this in the
2738
+ * incident `failed`-on-throw guard. Optional: a host with no user
2739
+ * `onChatRecovery` surface (e.g. the pi fixture) omits it and the engine
2740
+ * proceeds with empty options (`{}`).
2741
+ */
2742
+ invokeOnChatRecovery?(
2743
+ input: InvokeOnChatRecoveryInput
2744
+ ): Promise<ChatRecoveryOptions | void>;
2745
+ /**
2746
+ * The BASE persist gate: whether the orphaned partial is eligible to be
2747
+ * materialized at all (live stream, or terminal-but-not-yet-persisted). The
2748
+ * engine ANDs this with the shared `options.persist !== false ||
2749
+ * partial.hasSettledToolResults` clause, so settled work is never dropped.
2750
+ */
2751
+ shouldPersistOrphanedPartial(
2752
+ input: PersistOrphanedPartialInput
2753
+ ): boolean | Promise<boolean>;
2754
+ /** Materialize the orphaned stream's partial into a persisted assistant message. */
2755
+ persistOrphanedStream(streamId: string): Promise<void>;
2756
+ /** Mark the (still-active) recovered stream complete and schedule cleanup. */
2757
+ completeRecoveredStream(streamId: string): void | Promise<void>;
2758
+ /**
2759
+ * The retry/continue/skip DECISION — the package-owned core. Runs after persist
2760
+ * + complete; owns the leaf/submission computation, the schedule calls (via
2761
+ * {@link ChatRecoveryEngine.scheduleRecovery}), the skip transitions, and any
2762
+ * package-specific terminal/broadcast writes.
2763
+ */
2764
+ dispatchRecoveredTurn(
2765
+ input: DispatchRecoveredTurnInput<TClassify>
2766
+ ): Promise<void>;
2767
+ }
2768
+ /**
2769
+ * Drives the shared recovery orchestration over a {@link ChatRecoveryAdapter}.
2770
+ * The incident *budget math* lives in the pure `evaluateChatRecoveryIncident`;
2771
+ * this class owns the surrounding sequence and its ordering invariants.
2772
+ */
2773
+ declare class ChatRecoveryEngine {
2774
+ private readonly adapter;
2775
+ constructor(adapter: ChatRecoveryAdapter);
2776
+ /**
2777
+ * Open or re-evaluate the recovery incident for `input`, persist the result,
2778
+ * and broadcast its lifecycle events. Returns the incident, the resolved
2779
+ * config, and whether the budget is now exhausted.
2780
+ */
2781
+ /**
2782
+ * Dispatch a recovered fiber to the package's non-chat handler (the
2783
+ * messenger/workflow seam) before any chat-recovery processing. Returns `true`
2784
+ * when the package consumed the fiber — the caller must then skip chat
2785
+ * recovery for it. The engine owns the *ordering* (this runs before the
2786
+ * chat-fiber gate); the *behavior* is adapter-owned. No-op (`false`) when the
2787
+ * adapter omits {@link ChatRecoveryAdapter.tryHandleNonChatFiberRecovery}.
2788
+ */
2789
+ handleNonChatFiber(ctx: FiberRecoveryContext): Promise<boolean>;
2790
+ /**
2791
+ * The shared wake-recovery LIFECYCLE for an interrupted chat fiber. Both
2792
+ * packages drove this exact frame; the divergent organs are the
2793
+ * {@link ChatFiberWakeHooks}. In order:
2794
+ *
2795
+ * 1. non-chat dispatch ({@link handleNonChatFiber}) FIRST, then the chat-fiber
2796
+ * name gate — a non-chat fiber is never misread as an orphaned chat turn;
2797
+ * 2. parse the request id, unwrap the snapshot, resolve the orphaned stream +
2798
+ * reconstruct its partial;
2799
+ * 3. classify the turn (retry/continue + package detail) and open the incident;
2800
+ * 4. if the budget is already exhausted, persist the settled partial (so
2801
+ * non-idempotent tool results are not discarded — #1631) and terminalize
2802
+ * BEFORE consulting `onChatRecovery`;
2803
+ * 5. otherwise, inside a `failed`-on-throw guard: invoke `onChatRecovery`,
2804
+ * apply the shared persist gate (base eligibility AND `persist !== false ||
2805
+ * settled tool results`), complete the live stream, then hand the
2806
+ * retry/continue/skip DECISION to {@link ChatFiberWakeHooks.dispatchRecoveredTurn}.
2807
+ *
2808
+ * Returns `true` when the fiber was a chat (or non-chat) recovery the engine
2809
+ * handled, `false` when it was not a chat fiber (the caller keeps looking). Any
2810
+ * throw after the incident opens flips it to `failed` so it is never left
2811
+ * leaking in `attempting`.
2812
+ */
2813
+ handleChatFiberRecovery<TClassify>(
2814
+ ctx: FiberRecoveryContext,
2815
+ wake: ChatFiberWakeHooks<TClassify>
2816
+ ): Promise<boolean>;
2817
+ /**
2818
+ * The shared persist gate: base eligibility (the package's
2819
+ * {@link ChatFiberWakeHooks.shouldPersistOrphanedPartial}) AND the
2820
+ * never-drop-settled-work clause `options.persist !== false ||
2821
+ * partial.hasSettledToolResults`. `options: undefined` (the exhausted branch)
2822
+ * collapses the clause to the base gate. The clause lives here — not in each
2823
+ * package — because settled-work preservation is a cross-package invariant
2824
+ * (#1631), and the codec (not the engine) decides whether a partial carries
2825
+ * settled tool work, so the engine stays wire-vocabulary-agnostic.
2826
+ */
2827
+ private _shouldPersistOrphanedPartial;
2828
+ beginIncident(
2829
+ input: BeginChatRecoveryIncidentInput
2830
+ ): Promise<BeginChatRecoveryIncidentResult>;
2831
+ /**
2832
+ * Schedule a recovery continuation/retry: the transition + emit + enqueue
2833
+ * triplet both packages repeat at every fiber-recovery and stall-routing
2834
+ * decision. In order:
2835
+ *
2836
+ * 1. transition the incident to `scheduled` (persist + drive the #1620
2837
+ * "recovering…" status) via {@link updateIncident};
2838
+ * 2. emit `chat:recovery:scheduled`; and
2839
+ * 3. enqueue the callback through the adapter's idempotent schedule.
2840
+ *
2841
+ * `recoveryKind` is passed explicitly (not read off the incident) because a
2842
+ * caller can legitimately report a different kind than the incident was opened
2843
+ * with — e.g. `AIChatAgent`'s lost-partial branch opens a `continue` incident
2844
+ * but schedules (and reports) a `retry`. `requestId` always matches
2845
+ * `incident.requestId` (the evaluation rewrites it to the current attempt), so
2846
+ * it is read from the incident.
2847
+ */
2848
+ scheduleRecovery(input: {
2849
+ incident: ChatRecoveryIncident;
2850
+ recoveryKind: ChatRecoveryKind;
2851
+ callback: ChatRecoveryScheduleCallback;
2852
+ data: Record<string, unknown>;
2853
+ reason?: ChatRecoveryScheduleReason;
2854
+ }): Promise<void>;
2855
+ /**
2856
+ * Reschedule a recovery continuation/retry that timed out waiting for stable
2857
+ * state, INSIDE the currently-executing one-shot schedule row. Reads the
2858
+ * incident; if it is still under the attempt cap, bumps `attempt`, marks it
2859
+ * `scheduled` with `reason:"stable_timeout_retry"`, and issues a delayed,
2860
+ * NON-idempotent schedule (`alarm()` deletes the executing row only after this
2861
+ * returns, so an idempotent reschedule would dedup onto that doomed row and
2862
+ * never fire — see {@link chatRecoverySchedulePolicy}).
2863
+ *
2864
+ * Returns `true` when a retry was scheduled, `false` when there is no incident
2865
+ * (no id / record gone) or the attempt budget is already spent — in which case
2866
+ * the caller falls through to the give-up path. Deliberately bypasses the
2867
+ * `evaluateChatRecoveryIncident` budget (this is a coarse stable-state retry,
2868
+ * not a fresh interruption) and {@link updateIncident} (no `scheduled` event /
2869
+ * recovering-flag churn on a same-turn reschedule).
2870
+ */
2871
+ rescheduleAfterStableTimeout(input: {
2872
+ incidentId: string | undefined;
2873
+ callback: ChatRecoveryScheduleCallback;
2874
+ data: Record<string, unknown> | undefined;
2875
+ fallbackMaxAttempts: number;
2876
+ }): Promise<boolean>;
2877
+ /**
2878
+ * Record that a recovery callback observed a Durable Object memory-limit reset
2879
+ * (the isolate exceeded its 128 MB limit — `isDurableObjectMemoryLimitReset`)
2880
+ * and decide what to do next (#1825).
2881
+ *
2882
+ * Bumps the incident's durable `oomAttempts` counter, then:
2883
+ * - if it is still within `maxOomRetries`, issues a delayed, NON-idempotent
2884
+ * reschedule of the SAME callback (same machinery as
2885
+ * {@link rescheduleAfterStableTimeout}: the executing one-shot row is
2886
+ * deleted only after the callback returns, so an idempotent reschedule
2887
+ * would dedup onto that doomed row) and returns `"rescheduled"`. The small
2888
+ * delay lets a transient memory spike clear before the re-run;
2889
+ * - otherwise leaves the incremented count persisted (so a begin-path
2890
+ * re-evaluation agrees) and returns `"exhausted"` — the caller then
2891
+ * terminalizes via the give-up path with `reason="out_of_memory"`.
2892
+ *
2893
+ * Returns `"exhausted"` when there is no incident to track against (no id /
2894
+ * record gone): an OOM we cannot bound must seal rather than loop. Unlike a
2895
+ * stable-state retry this is gated by the OOM-specific budget, NOT the generic
2896
+ * attempt cap — re-running an OOM streams a little "progress" that would
2897
+ * otherwise reset the attempt cap forever (the #1825 loop).
2898
+ */
2899
+ recordOomAndDecide(input: {
2900
+ incidentId: string | undefined;
2901
+ callback: ChatRecoveryScheduleCallback;
2902
+ data: Record<string, unknown> | undefined;
2903
+ maxOomRetries: number;
2904
+ }): Promise<"rescheduled" | "exhausted">;
2905
+ /**
2906
+ * Give up on a recovery turn whose retry budget drained, terminalizing it so
2907
+ * it can never become an eternal spinner (#1645). The shared spine both
2908
+ * packages repeated verbatim:
2909
+ *
2910
+ * 1. resolve config + the incident key from `data.incidentId`;
2911
+ * 2. best-effort READ the stored incident — a failed read is tolerated
2912
+ * (reported via `onGiveUpBookkeepingError("read", …)`) and the incident is
2913
+ * synthesized, because the read backs only the re-entry guard, not the
2914
+ * terminal UX;
2915
+ * 3. re-entry guard: a `stored.status === "exhausted"` record means
2916
+ * terminalization already fired, so a duplicate stale alarm returns without
2917
+ * re-broadcasting the banner;
2918
+ * 4. build the exhausted incident (reuse `stored`, or synthesize a minimal one
2919
+ * so a swept/missing record STILL terminalizes through `onExhausted`);
2920
+ * 5. resolve the orphaned stream id + partial;
2921
+ * 6. terminalize via `exhaustChatRecovery` — BEFORE sealing. The terminal
2922
+ * writes can reject with a platform transient in the deploy/storage window
2923
+ * a give-up runs in (#1730); letting that throw propagate is deliberate, so
2924
+ * `Agent._executeScheduleCallback` defers the one-shot row and the WHOLE
2925
+ * give-up re-runs on a healthy isolate. Sealing first would arm the
2926
+ * re-entry guard and turn that re-run into a no-op, dropping the durable
2927
+ * terminal record. The re-run is idempotent (terminal writes overwrite the
2928
+ * same key); a second banner is the documented at-least-once edge; and
2929
+ * 7. best-effort SEAL write so the re-entry guard sees `exhausted` on a
2930
+ * duplicate alarm — a failed seal (reported via
2931
+ * `onGiveUpBookkeepingError("seal", …)`) costs at most one re-delivered
2932
+ * banner.
2933
+ *
2934
+ * The two packages diverged only in parameters the caller supplies:
2935
+ * `reason` (`Think` passes `stable_timeout` | `recovery_error`; `AIChatAgent`
2936
+ * always `stable_timeout`) and the root-id chain (`Think` includes
2937
+ * `recoveredRequestId`; `AIChatAgent` never sets it, so the unified chain
2938
+ * collapses identically). Exactly-once terminalization rests on the re-entry
2939
+ * guard alone in `AIChatAgent`; `Think` additionally short-circuits duplicate
2940
+ * alarms earlier in its durable-submission layer.
2941
+ */
2942
+ exhaustRecoveryGiveUp(input: {
2943
+ callback: ChatRecoveryScheduleCallback;
2944
+ data:
2945
+ | {
2946
+ incidentId?: string;
2947
+ originalRequestId?: string;
2948
+ recoveredRequestId?: string;
2949
+ }
2950
+ | undefined;
2951
+ reason: string;
2952
+ }): Promise<void>;
2953
+ /**
2954
+ * Apply a status transition to the recovery incident `incidentId`:
2955
+ *
2956
+ * - `completed` → drop the record (terminal, never retried);
2957
+ * - any other status → persist the new status (and `reason`), so the attempt
2958
+ * budget survives restarts until the TTL sweep reclaims it;
2959
+ * - emit the matching `completed`/`skipped`/`failed` lifecycle event; and
2960
+ * - drive the live "recovering…" status (#1620): `scheduled` marks it active
2961
+ * (keyed by the recovery-root request id), terminal states clear it.
2962
+ *
2963
+ * No-op when `incidentId` is undefined or the record is already gone. This is
2964
+ * the transition twin of {@link beginIncident}: all I/O is adapter-owned, the
2965
+ * engine owns only the state-machine shape.
2966
+ */
2967
+ updateIncident(
2968
+ incidentId: string | undefined,
2969
+ status: ChatRecoveryIncident["status"],
2970
+ reason?: string
2971
+ ): Promise<void>;
2972
+ }
2973
+ /**
2974
+ * The complete give-up choreography from a single call: build the exhausted
2975
+ * context, fire the shared notification ({@link notifyChatRecoveryExhausted}),
2976
+ * then hand that context to the host's `terminalize` step. Folds the
2977
+ * `buildChatRecoveryExhaustedContext` → `notifyChatRecoveryExhausted` → host
2978
+ * terminalize sequence that every host's `_exhaustChatRecovery` repeated.
2979
+ *
2980
+ * What this OWNS (the invariant, so it cannot drift per host):
2981
+ * - the notification ALWAYS runs before any terminal write, and
2982
+ * - a throwing `onExhausted` can NEVER block terminal delivery — it is swallowed
2983
+ * via `onError` (a tested invariant in both published packages).
2984
+ *
2985
+ * What it deliberately does NOT own: the terminal-record / broadcast /
2986
+ * recovering-clear writes — their exact set diverges per host (both
2987
+ * `AIChatAgent` and `Think` broadcast the banner first so it survives a storage
2988
+ * write that rejects mid-deploy; `Think` additionally writes a submission row)
2989
+ * — see {@link ChatRecoveryAdapter.exhaustChatRecovery}. The host expresses
2990
+ * those writes inside `terminalize`. A `terminalize` that throws DOES propagate,
2991
+ * so the whole give-up re-runs on a healthy isolate (#1730); see
2992
+ * {@link ChatRecoveryEngine.exhaustRecoveryGiveUp}.
2993
+ *
2994
+ * `partialParts` is passed explicitly (not derived from a `RecoveryPartial`) so a
2995
+ * foreign-vocabulary host can pass `[]` rather than fabricate AI-SDK parts — the
2996
+ * engine seam stays parts-vocabulary-agnostic.
2997
+ */
2998
+ declare function runChatRecoveryExhaustion(
2999
+ input: {
3000
+ incident: ChatRecoveryIncident;
3001
+ config: ResolvedChatRecoveryConfig;
3002
+ partialText: string;
3003
+ partialParts: ChatRecoveryExhaustedContext["partialParts"];
3004
+ streamId: string;
3005
+ createdAt: number;
3006
+ },
3007
+ hooks: {
3008
+ emit: (ctx: ChatRecoveryExhaustedContext) => void;
3009
+ onExhausted?: (ctx: ChatRecoveryExhaustedContext) => void | Promise<void>;
3010
+ onError: (error: unknown) => void;
3011
+ terminalize: (ctx: ChatRecoveryExhaustedContext) => void | Promise<void>;
3012
+ }
3013
+ ): Promise<void>;
3014
+ //#endregion
3015
+ //#region src/chat/recovery-codec.d.ts
3016
+ /**
3017
+ * Reconstructs the partial assistant state of an interrupted turn from its
3018
+ * stored `ResumableStream` chunk bodies (oldest-first).
3019
+ */
3020
+ interface ChatRecoveryCodec {
3021
+ /**
3022
+ * Replay the stored chunk bodies into the engine's `RecoveryPartial`. The
3023
+ * codec — not the engine — both reconstructs `parts` (in its own vocabulary,
3024
+ * opaque to the engine) AND decides `hasSettledToolResults`, so the engine
3025
+ * never names a part type.
3026
+ */
3027
+ toRecoveryPartial(bodies: string[]): RecoveryPartial;
3028
+ /**
3029
+ * Whether a stored chunk of this wire `type` is a **progress milestone** — a
3030
+ * started text/reasoning segment or a settled tool input/output — that should
3031
+ * always credit the host's recovery no-progress window (#1637). The chunk-type
3032
+ * list lives HERE (the codec owns the chunk vocabulary). A `undefined` type (a
3033
+ * non-JSON / typeless body) is never progress.
3034
+ */
3035
+ isProgressChunk(type: string | undefined): boolean;
3036
+ /**
3037
+ * Whether a stored chunk of this wire `type` is **mid-segment streaming
3038
+ * content** — a delta extending an already-started segment (text/reasoning
3039
+ * body, partial tool input). On its own a delta is too granular to credit per
3040
+ * token, but a long single segment that produces only deltas (no new
3041
+ * milestone) must still register forward progress across repeated crashes, or
3042
+ * its no-progress window can false-fire while content is genuinely streaming.
3043
+ * Hosts credit these through a time throttle (see {@link
3044
+ * shouldCreditStreamProgress}). Disjoint from {@link isProgressChunk}; a
3045
+ * `undefined` type is never streaming content.
3046
+ */
3047
+ isStreamingContentChunk(type: string | undefined): boolean;
3048
+ }
3049
+ /** Minimal per-isolate throttle gate (see `StreamProgressCreditThrottle`). */
3050
+ interface ProgressCreditThrottle {
3051
+ shouldCredit(now: number): boolean;
3052
+ }
3053
+ /**
3054
+ * The single, host-agnostic rule for crediting recovery forward progress from a
3055
+ * stored stream chunk — the convergence of what `AIChatAgent` and `Think`
3056
+ * previously each decided on their own (ai-chat keyed on chunk type only; Think
3057
+ * keyed on its flush cadence). Both hosts now call this at chunk-store time so
3058
+ * the bump TIMING is identical:
3059
+ *
3060
+ * - a **milestone** ({@link ChatRecoveryCodec.isProgressChunk}) always credits;
3061
+ * - **streaming content** ({@link ChatRecoveryCodec.isStreamingContentChunk})
3062
+ * credits at most once per throttle window, so a long single segment still
3063
+ * registers progress across crashes without writing storage per token;
3064
+ * - anything else never credits.
3065
+ *
3066
+ * Finer than either host's prior cadence in the worst case and never coarser, so
3067
+ * it can only delay/avoid a false `no_progress_timeout`, never hasten give-up.
3068
+ */
3069
+ declare function shouldCreditStreamProgress(input: {
3070
+ codec: Pick<ChatRecoveryCodec, "isProgressChunk" | "isStreamingContentChunk">;
3071
+ type: string | undefined;
3072
+ throttle: ProgressCreditThrottle;
3073
+ now: number;
3074
+ }): boolean;
3075
+ /**
3076
+ * The AI SDK codec: replays SSE chunk bodies through {@link getPartialStreamText}
3077
+ * (`applyChunkToParts` under the hood). Stateless — share the
3078
+ * {@link aiSdkRecoveryCodec} singleton rather than constructing per call.
3079
+ */
3080
+ declare class AISDKRecoveryCodec implements ChatRecoveryCodec {
3081
+ toRecoveryPartial(bodies: string[]): {
3082
+ text: string;
3083
+ parts: MessagePart[];
3084
+ hasSettledToolResults: boolean;
3085
+ };
3086
+ isProgressChunk(type: string | undefined): boolean;
3087
+ isStreamingContentChunk(type: string | undefined): boolean;
3088
+ }
3089
+ /** Shared stateless {@link AISDKRecoveryCodec} instance. */
3090
+ declare const aiSdkRecoveryCodec: AISDKRecoveryCodec;
3091
+ //#endregion
3092
+ //#region src/chat/resume-handshake.d.ts
3093
+ /** A pending terminal outcome captured before connect (#1645). */
3094
+ interface PendingChatTerminal {
3095
+ requestId: string;
3096
+ body: string;
3097
+ }
3098
+ /**
3099
+ * The host-owned surface the resume handshake threads. `pendingResumeConnections`
3100
+ * (and the continuation `awaitingConnections` map) stay host-owned — they are
3101
+ * also touched by the streaming loop, which is NOT part of this extraction — so
3102
+ * the driver only reads/mutates them through this seam rather than owning them.
3103
+ */
3104
+ interface ResumeHandshakeHost {
3105
+ /** The host's use-chat-response message-type constant (wire string). */
3106
+ readonly responseMessageType: string;
3107
+ readonly resumableStream: ResumableStream;
3108
+ readonly continuation: ContinuationState<Connection>;
3109
+ /**
3110
+ * Accepted-but-not-yet-streamed turns (#1784). Optional: experimental
3111
+ * recovery adapters that don't track a pre-stream window omit it and keep the
3112
+ * legacy `resume_none` behavior. When present, the handshake parks a
3113
+ * reconnecting client here (sending a keep-waiting `STREAM_PENDING`) instead
3114
+ * of telling it there is nothing to resume.
3115
+ */
3116
+ readonly preStream?: PreStreamTurns<Connection>;
3117
+ /**
3118
+ * Connections notified of a resumable stream, excluded from live broadcast
3119
+ * until they ACK. Host-owned (shared with the streaming loop).
3120
+ */
3121
+ readonly pendingResumeConnections: Set<string>;
3122
+ /** Read the pending terminal outcome (#1645), or `null` when none survives. */
3123
+ pendingChatTerminal(): Promise<PendingChatTerminal | null>;
3124
+ /** Materialize an orphaned stream's partial into a persisted assistant message. */
3125
+ persistOrphanedStream(streamId: string): Promise<void>;
3126
+ /**
3127
+ * Whether the connection that owns the active continuation stream is still
3128
+ * connected. Optional: when omitted the handshake assumes it is (legacy
3129
+ * behavior). Hosts wire their live connection registry so a continuation
3130
+ * stream whose owner vanished on an abrupt (1006) reconnect can still be
3131
+ * resumed by the replacement connection (#1784).
3132
+ */
3133
+ isConnectionPresent?(connectionId: string): boolean;
3134
+ }
3135
+ /**
3136
+ * Drives the server side of the stream-resume protocol over a
3137
+ * {@link ResumeHandshakeHost}. Construct once per agent (the host wires its
3138
+ * `ResumableStream` / `ContinuationState` / pending set in) and call the three
3139
+ * public methods from the host's existing onConnect / onMessage wiring, so
3140
+ * handler registration timing stays host-owned.
3141
+ */
3142
+ declare class ResumeHandshake {
3143
+ private readonly host;
3144
+ constructor(host: ResumeHandshakeHost);
3145
+ /**
3146
+ * Notify a connection that an active stream can be resumed; it should reply
3147
+ * with `STREAM_RESUME_ACK` to receive the replay.
3148
+ *
3149
+ * A connection can legitimately be notified more than once for the same
3150
+ * request — proactively from onConnect AND in response to its explicit
3151
+ * `STREAM_RESUME_REQUEST` (#1733). This is intentional and must NOT be deduped
3152
+ * here: an explicit request always deserves a response (else the client's
3153
+ * `reconnectToStream` hangs to its timeout with no replay), and the proactive
3154
+ * notify is required for clients that never send a request. The notify is one
3155
+ * tiny frame; the client dedupes its ACK so the buffer is not replayed twice.
3156
+ */
3157
+ notifyStreamResuming(connection: Connection): void;
3158
+ /**
3159
+ * Handle a client `STREAM_RESUME_REQUEST`. The client sends this after its
3160
+ * message handler is registered, avoiding the race where a proactive
3161
+ * `STREAM_RESUMING` from onConnect arrives before the handler is ready.
3162
+ */
3163
+ handleResumeRequest(connection: Connection): Promise<void>;
3164
+ /** Send a keep-waiting `STREAM_PENDING` frame (#1784). */
3165
+ private _sendStreamPending;
3166
+ /** Whether the active continuation's owner connection is still present. */
3167
+ private _ownerStillPresent;
3168
+ /** Handle a client `STREAM_RESUME_ACK` for `requestId`. */
3169
+ handleResumeAck(connection: Connection, requestId: string): Promise<void>;
3170
+ /**
3171
+ * Replay a pending terminal outcome (#1645) over the resume handshake so a
3172
+ * reconnecting client surfaces it exactly like a live exhaustion. The bare
3173
+ * terminal frame is dropped by the client unless it arrives on a resumed
3174
+ * stream — the only path that reaches the transport's stream reader and
3175
+ * becomes `useChat.error` — so we drive `STREAM_RESUMING` here and deliver the
3176
+ * error frame once the client ACKs (see {@link _replayTerminalOnAck}). Returns
3177
+ * `true` if a terminal was pending (and `STREAM_RESUMING` was sent).
3178
+ */
3179
+ private _replayTerminalOnResume;
3180
+ /**
3181
+ * Deliver the pending terminal error frame on the resumed stream the client
3182
+ * ACKed (#1645). The record is retained (not cleared) so concurrent reconnects
3183
+ * (e.g. multiple tabs) each learn the outcome; it is cleared when a later turn
3184
+ * supersedes it.
3185
+ */
3186
+ private _replayTerminalOnAck;
3187
+ }
3188
+ //#endregion
3189
+ //#region src/chat/stall-watchdog.d.ts
3190
+ /**
3191
+ * Shared inactivity watchdog for UI-message streams.
3192
+ *
3193
+ * A model/transport stream can park indefinitely without ever throwing (a hung
3194
+ * provider, a wedged transport). Left unguarded, the consumer read-loop waits
3195
+ * forever. {@link iterateWithStallWatchdog} wraps such a stream so that a gap of
3196
+ * `timeoutMs` between chunks aborts the upstream and throws
3197
+ * {@link ChatStreamStalledError}, letting the consumer route the stall into
3198
+ * bounded recovery (#1626) — a transient hang is retried within the existing
3199
+ * recovery budget — while genuine in-band errors stay terminal.
3200
+ *
3201
+ * @internal Sibling-package support for `@cloudflare/ai-chat` and
3202
+ * `@cloudflare/think`, not a public API. See
3203
+ * `design/rfc-chat-recovery-foundation.md`.
3204
+ */
3205
+ /**
3206
+ * Thrown by {@link iterateWithStallWatchdog} when the inactivity watchdog fires
3207
+ * (a model/transport stream that parks without ever throwing). Distinct from
3208
+ * in-band model/stream errors so the read-loop catch can route a stall into
3209
+ * bounded recovery (#1626) — a transient hang is retried within the existing
3210
+ * recovery budget — while genuine errors stay terminal.
3211
+ */
3212
+ declare class ChatStreamStalledError extends Error {
3213
+ readonly isChatStreamStall = true;
3214
+ constructor(message: string);
3215
+ }
3216
+ /**
3217
+ * Wrap a UI-message stream with an inactivity watchdog. If no chunk arrives
3218
+ * within `timeoutMs`, `onStall` runs (aborting the upstream model stream) and
3219
+ * the iterator throws, so the consumer loop exits with a terminal error
3220
+ * instead of parking forever on a hung provider/transport. `timeoutMs <= 0`
3221
+ * passes the source through untouched.
3222
+ */
3223
+ declare function iterateWithStallWatchdog<T>(
3224
+ source: AsyncIterable<T>,
3225
+ timeoutMs: number,
3226
+ onStall: () => void
3227
+ ): AsyncGenerator<T>;
3228
+ //#endregion
1366
3229
  export {
3230
+ AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS,
1367
3231
  AbortRegistry,
3232
+ type AgentToolBroadcastHooks,
1368
3233
  type AgentToolEvent,
1369
3234
  type AgentToolEventMessage,
1370
3235
  type AgentToolEventState,
3236
+ type AgentToolProgressEmitHooks,
3237
+ type AgentToolProgressEmitResult,
3238
+ AgentToolProgressEmitter,
1371
3239
  type AgentToolRunState,
3240
+ AgentToolStreamProgressThrottle,
3241
+ AutoContinuationController,
3242
+ type AutoContinuationHost,
3243
+ type BeginChatRecoveryIncidentInput,
3244
+ type BeginChatRecoveryIncidentResult,
1372
3245
  type BroadcastStreamEvent,
1373
3246
  type BroadcastStreamState,
1374
3247
  type TransitionResult as BroadcastTransitionResult,
3248
+ CHAT_LAST_TERMINAL_KEY,
1375
3249
  CHAT_MESSAGE_TYPES,
3250
+ CHAT_RECOVERING_FLAG_TTL_MS,
3251
+ CHAT_RECOVERING_KEY,
3252
+ CHAT_RECOVERY_ALARM_DEBOUNCE_MS,
3253
+ CHAT_RECOVERY_INCIDENT_KEY_PREFIX,
3254
+ CHAT_RECOVERY_INCIDENT_TTL_MS,
3255
+ CHAT_RECOVERY_PROGRESS_KEY,
3256
+ CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS,
3257
+ CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS,
3258
+ type ChatConnection,
1376
3259
  type ChatFiberSnapshot,
3260
+ type ChatFiberWakeHooks,
1377
3261
  type ChatProtocolEvent,
3262
+ type ChatRecoveryAdapter,
3263
+ type ChatRecoveryCodec,
1378
3264
  type ChatRecoveryConfig,
1379
3265
  type ChatRecoveryContext,
3266
+ ChatRecoveryEngine,
1380
3267
  type ChatRecoveryExhaustedContext,
3268
+ type ChatRecoveryIncident,
3269
+ type ChatRecoveryIncidentEvent,
3270
+ type ChatRecoveryKind,
1381
3271
  type ChatRecoveryOptions,
1382
3272
  type ChatRecoveryProgressContext,
3273
+ type ChatRecoveryScheduleCallback,
3274
+ type ChatRecoveryScheduleReason,
1383
3275
  type ChatResponseResult,
3276
+ ChatStreamStalledError,
3277
+ type ChatStreamStatus,
3278
+ type ChatTerminalRecord,
1384
3279
  type ChunkAction,
1385
3280
  type ChunkResult,
3281
+ type ClassifyRecoveredTurnInput,
1386
3282
  type ClientToolExecutor,
1387
3283
  type ClientToolSchema,
1388
3284
  type ContinuationConnection,
1389
3285
  type ContinuationDeferred,
1390
3286
  type ContinuationPending,
3287
+ type ContinuationSpec,
1391
3288
  ContinuationState,
3289
+ DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS,
3290
+ DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES,
3291
+ DEFAULT_CHAT_RECOVERY_MAX_WORK,
3292
+ DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS,
3293
+ DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS,
3294
+ DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE,
3295
+ type DispatchRecoveredTurnInput,
3296
+ type EnforceRowSizeLimitOptions,
1392
3297
  type EnqueueOptions,
3298
+ type EvaluateChatRecoveryIncidentInput,
3299
+ type EvaluateChatRecoveryIncidentResult,
3300
+ type IncomingMessage,
3301
+ type InvokeOnChatRecoveryInput,
3302
+ KV_DELETE_MAX_KEYS,
1393
3303
  MAX_BOUND_PARAMS,
1394
3304
  type MessageConcurrency,
1395
3305
  type MessagePart,
1396
3306
  type MessageParts,
3307
+ MessageType,
1397
3308
  type NormalizedMessageConcurrency,
3309
+ type OrphanPersistStore,
3310
+ type OutgoingMessage,
3311
+ type PendingChatTerminal,
3312
+ type PersistOrphanedPartialInput,
3313
+ type PersistReconstructedOrphanOptions,
3314
+ PreStreamTurns,
3315
+ type ProgressCreditThrottle,
1398
3316
  ROW_MAX_BYTES,
3317
+ type RecoveryPartial,
3318
+ type RepairInterruptedToolPartsOptions,
3319
+ type RepairInterruptedToolPartsResult,
3320
+ type ReplyAttachment,
1399
3321
  type ResolvedChatRecoveryConfig,
3322
+ type ResolvedRecoveryStream,
1400
3323
  ResumableStream,
3324
+ ResumeHandshake,
3325
+ type ResumeHandshakeHost,
3326
+ STREAM_CLEANUP_DELAY_SECONDS,
1401
3327
  type SaveMessagesOptions,
1402
3328
  type SaveMessagesResult,
3329
+ type SnapshotMessage,
1403
3330
  type SqlTaggedTemplate,
1404
3331
  StreamAccumulator,
1405
3332
  type StreamAccumulatorOptions,
1406
3333
  type StreamChunkData,
3334
+ StreamProgressCreditThrottle,
1407
3335
  SubmitConcurrencyController,
1408
3336
  type SubmitConcurrencyDecision,
3337
+ TIMED_OUT,
1409
3338
  type ToolPartUpdate,
1410
3339
  TurnQueue,
1411
3340
  type TurnResult,
3341
+ aiSdkRecoveryCodec,
1412
3342
  applyAgentToolEvent,
1413
3343
  applyChunkToParts,
1414
3344
  applyToolUpdate,
1415
- assistantContentKey,
3345
+ awaitWithDeadline,
1416
3346
  transition as broadcastTransition,
3347
+ buildChatRecoveringFrame,
1417
3348
  buildInClauseStrings,
3349
+ bumpChatRecoveryProgress,
1418
3350
  byteLength,
3351
+ chatRecoverySchedulePolicy,
3352
+ classifyAgentToolChildRecovery,
3353
+ cleanupStreamBuffers,
3354
+ clearChatTerminal,
3355
+ clientResolvableToolNames,
1419
3356
  createAgentToolEventState,
1420
3357
  createChatFiberSnapshot,
1421
3358
  createToolsFromClientSchemas,
1422
3359
  crossMessageToolResultUpdate,
3360
+ drainInteractionApplies,
1423
3361
  enforceRowSizeLimit,
3362
+ hasIncompleteToolBatch,
3363
+ interceptAgentToolBroadcast,
1424
3364
  isReplayChunk,
3365
+ iterateWithStallWatchdog,
3366
+ listActiveChatRecoveryIncidents,
1425
3367
  normalizeToolInput,
1426
3368
  parseProtocolMessage,
3369
+ partAwaitsClientInteraction,
1427
3370
  pausedExecutionUpdate,
3371
+ pendingChatTerminal,
3372
+ persistReconstructedOrphan,
3373
+ readChatRecoveryProgress,
1428
3374
  reconcileMessages,
3375
+ reconcileOrphanPartial,
3376
+ recordChatTerminal,
3377
+ repairInterruptedToolParts,
3378
+ resolveChatRecoveryConfig,
1429
3379
  resolveToolMergeId,
3380
+ runChatRecoveryExhaustion,
1430
3381
  sanitizeMessage,
3382
+ sendIfOpen,
3383
+ setChatRecovering,
3384
+ shouldCreditStreamProgress,
3385
+ sweepStaleChatRecoveryIncidents,
1431
3386
  toolApprovalUpdate,
3387
+ toolPartHasSettledResult,
1432
3388
  toolResultUpdate,
1433
3389
  unwrapChatFiberSnapshot,
1434
3390
  wrapChatFiberSnapshot