agents 0.21.0 → 0.23.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 +21 -1
- package/dist/{agent-tool-types-CzGGB-20.d.ts → agent-routing-DE5zmCQ8.d.ts} +1844 -1336
- package/dist/agent-routing.d.ts +14 -0
- package/dist/agent-routing.js +187 -0
- package/dist/agent-routing.js.map +1 -0
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-zR2d5uij.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
- package/dist/agent-tools.d.ts +21 -21
- package/dist/agent-tools.js +2 -1
- package/dist/agent-tools.js.map +1 -1
- package/dist/browser/ai.js +6 -2
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
- package/dist/callable-decorator.d.ts +18 -0
- package/dist/callable-decorator.js +71 -0
- package/dist/callable-decorator.js.map +1 -0
- package/dist/capability-BjSKYpzg.js +42 -0
- package/dist/capability-BjSKYpzg.js.map +1 -0
- package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
- package/dist/channel-Bnm4S7T2.d.ts +491 -0
- package/dist/channels/ai-sdk.d.ts +46 -0
- package/dist/channels/ai-sdk.js +120 -0
- package/dist/channels/ai-sdk.js.map +1 -0
- package/dist/channels/email.d.ts +95 -0
- package/dist/channels/email.js +323 -0
- package/dist/channels/email.js.map +1 -0
- package/dist/channels/index.d.ts +233 -0
- package/dist/channels/index.js +608 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/slack.d.ts +140 -0
- package/dist/channels/slack.js +614 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/tanstack-ai.d.ts +39 -0
- package/dist/channels/tanstack-ai.js +17 -0
- package/dist/channels/tanstack-ai.js.map +1 -0
- package/dist/channels/telegram.d.ts +106 -0
- package/dist/channels/telegram.js +427 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/voice.d.ts +45 -0
- package/dist/channels/voice.js +122 -0
- package/dist/channels/voice.js.map +1 -0
- package/dist/chat/index.d.ts +2328 -2015
- package/dist/chat/index.js +891 -521
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +14 -1
- package/dist/chat/react.js +82 -52
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/transport.js +1 -1
- package/dist/chat-sdk/index.d.ts +7 -7
- package/dist/chat-sdk/index.js +1 -1
- package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
- package/dist/client-jagG8a9_.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
- package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
- package/dist/context/index.d.ts +216 -0
- package/dist/context/index.js +454 -0
- package/dist/context/index.js.map +1 -0
- package/dist/current-agent-Da_C9a3b.d.ts +266 -0
- package/dist/current-agent-DhoDkSnH.js +51 -0
- package/dist/current-agent-DhoDkSnH.js.map +1 -0
- package/dist/diagnostics-BzvaX2UT.js +45 -0
- package/dist/diagnostics-BzvaX2UT.js.map +1 -0
- package/dist/diagnostics-C4jcz3VK.js +360 -0
- package/dist/diagnostics-C4jcz3VK.js.map +1 -0
- package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
- package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
- package/dist/email-send.d.ts +15 -0
- package/dist/email-send.js +32 -0
- package/dist/email-send.js.map +1 -0
- package/dist/email.d.ts +14 -10
- package/dist/email.js.map +1 -1
- package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
- package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
- package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
- package/dist/index-BB0kqhIz.d.ts +101 -0
- package/dist/index-XDkuQ7zm.d.ts +89 -0
- package/dist/{index-BRnybD6X.d.ts → index-YSKgfgg9.d.ts} +21 -31
- package/dist/index.d.ts +113 -101
- package/dist/index.js +11 -7234
- package/dist/ingress-BfetZbMO.js +83 -0
- package/dist/ingress-BfetZbMO.js.map +1 -0
- package/dist/internal-CYlgHl1l.js +59 -0
- package/dist/internal-CYlgHl1l.js.map +1 -0
- package/dist/internal_context-BlxFEWfn.d.ts +19 -0
- package/dist/internal_context.d.ts +10 -4
- package/dist/internal_context.js +1 -10
- package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
- package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
- package/dist/invoker-CHMnoxIA.js.map +1 -0
- package/dist/lifecycle/index.d.ts +66 -0
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle-CMRGjZdw.js +1299 -0
- package/dist/lifecycle-CMRGjZdw.js.map +1 -0
- package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
- package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
- package/dist/mcp/client/index.d.ts +42 -0
- package/dist/mcp/{client.js → client/index.js} +1 -1
- package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
- package/dist/mcp/{x402.js → client/x402.js} +2 -2
- package/dist/mcp/client/x402.js.map +1 -0
- package/dist/mcp/index.d.ts +36 -36
- package/dist/mcp/index.js +14 -16
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
- package/dist/mcp/{server.js → server/index.js} +1 -1
- package/dist/observability/ai/index.js +50 -35
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/observability/index.d.ts +4 -4
- package/dist/observability/index.js +3 -50
- package/dist/observability/index.js.map +1 -1
- package/dist/{protocol-Dqc2MQxo.js → protocol-B0nh6KNf.js} +19 -21
- package/dist/protocol-B0nh6KNf.js.map +1 -0
- package/dist/react.d.ts +4 -4
- package/dist/react.js +1 -1
- package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +13 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +137 -0
- package/dist/routing/index.js +244 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sanitize-D9TujEK8.js +79 -0
- package/dist/sanitize-D9TujEK8.js.map +1 -0
- package/dist/schedule.d.ts +25 -94
- package/dist/schedule.js +1 -98
- package/dist/schedule.js.map +1 -1
- package/dist/scheduler-DD9NdYbF.js +665 -0
- package/dist/scheduler-DD9NdYbF.js.map +1 -0
- package/dist/scheduler-Dwh85ZGl.d.ts +223 -0
- package/dist/schedules/index.d.ts +22 -0
- package/dist/schedules/index.js +2 -0
- package/dist/schedules/parser.d.ts +79 -0
- package/dist/schedules/parser.js +103 -0
- package/dist/schedules/parser.js.map +1 -0
- package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +441 -0
- package/dist/sessions/index.js +2063 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/skills/index.d.ts +99 -0
- package/dist/skills/index.js +254 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/sql-error-CPY-GXyI.d.ts +12 -0
- package/dist/sql-error.d.ts +2 -0
- package/dist/sql-error.js +16 -0
- package/dist/sql-error.js.map +1 -0
- package/dist/src-DlSHshb2.js +6963 -0
- package/dist/src-DlSHshb2.js.map +1 -0
- package/dist/streams/index.d.ts +120 -0
- package/dist/streams/index.js +107 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams-D6tJ0NN9.d.ts +370 -0
- package/dist/streams-DZKgAj9b.js +709 -0
- package/dist/streams-DZKgAj9b.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/surface-bZZJqBka.js +17 -0
- package/dist/surface-bZZJqBka.js.map +1 -0
- package/dist/tasks/index.d.ts +64 -0
- package/dist/tasks/index.js +2 -0
- package/dist/tasks-BRJ5zgya.d.ts +517 -0
- package/dist/tasks-ylZgBjhj.js +1656 -0
- package/dist/tasks-ylZgBjhj.js.map +1 -0
- package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
- package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
- package/dist/text-stream-CpdiKrJB.js +272 -0
- package/dist/text-stream-CpdiKrJB.js.map +1 -0
- package/dist/tokens-nHAKcN6M.js +52 -0
- package/dist/tokens-nHAKcN6M.js.map +1 -0
- package/dist/tool-schema-CBjGPrsQ.js +31 -0
- package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
- package/dist/types-B7LojTe4.d.ts +202 -0
- package/dist/types-_Faxb570.d.ts +439 -0
- package/dist/voice/client.d.ts +226 -0
- package/dist/voice/client.js +932 -0
- package/dist/voice/client.js.map +1 -0
- package/dist/voice/errors.d.ts +43 -0
- package/dist/voice/errors.js +41 -0
- package/dist/voice/errors.js.map +1 -0
- package/dist/voice/index.d.ts +271 -0
- package/dist/voice/index.js +1812 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/react.d.ts +167 -0
- package/dist/voice/react.js +234 -0
- package/dist/voice/react.js.map +1 -0
- package/dist/voice/sfu.d.ts +71 -0
- package/dist/voice/sfu.js +157 -0
- package/dist/voice/sfu.js.map +1 -0
- package/dist/voice/text.d.ts +6 -0
- package/dist/voice/text.js +2 -0
- package/dist/voice/types.d.ts +58 -0
- package/dist/voice/types.js +18 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/workers-ai.d.ts +136 -0
- package/dist/voice/workers-ai.js +568 -0
- package/dist/voice/workers-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +192 -0
- package/dist/websockets/index.js +2 -0
- package/dist/websockets-DUfRHPRq.js +502 -0
- package/dist/websockets-DUfRHPRq.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +22 -22
- package/dist/workflows.js +2 -1
- package/dist/workflows.js.map +1 -1
- package/dist/{ws-chat-transport-CIoOBbO7.js → ws-chat-transport-rWwta645.js} +152 -15
- package/dist/ws-chat-transport-rWwta645.js.map +1 -0
- package/docs/agent-class.md +29 -87
- package/docs/agent-tools.md +2 -1
- package/docs/channels.md +323 -0
- package/docs/chat-agents.md +19 -25
- package/docs/context.md +131 -0
- package/docs/durable-execution.md +1 -1
- package/docs/http-websockets.md +1 -11
- package/docs/human-in-the-loop.md +1 -1
- package/docs/index.md +16 -12
- package/docs/lifecycle.md +370 -0
- package/docs/long-running-agents.md +4 -6
- package/docs/mcp-client.md +55 -0
- package/docs/mcp-servers.md +5 -1
- package/docs/observability.md +11 -11
- package/docs/resumable-streaming.md +2 -2
- package/docs/routing.md +105 -0
- package/docs/scheduling.md +175 -15
- package/docs/server-driven-messages.md +1 -1
- package/docs/sessions.md +237 -871
- package/docs/streams.md +213 -0
- package/docs/sub-agents.md +185 -125
- package/docs/tasks.md +246 -0
- package/docs/voice.md +745 -0
- package/package.json +144 -33
- package/dist/cli/index.js +0 -26
- package/dist/cli/index.js.map +0 -1
- package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
- package/dist/client-zqKcsyFa.js.map +0 -1
- package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
- package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
- package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
- package/dist/experimental/memory/session/index.d.ts +0 -670
- package/dist/experimental/memory/session/index.js +0 -2374
- package/dist/experimental/memory/session/index.js.map +0 -1
- package/dist/experimental/memory/utils/index.d.ts +0 -96
- package/dist/experimental/memory/utils/index.js +0 -79
- package/dist/experimental/memory/utils/index.js.map +0 -1
- package/dist/handler-stateless-CIkKPETH.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
- package/dist/internal_context.js.map +0 -1
- package/dist/mcp/client.d.ts +0 -42
- package/dist/mcp/do-oauth-client-provider.js.map +0 -1
- package/dist/mcp/x402.js.map +0 -1
- package/dist/protocol-Dqc2MQxo.js.map +0 -1
- package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
- package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
- package/dist/ws-chat-transport-CIoOBbO7.js.map +0 -1
- /package/dist/{cli/index.d.ts → index-BVVgDSdq.d.ts} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
a as
|
|
3
|
-
i as
|
|
2
|
+
a as subscribe,
|
|
3
|
+
i as genericObservability,
|
|
4
4
|
n as Observability,
|
|
5
|
-
o as
|
|
5
|
+
o as channels,
|
|
6
6
|
r as ObservabilityEvent,
|
|
7
7
|
t as ChannelEventMap
|
|
8
|
-
} from "../index-
|
|
8
|
+
} from "../index-YSKgfgg9.js";
|
|
9
9
|
export {
|
|
10
10
|
ChannelEventMap,
|
|
11
11
|
Observability,
|
|
@@ -1,67 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as publishDiagnosticsEvent, t as channels } from "../diagnostics-BzvaX2UT.js";
|
|
2
|
+
import { subscribe as subscribe$1, unsubscribe } from "node:diagnostics_channel";
|
|
2
3
|
//#region src/observability/index.ts
|
|
3
4
|
/**
|
|
4
|
-
* Diagnostics channels for agent observability.
|
|
5
|
-
*
|
|
6
|
-
* Events are published to named channels using the Node.js diagnostics_channel API.
|
|
7
|
-
* By default, publishing to a channel with no subscribers is a no-op (zero overhead).
|
|
8
|
-
*
|
|
9
|
-
* To observe events, subscribe to the channels you care about:
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { subscribe } from "node:diagnostics_channel";
|
|
12
|
-
* subscribe("agents:rpc", (event) => console.log(event));
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* In production, all published messages are automatically forwarded to
|
|
16
|
-
* Tail Workers via `event.diagnosticsChannelEvents` — no subscription needed.
|
|
17
|
-
*/
|
|
18
|
-
const channels = {
|
|
19
|
-
state: channel("agents:state"),
|
|
20
|
-
rpc: channel("agents:rpc"),
|
|
21
|
-
message: channel("agents:message"),
|
|
22
|
-
chat: channel("agents:chat"),
|
|
23
|
-
transcript: channel("agents:transcript"),
|
|
24
|
-
fiber: channel("agents:fiber"),
|
|
25
|
-
agentTool: channel("agents:agent_tool"),
|
|
26
|
-
schedule: channel("agents:schedule"),
|
|
27
|
-
lifecycle: channel("agents:lifecycle"),
|
|
28
|
-
workflow: channel("agents:workflow"),
|
|
29
|
-
mcp: channel("agents:mcp"),
|
|
30
|
-
email: channel("agents:email"),
|
|
31
|
-
channel: channel("agents:channel")
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
5
|
* Channel keys whose diagnostics channel name differs from `agents:${key}`.
|
|
35
6
|
* Keep this in sync with {@link channels} for any camelCase key that maps to a
|
|
36
7
|
* snake_case diagnostics channel.
|
|
37
8
|
*/
|
|
38
9
|
const CHANNEL_DIAGNOSTIC_NAME_OVERRIDES = { agentTool: "agents:agent_tool" };
|
|
39
10
|
/**
|
|
40
|
-
* Map event type prefixes to their diagnostics channel.
|
|
41
|
-
*/
|
|
42
|
-
function getChannel(type) {
|
|
43
|
-
if (type.startsWith("mcp:")) return channels.mcp;
|
|
44
|
-
if (type.startsWith("workflow:")) return channels.workflow;
|
|
45
|
-
if (type.startsWith("fiber:")) return channels.fiber;
|
|
46
|
-
if (type.startsWith("transcript:") || type.startsWith("chat:transcript:")) return channels.transcript;
|
|
47
|
-
if (type.startsWith("chat:")) return channels.chat;
|
|
48
|
-
if (type.startsWith("agent_tool:")) return channels.agentTool;
|
|
49
|
-
if (type.startsWith("schedule:") || type.startsWith("queue:")) return channels.schedule;
|
|
50
|
-
if (type.startsWith("message:") || type.startsWith("tool:") || type.startsWith("submission:") || type.startsWith("action:")) return channels.message;
|
|
51
|
-
if (type === "rpc" || type.startsWith("rpc:")) return channels.rpc;
|
|
52
|
-
if (type.startsWith("state:")) return channels.state;
|
|
53
|
-
if (type.startsWith("email:")) return channels.email;
|
|
54
|
-
if (type.startsWith("channel:") || type.startsWith("notice:")) return channels.channel;
|
|
55
|
-
return channels.lifecycle;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
11
|
* The default observability implementation.
|
|
59
12
|
*
|
|
60
13
|
* Publishes events to diagnostics_channel. Events are silent unless
|
|
61
14
|
* a subscriber is registered or a Tail Worker is attached.
|
|
62
15
|
*/
|
|
63
16
|
const genericObservability = { emit(event) {
|
|
64
|
-
|
|
17
|
+
publishDiagnosticsEvent(event);
|
|
65
18
|
} };
|
|
66
19
|
/**
|
|
67
20
|
* Subscribe to a typed observability channel.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["dcUnsubscribe"],"sources":["../../src/observability/index.ts"],"sourcesContent":["import {\n
|
|
1
|
+
{"version":3,"file":"index.js","names":["dcUnsubscribe"],"sources":["../../src/observability/index.ts"],"sourcesContent":["import {\n subscribe as dcSubscribe,\n unsubscribe as dcUnsubscribe\n} from \"node:diagnostics_channel\";\nimport type { AgentObservabilityEvent } from \"./agent\";\nimport type { MCPObservabilityEvent } from \"./mcp\";\nimport { publishDiagnosticsEvent } from \"./diagnostics\";\nexport { channels } from \"./diagnostics\";\n\n/**\n * Union of all observability event types from different domains\n */\nexport type ObservabilityEvent =\n | AgentObservabilityEvent\n | MCPObservabilityEvent;\n\nexport interface Observability {\n /**\n * Emit an event for the Agent's observability implementation to handle.\n * @param event - The event to emit\n */\n emit(event: ObservabilityEvent): void;\n}\n\n/**\n * Channel keys whose diagnostics channel name differs from `agents:${key}`.\n * Keep this in sync with {@link channels} for any camelCase key that maps to a\n * snake_case diagnostics channel.\n */\nconst CHANNEL_DIAGNOSTIC_NAME_OVERRIDES: Partial<Record<string, string>> = {\n agentTool: \"agents:agent_tool\"\n};\n\n/**\n * The default observability implementation.\n *\n * Publishes events to diagnostics_channel. Events are silent unless\n * a subscriber is registered or a Tail Worker is attached.\n */\nexport const genericObservability: Observability = {\n emit(event) {\n publishDiagnosticsEvent(event);\n }\n};\n\n/**\n * Maps each channel key to the observability events it carries.\n */\nexport type ChannelEventMap = {\n state: Extract<ObservabilityEvent, { type: `state:${string}` }>;\n rpc: Extract<ObservabilityEvent, { type: \"rpc\" | `rpc:${string}` }>;\n message: Extract<\n ObservabilityEvent,\n {\n type:\n | `message:${string}`\n | `tool:${string}`\n | `submission:${string}`\n | `action:${string}`;\n }\n >;\n chat: Exclude<\n Extract<ObservabilityEvent, { type: `chat:${string}` }>,\n { type: `chat:transcript:${string}` }\n >;\n transcript: Extract<\n ObservabilityEvent,\n { type: `transcript:${string}` | `chat:transcript:${string}` }\n >;\n fiber: Extract<ObservabilityEvent, { type: `fiber:${string}` }>;\n agentTool: Extract<ObservabilityEvent, { type: `agent_tool:${string}` }>;\n schedule: Extract<\n ObservabilityEvent,\n { type: `schedule:${string}` | `queue:${string}` }\n >;\n lifecycle: Extract<\n ObservabilityEvent,\n { type: \"connect\" | \"disconnect\" | \"destroy\" }\n >;\n workflow: Extract<ObservabilityEvent, { type: `workflow:${string}` }>;\n mcp: Extract<ObservabilityEvent, { type: `mcp:${string}` }>;\n email: Extract<ObservabilityEvent, { type: `email:${string}` }>;\n channel: Extract<\n ObservabilityEvent,\n { type: `channel:${string}` | `notice:${string}` }\n >;\n};\n\n/**\n * Subscribe to a typed observability channel.\n *\n * ```ts\n * import { subscribe } from \"agents/observability\";\n *\n * const unsub = subscribe(\"rpc\", (event) => {\n * console.log(event.payload.method); // fully typed\n * });\n * ```\n *\n * @returns A function that unsubscribes the callback.\n */\nexport function subscribe<K extends keyof ChannelEventMap>(\n channelKey: K,\n callback: (event: ChannelEventMap[K]) => void\n): () => void {\n const name =\n CHANNEL_DIAGNOSTIC_NAME_OVERRIDES[channelKey] ?? `agents:${channelKey}`;\n const handler = (message: unknown, _name: string | symbol) =>\n callback(message as ChannelEventMap[K]);\n dcSubscribe(name, handler);\n return () => dcUnsubscribe(name, handler);\n}\n"],"mappings":";;;;;;;;AA6BA,MAAM,oCAAqE,EACzE,WAAW,oBACb;;;;;;;AAQA,MAAa,uBAAsC,EACjD,KAAK,OAAO;CACV,wBAAwB,KAAK;AAC/B,EACF;;;;;;;;;;;;;;AA0DA,SAAgB,UACd,YACA,UACY;CACZ,MAAM,OACJ,kCAAkC,eAAe,UAAU;CAC7D,MAAM,WAAW,SAAkB,UACjC,SAAS,OAA6B;CACxC,YAAY,MAAM,OAAO;CACzB,aAAaA,YAAc,MAAM,OAAO;AAC1C"}
|
|
@@ -9,8 +9,10 @@ var StreamAccumulator = class {
|
|
|
9
9
|
this._isContinuation = options.continuation ?? false;
|
|
10
10
|
this.parts = options.existingParts ? [...options.existingParts] : [];
|
|
11
11
|
this.metadata = options.existingMetadata ? { ...options.existingMetadata } : void 0;
|
|
12
|
+
this._pendingContinuationChunks = this._isContinuation && options.existingParts === void 0 && options.existingMetadata === void 0 ? [] : null;
|
|
12
13
|
}
|
|
13
14
|
applyChunk(chunk) {
|
|
15
|
+
this._pendingContinuationChunks?.push(chunk);
|
|
14
16
|
const handled = applyChunkToParts(this.parts, chunk);
|
|
15
17
|
if (chunk.type === "tool-approval-request" && chunk.toolCallId) return {
|
|
16
18
|
handled,
|
|
@@ -101,7 +103,8 @@ var StreamAccumulator = class {
|
|
|
101
103
|
/**
|
|
102
104
|
* Merge this accumulator's message into an existing message array.
|
|
103
105
|
* Handles continuation (walk backward for last assistant), replacement
|
|
104
|
-
* (update existing by messageId), or append (new message).
|
|
106
|
+
* (update existing by messageId), or append (new message). An unseeded
|
|
107
|
+
* continuation adopts current parts here before applying its queued chunks.
|
|
105
108
|
*/
|
|
106
109
|
mergeInto(messages) {
|
|
107
110
|
let existingIdx = messages.findIndex((m) => m.id === this.messageId);
|
|
@@ -111,6 +114,15 @@ var StreamAccumulator = class {
|
|
|
111
114
|
break;
|
|
112
115
|
}
|
|
113
116
|
}
|
|
117
|
+
if (this._pendingContinuationChunks !== null) {
|
|
118
|
+
const pendingChunks = this._pendingContinuationChunks;
|
|
119
|
+
this._pendingContinuationChunks = null;
|
|
120
|
+
this.parts.splice(0, this.parts.length, ...existingIdx >= 0 ? messages[existingIdx].parts : []);
|
|
121
|
+
const currentMetadata = existingIdx >= 0 ? asMetadata(messages[existingIdx].metadata) : void 0;
|
|
122
|
+
this.metadata = currentMetadata ? { ...currentMetadata } : void 0;
|
|
123
|
+
if (existingIdx >= 0) this.messageId = messages[existingIdx].id;
|
|
124
|
+
for (const chunk of pendingChunks) this.applyChunk(chunk);
|
|
125
|
+
}
|
|
114
126
|
const partialMessage = {
|
|
115
127
|
id: existingIdx >= 0 ? messages[existingIdx].id : this.messageId,
|
|
116
128
|
role: "assistant",
|
|
@@ -147,25 +159,11 @@ function transition(state, event) {
|
|
|
147
159
|
case "response": {
|
|
148
160
|
let accumulator;
|
|
149
161
|
const isReplayedStart = event.replay === true && event.chunkData?.type === "start";
|
|
150
|
-
if (state.status === "idle" || state.streamId !== event.streamId || isReplayedStart) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
for (let i = event.currentMessages.length - 1; i >= 0; i--) if (event.currentMessages[i].role === "assistant") {
|
|
156
|
-
messageId = event.currentMessages[i].id;
|
|
157
|
-
existingParts = [...event.currentMessages[i].parts];
|
|
158
|
-
if (event.currentMessages[i].metadata != null) existingMetadata = { ...event.currentMessages[i].metadata };
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
accumulator = new StreamAccumulator({
|
|
163
|
-
messageId,
|
|
164
|
-
continuation: event.continuation,
|
|
165
|
-
existingParts,
|
|
166
|
-
existingMetadata
|
|
167
|
-
});
|
|
168
|
-
} else accumulator = state.accumulator;
|
|
162
|
+
if (state.status === "idle" || state.streamId !== event.streamId || isReplayedStart) accumulator = new StreamAccumulator({
|
|
163
|
+
messageId: event.messageId,
|
|
164
|
+
continuation: event.continuation
|
|
165
|
+
});
|
|
166
|
+
else accumulator = state.accumulator;
|
|
169
167
|
if (event.chunkData) accumulator.applyChunk(event.chunkData);
|
|
170
168
|
let messagesUpdate;
|
|
171
169
|
if (event.done) {
|
|
@@ -224,4 +222,4 @@ const CHAT_MESSAGE_TYPES = {
|
|
|
224
222
|
//#endregion
|
|
225
223
|
export { StreamAccumulator as i, STREAM_RESUME_NONE_REASONS as n, transition as r, CHAT_MESSAGE_TYPES as t };
|
|
226
224
|
|
|
227
|
-
//# sourceMappingURL=protocol-
|
|
225
|
+
//# sourceMappingURL=protocol-B0nh6KNf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-B0nh6KNf.js","names":[],"sources":["../src/chat/stream-accumulator.ts","../src/chat/broadcast-state.ts","../src/chat/protocol.ts"],"sourcesContent":["/**\n * StreamAccumulator — unified chunk-to-message builder.\n *\n * Used by @cloudflare/ai-chat (server + client) and @cloudflare/think\n * to incrementally build a UIMessage from stream chunks. Wraps\n * applyChunkToParts and handles the metadata chunk types (start, finish,\n * message-metadata, error) that applyChunkToParts does not cover.\n *\n * The accumulator signals domain-specific concerns (early persistence,\n * cross-message tool updates) via ChunkAction returns — callers handle\n * these according to their context.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { applyChunkToParts, type StreamChunkData } from \"./message-builder\";\n\nfunction asMetadata(value: unknown): Record<string, unknown> | undefined {\n if (value != null && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\nexport interface StreamAccumulatorOptions {\n messageId: string;\n /** Whether chunks continue the current assistant message when merged. */\n continuation?: boolean;\n /** Eager seed; omitted continuations seed from the messages passed to `mergeInto`. */\n existingParts?: UIMessage[\"parts\"];\n existingMetadata?: Record<string, unknown>;\n}\n\nexport type ChunkAction =\n | {\n type: \"start\";\n messageId?: string;\n metadata?: Record<string, unknown>;\n }\n | {\n type: \"finish\";\n finishReason?: string;\n metadata?: Record<string, unknown>;\n }\n | { type: \"message-metadata\"; metadata: Record<string, unknown> }\n | { type: \"tool-approval-request\"; toolCallId: string }\n | {\n type: \"cross-message-tool-update\";\n updateType: \"output-available\" | \"output-error\";\n toolCallId: string;\n output?: unknown;\n errorText?: string;\n preliminary?: boolean;\n }\n | { type: \"error\"; error: string };\n\nexport interface ChunkResult {\n handled: boolean;\n action?: ChunkAction;\n}\n\nexport class StreamAccumulator {\n messageId: string;\n readonly parts: UIMessage[\"parts\"];\n metadata?: Record<string, unknown>;\n private _isContinuation: boolean;\n // Continuation chunks wait here until mergeInto can seed from current state.\n private _pendingContinuationChunks: StreamChunkData[] | null;\n\n constructor(options: StreamAccumulatorOptions) {\n this.messageId = options.messageId;\n this._isContinuation = options.continuation ?? false;\n this.parts = options.existingParts ? [...options.existingParts] : [];\n this.metadata = options.existingMetadata\n ? { ...options.existingMetadata }\n : undefined;\n this._pendingContinuationChunks =\n this._isContinuation &&\n options.existingParts === undefined &&\n options.existingMetadata === undefined\n ? []\n : null;\n }\n\n applyChunk(chunk: StreamChunkData): ChunkResult {\n this._pendingContinuationChunks?.push(chunk);\n const handled = applyChunkToParts(this.parts, chunk);\n\n // Detect tool-approval-request for early persistence signaling\n if (chunk.type === \"tool-approval-request\" && chunk.toolCallId) {\n return {\n handled,\n action: { type: \"tool-approval-request\", toolCallId: chunk.toolCallId }\n };\n }\n\n // Detect cross-message tool output/error: applyChunkToParts returns true\n // for recognized types but silently does nothing when the toolCallId\n // doesn't exist in the current parts array.\n if (\n (chunk.type === \"tool-output-available\" ||\n chunk.type === \"tool-output-error\") &&\n chunk.toolCallId\n ) {\n const foundInParts = this.parts.some(\n (p) => \"toolCallId\" in p && p.toolCallId === chunk.toolCallId\n );\n if (!foundInParts) {\n return {\n handled,\n action: {\n type: \"cross-message-tool-update\",\n updateType:\n chunk.type === \"tool-output-available\"\n ? \"output-available\"\n : \"output-error\",\n toolCallId: chunk.toolCallId,\n output: chunk.output,\n errorText: chunk.errorText,\n preliminary: chunk.preliminary\n }\n };\n }\n }\n\n if (!handled) {\n switch (chunk.type) {\n case \"start\": {\n if (chunk.messageId != null && !this._isContinuation) {\n this.messageId = chunk.messageId;\n }\n const startMeta = asMetadata(chunk.messageMetadata);\n if (startMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...startMeta }\n : { ...startMeta };\n }\n return {\n handled: true,\n action: {\n type: \"start\",\n messageId: chunk.messageId,\n metadata: startMeta\n }\n };\n }\n case \"finish\": {\n const finishMeta = asMetadata(chunk.messageMetadata);\n if (finishMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...finishMeta }\n : { ...finishMeta };\n }\n const finishReason =\n \"finishReason\" in chunk\n ? (chunk.finishReason as string)\n : undefined;\n return {\n handled: true,\n action: {\n type: \"finish\",\n finishReason,\n metadata: finishMeta\n }\n };\n }\n case \"message-metadata\": {\n const msgMeta = asMetadata(chunk.messageMetadata);\n if (msgMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...msgMeta }\n : { ...msgMeta };\n }\n return {\n handled: true,\n action: {\n type: \"message-metadata\",\n metadata: msgMeta ?? {}\n }\n };\n }\n case \"finish-step\": {\n return { handled: true };\n }\n case \"error\": {\n return {\n handled: true,\n action: {\n type: \"error\",\n error: chunk.errorText ?? JSON.stringify(chunk)\n }\n };\n }\n }\n }\n\n return { handled };\n }\n\n /** Snapshot the current state as a UIMessage. */\n toMessage(): UIMessage {\n return {\n id: this.messageId,\n role: \"assistant\",\n parts: [...this.parts],\n ...(this.metadata != null && { metadata: this.metadata })\n } as UIMessage;\n }\n\n /**\n * Merge this accumulator's message into an existing message array.\n * Handles continuation (walk backward for last assistant), replacement\n * (update existing by messageId), or append (new message). An unseeded\n * continuation adopts current parts here before applying its queued chunks.\n */\n mergeInto(messages: UIMessage[]): UIMessage[] {\n let existingIdx = messages.findIndex((m) => m.id === this.messageId);\n\n if (existingIdx < 0 && this._isContinuation) {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"assistant\") {\n existingIdx = i;\n break;\n }\n }\n }\n\n if (this._pendingContinuationChunks !== null) {\n const pendingChunks = this._pendingContinuationChunks;\n this._pendingContinuationChunks = null;\n this.parts.splice(\n 0,\n this.parts.length,\n ...(existingIdx >= 0 ? messages[existingIdx].parts : [])\n );\n const currentMetadata =\n existingIdx >= 0\n ? asMetadata(messages[existingIdx].metadata)\n : undefined;\n this.metadata = currentMetadata ? { ...currentMetadata } : undefined;\n if (existingIdx >= 0) {\n this.messageId = messages[existingIdx].id;\n }\n for (const chunk of pendingChunks) {\n this.applyChunk(chunk);\n }\n }\n\n const messageId =\n existingIdx >= 0 ? messages[existingIdx].id : this.messageId;\n\n const partialMessage: UIMessage = {\n id: messageId,\n role: \"assistant\",\n parts: [...this.parts],\n ...(this.metadata != null && { metadata: this.metadata })\n } as UIMessage;\n\n if (existingIdx >= 0) {\n const updated = [...messages];\n updated[existingIdx] = partialMessage;\n return updated;\n }\n return [...messages, partialMessage];\n }\n}\n","/**\n * Broadcast stream state machine.\n *\n * Manages the lifecycle of a StreamAccumulator for broadcast/resume\n * streams — the path where this client is *observing* a stream owned\n * by another tab or resumed after reconnect, rather than the transport-\n * owned path that feeds directly into useChat.\n *\n * The transition function is pure (no React, no WebSocket, no side\n * effects). Callers dispatch events and apply the returned state +\n * messagesUpdate. Side effects (sending ACKs, calling onData) stay\n * in the caller.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { StreamAccumulator } from \"./stream-accumulator\";\nimport type { StreamChunkData } from \"./message-builder\";\n\n// ── State ──────────────────────────────────────────────────────────\n\nexport type BroadcastStreamState =\n | { status: \"idle\" }\n | {\n status: \"observing\";\n streamId: string;\n accumulator: StreamAccumulator;\n };\n\n// ── Events ─────────────────────────────────────────────────────────\n\nexport type BroadcastStreamEvent =\n | {\n type: \"response\";\n streamId: string;\n /** Fallback message ID for a new accumulator (ignored if one exists for this stream). */\n messageId: string;\n chunkData?: unknown;\n done?: boolean;\n error?: boolean;\n replay?: boolean;\n replayComplete?: boolean;\n continuation?: boolean;\n /** @deprecated Continuations now seed from current messages in `messagesUpdate`. */\n currentMessages?: UIMessage[];\n }\n | {\n type: \"resume-fallback\";\n streamId: string;\n messageId: string;\n }\n | { type: \"clear\" };\n\n// ── Result ─────────────────────────────────────────────────────────\n\nexport interface TransitionResult {\n state: BroadcastStreamState;\n messagesUpdate?: (prev: UIMessage[]) => UIMessage[];\n isStreaming: boolean;\n}\n\n// ── Transition ─────────────────────────────────────────────────────\n\nexport function transition(\n state: BroadcastStreamState,\n event: BroadcastStreamEvent\n): TransitionResult {\n switch (event.type) {\n case \"clear\":\n return { state: { status: \"idle\" }, isStreaming: false };\n\n case \"resume-fallback\": {\n const accumulator = new StreamAccumulator({\n messageId: event.messageId\n });\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n isStreaming: true\n };\n }\n\n case \"response\": {\n let accumulator: StreamAccumulator;\n\n // A replayed `start` chunk means the server is re-sending the stream\n // buffer from chunk 0 (resume replay). Re-initialize the accumulator\n // instead of appending into an existing one: replaying into an\n // accumulator that already holds this stream's parts would duplicate\n // them (a second `text-start` unconditionally opens a second text\n // part — #1733). Re-initializing makes replay idempotent under any\n // number of replays, including a second replay triggered by a\n // duplicate STREAM_RESUMING → ACK cycle or a reconnect.\n const isReplayedStart =\n event.replay === true &&\n (event.chunkData as { type?: string } | null | undefined)?.type ===\n \"start\";\n\n if (\n state.status === \"idle\" ||\n state.streamId !== event.streamId ||\n isReplayedStart\n ) {\n accumulator = new StreamAccumulator({\n messageId: event.messageId,\n continuation: event.continuation\n });\n } else {\n accumulator = state.accumulator;\n }\n\n if (event.chunkData) {\n accumulator.applyChunk(event.chunkData as StreamChunkData);\n }\n\n let messagesUpdate: ((prev: UIMessage[]) => UIMessage[]) | undefined;\n\n if (event.done) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n return {\n state: { status: \"idle\" },\n messagesUpdate,\n isStreaming: false\n };\n }\n\n if (event.chunkData && !event.replay) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n } else if (event.replayComplete) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n }\n\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n messagesUpdate,\n isStreaming: true\n };\n }\n }\n}\n","/**\n * Wire protocol message type constants for the cf_agent_chat_* protocol.\n *\n * These are the string values used on the wire between agent servers and\n * clients. Both @cloudflare/ai-chat (via its MessageType enum) and\n * @cloudflare/think use these values.\n */\nexport const STREAM_RESUME_NONE_REASONS = {\n /** No active, pending, or terminal stream exists for this agent. */\n IDLE: \"idle\",\n /** An active tool continuation is owned by another live connection. */\n CONTINUATION_OWNED: \"continuation-owned\"\n} as const;\n\nexport type StreamResumeNoneReason =\n (typeof STREAM_RESUME_NONE_REASONS)[keyof typeof STREAM_RESUME_NONE_REASONS];\n\nexport const CHAT_MESSAGE_TYPES = {\n CHAT_MESSAGES: \"cf_agent_chat_messages\",\n USE_CHAT_REQUEST: \"cf_agent_use_chat_request\",\n USE_CHAT_RESPONSE: \"cf_agent_use_chat_response\",\n CHAT_CLEAR: \"cf_agent_chat_clear\",\n CHAT_REQUEST_CANCEL: \"cf_agent_chat_request_cancel\",\n STREAM_RESUMING: \"cf_agent_stream_resuming\",\n STREAM_RESUME_ACK: \"cf_agent_stream_resume_ack\",\n STREAM_RESUME_REQUEST: \"cf_agent_stream_resume_request\",\n STREAM_RESUME_NONE: \"cf_agent_stream_resume_none\",\n // Server→client: a turn has been accepted but its resumable stream has not\n // started yet (queued, debouncing, waiting on MCP setup, or running async\n // work in `onChatMessage`). Sent in response to a resume request (or on\n // connect) so a reconnecting/re-mounting client keeps its expectation instead\n // of resolving its resume probe to \"no stream\" and then false-timing-out the\n // turn. Resolved by a later `STREAM_RESUMING` (stream started) or\n // `STREAM_RESUME_NONE` (turn settled without streaming). Backward-compatible —\n // clients that don't understand it ignore it. See issue #1784.\n STREAM_PENDING: \"cf_agent_stream_pending\",\n TOOL_RESULT: \"cf_agent_tool_result\",\n TOOL_APPROVAL: \"cf_agent_tool_approval\",\n MESSAGE_UPDATED: \"cf_agent_message_updated\",\n // Server→client: a durable chat turn is being recovered (interrupted by a\n // deploy/eviction or a stream-stall watchdog abort and now resuming). Sent\n // when a recovery continuation is scheduled and cleared on every terminal\n // outcome; `@cloudflare/think` also replays it on connect so a client that\n // joins mid-recovery learns it. Purely a progress hint — backward-compatible\n // (clients that don't understand it ignore it). See issue #1620.\n CHAT_RECOVERING: \"cf_agent_chat_recovering\"\n} as const;\n"],"mappings":"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"}
|
package/dist/react.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./agent-
|
|
2
|
+
C as MCPServersState,
|
|
3
|
+
J as AgentToolRunPart,
|
|
4
|
+
Y as AgentToolRunState
|
|
5
|
+
} from "./agent-routing-DE5zmCQ8.js";
|
|
6
6
|
import { ClientParameters } from "./serializable.js";
|
|
7
7
|
import {
|
|
8
8
|
AgentConnectionError,
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./types.js";
|
|
2
1
|
import { camelCaseToKebabCase } from "./utils.js";
|
|
2
|
+
import "./types.js";
|
|
3
3
|
import { AgentConnectionError, createStubProxy, isTerminalCloseEvent } from "./client.js";
|
|
4
4
|
import { buildSubAgentPathUnchecked } from "./sub-routing.js";
|
|
5
5
|
import { n as applyAgentToolEvent, r as createAgentToolEventState } from "./agent-tools-y7zLfw4Q.js";
|
|
@@ -111,17 +111,28 @@ declare function isDurableObjectMemoryLimitReset(error: unknown): boolean;
|
|
|
111
111
|
* error — re-running yields the same failure). A genuine application error
|
|
112
112
|
* carries none of these signals, so it is never misclassified by this check.
|
|
113
113
|
*/
|
|
114
|
+
/**
|
|
115
|
+
* Whether a failure is the PLATFORM's rather than the application's — any
|
|
116
|
+
* platform transient (see {@link isPlatformTransientError}, which includes
|
|
117
|
+
* superseded-isolate resets) or a memory-limit reset. Failed work in this
|
|
118
|
+
* class must be PRESERVED and deferred, never completed as an application
|
|
119
|
+
* failure. The two sub-classes defer differently: transients re-run
|
|
120
|
+
* indefinitely (the platform recovers), while memory-limit deferral is
|
|
121
|
+
* bounded by the alarm circuit breaker (#1825).
|
|
122
|
+
*/
|
|
123
|
+
declare function isPlatformFailure(error: unknown): boolean;
|
|
114
124
|
declare function isPlatformTransientError(error: unknown): boolean;
|
|
115
125
|
//#endregion
|
|
116
126
|
export {
|
|
117
127
|
isErrorRetryable as a,
|
|
118
|
-
|
|
128
|
+
jitterBackoff as c,
|
|
119
129
|
isDurableObjectStorageReset as i,
|
|
120
|
-
|
|
130
|
+
tryN as l,
|
|
121
131
|
isDurableObjectCodeUpdateReset as n,
|
|
122
|
-
|
|
132
|
+
isPlatformFailure as o,
|
|
123
133
|
isDurableObjectMemoryLimitReset as r,
|
|
124
|
-
|
|
125
|
-
RetryOptions as t
|
|
134
|
+
isPlatformTransientError as s,
|
|
135
|
+
RetryOptions as t,
|
|
136
|
+
validateRetryOptions as u
|
|
126
137
|
};
|
|
127
|
-
//# sourceMappingURL=retries-
|
|
138
|
+
//# sourceMappingURL=retries-D9Ds-1lz.d.ts.map
|
package/dist/retries.d.ts
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
a as isErrorRetryable,
|
|
3
|
-
c as
|
|
3
|
+
c as jitterBackoff,
|
|
4
4
|
i as isDurableObjectStorageReset,
|
|
5
|
-
l as
|
|
5
|
+
l as tryN,
|
|
6
6
|
n as isDurableObjectCodeUpdateReset,
|
|
7
|
-
o as
|
|
7
|
+
o as isPlatformFailure,
|
|
8
8
|
r as isDurableObjectMemoryLimitReset,
|
|
9
|
-
s as
|
|
10
|
-
t as RetryOptions
|
|
11
|
-
|
|
9
|
+
s as isPlatformTransientError,
|
|
10
|
+
t as RetryOptions,
|
|
11
|
+
u as validateRetryOptions
|
|
12
|
+
} from "./retries-D9Ds-1lz.js";
|
|
12
13
|
export {
|
|
13
14
|
RetryOptions,
|
|
14
15
|
isDurableObjectCodeUpdateReset,
|
|
15
16
|
isDurableObjectMemoryLimitReset,
|
|
16
17
|
isDurableObjectStorageReset,
|
|
17
18
|
isErrorRetryable,
|
|
19
|
+
isPlatformFailure,
|
|
18
20
|
isPlatformTransientError,
|
|
19
21
|
jitterBackoff,
|
|
20
22
|
tryN,
|
package/dist/retries.js
CHANGED
|
@@ -213,6 +213,18 @@ function isDurableObjectMemoryLimitReset(error) {
|
|
|
213
213
|
* error — re-running yields the same failure). A genuine application error
|
|
214
214
|
* carries none of these signals, so it is never misclassified by this check.
|
|
215
215
|
*/
|
|
216
|
+
/**
|
|
217
|
+
* Whether a failure is the PLATFORM's rather than the application's — any
|
|
218
|
+
* platform transient (see {@link isPlatformTransientError}, which includes
|
|
219
|
+
* superseded-isolate resets) or a memory-limit reset. Failed work in this
|
|
220
|
+
* class must be PRESERVED and deferred, never completed as an application
|
|
221
|
+
* failure. The two sub-classes defer differently: transients re-run
|
|
222
|
+
* indefinitely (the platform recovers), while memory-limit deferral is
|
|
223
|
+
* bounded by the alarm circuit breaker (#1825).
|
|
224
|
+
*/
|
|
225
|
+
function isPlatformFailure(error) {
|
|
226
|
+
return isPlatformTransientError(error) || isDurableObjectMemoryLimitReset(error);
|
|
227
|
+
}
|
|
216
228
|
function isPlatformTransientError(error) {
|
|
217
229
|
for (const e of selfAndCauses(error)) {
|
|
218
230
|
const message = errorMessageOf(e);
|
|
@@ -224,6 +236,6 @@ function isPlatformTransientError(error) {
|
|
|
224
236
|
return false;
|
|
225
237
|
}
|
|
226
238
|
//#endregion
|
|
227
|
-
export { isDurableObjectCodeUpdateReset, isDurableObjectMemoryLimitReset, isDurableObjectStorageReset, isErrorRetryable, isPlatformTransientError, jitterBackoff, tryN, validateRetryOptions };
|
|
239
|
+
export { isDurableObjectCodeUpdateReset, isDurableObjectMemoryLimitReset, isDurableObjectStorageReset, isErrorRetryable, isPlatformFailure, isPlatformTransientError, jitterBackoff, tryN, validateRetryOptions };
|
|
228
240
|
|
|
229
241
|
//# sourceMappingURL=retries.js.map
|
package/dist/retries.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retries.js","names":[],"sources":["../src/retries.ts"],"sourcesContent":["/**\n * Retry options for schedule(), scheduleEvery(), queue(), and this.retry().\n */\nexport interface RetryOptions {\n /** Max number of attempts (including the first). Default: 3 */\n maxAttempts?: number;\n /** Base delay in ms for exponential backoff. Default: 100 */\n baseDelayMs?: number;\n /** Max delay cap in ms. Default: 3000 */\n maxDelayMs?: number;\n}\n\n/**\n * Internal options for tryN -- extends RetryOptions with a shouldRetry predicate.\n */\ninterface TryNOptions extends RetryOptions {\n /**\n * Predicate to determine if an error should be retried.\n * Receives the error and the next attempt number (so callers can\n * make attempt-aware decisions).\n * If not provided, all errors are retried.\n */\n shouldRetry?: (err: unknown, nextAttempt: number) => boolean;\n}\n\n/**\n * Validate retry options eagerly so invalid config fails at enqueue/schedule time\n * rather than at execution time. Checks individual field ranges, enforces integer\n * maxAttempts, and validates cross-field constraints after resolving against\n * defaults when provided.\n */\nexport function validateRetryOptions(\n options: RetryOptions,\n defaults?: Required<RetryOptions>\n): void {\n if (options.maxAttempts !== undefined) {\n if (!Number.isFinite(options.maxAttempts) || options.maxAttempts < 1) {\n throw new Error(\"retry.maxAttempts must be >= 1\");\n }\n if (!Number.isInteger(options.maxAttempts)) {\n throw new Error(\"retry.maxAttempts must be an integer\");\n }\n }\n if (options.baseDelayMs !== undefined) {\n if (!Number.isFinite(options.baseDelayMs) || options.baseDelayMs <= 0) {\n throw new Error(\"retry.baseDelayMs must be > 0\");\n }\n }\n if (options.maxDelayMs !== undefined) {\n if (!Number.isFinite(options.maxDelayMs) || options.maxDelayMs <= 0) {\n throw new Error(\"retry.maxDelayMs must be > 0\");\n }\n }\n\n // Resolve against defaults (when provided) so that cross-field checks\n // catch e.g. { baseDelayMs: 5000 } against default maxDelayMs: 3000.\n const resolvedBase = options.baseDelayMs ?? defaults?.baseDelayMs;\n const resolvedMax = options.maxDelayMs ?? defaults?.maxDelayMs;\n if (\n resolvedBase !== undefined &&\n resolvedMax !== undefined &&\n resolvedBase > resolvedMax\n ) {\n throw new Error(\"retry.baseDelayMs must be <= retry.maxDelayMs\");\n }\n}\n\n/**\n * Returns the number of milliseconds to wait before retrying a request.\n * Uses the \"Full Jitter\" approach from\n * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n *\n * @param attempt The current attempt number (1-indexed).\n * @param baseDelayMs Base delay multiplier in ms.\n * @param maxDelayMs Maximum delay cap in ms.\n * @returns Milliseconds to wait before retrying.\n */\nexport function jitterBackoff(\n attempt: number,\n baseDelayMs: number,\n maxDelayMs: number\n): number {\n const upperBoundMs = Math.min(2 ** attempt * baseDelayMs, maxDelayMs);\n return Math.floor(Math.random() * upperBoundMs);\n}\n\n/**\n * Retry an async function up to `n` total attempts with jittered exponential backoff.\n *\n * @param n Total number of attempts (must be a finite integer >= 1).\n * @param fn The async function to retry. Receives the current attempt number (1-indexed).\n * @param options Retry configuration.\n * @returns The result of `fn` on success.\n * @throws The last error if all attempts fail or `shouldRetry` returns false.\n */\nexport async function tryN<T>(\n n: number,\n fn: (attempt: number) => Promise<T>,\n options?: TryNOptions\n): Promise<T> {\n if (!Number.isFinite(n) || n < 1) {\n throw new Error(\"retry.maxAttempts must be >= 1\");\n }\n n = Math.floor(n);\n\n const rawBase = options?.baseDelayMs ?? 100;\n const rawMax = options?.maxDelayMs ?? 3000;\n\n if (!Number.isFinite(rawBase) || rawBase <= 0) {\n throw new Error(\"retry.baseDelayMs must be > 0\");\n }\n if (!Number.isFinite(rawMax) || rawMax <= 0) {\n throw new Error(\"retry.maxDelayMs must be > 0\");\n }\n\n const baseDelayMs = Math.floor(rawBase);\n const maxDelayMs = Math.floor(rawMax);\n\n if (baseDelayMs > maxDelayMs) {\n throw new Error(\"retry.baseDelayMs must be <= retry.maxDelayMs\");\n }\n\n let attempt = 1;\n while (true) {\n try {\n return await fn(attempt);\n } catch (err) {\n const nextAttempt = attempt + 1;\n if (\n nextAttempt > n ||\n (options?.shouldRetry && !options.shouldRetry(err, nextAttempt))\n ) {\n throw err;\n }\n const delay = jitterBackoff(attempt, baseDelayMs, maxDelayMs);\n await new Promise((resolve) => setTimeout(resolve, delay));\n attempt = nextAttempt;\n }\n }\n}\n\n/**\n * Returns true if the given error is retryable according to Durable Object error handling.\n * See https://developers.cloudflare.com/durable-objects/best-practices/error-handling/\n *\n * An error is retryable if it has `retryable: true` but is NOT an overloaded error.\n */\nexport function isErrorRetryable(err: unknown): boolean {\n if (typeof err !== \"object\" || err === null) {\n return false;\n }\n const msg = String(err);\n const typed = err as { retryable?: boolean; overloaded?: boolean };\n return (\n Boolean(typed.retryable) &&\n !typed.overloaded &&\n !msg.includes(\"Durable Object is overloaded\")\n );\n}\n\n/**\n * The \"superseded isolate\" platform messages — the invocation is running on an\n * isolate the platform has replaced with a new version (a deploy / code\n * update). For the rest of that invocation every operation throws the same\n * error (code never reloads mid-invocation), so in-process retries are futile;\n * but the next fresh invocation runs the new code and succeeds.\n *\n * workerd surfaces this as a plain `Error` with one of a few messages, all the\n * same failure class — a message match is the only signal:\n * - \"Durable Object reset because its code was updated.\" (DO storage op on a\n * superseded isolate / deploy bounce)\n * - \"This script has been upgraded. Please send a new request to connect to\n * the new version.\" (a stub/connection to a superseded script; the message\n * literally instructs the caller to retry on the new version)\n *\n * The match stays close to the verbatim platform strings (rather than a loose\n * \"upgraded\"/\"reset\" substring) so an ordinary application error that happens\n * to mention those words is NOT misclassified as a supersede.\n */\nconst SUPERSEDED_ISOLATE_PATTERN =\n /reset because its code was updated|this script has been upgraded/i;\n\n/**\n * The \"Network connection lost.\" platform transient — the connection between\n * the isolate and its storage (or another DO) dropped. Unlike a supersede this\n * MAY succeed on an in-process retry (a momentary blip), so it must not skip\n * the in-process retry budget — but during a deploy-reset window it never\n * succeeds in-process and surfaces interleaved with the supersede messages\n * (SQL ops throw `SqlError: SQL query failed: Network connection lost.` while\n * KV ops throw the reset message), so on retry exhaustion it must be treated\n * as the platform's failure, not the callback's.\n */\nconst CONNECTION_LOST_PATTERN = /network connection lost/i;\n\n/**\n * The exact Durable Object storage-reset platform signal. Keep this narrow:\n * ordinary SQL and generic internal errors are application failures. This is a\n * transient storage reset, not a memory-limit poison pill.\n */\nconst STORAGE_RESET_PATTERN =\n /Internal error in Durable Object storage caused object to be reset/i;\n\n/**\n * The Durable Object memory-limit reset — the isolate exceeded its 128 MB limit\n * and was reset by the platform (workerd surfaces this verbatim as\n * \"Durable Object's isolate exceeded its memory limit and was reset.\"; the D1\n * sibling is \"D1 DB's isolate exceeded its memory limit and was reset.\").\n *\n * The match is the broad shared fragment \"exceeded its memory limit\" rather than\n * the full \"...and was reset\" sentence: real-world surfacings truncate or reword\n * the tail (some log pipelines clip the message; D1/storage wrappers re-prefix\n * it), and a customer-reported loop (#1825) showed lines carrying only the\n * \"exceeded its memory limit\" fragment. Missing a surfacing here means the\n * circuit breaker never engages, so we err toward the broader match — and even a\n * false positive is fail-safe (a tightly-bounded retry-then-seal, not data loss).\n *\n * This is DELIBERATELY a separate class from `SUPERSEDED_ISOLATE_PATTERN` /\n * {@link isPlatformTransientError}, and is NOT folded into them. A supersede or\n * connection-lost transient means \"re-run the same work and it succeeds on a\n * healthy isolate\" — those classes can be deferred and retried *indefinitely*. A\n * memory-limit reset is the opposite: re-running the SAME memory-heavy work\n * deterministically re-OOMs (the footprint, not the platform, is the cause), so\n * deferring it indefinitely would PRESERVE the one-shot row and re-run the\n * doomed work forever (amplifying the loop and cost — see #1825). It is a\n * poison-pill signal: callers must bound retries tightly and then SEAL.\n *\n * Accordingly the schedule executor (`_executeScheduleCallback`) and the\n * alarm-boundary circuit breaker (`Agent.alarm`) treat it as its OWN class: a\n * memory-limit reset is re-thrown (row preserved) so it reaches the breaker,\n * which tolerates a few strikes (`maxAlarmMemoryLimitStrikes`) and then seals +\n * purges the looping row — i.e. *bounded* deferral, never the unbounded deferral\n * the transient classes get.\n */\nconst MEMORY_LIMIT_RESET_PATTERN = /exceeded its memory limit/i;\n\nfunction errorMessageOf(error: unknown): string {\n return error instanceof Error\n ? error.message\n : typeof error === \"string\"\n ? error\n : \"\";\n}\n\n/**\n * Iterate an error and its `cause` chain (depth-limited so a cyclic chain\n * can't spin). Wrappers like `SqlError` carry the original platform error in\n * `cause` and may not propagate signal properties (e.g. the CF `retryable`\n * flag), so classification must look through them.\n */\nfunction* selfAndCauses(error: unknown): Generator<unknown> {\n let current = error;\n for (let depth = 0; depth < 8 && current != null; depth++) {\n yield current;\n current =\n typeof current === \"object\"\n ? (current as { cause?: unknown }).cause\n : undefined;\n }\n}\n\n/**\n * Whether an error (or anything in its `cause` chain) is a transient\n * \"superseded isolate\" failure — see `SUPERSEDED_ISOLATE_PATTERN`. In-process\n * retries are futile for this class; the work must be deferred to a fresh\n * invocation, which runs the new code and succeeds.\n */\nexport function isDurableObjectCodeUpdateReset(error: unknown): boolean {\n for (const e of selfAndCauses(error)) {\n if (SUPERSEDED_ISOLATE_PATTERN.test(errorMessageOf(e))) return true;\n }\n return false;\n}\n\n/**\n * Whether an error (or anything in its `cause` chain) carries the exact\n * Durable Object storage-reset platform fragment. Generic SQL/internal errors\n * deliberately do not qualify.\n */\nexport function isDurableObjectStorageReset(error: unknown): boolean {\n for (const e of selfAndCauses(error)) {\n if (STORAGE_RESET_PATTERN.test(errorMessageOf(e))) return true;\n }\n return false;\n}\n\n/**\n * Whether an error (or anything in its `cause` chain, or a raw error-message\n * string) is a Durable Object memory-limit reset — see\n * {@link MEMORY_LIMIT_RESET_PATTERN}. Unlike {@link isPlatformTransientError},\n * re-running the same work re-OOMs deterministically, so callers must NOT defer\n * it like a transient; they should bound retries tightly and then seal (#1825).\n */\nexport function isDurableObjectMemoryLimitReset(error: unknown): boolean {\n for (const e of selfAndCauses(error)) {\n if (MEMORY_LIMIT_RESET_PATTERN.test(errorMessageOf(e))) return true;\n }\n return false;\n}\n\n/**\n * Whether an error (or anything in its `cause` chain) is a transient failure\n * of the PLATFORM rather than of the code that threw it:\n *\n * - a superseded-isolate reset (\"reset because its code was updated\" /\n * \"this script has been upgraded\") — a deploy replaced the isolate;\n * - an error the platform itself flags `retryable: true` (excluding\n * overloaded errors, where retrying the same object won't help) — see\n * `isErrorRetryable`;\n * - \"Network connection lost.\" — the storage/stub connection dropped. The\n * CF `retryable` flag does not survive error wrappers (e.g. `SqlError`\n * copies only the message + `cause`) and is absent in some local-dev\n * shapes, so the verbatim message is matched as well;\n * - the exact \"Internal error in Durable Object storage caused object to be\n * reset\" platform fragment. Generic internal and SQL errors remain fatal.\n *\n * Used to decide whether failed work should be RE-RUN LATER (platform\n * transient — the same work succeeds once the platform recovers, typically\n * seconds after a deploy) versus ABANDONED as genuinely failing (application\n * error — re-running yields the same failure). A genuine application error\n * carries none of these signals, so it is never misclassified by this check.\n */\nexport function isPlatformTransientError(error: unknown): boolean {\n for (const e of selfAndCauses(error)) {\n const message = errorMessageOf(e);\n if (SUPERSEDED_ISOLATE_PATTERN.test(message)) return true;\n if (CONNECTION_LOST_PATTERN.test(message)) return true;\n if (STORAGE_RESET_PATTERN.test(message)) return true;\n if (isErrorRetryable(e)) return true;\n }\n return false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"retries.js","names":[],"sources":["../src/retries.ts"],"sourcesContent":["/**\n * Retry options for schedule(), scheduleEvery(), queue(), and this.retry().\n */\nexport interface RetryOptions {\n /** Max number of attempts (including the first). Default: 3 */\n maxAttempts?: number;\n /** Base delay in ms for exponential backoff. Default: 100 */\n baseDelayMs?: number;\n /** Max delay cap in ms. Default: 3000 */\n maxDelayMs?: number;\n}\n\n/**\n * Internal options for tryN -- extends RetryOptions with a shouldRetry predicate.\n */\ninterface TryNOptions extends RetryOptions {\n /**\n * Predicate to determine if an error should be retried.\n * Receives the error and the next attempt number (so callers can\n * make attempt-aware decisions).\n * If not provided, all errors are retried.\n */\n shouldRetry?: (err: unknown, nextAttempt: number) => boolean;\n}\n\n/**\n * Validate retry options eagerly so invalid config fails at enqueue/schedule time\n * rather than at execution time. Checks individual field ranges, enforces integer\n * maxAttempts, and validates cross-field constraints after resolving against\n * defaults when provided.\n */\nexport function validateRetryOptions(\n options: RetryOptions,\n defaults?: Required<RetryOptions>\n): void {\n if (options.maxAttempts !== undefined) {\n if (!Number.isFinite(options.maxAttempts) || options.maxAttempts < 1) {\n throw new Error(\"retry.maxAttempts must be >= 1\");\n }\n if (!Number.isInteger(options.maxAttempts)) {\n throw new Error(\"retry.maxAttempts must be an integer\");\n }\n }\n if (options.baseDelayMs !== undefined) {\n if (!Number.isFinite(options.baseDelayMs) || options.baseDelayMs <= 0) {\n throw new Error(\"retry.baseDelayMs must be > 0\");\n }\n }\n if (options.maxDelayMs !== undefined) {\n if (!Number.isFinite(options.maxDelayMs) || options.maxDelayMs <= 0) {\n throw new Error(\"retry.maxDelayMs must be > 0\");\n }\n }\n\n // Resolve against defaults (when provided) so that cross-field checks\n // catch e.g. { baseDelayMs: 5000 } against default maxDelayMs: 3000.\n const resolvedBase = options.baseDelayMs ?? defaults?.baseDelayMs;\n const resolvedMax = options.maxDelayMs ?? defaults?.maxDelayMs;\n if (\n resolvedBase !== undefined &&\n resolvedMax !== undefined &&\n resolvedBase > resolvedMax\n ) {\n throw new Error(\"retry.baseDelayMs must be <= retry.maxDelayMs\");\n }\n}\n\n/**\n * Returns the number of milliseconds to wait before retrying a request.\n * Uses the \"Full Jitter\" approach from\n * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n *\n * @param attempt The current attempt number (1-indexed).\n * @param baseDelayMs Base delay multiplier in ms.\n * @param maxDelayMs Maximum delay cap in ms.\n * @returns Milliseconds to wait before retrying.\n */\nexport function jitterBackoff(\n attempt: number,\n baseDelayMs: number,\n maxDelayMs: number\n): number {\n const upperBoundMs = Math.min(2 ** attempt * baseDelayMs, maxDelayMs);\n return Math.floor(Math.random() * upperBoundMs);\n}\n\n/**\n * Retry an async function up to `n` total attempts with jittered exponential backoff.\n *\n * @param n Total number of attempts (must be a finite integer >= 1).\n * @param fn The async function to retry. Receives the current attempt number (1-indexed).\n * @param options Retry configuration.\n * @returns The result of `fn` on success.\n * @throws The last error if all attempts fail or `shouldRetry` returns false.\n */\nexport async function tryN<T>(\n n: number,\n fn: (attempt: number) => Promise<T>,\n options?: TryNOptions\n): Promise<T> {\n if (!Number.isFinite(n) || n < 1) {\n throw new Error(\"retry.maxAttempts must be >= 1\");\n }\n n = Math.floor(n);\n\n const rawBase = options?.baseDelayMs ?? 100;\n const rawMax = options?.maxDelayMs ?? 3000;\n\n if (!Number.isFinite(rawBase) || rawBase <= 0) {\n throw new Error(\"retry.baseDelayMs must be > 0\");\n }\n if (!Number.isFinite(rawMax) || rawMax <= 0) {\n throw new Error(\"retry.maxDelayMs must be > 0\");\n }\n\n const baseDelayMs = Math.floor(rawBase);\n const maxDelayMs = Math.floor(rawMax);\n\n if (baseDelayMs > maxDelayMs) {\n throw new Error(\"retry.baseDelayMs must be <= retry.maxDelayMs\");\n }\n\n let attempt = 1;\n while (true) {\n try {\n return await fn(attempt);\n } catch (err) {\n const nextAttempt = attempt + 1;\n if (\n nextAttempt > n ||\n (options?.shouldRetry && !options.shouldRetry(err, nextAttempt))\n ) {\n throw err;\n }\n const delay = jitterBackoff(attempt, baseDelayMs, maxDelayMs);\n await new Promise((resolve) => setTimeout(resolve, delay));\n attempt = nextAttempt;\n }\n }\n}\n\n/**\n * Returns true if the given error is retryable according to Durable Object error handling.\n * See https://developers.cloudflare.com/durable-objects/best-practices/error-handling/\n *\n * An error is retryable if it has `retryable: true` but is NOT an overloaded error.\n */\nexport function isErrorRetryable(err: unknown): boolean {\n if (typeof err !== \"object\" || err === null) {\n return false;\n }\n const msg = String(err);\n const typed = err as { retryable?: boolean; overloaded?: boolean };\n return (\n Boolean(typed.retryable) &&\n !typed.overloaded &&\n !msg.includes(\"Durable Object is overloaded\")\n );\n}\n\n/**\n * The \"superseded isolate\" platform messages — the invocation is running on an\n * isolate the platform has replaced with a new version (a deploy / code\n * update). For the rest of that invocation every operation throws the same\n * error (code never reloads mid-invocation), so in-process retries are futile;\n * but the next fresh invocation runs the new code and succeeds.\n *\n * workerd surfaces this as a plain `Error` with one of a few messages, all the\n * same failure class — a message match is the only signal:\n * - \"Durable Object reset because its code was updated.\" (DO storage op on a\n * superseded isolate / deploy bounce)\n * - \"This script has been upgraded. Please send a new request to connect to\n * the new version.\" (a stub/connection to a superseded script; the message\n * literally instructs the caller to retry on the new version)\n *\n * The match stays close to the verbatim platform strings (rather than a loose\n * \"upgraded\"/\"reset\" substring) so an ordinary application error that happens\n * to mention those words is NOT misclassified as a supersede.\n */\nconst SUPERSEDED_ISOLATE_PATTERN =\n /reset because its code was updated|this script has been upgraded/i;\n\n/**\n * The \"Network connection lost.\" platform transient — the connection between\n * the isolate and its storage (or another DO) dropped. Unlike a supersede this\n * MAY succeed on an in-process retry (a momentary blip), so it must not skip\n * the in-process retry budget — but during a deploy-reset window it never\n * succeeds in-process and surfaces interleaved with the supersede messages\n * (SQL ops throw `SqlError: SQL query failed: Network connection lost.` while\n * KV ops throw the reset message), so on retry exhaustion it must be treated\n * as the platform's failure, not the callback's.\n */\nconst CONNECTION_LOST_PATTERN = /network connection lost/i;\n\n/**\n * The exact Durable Object storage-reset platform signal. Keep this narrow:\n * ordinary SQL and generic internal errors are application failures. This is a\n * transient storage reset, not a memory-limit poison pill.\n */\nconst STORAGE_RESET_PATTERN =\n /Internal error in Durable Object storage caused object to be reset/i;\n\n/**\n * The Durable Object memory-limit reset — the isolate exceeded its 128 MB limit\n * and was reset by the platform (workerd surfaces this verbatim as\n * \"Durable Object's isolate exceeded its memory limit and was reset.\"; the D1\n * sibling is \"D1 DB's isolate exceeded its memory limit and was reset.\").\n *\n * The match is the broad shared fragment \"exceeded its memory limit\" rather than\n * the full \"...and was reset\" sentence: real-world surfacings truncate or reword\n * the tail (some log pipelines clip the message; D1/storage wrappers re-prefix\n * it), and a customer-reported loop (#1825) showed lines carrying only the\n * \"exceeded its memory limit\" fragment. Missing a surfacing here means the\n * circuit breaker never engages, so we err toward the broader match — and even a\n * false positive is fail-safe (a tightly-bounded retry-then-seal, not data loss).\n *\n * This is DELIBERATELY a separate class from `SUPERSEDED_ISOLATE_PATTERN` /\n * {@link isPlatformTransientError}, and is NOT folded into them. A supersede or\n * connection-lost transient means \"re-run the same work and it succeeds on a\n * healthy isolate\" — those classes can be deferred and retried *indefinitely*. A\n * memory-limit reset is the opposite: re-running the SAME memory-heavy work\n * deterministically re-OOMs (the footprint, not the platform, is the cause), so\n * deferring it indefinitely would PRESERVE the one-shot row and re-run the\n * doomed work forever (amplifying the loop and cost — see #1825). It is a\n * poison-pill signal: callers must bound retries tightly and then SEAL.\n *\n * Accordingly the schedule executor (`_executeScheduleCallback`) and the\n * alarm-boundary circuit breaker (`Agent.alarm`) treat it as its OWN class: a\n * memory-limit reset is re-thrown (row preserved) so it reaches the breaker,\n * which tolerates a few strikes (`maxAlarmMemoryLimitStrikes`) and then seals +\n * purges the looping row — i.e. *bounded* deferral, never the unbounded deferral\n * the transient classes get.\n */\nconst MEMORY_LIMIT_RESET_PATTERN = /exceeded its memory limit/i;\n\nfunction errorMessageOf(error: unknown): string {\n return error instanceof Error\n ? error.message\n : typeof error === \"string\"\n ? error\n : \"\";\n}\n\n/**\n * Iterate an error and its `cause` chain (depth-limited so a cyclic chain\n * can't spin). Wrappers like `SqlError` carry the original platform error in\n * `cause` and may not propagate signal properties (e.g. the CF `retryable`\n * flag), so classification must look through them.\n */\nfunction* selfAndCauses(error: unknown): Generator<unknown> {\n let current = error;\n for (let depth = 0; depth < 8 && current != null; depth++) {\n yield current;\n current =\n typeof current === \"object\"\n ? (current as { cause?: unknown }).cause\n : undefined;\n }\n}\n\n/**\n * Whether an error (or anything in its `cause` chain) is a transient\n * \"superseded isolate\" failure — see `SUPERSEDED_ISOLATE_PATTERN`. In-process\n * retries are futile for this class; the work must be deferred to a fresh\n * invocation, which runs the new code and succeeds.\n */\nexport function isDurableObjectCodeUpdateReset(error: unknown): boolean {\n for (const e of selfAndCauses(error)) {\n if (SUPERSEDED_ISOLATE_PATTERN.test(errorMessageOf(e))) return true;\n }\n return false;\n}\n\n/**\n * Whether an error (or anything in its `cause` chain) carries the exact\n * Durable Object storage-reset platform fragment. Generic SQL/internal errors\n * deliberately do not qualify.\n */\nexport function isDurableObjectStorageReset(error: unknown): boolean {\n for (const e of selfAndCauses(error)) {\n if (STORAGE_RESET_PATTERN.test(errorMessageOf(e))) return true;\n }\n return false;\n}\n\n/**\n * Whether an error (or anything in its `cause` chain, or a raw error-message\n * string) is a Durable Object memory-limit reset — see\n * {@link MEMORY_LIMIT_RESET_PATTERN}. Unlike {@link isPlatformTransientError},\n * re-running the same work re-OOMs deterministically, so callers must NOT defer\n * it like a transient; they should bound retries tightly and then seal (#1825).\n */\nexport function isDurableObjectMemoryLimitReset(error: unknown): boolean {\n for (const e of selfAndCauses(error)) {\n if (MEMORY_LIMIT_RESET_PATTERN.test(errorMessageOf(e))) return true;\n }\n return false;\n}\n\n/**\n * Whether an error (or anything in its `cause` chain) is a transient failure\n * of the PLATFORM rather than of the code that threw it:\n *\n * - a superseded-isolate reset (\"reset because its code was updated\" /\n * \"this script has been upgraded\") — a deploy replaced the isolate;\n * - an error the platform itself flags `retryable: true` (excluding\n * overloaded errors, where retrying the same object won't help) — see\n * `isErrorRetryable`;\n * - \"Network connection lost.\" — the storage/stub connection dropped. The\n * CF `retryable` flag does not survive error wrappers (e.g. `SqlError`\n * copies only the message + `cause`) and is absent in some local-dev\n * shapes, so the verbatim message is matched as well;\n * - the exact \"Internal error in Durable Object storage caused object to be\n * reset\" platform fragment. Generic internal and SQL errors remain fatal.\n *\n * Used to decide whether failed work should be RE-RUN LATER (platform\n * transient — the same work succeeds once the platform recovers, typically\n * seconds after a deploy) versus ABANDONED as genuinely failing (application\n * error — re-running yields the same failure). A genuine application error\n * carries none of these signals, so it is never misclassified by this check.\n */\n/**\n * Whether a failure is the PLATFORM's rather than the application's — any\n * platform transient (see {@link isPlatformTransientError}, which includes\n * superseded-isolate resets) or a memory-limit reset. Failed work in this\n * class must be PRESERVED and deferred, never completed as an application\n * failure. The two sub-classes defer differently: transients re-run\n * indefinitely (the platform recovers), while memory-limit deferral is\n * bounded by the alarm circuit breaker (#1825).\n */\nexport function isPlatformFailure(error: unknown): boolean {\n return (\n isPlatformTransientError(error) || isDurableObjectMemoryLimitReset(error)\n );\n}\n\nexport function isPlatformTransientError(error: unknown): boolean {\n for (const e of selfAndCauses(error)) {\n const message = errorMessageOf(e);\n if (SUPERSEDED_ISOLATE_PATTERN.test(message)) return true;\n if (CONNECTION_LOST_PATTERN.test(message)) return true;\n if (STORAGE_RESET_PATTERN.test(message)) return true;\n if (isErrorRetryable(e)) return true;\n }\n return false;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
r as CapabilityWebSocketUpgradeContext,
|
|
3
|
+
s as LifecycleCapability,
|
|
4
|
+
t as CapabilityRequestContext
|
|
5
|
+
} from "../capability-runner-Be_-PLR1.js";
|
|
6
|
+
import {
|
|
7
|
+
a as routeAgentRequest,
|
|
8
|
+
c as Agent,
|
|
9
|
+
i as getAgentByName,
|
|
10
|
+
n as AgentOptions,
|
|
11
|
+
r as RoutingRetryOptions,
|
|
12
|
+
t as AgentGetOptions
|
|
13
|
+
} from "../agent-routing-DE5zmCQ8.js";
|
|
14
|
+
|
|
15
|
+
//#region src/routing/routed-agents.d.ts
|
|
16
|
+
/** A public entry in an {@link RoutedAgents}. */
|
|
17
|
+
type RoutedAgentEntry<Metadata = unknown> = {
|
|
18
|
+
/** Stable application-facing identifier used in routes. */ readonly id: string /** Application-owned metadata stored with the entry. */;
|
|
19
|
+
readonly metadata: Metadata | null /** Creation time, as Unix milliseconds. */;
|
|
20
|
+
readonly createdAt: number /** Time the entry or its metadata last changed, as Unix milliseconds. */;
|
|
21
|
+
readonly updatedAt: number;
|
|
22
|
+
};
|
|
23
|
+
/** Options for creating an entry in an {@link RoutedAgents}. */
|
|
24
|
+
type RoutedAgentCreateOptions<Metadata = unknown> = {
|
|
25
|
+
/** Initial application-owned metadata. */ readonly metadata?: Metadata;
|
|
26
|
+
};
|
|
27
|
+
/** Configuration for an {@link RoutedAgents}. */
|
|
28
|
+
type RoutedAgentsOptions<TAgent extends Agent> = {
|
|
29
|
+
/** Top-level Durable Object namespace the entries are created in. */ readonly namespace: DurableObjectNamespace<TAgent> /** One URL-safe path segment under the owning Durable Object. */;
|
|
30
|
+
readonly route: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* A durable, routed collection of independent top-level Agents.
|
|
34
|
+
*
|
|
35
|
+
* Install this on the owning Durable Object, typically a per-user hub. It
|
|
36
|
+
* maps public entry IDs to opaque physical Agent names, handles catalog
|
|
37
|
+
* CRUD without waking any target, and forwards matching HTTP requests and
|
|
38
|
+
* WebSocket upgrades to the selected Agent. After an upgrade the target
|
|
39
|
+
* owns the socket, so ordinary frames never wake the owner. The target
|
|
40
|
+
* Agent needs no matching capability. Destroying the owner condemns every
|
|
41
|
+
* remaining entry with a few retries so targets don't casually outlive
|
|
42
|
+
* their catalog — this is best-effort, not a durability guarantee; see
|
|
43
|
+
* {@link RoutedAgents.dispose}.
|
|
44
|
+
*
|
|
45
|
+
* Pick a `route` that cannot appear as a literal path segment elsewhere
|
|
46
|
+
* under the owner (its own name, another route, or a path the owner's own
|
|
47
|
+
* `onRequest` handles) — forwarding matches every occurrence of the route
|
|
48
|
+
* segment in the path, so a coincidental match with no active entry
|
|
49
|
+
* behind it is answered `404` instead of reaching the owner.
|
|
50
|
+
*
|
|
51
|
+
* A forwarded suffix is not searched for a `/sub/{class}/{name}` dynamic
|
|
52
|
+
* agents marker: `Agent.fetch()` resolves that marker against the OWNER's
|
|
53
|
+
* exported classes before this capability's `onRequest` ever runs, so a
|
|
54
|
+
* matching marker is served as a facet of the owner, not forwarded to the
|
|
55
|
+
* target. Address a target's own dynamic agents through a direct
|
|
56
|
+
* connection to that target, not through the owner's route.
|
|
57
|
+
*
|
|
58
|
+
* @experimental The API surface may change before stabilizing.
|
|
59
|
+
*/
|
|
60
|
+
declare class RoutedAgents<
|
|
61
|
+
TAgent extends Agent = Agent,
|
|
62
|
+
Metadata = unknown
|
|
63
|
+
> extends LifecycleCapability {
|
|
64
|
+
#private;
|
|
65
|
+
/**
|
|
66
|
+
* @param options - Target binding and the route segment this capability
|
|
67
|
+
* claims. Install with `this.lifecycle.use()` before startup.
|
|
68
|
+
*/
|
|
69
|
+
constructor(options: RoutedAgentsOptions<TAgent>);
|
|
70
|
+
/** Create an entry without waking the target Agent. */
|
|
71
|
+
create(
|
|
72
|
+
options?: RoutedAgentCreateOptions<Metadata>
|
|
73
|
+
): Promise<RoutedAgentEntry<Metadata>>;
|
|
74
|
+
/** Resolve an active entry to an initialized, typed Agent stub. */
|
|
75
|
+
get(id: string): Promise<DurableObjectStub<TAgent> | null>;
|
|
76
|
+
/**
|
|
77
|
+
* List active entries, most recently updated first. Entries whose
|
|
78
|
+
* `updatedAt` ties are ordered by actual write order, not by the
|
|
79
|
+
* random entry `id`.
|
|
80
|
+
*/
|
|
81
|
+
list(): Promise<ReadonlyArray<RoutedAgentEntry<Metadata>>>;
|
|
82
|
+
/** Replace an active entry's metadata. Returns false for unknown IDs. */
|
|
83
|
+
setMetadata(id: string, metadata: Metadata | null): Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Make an entry unreachable, condemn its Agent, then remove the row.
|
|
86
|
+
* Returns false for unknown IDs.
|
|
87
|
+
*
|
|
88
|
+
* The target is condemned through Agent's deferred teardown, which
|
|
89
|
+
* durably marks it and returns without aborting the isolate; its storage
|
|
90
|
+
* is wiped on its own next wake, moments later, and the marker survives
|
|
91
|
+
* interruption. A failed RPC leaves a hidden `deleting` row so a
|
|
92
|
+
* repeated call retries.
|
|
93
|
+
*/
|
|
94
|
+
delete(id: string): Promise<boolean>;
|
|
95
|
+
onStart(): void;
|
|
96
|
+
/**
|
|
97
|
+
* Condemn every remaining entry (including one already `deleting`, in
|
|
98
|
+
* case its own condemnation RPC never landed) when the owner itself is
|
|
99
|
+
* destroyed.
|
|
100
|
+
*
|
|
101
|
+
* `Agent.destroy()` disposes capabilities before it wipes its own
|
|
102
|
+
* storage, so the catalog is still readable here — without this, the
|
|
103
|
+
* catalog would vanish with the owner while every target it named kept
|
|
104
|
+
* running and billing storage, unreachable forever.
|
|
105
|
+
*
|
|
106
|
+
* This is best-effort, not a durability guarantee: `Agent.destroy()`
|
|
107
|
+
* wipes the owner's storage immediately after disposal regardless of
|
|
108
|
+
* whether any capability's `dispose()` reports failure, so a target
|
|
109
|
+
* that is still unreachable after retries here is orphaned for good —
|
|
110
|
+
* there is no later "repeated call retries" for a catalog row that no
|
|
111
|
+
* longer exists. Retrying briefly here converts the common transient
|
|
112
|
+
* failure into a condemned target instead of an orphan; it cannot
|
|
113
|
+
* convert a target that is durably unreachable.
|
|
114
|
+
*/
|
|
115
|
+
dispose(): Promise<void>;
|
|
116
|
+
/** Forward a matching HTTP request to the selected Agent. */
|
|
117
|
+
onRequest({
|
|
118
|
+
request
|
|
119
|
+
}: CapabilityRequestContext): Promise<Response | undefined>;
|
|
120
|
+
/** Forward a matching upgrade so the selected Agent owns the WebSocket. */
|
|
121
|
+
onWebSocketUpgrade({
|
|
122
|
+
request
|
|
123
|
+
}: CapabilityWebSocketUpgradeContext): Promise<Response | undefined>;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export {
|
|
127
|
+
type AgentGetOptions,
|
|
128
|
+
type AgentOptions,
|
|
129
|
+
type RoutedAgentCreateOptions,
|
|
130
|
+
type RoutedAgentEntry,
|
|
131
|
+
RoutedAgents,
|
|
132
|
+
type RoutedAgentsOptions,
|
|
133
|
+
type RoutingRetryOptions,
|
|
134
|
+
getAgentByName,
|
|
135
|
+
routeAgentRequest
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=index.d.ts.map
|