agents 0.17.0 → 0.17.3
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/dist/{agent-tool-types-Cd1TZPfB.d.ts → agent-tool-types-CNyE1iz_.d.ts} +86 -3
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-_E8wxUIK.d.ts → agent-tools-BeOheFBK.d.ts} +17 -3
- package/dist/{agent-tools-BXlsuX0d.js → agent-tools-y7zLfw4Q.js} +2 -2
- package/dist/agent-tools-y7zLfw4Q.js.map +1 -0
- package/dist/agent-tools.d.ts +1 -1
- package/dist/chat/index.d.ts +123 -13
- package/dist/chat/index.js +109 -11
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.js +13 -2
- package/dist/chat/react.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/{index-CcbnKkNh.d.ts → index-BRnybD6X.d.ts} +10 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +135 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{retries-CwlpAGet.d.ts → retries-CvHJwSuh.d.ts} +15 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +44 -1
- package/dist/retries.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sub-routing.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/docs/chat-agents.md +22 -13
- package/package.json +4 -4
- package/dist/agent-tools-BXlsuX0d.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { n as AgentEmail } from "./internal_context-Dg4Cgjcu.js";
|
|
2
|
-
import { t as RetryOptions } from "./retries-
|
|
2
|
+
import { t as RetryOptions } from "./retries-CvHJwSuh.js";
|
|
3
3
|
import {
|
|
4
4
|
n as Observability,
|
|
5
5
|
r as ObservabilityEvent,
|
|
6
6
|
s as MCPObservabilityEvent
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-BRnybD6X.js";
|
|
8
8
|
import { t as AgentMcpOAuthProvider } from "./do-oauth-client-provider-D4ZwyBDu.js";
|
|
9
9
|
import {
|
|
10
10
|
_ as WorkflowEventPayload,
|
|
@@ -2349,6 +2349,15 @@ declare const DEFAULT_AGENT_STATIC_OPTIONS: {
|
|
|
2349
2349
|
agentToolReattachMaxWindowMs: number;
|
|
2350
2350
|
detachedMaxBudgetMs: number;
|
|
2351
2351
|
detachedNoProgressBudgetMs: number;
|
|
2352
|
+
/**
|
|
2353
|
+
* Consecutive alarm invocations that may end in a Durable Object memory-limit
|
|
2354
|
+
* reset (the isolate exceeded its 128 MB limit) before the alarm-boundary
|
|
2355
|
+
* circuit breaker stops the platform's auto-retry loop and seals the looping
|
|
2356
|
+
* work (#1825). A small budget tolerates a genuinely transient memory spike;
|
|
2357
|
+
* a deterministic OOM (the work's footprint, not the platform, is the cause)
|
|
2358
|
+
* is bounded here regardless of whether the in-DO recovery budgets could run.
|
|
2359
|
+
*/
|
|
2360
|
+
maxAlarmMemoryLimitStrikes: number;
|
|
2352
2361
|
};
|
|
2353
2362
|
/**
|
|
2354
2363
|
* Configuration options for the Agent.
|
|
@@ -2436,6 +2445,20 @@ interface AgentStaticOptions {
|
|
|
2436
2445
|
* `detached: { noProgressBudgetMs }`.
|
|
2437
2446
|
*/
|
|
2438
2447
|
detachedNoProgressBudgetMs?: number;
|
|
2448
|
+
/**
|
|
2449
|
+
* Consecutive alarm invocations that may end in a Durable Object memory-limit
|
|
2450
|
+
* reset (the isolate exceeded its 128 MB limit) before the alarm-boundary
|
|
2451
|
+
* circuit breaker stops the platform's auto-retry loop and seals the looping
|
|
2452
|
+
* recovery work (#1825). Default: 3. Set to `0` to seal on the first such
|
|
2453
|
+
* reset. This is the universal backstop for the case where the in-DO recovery
|
|
2454
|
+
* budgets (`chatRecovery.maxOomRetries` / `maxRecoveryWork`) can't engage
|
|
2455
|
+
* because the OOM bypasses them — e.g. it is thrown before the budget code
|
|
2456
|
+
* runs, or its own writes also OOM. The boundary handler runs at the outermost
|
|
2457
|
+
* alarm frame, after the heavy turn has unwound and GC has reclaimed its
|
|
2458
|
+
* footprint, so its small seal/purge writes can land where mid-turn writes
|
|
2459
|
+
* could not.
|
|
2460
|
+
*/
|
|
2461
|
+
maxAlarmMemoryLimitStrikes?: number;
|
|
2439
2462
|
}
|
|
2440
2463
|
declare function getCurrentAgent<
|
|
2441
2464
|
T extends Agent<Cloudflare.Env> = Agent<Cloudflare.Env>
|
|
@@ -3420,6 +3443,66 @@ declare class Agent<
|
|
|
3420
3443
|
* See {@link https://developers.cloudflare.com/agents/api-reference/schedule-tasks/}
|
|
3421
3444
|
*/
|
|
3422
3445
|
alarm(): Promise<void>;
|
|
3446
|
+
/**
|
|
3447
|
+
* The alarm body: PartyServer init + due-schedule processing + housekeeping +
|
|
3448
|
+
* next-alarm arm. Extracted from {@link alarm} so the memory-limit circuit
|
|
3449
|
+
* breaker can wrap it at the outermost frame (see {@link alarm}).
|
|
3450
|
+
*/
|
|
3451
|
+
private _cf_runAlarmBody;
|
|
3452
|
+
/**
|
|
3453
|
+
* Durable storage key for the alarm memory-limit strike counter (#1825).
|
|
3454
|
+
*/
|
|
3455
|
+
private static readonly _CF_OOM_ALARM_STRIKES_KEY;
|
|
3456
|
+
/**
|
|
3457
|
+
* The schedule row id currently executing in the alarm loop, so the
|
|
3458
|
+
* memory-limit circuit breaker can purge the exact looping row (#1825).
|
|
3459
|
+
* `undefined` outside a callback (e.g. an OOM from `super.alarm()`/onStart).
|
|
3460
|
+
*/
|
|
3461
|
+
private _cf_executingScheduleRowId?;
|
|
3462
|
+
/**
|
|
3463
|
+
* The schedule-callback names whose alarm rows drive a recovery loop that can
|
|
3464
|
+
* deterministically OOM. The base agent has none; chat hosts (`Think`,
|
|
3465
|
+
* `AIChatAgent`) override this to return their recovery continuation callbacks
|
|
3466
|
+
* so the circuit breaker can surgically back them off / purge them WITHOUT
|
|
3467
|
+
* disturbing unrelated scheduled tasks. See {@link _cf_handleAlarmMemoryLimitReset}.
|
|
3468
|
+
*/
|
|
3469
|
+
protected _cf_recoveryAlarmCallbacks(): string[];
|
|
3470
|
+
/**
|
|
3471
|
+
* Hook for a host to terminalize ("seal") any in-flight recovery work as an
|
|
3472
|
+
* out-of-memory exhaustion when the alarm circuit breaker trips at its strike
|
|
3473
|
+
* budget (#1825). Runs at the outermost alarm frame (post-unwind, so writes
|
|
3474
|
+
* can land). Default: no-op. Chat hosts override to fire `onExhausted` + the
|
|
3475
|
+
* terminal banner and persist the sealed incident.
|
|
3476
|
+
*/
|
|
3477
|
+
protected _cf_sealMemoryLimitedRecovery(): Promise<void>;
|
|
3478
|
+
/**
|
|
3479
|
+
* Clear the durable memory-limit strike counter after a clean alarm so the
|
|
3480
|
+
* circuit breaker counts CONSECUTIVE resets rather than lifetime ones
|
|
3481
|
+
* (#1825). Reads first (cheap, usually cached) and only writes when a strike
|
|
3482
|
+
* is actually recorded, so the common no-strike path costs no write.
|
|
3483
|
+
* Best-effort: a stale strike only costs one extra tolerated spike later.
|
|
3484
|
+
*/
|
|
3485
|
+
private _cf_clearAlarmMemoryLimitStrikes;
|
|
3486
|
+
/**
|
|
3487
|
+
* Alarm-boundary circuit breaker for Durable Object memory-limit resets
|
|
3488
|
+
* (#1825). The in-DO recovery budgets (`chatRecovery.maxOomRetries` /
|
|
3489
|
+
* `maxRecoveryWork`) only engage if their code runs AND its writes land; a
|
|
3490
|
+
* severe OOM can defeat both — thrown before the budget runs (boot hydration),
|
|
3491
|
+
* or its own small writes also OOM under memory pressure. In that case the
|
|
3492
|
+
* error reaches {@link alarm} and, unhandled, the platform auto-retries the
|
|
3493
|
+
* alarm indefinitely (re-running the doomed, billable turn each cycle).
|
|
3494
|
+
*
|
|
3495
|
+
* This runs at the OUTERMOST frame: the heavy turn has unwound and GC has
|
|
3496
|
+
* reclaimed its footprint, so the small writes here can land where mid-turn
|
|
3497
|
+
* ones (e.g. give-up's incident read) OOMed. A durable strike counter tolerates
|
|
3498
|
+
* a few resets (a transient spike may clear), backing off the recovery rows so
|
|
3499
|
+
* the retry is not a hot loop. At the `maxAlarmMemoryLimitStrikes` budget it
|
|
3500
|
+
* seals the recovery work and purges the looping rows so the loop — and the
|
|
3501
|
+
* bill — stops. Each step is best-effort: even these tiny writes can OOM, but
|
|
3502
|
+
* swallowing (not re-throwing) still halts the platform's auto-retry, and a
|
|
3503
|
+
* later wake re-arms legitimate schedules.
|
|
3504
|
+
*/
|
|
3505
|
+
private _cf_handleAlarmMemoryLimitReset;
|
|
3423
3506
|
/**
|
|
3424
3507
|
* Intercept incoming HTTP/WS requests whose URL contains a
|
|
3425
3508
|
* `/sub/{child-class}/{child-name}` marker and forward them to
|
|
@@ -5305,4 +5388,4 @@ export {
|
|
|
5305
5388
|
EmailRoutingOptions as z,
|
|
5306
5389
|
ElicitResult$1 as zt
|
|
5307
5390
|
};
|
|
5308
|
-
//# sourceMappingURL=agent-tool-types-
|
|
5391
|
+
//# sourceMappingURL=agent-tool-types-CNyE1iz_.d.ts.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
o as AgentToolEventMessage,
|
|
5
5
|
s as AgentToolEventState,
|
|
6
6
|
y as AgentToolStoredChunk
|
|
7
|
-
} from "./agent-tool-types-
|
|
7
|
+
} from "./agent-tool-types-CNyE1iz_.js";
|
|
8
8
|
|
|
9
9
|
//#region src/chat/agent-tools.d.ts
|
|
10
10
|
type AgentToolProgressEmitResult = "emitted" | "coalesced" | "inactive";
|
|
@@ -80,7 +80,21 @@ declare function applyAgentToolEvent<
|
|
|
80
80
|
interface AgentToolBroadcastHooks {
|
|
81
81
|
/** Live tailers per run; iterated to forward each progress chunk. */
|
|
82
82
|
forwarders: Map<string, Set<(chunk: AgentToolStoredChunk) => void>>;
|
|
83
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Per-run forwarded-chunk counter; advanced even with no tailer attached.
|
|
85
|
+
*
|
|
86
|
+
* This is deliberately a SEPARATE counter from the resumable stream's stored
|
|
87
|
+
* chunk_index — do NOT try to "simplify" it away by sequencing off the store
|
|
88
|
+
* position. Not every forwarded frame is durably stored: progress/milestone
|
|
89
|
+
* frames (`reportProgress`) ride the same `USE_CHAT_RESPONSE` wire type and
|
|
90
|
+
* are snooped + forwarded here, but persist out-of-band (progress snapshot /
|
|
91
|
+
* milestone rows), so they have no store position. Sourcing the sequence from
|
|
92
|
+
* the store would give them a colliding position and the tail's high-water
|
|
93
|
+
* dedupe (`emit`) would silently drop them, breaking live progress/milestone
|
|
94
|
+
* delivery to the parent. This counter sequences stored AND non-stored frames
|
|
95
|
+
* on one monotonic line; the tail realigns it to the stored high-water on each
|
|
96
|
+
* (re)attach so a replay→live handoff stays gap/duplicate-free.
|
|
97
|
+
*/
|
|
84
98
|
liveSequences: Map<string, number>;
|
|
85
99
|
/** Per-run last error body, captured for replay to a late-attaching tailer. */
|
|
86
100
|
lastErrors: Map<string, string>;
|
|
@@ -116,4 +130,4 @@ export {
|
|
|
116
130
|
interceptAgentToolBroadcast as s,
|
|
117
131
|
AgentToolBroadcastHooks as t
|
|
118
132
|
};
|
|
119
|
-
//# sourceMappingURL=agent-tools-
|
|
133
|
+
//# sourceMappingURL=agent-tools-BeOheFBK.d.ts.map
|
|
@@ -166,7 +166,7 @@ function applyToRun(prev, message) {
|
|
|
166
166
|
};
|
|
167
167
|
case "chunk": {
|
|
168
168
|
if (!seeded) return void 0;
|
|
169
|
-
const parts =
|
|
169
|
+
const parts = seeded.parts.map((part) => ({ ...part }));
|
|
170
170
|
let parsed;
|
|
171
171
|
try {
|
|
172
172
|
parsed = JSON.parse(event.body);
|
|
@@ -301,4 +301,4 @@ function interceptAgentToolBroadcast(msg, hooks) {
|
|
|
301
301
|
//#endregion
|
|
302
302
|
export { interceptAgentToolBroadcast as i, applyAgentToolEvent as n, createAgentToolEventState as r, AgentToolProgressEmitter as t };
|
|
303
303
|
|
|
304
|
-
//# sourceMappingURL=agent-tools-
|
|
304
|
+
//# sourceMappingURL=agent-tools-y7zLfw4Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools-y7zLfw4Q.js","names":[],"sources":["../src/chat/agent-tools.ts"],"sourcesContent":["import { applyChunkToParts, type MessagePart } from \"./message-builder\";\nimport {\n AGENT_TOOL_MILESTONE_PART,\n AGENT_TOOL_PROGRESS_PART\n} from \"../agent-tool-types\";\nimport type {\n AgentToolEventMessage,\n AgentToolEventState,\n AgentToolMilestone,\n AgentToolProgress,\n AgentToolProgressSnapshot,\n AgentToolRunPart,\n AgentToolRunState,\n AgentToolStoredChunk\n} from \"../agent-tool-types\";\n\n/**\n * Pull a reserved `data-agent-progress` chunk (emitted by a running sub-agent's\n * `reportProgress`) into a latest-wins snapshot. Returns `undefined` for any\n * other chunk so the caller keeps the prior snapshot.\n */\nfunction readAgentToolProgressChunk(\n chunk: unknown\n): AgentToolProgressSnapshot | undefined {\n if (\n typeof chunk !== \"object\" ||\n chunk === null ||\n (chunk as { type?: unknown }).type !== AGENT_TOOL_PROGRESS_PART\n ) {\n return undefined;\n }\n const data = (chunk as { data?: AgentToolProgress }).data ?? {};\n return {\n ...(typeof data.fraction === \"number\" ? { fraction: data.fraction } : {}),\n ...(typeof data.message === \"string\" ? { message: data.message } : {}),\n ...(typeof data.phase === \"string\" ? { phase: data.phase } : {}),\n ...(data.data !== undefined ? { data: data.data } : {}),\n at: Date.now()\n };\n}\n\n/**\n * Pull a reserved `data-agent-milestone` chunk into a durable milestone record,\n * or `undefined` for any other chunk. Milestones carry their own monotonic\n * `sequence` so the caller can dedupe replay-vs-live races.\n */\nfunction readAgentToolMilestoneChunk(\n chunk: unknown\n): AgentToolMilestone | undefined {\n if (\n typeof chunk !== \"object\" ||\n chunk === null ||\n (chunk as { type?: unknown }).type !== AGENT_TOOL_MILESTONE_PART\n ) {\n return undefined;\n }\n const data = (chunk as { data?: Partial<AgentToolMilestone> }).data ?? {};\n if (typeof data.name !== \"string\") return undefined;\n return {\n name: data.name,\n sequence: typeof data.sequence === \"number\" ? data.sequence : 0,\n at: typeof data.at === \"number\" ? data.at : Date.now(),\n ...(data.data !== undefined ? { data: data.data } : {})\n };\n}\n\n/**\n * Merge a milestone into a run's ordered milestone list, deduping on `sequence`\n * (idempotent across replay + live races) and keeping the list sorted.\n */\nfunction mergeMilestone(\n existing: AgentToolMilestone[] | undefined,\n milestone: AgentToolMilestone\n): AgentToolMilestone[] {\n // Returns `existing` unchanged (same reference) when the milestone is a dup,\n // so callers can identity-compare to detect a genuinely new milestone.\n if (existing?.some((m) => m.sequence === milestone.sequence)) return existing;\n const list = existing ? [...existing, milestone] : [milestone];\n list.sort((a, b) => a.sequence - b.sequence);\n return list;\n}\n\n/** Latest-wins coalescing window for `reportProgress` emits (per run). */\nconst AGENT_TOOL_PROGRESS_COALESCE_MS = 200;\n\nexport type AgentToolProgressEmitResult = \"emitted\" | \"coalesced\" | \"inactive\";\n\n/**\n * Host-injected seams the shared progress emitter needs. Keeps the per-host\n * `reportProgress` thin: Think / AIChatAgent supply how to resolve the active\n * agent-tool run, how to broadcast a chat-response frame, and how to persist the\n * latest snapshot on their own child-run table.\n */\nexport type AgentToolProgressEmitHooks = {\n /** The agent-tool run currently executing in this turn, or null. */\n resolveActiveRun: () => { runId: string; requestId: string } | null;\n /** Broadcast a chat-response frame (id = requestId) to clients/tailers. */\n broadcast: (requestId: string, chunkBody: string) => void;\n /** Persist the latest snapshot + signal timestamp on the child run row. */\n persistSnapshot: (\n runId: string,\n snapshot: {\n fraction?: number;\n message?: string;\n phase?: string;\n data?: unknown;\n },\n at: number\n ) => void;\n /**\n * Persist a durable milestone row, bump the run's signal timestamp, and return\n * the assigned monotonic per-run `sequence` (used to dedupe replay/live races).\n */\n persistMilestone: (\n runId: string,\n name: string,\n data: unknown,\n at: number\n ) => number;\n};\n\n/**\n * Shared implementation of `reportProgress` for chat hosts. Builds the reserved\n * transient `data-agent-progress` wire frame, coalesces bursts to a bounded\n * cadence (latest-wins; a `fraction >= 1` \"done\" frame always flushes), and\n * persists a latest snapshot. `data` rides the live frame but is only persisted\n * when the caller opts in via `{ persist: true }`.\n */\nexport class AgentToolProgressEmitter {\n private readonly _lastEmitAt = new Map<string, number>();\n\n constructor(private readonly hooks: AgentToolProgressEmitHooks) {}\n\n report(\n progress: AgentToolProgress,\n options?: { persist?: boolean }\n ): AgentToolProgressEmitResult {\n const active = this.hooks.resolveActiveRun();\n if (!active) return \"inactive\";\n const { runId, requestId } = active;\n const now = Date.now();\n\n // Durable milestone: never coalesced (each named boundary must land,\n // persist, and replay). Rides the stream as a PERSISTED data part.\n if (typeof progress.milestone === \"string\" && progress.milestone) {\n this._lastEmitAt.set(runId, now);\n const sequence = this.hooks.persistMilestone(\n runId,\n progress.milestone,\n progress.data,\n now\n );\n this.hooks.broadcast(\n requestId,\n JSON.stringify({\n type: AGENT_TOOL_MILESTONE_PART,\n data: {\n name: progress.milestone,\n sequence,\n at: now,\n ...(typeof progress.fraction === \"number\"\n ? { fraction: progress.fraction }\n : {}),\n ...(typeof progress.message === \"string\"\n ? { message: progress.message }\n : {}),\n ...(typeof progress.phase === \"string\"\n ? { phase: progress.phase }\n : {}),\n ...(progress.data !== undefined ? { data: progress.data } : {})\n }\n })\n );\n return \"emitted\";\n }\n\n const last = this._lastEmitAt.get(runId) ?? 0;\n const isDone =\n typeof progress.fraction === \"number\" && progress.fraction >= 1;\n if (now - last < AGENT_TOOL_PROGRESS_COALESCE_MS && !isDone) {\n return \"coalesced\";\n }\n this._lastEmitAt.set(runId, now);\n\n const wire: AgentToolProgress = {\n ...(typeof progress.fraction === \"number\"\n ? { fraction: progress.fraction }\n : {}),\n ...(typeof progress.message === \"string\"\n ? { message: progress.message }\n : {}),\n ...(typeof progress.phase === \"string\" ? { phase: progress.phase } : {}),\n ...(progress.data !== undefined ? { data: progress.data } : {})\n };\n this.hooks.broadcast(\n requestId,\n JSON.stringify({\n type: AGENT_TOOL_PROGRESS_PART,\n transient: true,\n data: wire\n })\n );\n this.hooks.persistSnapshot(\n runId,\n {\n ...(typeof progress.fraction === \"number\"\n ? { fraction: progress.fraction }\n : {}),\n ...(typeof progress.message === \"string\"\n ? { message: progress.message }\n : {}),\n ...(typeof progress.phase === \"string\"\n ? { phase: progress.phase }\n : {}),\n ...(options?.persist && progress.data !== undefined\n ? { data: progress.data }\n : {})\n },\n now\n );\n return \"emitted\";\n }\n\n /** Drop coalescing state for a settled run (called on terminal). */\n forget(runId: string): void {\n this._lastEmitAt.delete(runId);\n }\n}\n\nfunction sortRuns<Part extends AgentToolRunPart>(\n runs: AgentToolRunState<Part>[]\n): AgentToolRunState<Part>[] {\n return [...runs].sort((a, b) => {\n if (a.order !== b.order) return a.order - b.order;\n return a.runId.localeCompare(b.runId);\n });\n}\n\nfunction rebuildIndexes<Part extends AgentToolRunPart>(\n runsById: Record<string, AgentToolRunState<Part>>\n): Pick<AgentToolEventState<Part>, \"runsByToolCallId\" | \"unboundRuns\"> {\n const grouped: Record<string, AgentToolRunState<Part>[]> = {};\n const unboundRuns: AgentToolRunState<Part>[] = [];\n for (const run of Object.values(runsById)) {\n if (run.parentToolCallId) {\n grouped[run.parentToolCallId] = grouped[run.parentToolCallId] ?? [];\n grouped[run.parentToolCallId].push(run);\n } else {\n unboundRuns.push(run);\n }\n }\n for (const [toolCallId, runs] of Object.entries(grouped)) {\n grouped[toolCallId] = sortRuns(runs);\n }\n return { runsByToolCallId: grouped, unboundRuns: sortRuns(unboundRuns) };\n}\n\nfunction emptyRun<Part extends AgentToolRunPart>(\n message: AgentToolEventMessage\n): AgentToolRunState<Part> | undefined {\n const { event } = message;\n if (event.kind === \"started\") {\n return {\n runId: event.runId,\n agentType: event.agentType,\n parentToolCallId: message.parentToolCallId,\n inputPreview: event.inputPreview,\n order: event.order,\n display: event.display,\n status: \"running\",\n parts: [],\n subAgent: { agent: event.agentType, name: event.runId }\n };\n }\n return undefined;\n}\n\nfunction applyToRun<Part extends AgentToolRunPart>(\n prev: AgentToolRunState<Part> | undefined,\n message: AgentToolEventMessage\n): AgentToolRunState<Part> | undefined {\n const seeded = prev ?? emptyRun(message);\n const { event } = message;\n\n switch (event.kind) {\n case \"started\":\n if (\n seeded?.status === \"completed\" ||\n seeded?.status === \"error\" ||\n seeded?.status === \"aborted\" ||\n seeded?.status === \"interrupted\"\n ) {\n return seeded;\n }\n return {\n ...seeded,\n runId: event.runId,\n agentType: event.agentType,\n parentToolCallId: message.parentToolCallId,\n inputPreview: event.inputPreview,\n order: event.order,\n display: event.display,\n status: \"running\",\n parts: seeded?.parts ?? [],\n subAgent: { agent: event.agentType, name: event.runId }\n };\n case \"chunk\": {\n if (!seeded) return undefined;\n // `applyChunkToParts` mutates part objects in place (e.g.\n // `lastTextPart.text += delta`). A shallow array copy (`[...seeded.parts]`)\n // keeps the *element* references shared with the previous state, so those\n // in-place mutations leak back into `prev`. React double-invokes setState\n // updaters in StrictMode / dev hydration, replaying each chunk against the\n // same (already-mutated) `prev` and doubling the text (#1835). Clone each\n // part so the reducer stays pure — every mutation here is to a top-level\n // field, so a per-part shallow copy is sufficient.\n const parts = seeded.parts.map((part) => ({ ...part }) as Part);\n let parsed: unknown;\n try {\n parsed = JSON.parse(event.body);\n applyChunkToParts(\n parts as MessagePart[],\n parsed as Parameters<typeof applyChunkToParts>[1]\n );\n } catch {\n return seeded;\n }\n // Project a reserved `data-agent-progress` part onto the run's latest\n // progress snapshot so a tray can render a bar/phase without drilling in.\n // The part is transient (not persisted into `parts`), so it is read here\n // off the raw chunk rather than from the reduced parts array.\n const progress = readAgentToolProgressChunk(parsed);\n if (progress) {\n return { ...seeded, parts, progress };\n }\n // Durable milestones land as a persisted `data-agent-milestone` part:\n // append (deduped by sequence) to the run's milestone list, and reflect\n // any progress fields the milestone carried onto the latest snapshot.\n const milestone = readAgentToolMilestoneChunk(parsed);\n if (milestone) {\n const milestones = mergeMilestone(seeded.milestones, milestone);\n // Only advance the snapshot for a genuinely new, not-older milestone, so\n // a late replay of an earlier milestone never rolls `progress` backward.\n const isNew = milestones !== seeded.milestones;\n const notOlder =\n seeded.progress === undefined || milestone.at >= seeded.progress.at;\n if (!isNew || !notOlder) {\n return { ...seeded, parts, milestones };\n }\n const data = (parsed as { data?: AgentToolProgress }).data ?? {};\n const snapshot: AgentToolProgressSnapshot = {\n ...(typeof data.fraction === \"number\"\n ? { fraction: data.fraction }\n : {}),\n ...(typeof data.message === \"string\"\n ? { message: data.message }\n : {}),\n ...(typeof data.phase === \"string\" ? { phase: data.phase } : {}),\n milestone: milestone.name,\n at: milestone.at\n };\n return { ...seeded, parts, progress: snapshot, milestones };\n }\n return { ...seeded, parts };\n }\n case \"finished\":\n if (!seeded) return undefined;\n return {\n ...seeded,\n status: \"completed\",\n summary: event.summary,\n error: undefined\n };\n case \"error\":\n if (!seeded) return undefined;\n return { ...seeded, status: \"error\", error: event.error };\n case \"aborted\":\n if (!seeded) return undefined;\n return { ...seeded, status: \"aborted\", error: event.reason };\n case \"interrupted\":\n if (!seeded) return undefined;\n return {\n ...seeded,\n status: \"interrupted\",\n error: event.error,\n reason: event.reason,\n childStillRunning: event.childStillRunning\n };\n }\n}\n\nexport function createAgentToolEventState<\n Part extends AgentToolRunPart = AgentToolRunPart\n>(): AgentToolEventState<Part> {\n return {\n runsById: {},\n runsByToolCallId: {},\n unboundRuns: []\n };\n}\n\nexport function applyAgentToolEvent<\n Part extends AgentToolRunPart = AgentToolRunPart\n>(\n state: AgentToolEventState<Part>,\n message: AgentToolEventMessage\n): AgentToolEventState<Part> {\n if (message.type !== \"agent-tool-event\") return state;\n const runId = message.event.runId;\n const nextRun = applyToRun(state.runsById[runId], message);\n if (!nextRun) return state;\n\n const runsById = { ...state.runsById, [runId]: nextRun };\n return { runsById, ...rebuildIndexes(runsById) };\n}\n\nexport type {\n AgentToolEvent,\n AgentToolEventMessage,\n AgentToolEventState,\n AgentToolRunPart,\n AgentToolRunState\n} from \"../agent-tool-types\";\n\n/**\n * @internal Host substrate the {@link interceptAgentToolBroadcast} snoop reads,\n * abstracting the divergent per-host run-lookup and response-frame constant.\n */\nexport interface AgentToolBroadcastHooks {\n /** Live tailers per run; iterated to forward each progress chunk. */\n forwarders: Map<string, Set<(chunk: AgentToolStoredChunk) => void>>;\n /**\n * Per-run forwarded-chunk counter; advanced even with no tailer attached.\n *\n * This is deliberately a SEPARATE counter from the resumable stream's stored\n * chunk_index — do NOT try to \"simplify\" it away by sequencing off the store\n * position. Not every forwarded frame is durably stored: progress/milestone\n * frames (`reportProgress`) ride the same `USE_CHAT_RESPONSE` wire type and\n * are snooped + forwarded here, but persist out-of-band (progress snapshot /\n * milestone rows), so they have no store position. Sourcing the sequence from\n * the store would give them a colliding position and the tail's high-water\n * dedupe (`emit`) would silently drop them, breaking live progress/milestone\n * delivery to the parent. This counter sequences stored AND non-stored frames\n * on one monotonic line; the tail realigns it to the stored high-water on each\n * (re)attach so a replay→live handoff stays gap/duplicate-free.\n */\n liveSequences: Map<string, number>;\n /** Per-run last error body, captured for replay to a late-attaching tailer. */\n lastErrors: Map<string, string>;\n /** The host's use-chat-response wire type (`CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE`). */\n responseType: string;\n /** Resolve the agent-tool run that owns a turn request id, or null. */\n runForRequest: (requestId: string) => string | null;\n}\n\n/**\n * Snoop a host's outgoing chat frames while any agent-tool run is in flight and\n * forward the owning run's streamed body to its live tailers (or capture its\n * error), without altering the frame — the caller still broadcasts it (#1575).\n *\n * Shared verbatim by `@cloudflare/ai-chat` and `@cloudflare/think`; the only\n * per-host variance (the response-frame type constant and the run-lookup, whose\n * SQL differs) is supplied via {@link AgentToolBroadcastHooks}. Inspection runs\n * for a run's whole lifecycle (live sequences exist even with no tailer), so\n * error capture never depends on tailer timing. A frame belongs to a run iff it\n * carries that run's turn request id, so concurrent runs can't cross-contaminate\n * each other's progress or error state.\n */\nexport function interceptAgentToolBroadcast(\n msg: string | ArrayBuffer | ArrayBufferView,\n hooks: AgentToolBroadcastHooks\n): void {\n if (\n (hooks.forwarders.size > 0 || hooks.liveSequences.size > 0) &&\n typeof msg === \"string\"\n ) {\n try {\n const parsed = JSON.parse(msg) as {\n type?: unknown;\n body?: unknown;\n error?: unknown;\n id?: unknown;\n };\n if (parsed.type === hooks.responseType && typeof parsed.id === \"string\") {\n const runId = hooks.runForRequest(parsed.id);\n if (runId !== null) {\n if (parsed.error === true && typeof parsed.body === \"string\") {\n hooks.lastErrors.set(runId, parsed.body);\n } else if (\n typeof parsed.body === \"string\" &&\n parsed.body.length > 0\n ) {\n // Advance the live sequence even with no tailer attached so a tailer\n // registering mid-run resumes at the right offset.\n const sequence = hooks.liveSequences.get(runId) ?? 0;\n hooks.liveSequences.set(runId, sequence + 1);\n const chunk: AgentToolStoredChunk = { sequence, body: parsed.body };\n const forwarders = hooks.forwarders.get(runId);\n if (forwarders) {\n for (const forward of forwarders) forward(chunk);\n }\n }\n }\n }\n } catch {\n // Non-chat frames pass through unchanged.\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/agent-tools.d.ts
CHANGED
package/dist/chat/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
a as AgentToolEvent,
|
|
5
5
|
o as AgentToolEventMessage,
|
|
6
6
|
s as AgentToolEventState
|
|
7
|
-
} from "../agent-tool-types-
|
|
7
|
+
} from "../agent-tool-types-CNyE1iz_.js";
|
|
8
8
|
import {
|
|
9
9
|
n as ClientToolSchema,
|
|
10
10
|
r as createToolsFromClientSchemas,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
r as AgentToolProgressEmitResult,
|
|
19
19
|
s as interceptAgentToolBroadcast,
|
|
20
20
|
t as AgentToolBroadcastHooks
|
|
21
|
-
} from "../agent-tools-
|
|
21
|
+
} from "../agent-tools-BeOheFBK.js";
|
|
22
22
|
import { JSONSchema7, UIMessage } from "ai";
|
|
23
23
|
import { Connection } from "agents";
|
|
24
24
|
|
|
@@ -457,6 +457,9 @@ type ChatRecoveryExhaustedContext = Pick<
|
|
|
457
457
|
* - `work_budget_exceeded` — the turn kept producing content but exceeded the
|
|
458
458
|
* configured `maxRecoveryWork` runaway-loop budget.
|
|
459
459
|
* - `recovery_aborted` — the caller's `shouldKeepRecovering` hook returned `false`.
|
|
460
|
+
* - `out_of_memory` — recovery attempts kept hitting a Durable Object
|
|
461
|
+
* memory-limit reset (the isolate exceeded its 128 MB limit) until the
|
|
462
|
+
* tight `maxOomRetries` budget drained (#1825).
|
|
460
463
|
* - `stable_timeout` — a recovery attempt kept timing out waiting for the
|
|
461
464
|
* isolate to reach stable state until the budget drained (extreme churn).
|
|
462
465
|
* - `max_recovery_window_exceeded` — DEPRECATED. The old absolute incident-age
|
|
@@ -494,7 +497,7 @@ type ChatRecoveryProgressContext = {
|
|
|
494
497
|
/**
|
|
495
498
|
* Configuration for durable chat recovery. `true` uses these defaults:
|
|
496
499
|
* `maxAttempts: 10`, `stableTimeoutMs: 10_000`, `noProgressTimeoutMs: 300_000`
|
|
497
|
-
* (5 min), `maxRecoveryWork:
|
|
500
|
+
* (5 min), `maxRecoveryWork: 1000`, and a generic terminal message.
|
|
498
501
|
*
|
|
499
502
|
* **Apply this as a class field or in the constructor — never assign it in
|
|
500
503
|
* `onStart()`.** On every wake the SDK evaluates recovery budgets (and may seal
|
|
@@ -522,12 +525,31 @@ type ChatRecoveryConfig =
|
|
|
522
525
|
/**
|
|
523
526
|
* Runaway-loop guard. Maximum recovery WORK — produced content/tool units
|
|
524
527
|
* since the incident began — before a still-progressing turn is sealed
|
|
525
|
-
* with `reason="work_budget_exceeded"`. Defaults to `
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
528
|
+
* with `reason="work_budget_exceeded"`. Defaults to `1000`: a generous
|
|
529
|
+
* backstop that bounds wasted re-run cost when a turn keeps emitting a
|
|
530
|
+
* little content but never converges (e.g. an isolate that OOMs mid-stream
|
|
531
|
+
* on every recovery — #1825 — which otherwise resets the attempt cap and
|
|
532
|
+
* no-progress window forever). Work only accrues from the first
|
|
533
|
+
* interruption until the turn completes, so a normal interrupted turn
|
|
534
|
+
* never approaches it. A very long agentic turn under heavy interruption
|
|
535
|
+
* that legitimately needs more can raise this (or set `Infinity` to
|
|
536
|
+
* disable the framework cap and bound the runaway via `shouldKeepRecovering`
|
|
537
|
+
* instead).
|
|
529
538
|
*/
|
|
530
539
|
maxRecoveryWork?: number;
|
|
540
|
+
/**
|
|
541
|
+
* Tight retry budget for the specific case of a Durable Object isolate
|
|
542
|
+
* exceeding its memory limit and being reset mid-turn. An OOM is usually
|
|
543
|
+
* deterministic (the turn's working set no longer fits in 128 MB), so
|
|
544
|
+
* re-running re-OOMs; but a single OOM can be a transient spike, so
|
|
545
|
+
* recovery retries this many times before sealing with
|
|
546
|
+
* `reason="out_of_memory"`. Counts only attempts that ended in an OOM (not
|
|
547
|
+
* total attempts), so a turn interrupted by deploys is unaffected.
|
|
548
|
+
* Defaults to `3`. Set `0` to seal on the first OOM. Much tighter than
|
|
549
|
+
* `maxRecoveryWork` because an OOM is attributable and each re-run is
|
|
550
|
+
* expensive (it re-runs the model).
|
|
551
|
+
*/
|
|
552
|
+
maxOomRetries?: number;
|
|
531
553
|
/**
|
|
532
554
|
* Caller policy consulted on each recovery attempt from the second
|
|
533
555
|
* onward — it is NOT called on the first detection (the attempt that
|
|
@@ -555,6 +577,7 @@ type ResolvedChatRecoveryConfig = {
|
|
|
555
577
|
terminalMessage: string;
|
|
556
578
|
noProgressTimeoutMs: number;
|
|
557
579
|
maxRecoveryWork: number;
|
|
580
|
+
maxOomRetries: number;
|
|
558
581
|
shouldKeepRecovering?: (
|
|
559
582
|
ctx: ChatRecoveryProgressContext
|
|
560
583
|
) => boolean | Promise<boolean>;
|
|
@@ -2094,6 +2117,18 @@ type ChatRecoveryIncident = {
|
|
|
2094
2117
|
* from an older build is never falsely sealed.
|
|
2095
2118
|
*/
|
|
2096
2119
|
workBaseline?: number;
|
|
2120
|
+
/**
|
|
2121
|
+
* Count of recovery attempts for this incident that ended in a Durable Object
|
|
2122
|
+
* memory-limit reset (the isolate exceeded its 128 MB limit — see
|
|
2123
|
+
* `isDurableObjectMemoryLimitReset`). An OOM is a poison signal: re-running the
|
|
2124
|
+
* same memory-heavy turn deterministically re-OOMs, so unlike a deploy/eviction
|
|
2125
|
+
* it must NOT credit forward progress and must be bounded by a tight,
|
|
2126
|
+
* OOM-specific budget (`maxOomRetries`) rather than the generic attempt cap
|
|
2127
|
+
* (which resets on progress). Bumped by `ChatRecoveryEngine.recordOomAndDecide`
|
|
2128
|
+
* when a recovery callback observes an OOM; exceeding `maxOomRetries` seals the
|
|
2129
|
+
* incident with `reason="out_of_memory"` (#1825). Optional for backward-compat.
|
|
2130
|
+
*/
|
|
2131
|
+
oomAttempts?: number;
|
|
2097
2132
|
};
|
|
2098
2133
|
declare const CHAT_RECOVERY_INCIDENT_KEY_PREFIX = "cf:chat-recovery:incident:";
|
|
2099
2134
|
/**
|
|
@@ -2123,12 +2158,42 @@ declare const CHAT_LAST_TERMINAL_KEY = "cf:chat:last-terminal";
|
|
|
2123
2158
|
*/
|
|
2124
2159
|
declare const DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS = 10;
|
|
2125
2160
|
/**
|
|
2126
|
-
* Runaway-loop guard default
|
|
2127
|
-
*
|
|
2128
|
-
*
|
|
2129
|
-
*
|
|
2130
|
-
|
|
2131
|
-
|
|
2161
|
+
* Runaway-loop guard default — the framework-imposed backstop on cumulative
|
|
2162
|
+
* recovery WORK (produced content/tool units) since an incident opened.
|
|
2163
|
+
*
|
|
2164
|
+
* Originally `Infinity` (rfc-chat-recovery-work-budget): the SDK shipped the
|
|
2165
|
+
* *mechanism* but no default cap, so a progressing turn was never terminated on
|
|
2166
|
+
* its own. Production issue #1825 showed that this is a footgun: an isolate that
|
|
2167
|
+
* OOMs mid-stream still credits a little progress before it dies, which resets
|
|
2168
|
+
* BOTH progress-keyed bounds (the attempt cap and the no-progress window) on
|
|
2169
|
+
* every wake — and a fast crash loop (each attempt inside the alarm-debounce
|
|
2170
|
+
* window) pins the attempt counter too. With `maxRecoveryWork = Infinity` the
|
|
2171
|
+
* ONLY instrument whose meter still climbs across such a loop is disabled, so
|
|
2172
|
+
* recovery re-runs the turn (and its LLM calls) forever.
|
|
2173
|
+
*
|
|
2174
|
+
* A finite default closes that loop out of the box: work climbs regardless of
|
|
2175
|
+
* debounce/progress resets, so a content-emitting runaway is always sealed with
|
|
2176
|
+
* `reason="work_budget_exceeded"`. The value is deliberately generous — it
|
|
2177
|
+
* bounds wasted re-run cost without clipping a normal interrupted turn (work
|
|
2178
|
+
* only accrues from the first interruption until the turn completes, after which
|
|
2179
|
+
* the incident is deleted). A very long agentic turn under heavy interruption
|
|
2180
|
+
* that legitimately needs more should raise `maxRecoveryWork` (or set it to
|
|
2181
|
+
* `Infinity` to restore the pre-#1825 unbounded behavior).
|
|
2182
|
+
*/
|
|
2183
|
+
declare const DEFAULT_CHAT_RECOVERY_MAX_WORK = 1000;
|
|
2184
|
+
/**
|
|
2185
|
+
* Tight, OOM-specific retry budget (#1825). A Durable Object memory-limit reset
|
|
2186
|
+
* (`isDurableObjectMemoryLimitReset`) is usually deterministic — the turn's
|
|
2187
|
+
* working set no longer fits in the isolate's 128 MB — so re-running it re-OOMs.
|
|
2188
|
+
* But a single OOM CAN be a transient spike (the isolate's 128 MB is shared
|
|
2189
|
+
* across the global scope / noisy neighbors), so recovery retries a small number
|
|
2190
|
+
* of times before sealing with `reason="out_of_memory"` rather than abandoning a
|
|
2191
|
+
* turn that one more attempt might have completed. Far tighter than the generic
|
|
2192
|
+
* `maxRecoveryWork` backstop because an OOM is attributable and re-running it is
|
|
2193
|
+
* expensive (it re-runs the model). Counts attempts that ended in an OOM, not
|
|
2194
|
+
* total attempts, so a turn interrupted by deploys (no OOM) is unaffected.
|
|
2195
|
+
*/
|
|
2196
|
+
declare const DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES = 3;
|
|
2132
2197
|
declare const DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS = 10000;
|
|
2133
2198
|
/**
|
|
2134
2199
|
* Delay before retrying a recovery that timed out waiting for stable state.
|
|
@@ -2182,6 +2247,21 @@ declare function sweepStaleChatRecoveryIncidents(
|
|
|
2182
2247
|
storage: Pick<DurableObjectStorage, "list" | "delete">,
|
|
2183
2248
|
now: number
|
|
2184
2249
|
): Promise<void>;
|
|
2250
|
+
/**
|
|
2251
|
+
* List the persisted recovery incidents that are still live (status
|
|
2252
|
+
* `detected` / `scheduled` / `attempting`) — i.e. NOT yet terminalized
|
|
2253
|
+
* (`exhausted` / `failed`). Used by the alarm-boundary OOM circuit breaker
|
|
2254
|
+
* (#1825) to find the incident(s) it must seal when the in-DO budgets could not.
|
|
2255
|
+
* Lists by the incident key prefix so the storage layout stays encapsulated.
|
|
2256
|
+
*/
|
|
2257
|
+
declare function listActiveChatRecoveryIncidents(
|
|
2258
|
+
storage: Pick<DurableObjectStorage, "list">
|
|
2259
|
+
): Promise<
|
|
2260
|
+
{
|
|
2261
|
+
key: string;
|
|
2262
|
+
incident: ChatRecoveryIncident;
|
|
2263
|
+
}[]
|
|
2264
|
+
>;
|
|
2185
2265
|
/**
|
|
2186
2266
|
* Summarize a child agent's persisted recovery incidents for the parent's
|
|
2187
2267
|
* agent-tool reattach decision: `"in-progress"` if any incident is still live
|
|
@@ -2794,6 +2874,34 @@ declare class ChatRecoveryEngine {
|
|
|
2794
2874
|
data: Record<string, unknown> | undefined;
|
|
2795
2875
|
fallbackMaxAttempts: number;
|
|
2796
2876
|
}): Promise<boolean>;
|
|
2877
|
+
/**
|
|
2878
|
+
* Record that a recovery callback observed a Durable Object memory-limit reset
|
|
2879
|
+
* (the isolate exceeded its 128 MB limit — `isDurableObjectMemoryLimitReset`)
|
|
2880
|
+
* and decide what to do next (#1825).
|
|
2881
|
+
*
|
|
2882
|
+
* Bumps the incident's durable `oomAttempts` counter, then:
|
|
2883
|
+
* - if it is still within `maxOomRetries`, issues a delayed, NON-idempotent
|
|
2884
|
+
* reschedule of the SAME callback (same machinery as
|
|
2885
|
+
* {@link rescheduleAfterStableTimeout}: the executing one-shot row is
|
|
2886
|
+
* deleted only after the callback returns, so an idempotent reschedule
|
|
2887
|
+
* would dedup onto that doomed row) and returns `"rescheduled"`. The small
|
|
2888
|
+
* delay lets a transient memory spike clear before the re-run;
|
|
2889
|
+
* - otherwise leaves the incremented count persisted (so a begin-path
|
|
2890
|
+
* re-evaluation agrees) and returns `"exhausted"` — the caller then
|
|
2891
|
+
* terminalizes via the give-up path with `reason="out_of_memory"`.
|
|
2892
|
+
*
|
|
2893
|
+
* Returns `"exhausted"` when there is no incident to track against (no id /
|
|
2894
|
+
* record gone): an OOM we cannot bound must seal rather than loop. Unlike a
|
|
2895
|
+
* stable-state retry this is gated by the OOM-specific budget, NOT the generic
|
|
2896
|
+
* attempt cap — re-running an OOM streams a little "progress" that would
|
|
2897
|
+
* otherwise reset the attempt cap forever (the #1825 loop).
|
|
2898
|
+
*/
|
|
2899
|
+
recordOomAndDecide(input: {
|
|
2900
|
+
incidentId: string | undefined;
|
|
2901
|
+
callback: ChatRecoveryScheduleCallback;
|
|
2902
|
+
data: Record<string, unknown> | undefined;
|
|
2903
|
+
maxOomRetries: number;
|
|
2904
|
+
}): Promise<"rescheduled" | "exhausted">;
|
|
2797
2905
|
/**
|
|
2798
2906
|
* Give up on a recovery turn whose retry budget drained, terminalizing it so
|
|
2799
2907
|
* it can never become an eternal spinner (#1645). The shared spine both
|
|
@@ -3179,6 +3287,7 @@ export {
|
|
|
3179
3287
|
type ContinuationSpec,
|
|
3180
3288
|
ContinuationState,
|
|
3181
3289
|
DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS,
|
|
3290
|
+
DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES,
|
|
3182
3291
|
DEFAULT_CHAT_RECOVERY_MAX_WORK,
|
|
3183
3292
|
DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS,
|
|
3184
3293
|
DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS,
|
|
@@ -3254,6 +3363,7 @@ export {
|
|
|
3254
3363
|
interceptAgentToolBroadcast,
|
|
3255
3364
|
isReplayChunk,
|
|
3256
3365
|
iterateWithStallWatchdog,
|
|
3366
|
+
listActiveChatRecoveryIncidents,
|
|
3257
3367
|
normalizeToolInput,
|
|
3258
3368
|
parseProtocolMessage,
|
|
3259
3369
|
partAwaitsClientInteraction,
|