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