agents 0.26.0 → 0.28.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 +8 -9
- package/dist/abortable-C1SpfVoi.js +21 -0
- package/dist/abortable-C1SpfVoi.js.map +1 -0
- package/dist/{agent-routing-0DR0Tk2v.d.ts → agent-routing-QDcchcQP.d.ts} +16 -16
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-YrATZJu2.d.ts → agent-tools-DHL2XI7u.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/ai-ahruGw7e.d.ts +239 -0
- package/dist/ai-sdk-Bdsf6mSA.js +2444 -0
- package/dist/ai-sdk-Bdsf6mSA.js.map +1 -0
- package/dist/apply-chunks-1FheRb9W.js +99 -0
- package/dist/apply-chunks-1FheRb9W.js.map +1 -0
- package/dist/broadcast-state-CEyI4uAK.js +69 -0
- package/dist/broadcast-state-CEyI4uAK.js.map +1 -0
- package/dist/browser/ai.d.ts +8 -227
- package/dist/browser/ai.js +259 -1
- package/dist/browser/ai.js.map +1 -0
- package/dist/browser/index.d.ts +56 -46
- package/dist/browser/index.js +4 -361
- package/dist/browser/{ai-sdk.d.ts → tools/ai-sdk.d.ts} +7 -7
- package/dist/browser/{ai-sdk.js → tools/ai-sdk.js} +6 -5
- package/dist/browser/tools/ai-sdk.js.map +1 -0
- package/dist/browser/tools/pi.d.ts +111 -0
- package/dist/browser/tools/pi.js +119 -0
- package/dist/browser/tools/pi.js.map +1 -0
- package/dist/browser/{tanstack-ai.d.ts → tools/tanstack-ai.d.ts} +39 -37
- package/dist/browser/{tanstack-ai.js → tools/tanstack-ai.js} +73 -59
- package/dist/browser/tools/tanstack-ai.js.map +1 -0
- package/dist/browser-CEdNlsaD.js +481 -0
- package/dist/browser-CEdNlsaD.js.map +1 -0
- package/dist/{browser-DE3d-RLk.d.ts → browser-D30VlpcA.d.ts} +63 -37
- package/dist/{browser-tool-CQ10e6Kj.js → browser-tool-4iomavs_.js} +198 -62
- package/dist/browser-tool-4iomavs_.js.map +1 -0
- package/dist/{browser-tool-DuNdlwZ9.d.ts → browser-tool-vtrPOV8e.d.ts} +25 -6
- package/dist/{capability-B4WbF81e.js → capability-DBAKc6UZ.js} +1 -3
- package/dist/capability-DBAKc6UZ.js.map +1 -0
- package/dist/{capability-runner-DBAnGr6L.d.ts → capability-runner-BiVUtjqC.d.ts} +9 -7
- package/dist/channel-Bak_6zDP.d.ts +333 -0
- package/dist/chat/index.d.ts +11 -9
- package/dist/chat/index.js +5 -194
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +2 -4
- package/dist/chat/react.js +3 -1
- package/dist/chat/react.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +144 -0
- package/dist/{client-je3koD9Z.d.ts → client-BGIQKJhX.d.ts} +2 -2
- package/dist/{client-fyQrLGkD.js → client-BnYbNT3z.js} +29 -5
- package/dist/client-BnYbNT3z.js.map +1 -0
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +1 -1
- package/dist/{connector-58XDGvc3.js → connector-CtZylXdk.js} +3 -603
- package/dist/connector-CtZylXdk.js.map +1 -0
- package/dist/{connector-Cl-VqPmA.d.ts → connector-DEXzVoaY.d.ts} +9 -374
- package/dist/conversations-FjHDRZyD.d.ts +192 -0
- package/dist/{current-agent-vAk_5JMt.d.ts → current-agent-CKgT5d1O.d.ts} +3 -5
- package/dist/current-agent-DhoDkSnH.js.map +1 -1
- package/dist/daemon-core-Buubj-y1.js +481 -0
- package/dist/daemon-core-Buubj-y1.js.map +1 -0
- package/dist/daemon-core-CbohVPAu.d.ts +132 -0
- package/dist/{do-oauth-client-provider-Tmf1vgKz.d.ts → do-oauth-client-provider-DfsUcpc3.d.ts} +7 -1
- package/dist/{durable-object-lifecycle-HhgOMWq9.d.ts → durable-object-lifecycle-DOI_bzCH.d.ts} +2 -2
- package/dist/{channels/email.d.ts → experimental/channels/email/index.d.ts} +25 -12
- package/dist/{channels/email.js → experimental/channels/email/index.js} +13 -35
- package/dist/experimental/channels/email/index.js.map +1 -0
- package/dist/experimental/channels/index.d.ts +270 -0
- package/dist/experimental/channels/index.js +1186 -0
- package/dist/experimental/channels/index.js.map +1 -0
- package/dist/{channels/slack.d.ts → experimental/channels/slack/index.d.ts} +19 -9
- package/dist/experimental/channels/slack/index.js +267 -0
- package/dist/experimental/channels/slack/index.js.map +1 -0
- package/dist/{channels/telegram.d.ts → experimental/channels/telegram/index.d.ts} +19 -9
- package/dist/experimental/channels/telegram/index.js +198 -0
- package/dist/experimental/channels/telegram/index.js.map +1 -0
- package/dist/experimental/channels/web/ai-sdk.d.ts +55 -0
- package/dist/experimental/channels/web/ai-sdk.js +266 -0
- package/dist/experimental/channels/web/ai-sdk.js.map +1 -0
- package/dist/experimental/channels/web/client.d.ts +97 -0
- package/dist/experimental/channels/web/client.js +349 -0
- package/dist/experimental/channels/web/client.js.map +1 -0
- package/dist/experimental/channels/web/index.d.ts +189 -0
- package/dist/experimental/channels/web/index.js +332 -0
- package/dist/experimental/channels/web/index.js.map +1 -0
- package/dist/gateway-BFO6vRDn.d.ts +100 -0
- package/dist/handle-zAzh0UW2.d.ts +460 -0
- package/dist/harness/ai-sdk/index.d.ts +175 -0
- package/dist/harness/ai-sdk/index.js +605 -0
- package/dist/harness/ai-sdk/index.js.map +1 -0
- package/dist/harness/container/daemon/index.d.ts +76 -0
- package/dist/harness/container/daemon/index.js +3 -0
- package/dist/harness/container/index.d.ts +740 -0
- package/dist/harness/container/index.js +2152 -0
- package/dist/harness/container/index.js.map +1 -0
- package/dist/harness/container/runtime/index.d.ts +144 -0
- package/dist/harness/container/runtime/index.js +1082 -0
- package/dist/harness/container/runtime/index.js.map +1 -0
- package/dist/harness/opencode/index.d.ts +369 -0
- package/dist/harness/opencode/index.js +1072 -0
- package/dist/harness/opencode/index.js.map +1 -0
- package/dist/harness/pi/index.d.ts +3 -2
- package/dist/harness/pi/index.js +3 -3
- package/dist/harness/store/index.d.ts +24 -0
- package/dist/harness/store/index.js +2 -0
- package/dist/harness/think/index.d.ts +821 -0
- package/dist/harness/think/index.js +2721 -0
- package/dist/harness/think/index.js.map +1 -0
- package/dist/harness-B6B9S3rb.d.ts +203 -0
- package/dist/{images-iiTDSyRu.js → images-Ce9SCu3X.js} +2 -248
- package/dist/images-Ce9SCu3X.js.map +1 -0
- package/dist/{index-BreL0YrQ.d.ts → index-BEL3ZV-s.d.ts} +2 -6
- package/dist/index-CBt8oRJt.d.ts +198 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/ingress-EFbrS547.js +9 -0
- package/dist/ingress-EFbrS547.js.map +1 -0
- package/dist/input-schema-BQykTEp_.js +20 -0
- package/dist/input-schema-BQykTEp_.js.map +1 -0
- package/dist/input-schema-D2QAtHZH.js +23 -0
- package/dist/input-schema-D2QAtHZH.js.map +1 -0
- package/dist/internal-kmw70xwR.js +46 -0
- package/dist/internal-kmw70xwR.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +3 -3
- package/dist/lifecycle/index.js +2 -2
- package/dist/{lifecycle-CFu6OV03.js → lifecycle-DGorpAmA.js} +2 -4
- package/dist/lifecycle-DGorpAmA.js.map +1 -0
- package/dist/mcp/client/do-oauth-client-provider.d.ts +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js +9 -0
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/client/x402.d.ts +35 -4
- package/dist/mcp/client/x402.js +24 -3
- package/dist/mcp/client/x402.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +2 -2
- package/dist/models/ai-sdk/index.d.ts +16 -14
- package/dist/models/ai-sdk/index.js +1 -2442
- package/dist/models/opencode/index.d.ts +65 -0
- package/dist/models/opencode/index.js +285 -0
- package/dist/models/opencode/index.js.map +1 -0
- package/dist/models/pi-ai/index.d.ts +9 -7
- package/dist/models/pi-ai/index.js +2 -1
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/options-CiIsJHXR.d.ts +130 -0
- package/dist/protocol-BEgrJYC5.d.ts +331 -0
- package/dist/protocol-CukuI73M.d.ts +386 -0
- package/dist/protocol-cOklVBBD.js +227 -0
- package/dist/protocol-cOklVBBD.js.map +1 -0
- package/dist/{protocol-kHsr-f0X.js → protocol-eXeJxhgu.js} +2 -67
- package/dist/protocol-eXeJxhgu.js.map +1 -0
- package/dist/protocol-y2sSY584.js +148 -0
- package/dist/protocol-y2sSY584.js.map +1 -0
- package/dist/queue/index.d.ts +1 -1
- package/dist/queue/index.js +1 -1
- package/dist/{queue-BB-zfIYF.js → queue-CcIjVYTE.js} +2 -2
- package/dist/{queue-BB-zfIYF.js.map → queue-CcIjVYTE.js.map} +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +2 -2
- package/dist/routing/index.js +1 -1
- package/dist/{scheduler-RdACNXDP.d.ts → scheduler-BCLESHSC.d.ts} +2 -12
- package/dist/{scheduler-DZ3xQfeK.js → scheduler-FIHv2-kJ.js} +2 -4
- package/dist/scheduler-FIHv2-kJ.js.map +1 -0
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/session-store-Do4xkDcf.js +606 -0
- package/dist/session-store-Do4xkDcf.js.map +1 -0
- package/dist/session-store-DoPY12xM.d.ts +383 -0
- package/dist/sessions/index.d.ts +11 -434
- package/dist/sessions/index.js +1 -2271
- package/dist/sessions-_vN5BEZX.js +2272 -0
- package/dist/sessions-_vN5BEZX.js.map +1 -0
- package/dist/settings-C45hDliX.js +253 -0
- package/dist/settings-C45hDliX.js.map +1 -0
- package/dist/{transport-PfFnt-Ld.d.ts → settings-NgGwF7Qt.d.ts} +14 -208
- package/dist/{src-BZeu-SCD.js → src-CQxJFv4l.js} +8 -8
- package/dist/src-CQxJFv4l.js.map +1 -0
- package/dist/stall-watchdog-BpRF2uwG.js +195 -0
- package/dist/stall-watchdog-BpRF2uwG.js.map +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.js +1 -3
- package/dist/state/index.js.map +1 -1
- package/dist/store-BKM4Xm9M.d.ts +332 -0
- package/dist/store-CslVzfN7.js +458 -0
- package/dist/store-CslVzfN7.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-DW-UCOUc.js → streams-lsHMJhRW.js} +17 -4
- package/dist/streams-lsHMJhRW.js.map +1 -0
- package/dist/{streams-Cmkj9QfW.d.ts → streams-vrCtbUIn.d.ts} +2 -2
- package/dist/sub-routing.d.ts +1 -1
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-68DESxf0.js → tasks-BN_5AWqI.js} +3 -2
- package/dist/tasks-BN_5AWqI.js.map +1 -0
- package/dist/{tasks-9YxYtByh.d.ts → tasks-E89KdwkS.d.ts} +2 -2
- package/dist/tool-CTp72Jb_.d.ts +278 -0
- package/dist/tool-CxKsvxjm.js +1126 -0
- package/dist/tool-CxKsvxjm.js.map +1 -0
- package/dist/tool-DoVberpN.d.ts +301 -0
- package/dist/tool-cQRkwSZZ.js +410 -0
- package/dist/tool-cQRkwSZZ.js.map +1 -0
- package/dist/tool-helpers-O5BDVEAY.js +58 -0
- package/dist/tool-helpers-O5BDVEAY.js.map +1 -0
- package/dist/transport-k2mFrtL8.d.ts +200 -0
- package/dist/turns-DCV3ycDl.js +317 -0
- package/dist/turns-DCV3ycDl.js.map +1 -0
- package/dist/voice/client.js +1 -1
- package/dist/voice/index.d.ts +2 -2
- package/dist/voice/index.js +1 -1
- package/dist/web/fetch/index.d.ts +89 -0
- package/dist/web/fetch/index.js +20 -0
- package/dist/web/fetch/index.js.map +1 -0
- package/dist/web/fetch/tools/ai-sdk.d.ts +80 -0
- package/dist/web/fetch/tools/ai-sdk.js +47 -0
- package/dist/web/fetch/tools/ai-sdk.js.map +1 -0
- package/dist/web/fetch/tools/pi.d.ts +84 -0
- package/dist/web/fetch/tools/pi.js +88 -0
- package/dist/web/fetch/tools/pi.js.map +1 -0
- package/dist/web/fetch/tools/tanstack-ai.d.ts +78 -0
- package/dist/web/fetch/tools/tanstack-ai.js +45 -0
- package/dist/web/fetch/tools/tanstack-ai.js.map +1 -0
- package/dist/web/search/index.d.ts +58 -0
- package/dist/web/search/index.js +2 -0
- package/dist/web/search/tools/ai-sdk.d.ts +77 -0
- package/dist/web/search/tools/ai-sdk.js +46 -0
- package/dist/web/search/tools/ai-sdk.js.map +1 -0
- package/dist/web/search/tools/pi.d.ts +82 -0
- package/dist/web/search/tools/pi.js +89 -0
- package/dist/web/search/tools/pi.js.map +1 -0
- package/dist/web/search/tools/tanstack-ai.d.ts +71 -0
- package/dist/web/search/tools/tanstack-ai.js +46 -0
- package/dist/web/search/tools/tanstack-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +12 -296
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-Ci-LZFHH.js → websockets-C3zy8YR0.js} +20 -8
- package/dist/websockets-C3zy8YR0.js.map +1 -0
- package/dist/{wire-types-CrjKvDyi.d.ts → wire-types-Cv0iwjk5.d.ts} +3 -51
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +1 -1
- package/docs/browse-the-web.md +29 -1
- package/docs/fetch-the-web.md +276 -0
- package/docs/harnesses/pi.md +25 -0
- package/docs/harnesses/think.md +215 -0
- package/docs/index.md +99 -75
- package/docs/lifecycle.md +42 -3
- package/docs/queue.md +2 -3
- package/docs/scheduling.md +2 -6
- package/docs/search-the-web.md +135 -0
- package/package.json +148 -44
- package/dist/ai-fm7xx1BK.js +0 -312
- package/dist/ai-fm7xx1BK.js.map +0 -1
- package/dist/browser/ai-sdk.js.map +0 -1
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/tanstack-ai.js.map +0 -1
- package/dist/browser-tool-CQ10e6Kj.js.map +0 -1
- package/dist/capability-B4WbF81e.js.map +0 -1
- package/dist/channel-Bnm4S7T2.d.ts +0 -491
- package/dist/channels/ai-sdk.d.ts +0 -46
- package/dist/channels/ai-sdk.js +0 -120
- package/dist/channels/ai-sdk.js.map +0 -1
- package/dist/channels/email.js.map +0 -1
- package/dist/channels/index.d.ts +0 -233
- package/dist/channels/index.js +0 -608
- package/dist/channels/index.js.map +0 -1
- package/dist/channels/slack.js +0 -614
- package/dist/channels/slack.js.map +0 -1
- package/dist/channels/tanstack-ai.d.ts +0 -39
- package/dist/channels/tanstack-ai.js +0 -17
- package/dist/channels/tanstack-ai.js.map +0 -1
- package/dist/channels/telegram.js +0 -427
- package/dist/channels/telegram.js.map +0 -1
- package/dist/channels/voice.d.ts +0 -45
- package/dist/channels/voice.js +0 -122
- package/dist/channels/voice.js.map +0 -1
- package/dist/client-fyQrLGkD.js.map +0 -1
- package/dist/connector-58XDGvc3.js.map +0 -1
- package/dist/images-iiTDSyRu.js.map +0 -1
- package/dist/index-BB0kqhIz.d.ts +0 -101
- package/dist/ingress-BfetZbMO.js +0 -83
- package/dist/ingress-BfetZbMO.js.map +0 -1
- package/dist/internal-CYlgHl1l.js +0 -59
- package/dist/internal-CYlgHl1l.js.map +0 -1
- package/dist/lifecycle-CFu6OV03.js.map +0 -1
- package/dist/models/ai-sdk/index.js.map +0 -1
- package/dist/protocol-kHsr-f0X.js.map +0 -1
- package/dist/scheduler-DZ3xQfeK.js.map +0 -1
- package/dist/sessions/index.js.map +0 -1
- package/dist/src-BZeu-SCD.js.map +0 -1
- package/dist/streams-DW-UCOUc.js.map +0 -1
- package/dist/surface-bZZJqBka.js +0 -17
- package/dist/surface-bZZJqBka.js.map +0 -1
- package/dist/tasks-68DESxf0.js.map +0 -1
- package/dist/tool-schema-CBjGPrsQ.js +0 -31
- package/dist/tool-schema-CBjGPrsQ.js.map +0 -1
- package/dist/websockets-Ci-LZFHH.js.map +0 -1
- package/docs/channels.md +0 -323
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { t as CHAT_MESSAGE_TYPES } from "./protocol-eXeJxhgu.js";
|
|
2
|
+
import { jsonSchema, tool } from "ai";
|
|
3
|
+
//#region src/chat/client-tools.ts
|
|
4
|
+
/**
|
|
5
|
+
* Converts client tool schemas to AI SDK tool format.
|
|
6
|
+
*
|
|
7
|
+
* By default these tools have no `execute` function — when the AI model calls
|
|
8
|
+
* them, the tool call is sent back to the client for execution.
|
|
9
|
+
*
|
|
10
|
+
* When `options.execute` is provided, each tool is built WITH an `execute` that
|
|
11
|
+
* delegates to it. This is used by the RPC path (e.g. a parent agent driving a
|
|
12
|
+
* Think sub-agent) so the model's client-tool call is resolved inline within
|
|
13
|
+
* the same turn.
|
|
14
|
+
*
|
|
15
|
+
* @param clientTools - Array of tool schemas from the client
|
|
16
|
+
* @param options - Optional `execute` delegate to run the tools inline
|
|
17
|
+
* @returns Record of AI SDK tools that can be spread into your tools object
|
|
18
|
+
*/
|
|
19
|
+
function createToolsFromClientSchemas(clientTools, options) {
|
|
20
|
+
if (!clientTools || clientTools.length === 0) return {};
|
|
21
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const t of clientTools) {
|
|
23
|
+
if (seenNames.has(t.name)) console.warn(`[createToolsFromClientSchemas] Duplicate tool name "${t.name}" found. Later definitions will override earlier ones.`);
|
|
24
|
+
seenNames.add(t.name);
|
|
25
|
+
}
|
|
26
|
+
const execute = options?.execute;
|
|
27
|
+
const createTool = tool;
|
|
28
|
+
return Object.fromEntries(clientTools.map((t) => [t.name, createTool({
|
|
29
|
+
description: typeof t.description === "function" ? "" : t.description ?? "",
|
|
30
|
+
inputSchema: jsonSchema(t.parameters ?? { type: "object" }),
|
|
31
|
+
...execute ? { execute: (input, executeOptions) => execute({
|
|
32
|
+
toolName: t.name,
|
|
33
|
+
input,
|
|
34
|
+
toolCallId: executeOptions?.toolCallId ?? ""
|
|
35
|
+
}) } : {}
|
|
36
|
+
})]));
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/chat/parse-protocol.ts
|
|
40
|
+
/**
|
|
41
|
+
* Protocol Message Parser — typed parsing of cf_agent_chat_* WebSocket messages.
|
|
42
|
+
*
|
|
43
|
+
* Parses raw WebSocket messages into a discriminated union of protocol events.
|
|
44
|
+
* Both AIChatAgent and Think can use this instead of manual JSON.parse + type checking.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Parse a raw WebSocket message string into a typed protocol event.
|
|
48
|
+
*
|
|
49
|
+
* Returns `null` if the message is not valid JSON or not a recognized
|
|
50
|
+
* protocol message type. Callers should fall through to the user's
|
|
51
|
+
* `onMessage` handler when `null` is returned.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const event = parseProtocolMessage(rawMessage);
|
|
56
|
+
* if (!event) return userOnMessage(connection, rawMessage);
|
|
57
|
+
*
|
|
58
|
+
* switch (event.type) {
|
|
59
|
+
* case "chat-request": { ... }
|
|
60
|
+
* case "clear": { ... }
|
|
61
|
+
* case "tool-result": { ... }
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
function parseProtocolMessage(raw) {
|
|
66
|
+
let data;
|
|
67
|
+
try {
|
|
68
|
+
data = JSON.parse(raw);
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const wireType = data.type;
|
|
73
|
+
if (!wireType) return null;
|
|
74
|
+
switch (wireType) {
|
|
75
|
+
case CHAT_MESSAGE_TYPES.USE_CHAT_REQUEST: return {
|
|
76
|
+
type: "chat-request",
|
|
77
|
+
id: data.id,
|
|
78
|
+
init: data.init ?? {}
|
|
79
|
+
};
|
|
80
|
+
case CHAT_MESSAGE_TYPES.CHAT_CLEAR: return { type: "clear" };
|
|
81
|
+
case CHAT_MESSAGE_TYPES.CHAT_REQUEST_CANCEL: return {
|
|
82
|
+
type: "cancel",
|
|
83
|
+
id: data.id
|
|
84
|
+
};
|
|
85
|
+
case CHAT_MESSAGE_TYPES.TOOL_RESULT: return {
|
|
86
|
+
type: "tool-result",
|
|
87
|
+
toolCallId: data.toolCallId,
|
|
88
|
+
toolName: data.toolName ?? "",
|
|
89
|
+
output: data.output,
|
|
90
|
+
state: data.state,
|
|
91
|
+
errorText: data.errorText,
|
|
92
|
+
autoContinue: data.autoContinue,
|
|
93
|
+
clientTools: data.clientTools
|
|
94
|
+
};
|
|
95
|
+
case CHAT_MESSAGE_TYPES.TOOL_APPROVAL: return {
|
|
96
|
+
type: "tool-approval",
|
|
97
|
+
toolCallId: data.toolCallId,
|
|
98
|
+
approved: data.approved,
|
|
99
|
+
autoContinue: data.autoContinue
|
|
100
|
+
};
|
|
101
|
+
case CHAT_MESSAGE_TYPES.STREAM_RESUME_REQUEST: return {
|
|
102
|
+
type: "stream-resume-request",
|
|
103
|
+
...typeof data.probeId === "string" ? { probeId: data.probeId } : {}
|
|
104
|
+
};
|
|
105
|
+
case CHAT_MESSAGE_TYPES.STREAM_RESUME_ACK: return {
|
|
106
|
+
type: "stream-resume-ack",
|
|
107
|
+
id: data.id
|
|
108
|
+
};
|
|
109
|
+
case CHAT_MESSAGE_TYPES.CHAT_MESSAGES: return {
|
|
110
|
+
type: "messages",
|
|
111
|
+
messages: data.messages ?? []
|
|
112
|
+
};
|
|
113
|
+
default: return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/chat/stall-watchdog.ts
|
|
118
|
+
/**
|
|
119
|
+
* Shared inactivity watchdog for UI-message streams.
|
|
120
|
+
*
|
|
121
|
+
* A model/transport stream can park indefinitely without ever throwing (a hung
|
|
122
|
+
* provider, a wedged transport). Left unguarded, the consumer read-loop waits
|
|
123
|
+
* forever. {@link iterateWithStallWatchdog} wraps such a stream so that a gap of
|
|
124
|
+
* `timeoutMs` between chunks aborts the upstream and throws
|
|
125
|
+
* {@link ChatStreamStalledError}, letting the consumer route the stall into
|
|
126
|
+
* bounded recovery (#1626) — a transient hang is retried within the existing
|
|
127
|
+
* recovery budget — while genuine in-band errors stay terminal.
|
|
128
|
+
*
|
|
129
|
+
* @internal Sibling-package support for `@cloudflare/ai-chat` and
|
|
130
|
+
* `@cloudflare/think`, not a public API. See
|
|
131
|
+
* `design/rfc-chat-recovery-foundation.md`.
|
|
132
|
+
*/
|
|
133
|
+
/**
|
|
134
|
+
* Thrown by {@link iterateWithStallWatchdog} when the inactivity watchdog fires
|
|
135
|
+
* (a model/transport stream that parks without ever throwing). Distinct from
|
|
136
|
+
* in-band model/stream errors so the read-loop catch can route a stall into
|
|
137
|
+
* bounded recovery (#1626) — a transient hang is retried within the existing
|
|
138
|
+
* recovery budget — while genuine errors stay terminal.
|
|
139
|
+
*/
|
|
140
|
+
var ChatStreamStalledError = class extends Error {
|
|
141
|
+
constructor(message) {
|
|
142
|
+
super(message);
|
|
143
|
+
this.isChatStreamStall = true;
|
|
144
|
+
this.name = "ChatStreamStalledError";
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Wrap a UI-message stream with an inactivity watchdog. If no chunk arrives
|
|
149
|
+
* within `timeoutMs`, `onStall` runs (aborting the upstream model stream) and
|
|
150
|
+
* the iterator throws, so the consumer loop exits with a terminal error
|
|
151
|
+
* instead of parking forever on a hung provider/transport. `timeoutMs <= 0`
|
|
152
|
+
* passes the source through untouched.
|
|
153
|
+
*/
|
|
154
|
+
async function* iterateWithStallWatchdog(source, timeoutMs, onStall) {
|
|
155
|
+
if (!(timeoutMs > 0)) {
|
|
156
|
+
yield* source;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
160
|
+
let selfAborted = false;
|
|
161
|
+
try {
|
|
162
|
+
while (true) {
|
|
163
|
+
let timer;
|
|
164
|
+
let stalled = false;
|
|
165
|
+
const stall = new Promise((_, reject) => {
|
|
166
|
+
timer = setTimeout(() => {
|
|
167
|
+
stalled = true;
|
|
168
|
+
reject(new ChatStreamStalledError(`Chat stream stalled: no activity for ${timeoutMs}ms; the turn was aborted by the stall watchdog.`));
|
|
169
|
+
}, timeoutMs);
|
|
170
|
+
});
|
|
171
|
+
const nextPromise = iterator.next();
|
|
172
|
+
nextPromise.catch(() => {});
|
|
173
|
+
let next;
|
|
174
|
+
try {
|
|
175
|
+
next = await Promise.race([nextPromise, stall]);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (stalled) {
|
|
178
|
+
selfAborted = true;
|
|
179
|
+
onStall();
|
|
180
|
+
}
|
|
181
|
+
throw err;
|
|
182
|
+
} finally {
|
|
183
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
184
|
+
}
|
|
185
|
+
if (next.done) return;
|
|
186
|
+
yield next.value;
|
|
187
|
+
}
|
|
188
|
+
} finally {
|
|
189
|
+
if (!selfAborted) await iterator.return?.(void 0).catch(() => {});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
export { createToolsFromClientSchemas as i, iterateWithStallWatchdog as n, parseProtocolMessage as r, ChatStreamStalledError as t };
|
|
194
|
+
|
|
195
|
+
//# sourceMappingURL=stall-watchdog-BpRF2uwG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stall-watchdog-BpRF2uwG.js","names":[],"sources":["../src/chat/client-tools.ts","../src/chat/parse-protocol.ts","../src/chat/stall-watchdog.ts"],"sourcesContent":["/**\n * Client tool schema handling for the cf_agent_chat protocol.\n *\n * Converts client-provided tool schemas (JSON wire format) into AI SDK\n * tool definitions. By default these tools have no `execute` function —\n * when the model calls them, the tool call is sent back to the client.\n *\n * When an `execute` delegate is supplied (e.g. a parent agent that drives a\n * Think sub-agent over RPC and can run the client tools itself), the tools are\n * built WITH an `execute` so the model's call is resolved inline within the\n * same turn instead of being surfaced as a dangling tool call.\n *\n * Used by both @cloudflare/ai-chat and @cloudflare/think.\n */\n\nimport type { JSONSchema7, Tool, ToolSet } from \"ai\";\nimport { tool, jsonSchema } from \"ai\";\n\n/**\n * Wire-format tool schema sent from the client.\n * Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema`\n * because Zod schemas cannot be serialized over the wire.\n */\nexport type ClientToolSchema = {\n /** Unique name for the tool */\n name: string;\n /** Human-readable description of what the tool does */\n description?: Tool[\"description\"];\n /** JSON Schema defining the tool's input parameters */\n parameters?: JSONSchema7;\n};\n\n/**\n * Executes a client-defined tool and returns its output.\n *\n * Used for the RPC path (e.g. a parent agent delegating to a Think sub-agent)\n * where the caller can run the client tools itself, rather than the\n * browser/WebSocket path where results are sent back asynchronously.\n */\nexport type ClientToolExecutor = (call: {\n /** The name of the client tool the model called. */\n toolName: string;\n /** The model-generated input for the tool call. */\n input: unknown;\n /** The AI SDK tool-call id for the invocation. */\n toolCallId: string;\n}) => unknown | Promise<unknown>;\n\n/**\n * Converts client tool schemas to AI SDK tool format.\n *\n * By default these tools have no `execute` function — when the AI model calls\n * them, the tool call is sent back to the client for execution.\n *\n * When `options.execute` is provided, each tool is built WITH an `execute` that\n * delegates to it. This is used by the RPC path (e.g. a parent agent driving a\n * Think sub-agent) so the model's client-tool call is resolved inline within\n * the same turn.\n *\n * @param clientTools - Array of tool schemas from the client\n * @param options - Optional `execute` delegate to run the tools inline\n * @returns Record of AI SDK tools that can be spread into your tools object\n */\nexport function createToolsFromClientSchemas(\n clientTools?: ClientToolSchema[],\n options?: { execute?: ClientToolExecutor }\n): ToolSet {\n if (!clientTools || clientTools.length === 0) {\n return {};\n }\n\n const seenNames = new Set<string>();\n for (const t of clientTools) {\n if (seenNames.has(t.name)) {\n console.warn(\n `[createToolsFromClientSchemas] Duplicate tool name \"${t.name}\" found. Later definitions will override earlier ones.`\n );\n }\n seenNames.add(t.name);\n }\n\n const execute = options?.execute;\n // The AI SDK's `tool()` overloads infer the input type as `never` when an\n // `execute` is combined with a runtime `jsonSchema(...)`. Cast to a\n // permissive signature (same approach as `agentTool()`), since the wire\n // schema is untyped JSON.\n const createTool = tool as unknown as (config: {\n description: string | ((options: unknown) => string);\n inputSchema: ReturnType<typeof jsonSchema>;\n execute?: (\n input: unknown,\n executeOptions?: { toolCallId?: string }\n ) => unknown | Promise<unknown>;\n }) => Tool;\n\n return Object.fromEntries(\n clientTools.map((t) => [\n t.name,\n createTool({\n description:\n typeof t.description === \"function\" ? \"\" : (t.description ?? \"\"),\n inputSchema: jsonSchema(t.parameters ?? { type: \"object\" }),\n ...(execute\n ? {\n execute: (\n input: unknown,\n executeOptions?: { toolCallId?: string }\n ) =>\n execute({\n toolName: t.name,\n input,\n toolCallId: executeOptions?.toolCallId ?? \"\"\n })\n }\n : {})\n })\n ])\n );\n}\n","/**\n * Protocol Message Parser — typed parsing of cf_agent_chat_* WebSocket messages.\n *\n * Parses raw WebSocket messages into a discriminated union of protocol events.\n * Both AIChatAgent and Think can use this instead of manual JSON.parse + type checking.\n */\n\nimport { CHAT_MESSAGE_TYPES } from \"./protocol\";\n\n/**\n * Discriminated union of all incoming chat protocol events.\n *\n * Each agent handles the events it cares about and ignores the rest.\n * Returns `null` for non-JSON messages or unrecognized types.\n */\nexport type ChatProtocolEvent =\n | {\n type: \"chat-request\";\n id: string;\n init: { method?: string; body?: string; [key: string]: unknown };\n }\n | { type: \"clear\" }\n | { type: \"cancel\"; id: string }\n | {\n type: \"tool-result\";\n toolCallId: string;\n toolName: string;\n output: unknown;\n state?: string;\n errorText?: string;\n autoContinue?: boolean;\n clientTools?: Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>;\n }\n | {\n type: \"tool-approval\";\n toolCallId: string;\n approved: boolean;\n autoContinue?: boolean;\n }\n | { type: \"stream-resume-request\"; probeId?: string }\n | { type: \"stream-resume-ack\"; id: string }\n | { type: \"messages\"; messages: unknown[] };\n\n/**\n * Parse a raw WebSocket message string into a typed protocol event.\n *\n * Returns `null` if the message is not valid JSON or not a recognized\n * protocol message type. Callers should fall through to the user's\n * `onMessage` handler when `null` is returned.\n *\n * @example\n * ```typescript\n * const event = parseProtocolMessage(rawMessage);\n * if (!event) return userOnMessage(connection, rawMessage);\n *\n * switch (event.type) {\n * case \"chat-request\": { ... }\n * case \"clear\": { ... }\n * case \"tool-result\": { ... }\n * }\n * ```\n */\nexport function parseProtocolMessage(raw: string): ChatProtocolEvent | null {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n\n const wireType = data.type as string | undefined;\n if (!wireType) return null;\n\n switch (wireType) {\n case CHAT_MESSAGE_TYPES.USE_CHAT_REQUEST:\n return {\n type: \"chat-request\",\n id: data.id as string,\n init: (data.init as { method?: string; body?: string }) ?? {}\n };\n\n case CHAT_MESSAGE_TYPES.CHAT_CLEAR:\n return { type: \"clear\" };\n\n case CHAT_MESSAGE_TYPES.CHAT_REQUEST_CANCEL:\n return { type: \"cancel\", id: data.id as string };\n\n case CHAT_MESSAGE_TYPES.TOOL_RESULT:\n return {\n type: \"tool-result\",\n toolCallId: data.toolCallId as string,\n toolName: (data.toolName as string) ?? \"\",\n output: data.output,\n state: data.state as string | undefined,\n errorText: data.errorText as string | undefined,\n autoContinue: data.autoContinue as boolean | undefined,\n clientTools: data.clientTools as\n | Array<{\n name: string;\n description?: string;\n parameters?: unknown;\n }>\n | undefined\n };\n\n case CHAT_MESSAGE_TYPES.TOOL_APPROVAL:\n return {\n type: \"tool-approval\",\n toolCallId: data.toolCallId as string,\n approved: data.approved as boolean,\n autoContinue: data.autoContinue as boolean | undefined\n };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_REQUEST:\n return {\n type: \"stream-resume-request\",\n ...(typeof data.probeId === \"string\" ? { probeId: data.probeId } : {})\n };\n\n case CHAT_MESSAGE_TYPES.STREAM_RESUME_ACK:\n return {\n type: \"stream-resume-ack\",\n id: data.id as string\n };\n\n case CHAT_MESSAGE_TYPES.CHAT_MESSAGES:\n return {\n type: \"messages\",\n messages: (data.messages as unknown[]) ?? []\n };\n\n default:\n return null;\n }\n}\n","/**\n * Shared inactivity watchdog for UI-message streams.\n *\n * A model/transport stream can park indefinitely without ever throwing (a hung\n * provider, a wedged transport). Left unguarded, the consumer read-loop waits\n * forever. {@link iterateWithStallWatchdog} wraps such a stream so that a gap of\n * `timeoutMs` between chunks aborts the upstream and throws\n * {@link ChatStreamStalledError}, letting the consumer route the stall into\n * bounded recovery (#1626) — a transient hang is retried within the existing\n * recovery budget — while genuine in-band errors stay terminal.\n *\n * @internal Sibling-package support for `@cloudflare/ai-chat` and\n * `@cloudflare/think`, not a public API. See\n * `design/rfc-chat-recovery-foundation.md`.\n */\n\n/**\n * Thrown by {@link iterateWithStallWatchdog} when the inactivity watchdog fires\n * (a model/transport stream that parks without ever throwing). Distinct from\n * in-band model/stream errors so the read-loop catch can route a stall into\n * bounded recovery (#1626) — a transient hang is retried within the existing\n * recovery budget — while genuine errors stay terminal.\n */\nexport class ChatStreamStalledError extends Error {\n readonly isChatStreamStall = true;\n constructor(message: string) {\n super(message);\n this.name = \"ChatStreamStalledError\";\n }\n}\n\n/**\n * Wrap a UI-message stream with an inactivity watchdog. If no chunk arrives\n * within `timeoutMs`, `onStall` runs (aborting the upstream model stream) and\n * the iterator throws, so the consumer loop exits with a terminal error\n * instead of parking forever on a hung provider/transport. `timeoutMs <= 0`\n * passes the source through untouched.\n */\nexport async function* iterateWithStallWatchdog<T>(\n source: AsyncIterable<T>,\n timeoutMs: number,\n onStall: () => void\n): AsyncGenerator<T> {\n if (!(timeoutMs > 0)) {\n yield* source;\n return;\n }\n const iterator = source[Symbol.asyncIterator]();\n // Tracks whether the watchdog itself aborted the upstream. In that case we\n // must NOT also `iterator.return()` it: cancelling the readable after the\n // abort makes the AI SDK pipeline write to an already-cancelled readable\n // (\"readable side is no longer readable\"). Letting the abort error the\n // stream is the clean path.\n let selfAborted = false;\n try {\n while (true) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let stalled = false;\n const stall = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n stalled = true;\n reject(\n new ChatStreamStalledError(\n `Chat stream stalled: no activity for ${timeoutMs}ms; the turn was aborted by the stall watchdog.`\n )\n );\n }, timeoutMs);\n });\n const nextPromise = iterator.next();\n // If the watchdog wins the race we abandon this read; aborting the\n // upstream stream makes it reject later, so pre-attach a no-op catch to\n // keep that abandoned rejection from surfacing as an unhandled rejection.\n nextPromise.catch(() => {});\n let next: IteratorResult<T>;\n try {\n next = await Promise.race([nextPromise, stall]);\n } catch (err) {\n if (stalled) {\n selfAborted = true;\n onStall();\n }\n throw err;\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n if (next.done) return;\n yield next.value;\n }\n } finally {\n // Forward early termination (consumer `break`/`throw`, e.g. an in-band\n // stream error where the abort signal is NOT set) to the source so its\n // reader is cancelled — otherwise the wrapped source would leak when the\n // consumer stops reading mid-stream. Skipped after a watchdog stall, which\n // already aborted the upstream (see `selfAborted` above).\n if (!selfAborted) {\n await iterator.return?.(undefined as never).catch(() => {});\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+DA,SAAgB,6BACd,aACA,SACS;CACT,IAAI,CAAC,eAAe,YAAY,WAAW,GACzC,OAAO,CAAC;CAGV,MAAM,4BAAY,IAAI,IAAY;CAClC,KAAK,MAAM,KAAK,aAAa;EAC3B,IAAI,UAAU,IAAI,EAAE,IAAI,GACtB,QAAQ,KACN,uDAAuD,EAAE,KAAK,uDAChE;EAEF,UAAU,IAAI,EAAE,IAAI;CACtB;CAEA,MAAM,UAAU,SAAS;CAKzB,MAAM,aAAa;CASnB,OAAO,OAAO,YACZ,YAAY,KAAK,MAAM,CACrB,EAAE,MACF,WAAW;EACT,aACE,OAAO,EAAE,gBAAgB,aAAa,KAAM,EAAE,eAAe;EAC/D,aAAa,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;EAC1D,GAAI,UACA,EACE,UACE,OACA,mBAEA,QAAQ;GACN,UAAU,EAAE;GACZ;GACA,YAAY,gBAAgB,cAAc;EAC5C,CAAC,EACL,IACA,CAAC;CACP,CAAC,CACH,CAAC,CACH;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACpDA,SAAgB,qBAAqB,KAAuC;CAC1E,IAAI;CACJ,IAAI;EACF,OAAO,KAAK,MAAM,GAAG;CACvB,QAAQ;EACN,OAAO;CACT;CAEA,MAAM,WAAW,KAAK;CACtB,IAAI,CAAC,UAAU,OAAO;CAEtB,QAAQ,UAAR;EACE,KAAK,mBAAmB,kBACtB,OAAO;GACL,MAAM;GACN,IAAI,KAAK;GACT,MAAO,KAAK,QAA+C,CAAC;EAC9D;EAEF,KAAK,mBAAmB,YACtB,OAAO,EAAE,MAAM,QAAQ;EAEzB,KAAK,mBAAmB,qBACtB,OAAO;GAAE,MAAM;GAAU,IAAI,KAAK;EAAa;EAEjD,KAAK,mBAAmB,aACtB,OAAO;GACL,MAAM;GACN,YAAY,KAAK;GACjB,UAAW,KAAK,YAAuB;GACvC,QAAQ,KAAK;GACb,OAAO,KAAK;GACZ,WAAW,KAAK;GAChB,cAAc,KAAK;GACnB,aAAa,KAAK;EAOpB;EAEF,KAAK,mBAAmB,eACtB,OAAO;GACL,MAAM;GACN,YAAY,KAAK;GACjB,UAAU,KAAK;GACf,cAAc,KAAK;EACrB;EAEF,KAAK,mBAAmB,uBACtB,OAAO;GACL,MAAM;GACN,GAAI,OAAO,KAAK,YAAY,WAAW,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;EACtE;EAEF,KAAK,mBAAmB,mBACtB,OAAO;GACL,MAAM;GACN,IAAI,KAAK;EACX;EAEF,KAAK,mBAAmB,eACtB,OAAO;GACL,MAAM;GACN,UAAW,KAAK,YAA0B,CAAC;EAC7C;EAEF,SACE,OAAO;CACX;AACF;;;;;;;;;;;;;;;;;;;;;;;;;ACnHA,IAAa,yBAAb,cAA4C,MAAM;CAEhD,YAAY,SAAiB;EAC3B,MAAM,OAAO;EAFf,KAAS,oBAAoB;EAG3B,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,gBAAuB,yBACrB,QACA,WACA,SACmB;CACnB,IAAI,EAAE,YAAY,IAAI;EACpB,OAAO;EACP;CACF;CACA,MAAM,WAAW,OAAO,OAAO,cAAc,CAAC;CAM9C,IAAI,cAAc;CAClB,IAAI;EACF,OAAO,MAAM;GACX,IAAI;GACJ,IAAI,UAAU;GACd,MAAM,QAAQ,IAAI,SAAgB,GAAG,WAAW;IAC9C,QAAQ,iBAAiB;KACvB,UAAU;KACV,OACE,IAAI,uBACF,wCAAwC,UAAU,gDACpD,CACF;IACF,GAAG,SAAS;GACd,CAAC;GACD,MAAM,cAAc,SAAS,KAAK;GAIlC,YAAY,YAAY,CAAC,CAAC;GAC1B,IAAI;GACJ,IAAI;IACF,OAAO,MAAM,QAAQ,KAAK,CAAC,aAAa,KAAK,CAAC;GAChD,SAAS,KAAK;IACZ,IAAI,SAAS;KACX,cAAc;KACd,QAAQ;IACV;IACA,MAAM;GACR,UAAU;IACR,IAAI,UAAU,KAAA,GAAW,aAAa,KAAK;GAC7C;GACA,IAAI,KAAK,MAAM;GACf,MAAM,KAAK;EACb;CACF,UAAU;EAMR,IAAI,CAAC,aACH,MAAM,SAAS,SAAS,KAAA,CAAkB,CAAC,CAAC,YAAY,CAAC,CAAC;CAE9D;AACF"}
|
package/dist/state/index.d.ts
CHANGED
package/dist/state/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js";
|
|
2
2
|
import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
3
|
-
import { t as LifecycleCapability } from "../capability-
|
|
3
|
+
import { t as LifecycleCapability } from "../capability-DBAKc6UZ.js";
|
|
4
4
|
//#region src/state/index.ts
|
|
5
5
|
/**
|
|
6
6
|
* Namespaced KV key holding this capability's schema version. Kept separate
|
|
@@ -33,8 +33,6 @@ var _State_brand = /* @__PURE__ */ new WeakSet();
|
|
|
33
33
|
* validated persistence. Install the instance with `Lifecycle.use()`. State
|
|
34
34
|
* validation and the post-change notification hook stay on the host, which
|
|
35
35
|
* injects both callbacks. This capability never touches connections.
|
|
36
|
-
*
|
|
37
|
-
* @experimental The API surface may change before stabilizing.
|
|
38
36
|
*/
|
|
39
37
|
var State = class extends LifecycleCapability {
|
|
40
38
|
/**
|
package/dist/state/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/state/index.ts"],"sourcesContent":["import { LifecycleCapability } from \"../lifecycle/capability\";\nimport type { Connection } from \"../lifecycle/durable-object-lifecycle\";\n\n/**\n * Source of a state change: `\"server\"` for host code (e.g. `setState()`), or\n * the {@link Connection} the change arrived from. Hosts use it to exclude the\n * originating connection from a broadcast.\n */\nexport type StateChangeSource = Connection | \"server\";\n\n/**\n * Options for a {@link State} capability. Validation and the post-change hook\n * stay on the host, which passes them in; the capability owns storage and\n * change ordering.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport interface StateOptions<T = unknown> {\n /** Seeded on first access when nothing is stored. `undefined` seeds nothing. */\n readonly initialState?: T;\n\n /** Called after a change is validated and persisted. May be async. */\n readonly onChanged?: (\n state: T,\n source: StateChangeSource\n ) => void | Promise<void>;\n\n /**\n * Synchronous gating hook run before a change is persisted. Throw to reject\n * the change; the throw propagates to the caller of {@link State.set}.\n */\n readonly validateStateChange?: (\n nextState: T,\n source: StateChangeSource\n ) => void;\n}\n\n/**\n * Namespaced KV key holding this capability's schema version. Kept separate\n * from the host's global schema version so State owns its own migrations\n * without gating the host's DDL.\n */\nconst STATE_SCHEMA_VERSION_KEY = \"cf_agents:state_schema_version\";\nconst CURRENT_STATE_SCHEMA_VERSION = 1;\n\n/** Row id under which the single state value is stored in `cf_agents_state`. */\nconst STATE_ROW_ID = \"cf_state_row_id\";\n\n/**\n * Legacy row written by SDKs that predate the single-row state optimization.\n * Never written now; the v1 migration deletes it.\n */\nconst LEGACY_WAS_CHANGED_ROW_ID = \"cf_state_was_changed\";\n\n/**\n * Sentinel distinguishing \"state not yet loaded / never set\" from a stored\n * value. A distinct object reference means falsy states (null, 0, false, \"\")\n * still read back as set.\n */\nconst DEFAULT_STATE = {} as unknown;\n\n/**\n * Durable state storage for a Lifecycle Object.\n *\n * Owns the `cf_agents_state` state row, lazy load with an in-memory cache, and\n * validated persistence. Install the instance with `Lifecycle.use()`. State\n * validation and the post-change notification hook stay on the host, which\n * injects both callbacks. This capability never touches connections.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport class State<T = unknown> extends LifecycleCapability {\n #state = DEFAULT_STATE as T;\n #tableEnsured = false;\n readonly #options: StateOptions<T>;\n\n /**\n * Create a durable state capability.\n *\n * @param options - Optional initial state and a synchronous validation hook\n * injected by the host.\n */\n constructor(options: StateOptions<T> = {}) {\n super(\"state\");\n this.#options = options;\n }\n\n // ── Lifecycle capability hooks ─────────────────────────────────────────────\n\n /** Initialize state storage during Lifecycle startup. */\n async onStart(): Promise<void> {\n const version =\n (await this.lifecycle.storage.get<number>(STATE_SCHEMA_VERSION_KEY)) ?? 0;\n if (version >= CURRENT_STATE_SCHEMA_VERSION) {\n this.#tableEnsured = true;\n return;\n }\n\n // v1: own the table and clear the legacy wasChanged row left behind by\n // pre-optimization SDKs (state itself lives in STATE_ROW_ID).\n this.#ensureTable();\n this.lifecycle.storage.sql.exec(\n \"DELETE FROM cf_agents_state WHERE id = ?\",\n LEGACY_WAS_CHANGED_ROW_ID\n );\n await this.lifecycle.storage.put(\n STATE_SCHEMA_VERSION_KEY,\n CURRENT_STATE_SCHEMA_VERSION\n );\n }\n\n #ensureTable(): void {\n if (this.#tableEnsured) return;\n this.lifecycle.storage.sql.exec(`\n CREATE TABLE IF NOT EXISTS cf_agents_state (\n id TEXT PRIMARY KEY NOT NULL,\n state TEXT\n )\n `);\n this.#tableEnsured = true;\n }\n\n // ── State access ───────────────────────────────────────────────────────────\n\n /**\n * Current state.\n *\n * Loads lazily from storage on first access and caches in memory. Row\n * existence in `cf_agents_state` is the signal that state was previously\n * set, so falsy values persist correctly. On a corrupt row, falls back to\n * the initial state (re-persisting it) or clears the row.\n */\n get(): T | undefined {\n if (this.#state !== DEFAULT_STATE) {\n // state was previously set, and populated internal state\n return this.#state;\n }\n // looks like this is the first time the state is being accessed\n // check if the state was set in a previous life\n this.#ensureTable();\n const result = this.lifecycle.storage.sql\n .exec(\"SELECT state FROM cf_agents_state WHERE id = ?\", STATE_ROW_ID)\n .toArray() as { state: string | null }[];\n\n // Row existence is the signal that state was previously set.\n // This handles all values including falsy ones (null, 0, false, \"\").\n if (result.length > 0) {\n const state = result[0].state as string;\n\n try {\n this.#state = JSON.parse(state);\n } catch (e) {\n console.error(\n \"Failed to parse stored state, falling back to initialState:\",\n e\n );\n const initial = this.#options.initialState;\n if (initial !== undefined) {\n this.#state = initial;\n // Persist the fixed state to prevent future parse errors\n this.set(initial, \"server\");\n } else {\n // No initialState defined - clear corrupted data to prevent infinite retry loop\n this.lifecycle.storage.sql.exec(\n \"DELETE FROM cf_agents_state WHERE id = ?\",\n STATE_ROW_ID\n );\n return undefined;\n }\n }\n return this.#state;\n }\n\n // ok, this is the first time the state is being accessed\n // and the state was not set in a previous life\n // so we need to set the initial state (if provided)\n const initial = this.#options.initialState;\n if (initial === undefined) {\n // no initial state provided, so we return undefined\n return undefined;\n }\n // initial state provided, so we set the state,\n // update db and return the initial state\n this.set(initial, \"server\");\n return initial;\n }\n\n /**\n * Validate and persist a state change, then call the change hook.\n *\n * @param nextState - The new state to persist.\n * @param source - `\"server\"` for host-originated changes, or the originating\n * connection for client-originated changes.\n * @throws Whatever the injected `validateStateChange` throws.\n */\n set(nextState: T, source: StateChangeSource = \"server\"): void {\n // Validation/gating hook (sync only)\n this.#options.validateStateChange?.(nextState, source);\n this.#ensureTable();\n\n // Persist first, cache second: a value that fails to serialize or to\n // write must not be served by later get() calls. Row existence in\n // cf_agents_state is the signal that state was set.\n const serialized = JSON.stringify(nextState);\n this.lifecycle.storage.sql.exec(\n \"INSERT OR REPLACE INTO cf_agents_state (id, state) VALUES (?, ?)\",\n STATE_ROW_ID,\n serialized\n );\n this.#state = nextState;\n\n let pending: void | Promise<void>;\n try {\n pending = this.#options.onChanged?.(nextState, source);\n } catch (error) {\n console.error(\"State onChanged hook failed:\", error);\n return;\n }\n\n if (pending) {\n // Durable Objects remain active for pending I/O without waitUntil. Keep\n // set() synchronous while ensuring asynchronous failures are observed.\n // Promise.resolve normalizes thenables from other realms or libraries.\n void Promise.resolve(pending).catch((error) => {\n console.error(\"State onChanged hook failed:\", error);\n });\n }\n }\n}\n"],"mappings":";;;;;;;;;AA0CA,MAAM,2BAA2B;AACjC,MAAM,+BAA+B;;AAGrC,MAAM,eAAe;;;;;AAMrB,MAAM,4BAA4B;;;;;;AAOlC,MAAM,gBAAgB,CAAC;;;;;;;;;;;;;;;AAYvB,IAAa,QAAb,cAAwC,oBAAoB;;;;;;;CAW1D,YAAY,UAA2B,CAAC,GAAG;EACzC,MAAM,OAAO;;2CAXN,aAAA;kDACO,KAAA;;EAWd,uBAAA,UAAA,MAAgB,OAAA;CAClB;;CAKA,MAAM,UAAyB;EAG7B,KADG,MAAM,KAAK,UAAU,QAAQ,IAAY,wBAAwB,KAAM,MAC3D,8BAA8B;GAC3C,uBAAA,eAAA,MAAqB,IAAA;GACrB;EACF;EAIA,kBAAA,cAAA,MAAA,YAAA,CAAA,CAAA,KAAA,IAAkB;EAClB,KAAK,UAAU,QAAQ,IAAI,KACzB,4CACA,yBACF;EACA,MAAM,KAAK,UAAU,QAAQ,IAC3B,0BACA,4BACF;CACF;;;;;;;;;CAuBA,MAAqB;EACnB,IAAA,uBAAA,QAAI,IAAA,MAAgB,eAElB,OAAA,uBAAA,QAAO,IAAA;EAIT,kBAAA,cAAA,MAAA,YAAA,CAAA,CAAA,KAAA,IAAkB;EAClB,MAAM,SAAS,KAAK,UAAU,QAAQ,IACnC,KAAK,kDAAkD,YAAY,CAAC,CACpE,QAAQ;EAIX,IAAI,OAAO,SAAS,GAAG;GACrB,MAAM,QAAQ,OAAO,EAAE,CAAC;GAExB,IAAI;IACF,uBAAA,QAAA,MAAc,KAAK,MAAM,KAAK,CAAA;GAChC,SAAS,GAAG;IACV,QAAQ,MACN,+DACA,CACF;IACA,MAAM,UAAA,uBAAA,UAAU,IAAA,CAAA,CAAc;IAC9B,IAAI,YAAY,KAAA,GAAW;KACzB,uBAAA,QAAA,MAAc,OAAA;KAEd,KAAK,IAAI,SAAS,QAAQ;IAC5B,OAAO;KAEL,KAAK,UAAU,QAAQ,IAAI,KACzB,4CACA,YACF;KACA;IACF;GACF;GACA,OAAA,uBAAA,QAAO,IAAA;EACT;EAKA,MAAM,UAAA,uBAAA,UAAU,IAAA,CAAA,CAAc;EAC9B,IAAI,YAAY,KAAA,GAEd;EAIF,KAAK,IAAI,SAAS,QAAQ;EAC1B,OAAO;CACT;;;;;;;;;CAUA,IAAI,WAAc,SAA4B,UAAgB;EAE5D,uBAAA,UAAA,IAAA,CAAA,CAAc,sBAAsB,WAAW,MAAM;EACrD,kBAAA,cAAA,MAAA,YAAA,CAAA,CAAA,KAAA,IAAkB;EAKlB,MAAM,aAAa,KAAK,UAAU,SAAS;EAC3C,KAAK,UAAU,QAAQ,IAAI,KACzB,oEACA,cACA,UACF;EACA,uBAAA,QAAA,MAAc,SAAA;EAEd,IAAI;EACJ,IAAI;GACF,UAAA,uBAAA,UAAU,IAAA,CAAA,CAAc,YAAY,WAAW,MAAM;EACvD,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD;EACF;EAEA,IAAI,SAIF,QAAa,QAAQ,OAAO,CAAC,CAAC,OAAO,UAAU;GAC7C,QAAQ,MAAM,gCAAgC,KAAK;EACrD,CAAC;CAEL;AACF;AArHE,SAAA,eAAqB;CACnB,IAAA,uBAAA,eAAI,IAAA,GAAoB;CACxB,KAAK,UAAU,QAAQ,IAAI,KAAK;;;;;KAK/B;CACD,uBAAA,eAAA,MAAqB,IAAA;AACvB"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/state/index.ts"],"sourcesContent":["import { LifecycleCapability } from \"../lifecycle/capability\";\nimport type { Connection } from \"../lifecycle/durable-object-lifecycle\";\n\n/**\n * Source of a state change: `\"server\"` for host code (e.g. `setState()`), or\n * the {@link Connection} the change arrived from. Hosts use it to exclude the\n * originating connection from a broadcast.\n */\nexport type StateChangeSource = Connection | \"server\";\n\n/**\n * Options for a {@link State} capability. Validation and the post-change hook\n * stay on the host, which passes them in; the capability owns storage and\n * change ordering.\n */\nexport interface StateOptions<T = unknown> {\n /** Seeded on first access when nothing is stored. `undefined` seeds nothing. */\n readonly initialState?: T;\n\n /** Called after a change is validated and persisted. May be async. */\n readonly onChanged?: (\n state: T,\n source: StateChangeSource\n ) => void | Promise<void>;\n\n /**\n * Synchronous gating hook run before a change is persisted. Throw to reject\n * the change; the throw propagates to the caller of {@link State.set}.\n */\n readonly validateStateChange?: (\n nextState: T,\n source: StateChangeSource\n ) => void;\n}\n\n/**\n * Namespaced KV key holding this capability's schema version. Kept separate\n * from the host's global schema version so State owns its own migrations\n * without gating the host's DDL.\n */\nconst STATE_SCHEMA_VERSION_KEY = \"cf_agents:state_schema_version\";\nconst CURRENT_STATE_SCHEMA_VERSION = 1;\n\n/** Row id under which the single state value is stored in `cf_agents_state`. */\nconst STATE_ROW_ID = \"cf_state_row_id\";\n\n/**\n * Legacy row written by SDKs that predate the single-row state optimization.\n * Never written now; the v1 migration deletes it.\n */\nconst LEGACY_WAS_CHANGED_ROW_ID = \"cf_state_was_changed\";\n\n/**\n * Sentinel distinguishing \"state not yet loaded / never set\" from a stored\n * value. A distinct object reference means falsy states (null, 0, false, \"\")\n * still read back as set.\n */\nconst DEFAULT_STATE = {} as unknown;\n\n/**\n * Durable state storage for a Lifecycle Object.\n *\n * Owns the `cf_agents_state` state row, lazy load with an in-memory cache, and\n * validated persistence. Install the instance with `Lifecycle.use()`. State\n * validation and the post-change notification hook stay on the host, which\n * injects both callbacks. This capability never touches connections.\n */\nexport class State<T = unknown> extends LifecycleCapability {\n #state = DEFAULT_STATE as T;\n #tableEnsured = false;\n readonly #options: StateOptions<T>;\n\n /**\n * Create a durable state capability.\n *\n * @param options - Optional initial state and a synchronous validation hook\n * injected by the host.\n */\n constructor(options: StateOptions<T> = {}) {\n super(\"state\");\n this.#options = options;\n }\n\n // ── Lifecycle capability hooks ─────────────────────────────────────────────\n\n /** Initialize state storage during Lifecycle startup. */\n async onStart(): Promise<void> {\n const version =\n (await this.lifecycle.storage.get<number>(STATE_SCHEMA_VERSION_KEY)) ?? 0;\n if (version >= CURRENT_STATE_SCHEMA_VERSION) {\n this.#tableEnsured = true;\n return;\n }\n\n // v1: own the table and clear the legacy wasChanged row left behind by\n // pre-optimization SDKs (state itself lives in STATE_ROW_ID).\n this.#ensureTable();\n this.lifecycle.storage.sql.exec(\n \"DELETE FROM cf_agents_state WHERE id = ?\",\n LEGACY_WAS_CHANGED_ROW_ID\n );\n await this.lifecycle.storage.put(\n STATE_SCHEMA_VERSION_KEY,\n CURRENT_STATE_SCHEMA_VERSION\n );\n }\n\n #ensureTable(): void {\n if (this.#tableEnsured) return;\n this.lifecycle.storage.sql.exec(`\n CREATE TABLE IF NOT EXISTS cf_agents_state (\n id TEXT PRIMARY KEY NOT NULL,\n state TEXT\n )\n `);\n this.#tableEnsured = true;\n }\n\n // ── State access ───────────────────────────────────────────────────────────\n\n /**\n * Current state.\n *\n * Loads lazily from storage on first access and caches in memory. Row\n * existence in `cf_agents_state` is the signal that state was previously\n * set, so falsy values persist correctly. On a corrupt row, falls back to\n * the initial state (re-persisting it) or clears the row.\n */\n get(): T | undefined {\n if (this.#state !== DEFAULT_STATE) {\n // state was previously set, and populated internal state\n return this.#state;\n }\n // looks like this is the first time the state is being accessed\n // check if the state was set in a previous life\n this.#ensureTable();\n const result = this.lifecycle.storage.sql\n .exec(\"SELECT state FROM cf_agents_state WHERE id = ?\", STATE_ROW_ID)\n .toArray() as { state: string | null }[];\n\n // Row existence is the signal that state was previously set.\n // This handles all values including falsy ones (null, 0, false, \"\").\n if (result.length > 0) {\n const state = result[0].state as string;\n\n try {\n this.#state = JSON.parse(state);\n } catch (e) {\n console.error(\n \"Failed to parse stored state, falling back to initialState:\",\n e\n );\n const initial = this.#options.initialState;\n if (initial !== undefined) {\n this.#state = initial;\n // Persist the fixed state to prevent future parse errors\n this.set(initial, \"server\");\n } else {\n // No initialState defined - clear corrupted data to prevent infinite retry loop\n this.lifecycle.storage.sql.exec(\n \"DELETE FROM cf_agents_state WHERE id = ?\",\n STATE_ROW_ID\n );\n return undefined;\n }\n }\n return this.#state;\n }\n\n // ok, this is the first time the state is being accessed\n // and the state was not set in a previous life\n // so we need to set the initial state (if provided)\n const initial = this.#options.initialState;\n if (initial === undefined) {\n // no initial state provided, so we return undefined\n return undefined;\n }\n // initial state provided, so we set the state,\n // update db and return the initial state\n this.set(initial, \"server\");\n return initial;\n }\n\n /**\n * Validate and persist a state change, then call the change hook.\n *\n * @param nextState - The new state to persist.\n * @param source - `\"server\"` for host-originated changes, or the originating\n * connection for client-originated changes.\n * @throws Whatever the injected `validateStateChange` throws.\n */\n set(nextState: T, source: StateChangeSource = \"server\"): void {\n // Validation/gating hook (sync only)\n this.#options.validateStateChange?.(nextState, source);\n this.#ensureTable();\n\n // Persist first, cache second: a value that fails to serialize or to\n // write must not be served by later get() calls. Row existence in\n // cf_agents_state is the signal that state was set.\n const serialized = JSON.stringify(nextState);\n this.lifecycle.storage.sql.exec(\n \"INSERT OR REPLACE INTO cf_agents_state (id, state) VALUES (?, ?)\",\n STATE_ROW_ID,\n serialized\n );\n this.#state = nextState;\n\n let pending: void | Promise<void>;\n try {\n pending = this.#options.onChanged?.(nextState, source);\n } catch (error) {\n console.error(\"State onChanged hook failed:\", error);\n return;\n }\n\n if (pending) {\n // Durable Objects remain active for pending I/O without waitUntil. Keep\n // set() synchronous while ensuring asynchronous failures are observed.\n // Promise.resolve normalizes thenables from other realms or libraries.\n void Promise.resolve(pending).catch((error) => {\n console.error(\"State onChanged hook failed:\", error);\n });\n }\n }\n}\n"],"mappings":";;;;;;;;;AAwCA,MAAM,2BAA2B;AACjC,MAAM,+BAA+B;;AAGrC,MAAM,eAAe;;;;;AAMrB,MAAM,4BAA4B;;;;;;AAOlC,MAAM,gBAAgB,CAAC;;;;;;;;;;;;;AAUvB,IAAa,QAAb,cAAwC,oBAAoB;;;;;;;CAW1D,YAAY,UAA2B,CAAC,GAAG;EACzC,MAAM,OAAO;;2CAXN,aAAA;kDACO,KAAA;;EAWd,uBAAA,UAAA,MAAgB,OAAA;CAClB;;CAKA,MAAM,UAAyB;EAG7B,KADG,MAAM,KAAK,UAAU,QAAQ,IAAY,wBAAwB,KAAM,MAC3D,8BAA8B;GAC3C,uBAAA,eAAA,MAAqB,IAAA;GACrB;EACF;EAIA,kBAAA,cAAA,MAAA,YAAA,CAAA,CAAA,KAAA,IAAkB;EAClB,KAAK,UAAU,QAAQ,IAAI,KACzB,4CACA,yBACF;EACA,MAAM,KAAK,UAAU,QAAQ,IAC3B,0BACA,4BACF;CACF;;;;;;;;;CAuBA,MAAqB;EACnB,IAAA,uBAAA,QAAI,IAAA,MAAgB,eAElB,OAAA,uBAAA,QAAO,IAAA;EAIT,kBAAA,cAAA,MAAA,YAAA,CAAA,CAAA,KAAA,IAAkB;EAClB,MAAM,SAAS,KAAK,UAAU,QAAQ,IACnC,KAAK,kDAAkD,YAAY,CAAC,CACpE,QAAQ;EAIX,IAAI,OAAO,SAAS,GAAG;GACrB,MAAM,QAAQ,OAAO,EAAE,CAAC;GAExB,IAAI;IACF,uBAAA,QAAA,MAAc,KAAK,MAAM,KAAK,CAAA;GAChC,SAAS,GAAG;IACV,QAAQ,MACN,+DACA,CACF;IACA,MAAM,UAAA,uBAAA,UAAU,IAAA,CAAA,CAAc;IAC9B,IAAI,YAAY,KAAA,GAAW;KACzB,uBAAA,QAAA,MAAc,OAAA;KAEd,KAAK,IAAI,SAAS,QAAQ;IAC5B,OAAO;KAEL,KAAK,UAAU,QAAQ,IAAI,KACzB,4CACA,YACF;KACA;IACF;GACF;GACA,OAAA,uBAAA,QAAO,IAAA;EACT;EAKA,MAAM,UAAA,uBAAA,UAAU,IAAA,CAAA,CAAc;EAC9B,IAAI,YAAY,KAAA,GAEd;EAIF,KAAK,IAAI,SAAS,QAAQ;EAC1B,OAAO;CACT;;;;;;;;;CAUA,IAAI,WAAc,SAA4B,UAAgB;EAE5D,uBAAA,UAAA,IAAA,CAAA,CAAc,sBAAsB,WAAW,MAAM;EACrD,kBAAA,cAAA,MAAA,YAAA,CAAA,CAAA,KAAA,IAAkB;EAKlB,MAAM,aAAa,KAAK,UAAU,SAAS;EAC3C,KAAK,UAAU,QAAQ,IAAI,KACzB,oEACA,cACA,UACF;EACA,uBAAA,QAAA,MAAc,SAAA;EAEd,IAAI;EACJ,IAAI;GACF,UAAA,uBAAA,UAAU,IAAA,CAAA,CAAc,YAAY,WAAW,MAAM;EACvD,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD;EACF;EAEA,IAAI,SAIF,QAAa,QAAQ,OAAO,CAAC,CAAC,OAAO,UAAU;GAC7C,QAAQ,MAAM,gCAAgC,KAAK;EACrD,CAAC;CAEL;AACF;AArHE,SAAA,eAAqB;CACnB,IAAA,uBAAA,eAAI,IAAA,GAAoB;CACxB,KAAK,UAAU,QAAQ,IAAI,KAAK;;;;;KAK/B;CACD,uBAAA,eAAA,MAAqB,IAAA;AACvB"}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
//#region src/harness/store/store.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A session store for harnesses, on a Durable Object's SQLite database.
|
|
4
|
+
*
|
|
5
|
+
* Any harness that runs its agent somewhere other than the object (a
|
|
6
|
+
* container, a sandbox, a remote process) needs the same few things kept
|
|
7
|
+
* durably next to it: which sessions exist, which operations were submitted
|
|
8
|
+
* and how each one ended, and the session's history in whatever format the
|
|
9
|
+
* harness speaks. This store keeps those and nothing more. It knows no
|
|
10
|
+
* message format, no event vocabulary and no runtime: inputs, results,
|
|
11
|
+
* session state and log entries are opaque JSON that the harness parses.
|
|
12
|
+
*
|
|
13
|
+
* Three record kinds:
|
|
14
|
+
*
|
|
15
|
+
* - **Sessions**: an id, an optional parent (a fork), and one JSON state
|
|
16
|
+
* value the harness owns (model, cursors, resume handles).
|
|
17
|
+
* - **Operations**: one row per submitted operation, keyed by an
|
|
18
|
+
* idempotency id within its session, moving `queued` → `running` →
|
|
19
|
+
* `done` | `unanswered`. Transitions are guarded in SQL, so a duplicate or
|
|
20
|
+
* late transition is a no-op rather than a regression.
|
|
21
|
+
* - **Logs**: named, append-ordered streams of JSON entries per session
|
|
22
|
+
* (`"messages"`, `"engine"`, …). An entry may carry an id, and an entry
|
|
23
|
+
* written again under the same id replaces the old one in place. Entries
|
|
24
|
+
* larger than one SQLite row are split across continuation rows.
|
|
25
|
+
*
|
|
26
|
+
* The store is synchronous, like the SQL API under it, so a harness can
|
|
27
|
+
* group several writes in `transaction()` without awaiting.
|
|
28
|
+
*
|
|
29
|
+
* @experimental The API may change before it stabilizes.
|
|
30
|
+
*/
|
|
31
|
+
/** A JSON value, as the store keeps it. */
|
|
32
|
+
type JsonValue =
|
|
33
|
+
| string
|
|
34
|
+
| number
|
|
35
|
+
| boolean
|
|
36
|
+
| null
|
|
37
|
+
| readonly JsonValue[]
|
|
38
|
+
| {
|
|
39
|
+
readonly [key: string]: JsonValue;
|
|
40
|
+
};
|
|
41
|
+
/** Where an operation is in its life. */
|
|
42
|
+
type OperationStatus = "queued" | "running" | "done" | "unanswered";
|
|
43
|
+
/** A session, as stored. */
|
|
44
|
+
type SessionRecord = {
|
|
45
|
+
readonly id: string /** The session this one was forked from. */;
|
|
46
|
+
readonly parent:
|
|
47
|
+
| string
|
|
48
|
+
| undefined /** Harness-owned state. `null` until the harness writes some. */;
|
|
49
|
+
readonly state: JsonValue;
|
|
50
|
+
readonly createdAt: number;
|
|
51
|
+
readonly updatedAt: number;
|
|
52
|
+
};
|
|
53
|
+
/** One operation, as stored. */
|
|
54
|
+
type OperationRecord = {
|
|
55
|
+
readonly session: string /** The idempotency key, unique within the session. */;
|
|
56
|
+
readonly id: string /** Submission order within the whole store. */;
|
|
57
|
+
readonly seq: number;
|
|
58
|
+
readonly status: OperationStatus /** What was submitted, in the harness's format. */;
|
|
59
|
+
readonly input: JsonValue /** Harness-owned submission options and bookkeeping. */;
|
|
60
|
+
readonly meta: JsonValue /** The harness's result for a `done` operation, else `null`. */;
|
|
61
|
+
readonly result: JsonValue /** Why an `unanswered` operation ended. */;
|
|
62
|
+
readonly reason: string | undefined;
|
|
63
|
+
readonly createdAt: number;
|
|
64
|
+
readonly settledAt: number | undefined;
|
|
65
|
+
};
|
|
66
|
+
/** One log entry, as stored. */
|
|
67
|
+
type LogEntry = {
|
|
68
|
+
/** Position in the log. Stable when the entry is replaced by id. */ readonly seq: number;
|
|
69
|
+
readonly id: string | undefined;
|
|
70
|
+
readonly data: JsonValue;
|
|
71
|
+
readonly createdAt: number;
|
|
72
|
+
};
|
|
73
|
+
/** One entry to append. */
|
|
74
|
+
type LogAppend = {
|
|
75
|
+
/** Replace an existing entry with this id in place, instead of appending. */ readonly id?: string;
|
|
76
|
+
readonly data: JsonValue;
|
|
77
|
+
};
|
|
78
|
+
/** How an operation ended. */
|
|
79
|
+
type OperationOutcome =
|
|
80
|
+
| {
|
|
81
|
+
readonly status: "done";
|
|
82
|
+
readonly result: JsonValue;
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
readonly status: "unanswered";
|
|
86
|
+
readonly reason: string;
|
|
87
|
+
};
|
|
88
|
+
/** `openHarnessStore`'s options. */
|
|
89
|
+
type HarnessStoreOptions = {
|
|
90
|
+
/**
|
|
91
|
+
* Prefix for the store's tables. Two harnesses in one object use two
|
|
92
|
+
* prefixes. Must not start with `_cf_`, which Durable Objects reserve.
|
|
93
|
+
* Default `harness_`.
|
|
94
|
+
*/
|
|
95
|
+
readonly prefix?: string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Open the store over this object's SQLite database, creating its tables on
|
|
99
|
+
* first use. Cheap: call it once per isolate and keep the result.
|
|
100
|
+
*
|
|
101
|
+
* @param storage - The Durable Object's storage.
|
|
102
|
+
* @param options - Table prefix.
|
|
103
|
+
* @returns The store.
|
|
104
|
+
*/
|
|
105
|
+
declare function openHarnessStore(
|
|
106
|
+
storage: DurableObjectStorage,
|
|
107
|
+
options?: HarnessStoreOptions
|
|
108
|
+
): HarnessStore;
|
|
109
|
+
/**
|
|
110
|
+
* The store. Construct it with `openHarnessStore`.
|
|
111
|
+
*
|
|
112
|
+
* @experimental The API may change before it stabilizes.
|
|
113
|
+
*/
|
|
114
|
+
declare class HarnessStore {
|
|
115
|
+
#private;
|
|
116
|
+
/**
|
|
117
|
+
* @param storage - The Durable Object's storage.
|
|
118
|
+
* @param options - Table prefix.
|
|
119
|
+
*/
|
|
120
|
+
constructor(storage: DurableObjectStorage, options: HarnessStoreOptions);
|
|
121
|
+
/**
|
|
122
|
+
* Run `fn` in one storage transaction. Writes inside it commit together or
|
|
123
|
+
* not at all.
|
|
124
|
+
*
|
|
125
|
+
* @template T - What `fn` returns.
|
|
126
|
+
* @param fn - Synchronous work against this store.
|
|
127
|
+
* @returns What `fn` returned.
|
|
128
|
+
*/
|
|
129
|
+
transaction<T>(fn: () => T): T;
|
|
130
|
+
/**
|
|
131
|
+
* Create a session, or return the existing one with this id unchanged.
|
|
132
|
+
*
|
|
133
|
+
* @param input - The id, an optional parent, and the initial state.
|
|
134
|
+
* `createdAt` keeps the creation time of a session imported from
|
|
135
|
+
* elsewhere, so the session list stays in creation order; default now.
|
|
136
|
+
* @returns The stored session.
|
|
137
|
+
*/
|
|
138
|
+
createSession(input: {
|
|
139
|
+
readonly id: string;
|
|
140
|
+
readonly parent?: string;
|
|
141
|
+
readonly state?: JsonValue;
|
|
142
|
+
readonly createdAt?: number;
|
|
143
|
+
}): SessionRecord;
|
|
144
|
+
/**
|
|
145
|
+
* Read one session.
|
|
146
|
+
*
|
|
147
|
+
* @param id - The session id.
|
|
148
|
+
* @returns The session, or `undefined` when there is none.
|
|
149
|
+
*/
|
|
150
|
+
session(id: string): SessionRecord | undefined;
|
|
151
|
+
/**
|
|
152
|
+
* Every session, oldest first.
|
|
153
|
+
*
|
|
154
|
+
* @returns The sessions.
|
|
155
|
+
*/
|
|
156
|
+
sessions(): SessionRecord[];
|
|
157
|
+
/**
|
|
158
|
+
* Replace a session's harness-owned state.
|
|
159
|
+
*
|
|
160
|
+
* @param id - The session id.
|
|
161
|
+
* @param state - The new state.
|
|
162
|
+
* @returns False when there is no such session.
|
|
163
|
+
*/
|
|
164
|
+
setSessionState(id: string, state: JsonValue): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Delete a session with its operations and logs.
|
|
167
|
+
*
|
|
168
|
+
* @param id - The session id.
|
|
169
|
+
* @returns False when there was no such session.
|
|
170
|
+
*/
|
|
171
|
+
deleteSession(id: string): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Durably queue an operation. Idempotent on `(session, id)`: a second
|
|
174
|
+
* enqueue with the same id stores nothing and returns the first record.
|
|
175
|
+
*
|
|
176
|
+
* @param input - The session, the idempotency id, the input and options.
|
|
177
|
+
* @returns The stored record, and whether this call created it.
|
|
178
|
+
*/
|
|
179
|
+
enqueue(input: {
|
|
180
|
+
readonly session: string;
|
|
181
|
+
readonly id: string;
|
|
182
|
+
readonly input: JsonValue;
|
|
183
|
+
readonly meta?: JsonValue;
|
|
184
|
+
}): {
|
|
185
|
+
readonly record: OperationRecord;
|
|
186
|
+
readonly accepted: boolean;
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Read one operation.
|
|
190
|
+
*
|
|
191
|
+
* @param session - The session id.
|
|
192
|
+
* @param id - The operation id.
|
|
193
|
+
* @returns The operation, or `undefined` when there is none.
|
|
194
|
+
*/
|
|
195
|
+
operation(session: string, id: string): OperationRecord | undefined;
|
|
196
|
+
/**
|
|
197
|
+
* Operations in submission order, optionally narrowed to one session and
|
|
198
|
+
* to some statuses.
|
|
199
|
+
*
|
|
200
|
+
* @param filter - The session and statuses to keep. Default: all.
|
|
201
|
+
* @returns The matching operations, oldest first.
|
|
202
|
+
*/
|
|
203
|
+
operations(filter?: {
|
|
204
|
+
readonly session?: string;
|
|
205
|
+
readonly status?: readonly OperationStatus[];
|
|
206
|
+
}): OperationRecord[];
|
|
207
|
+
/**
|
|
208
|
+
* Move a queued operation to `running`.
|
|
209
|
+
*
|
|
210
|
+
* @param session - The session id.
|
|
211
|
+
* @param id - The operation id.
|
|
212
|
+
* @returns False when it was not queued.
|
|
213
|
+
*/
|
|
214
|
+
start(session: string, id: string): boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Move a running operation back to `queued`, to run it again.
|
|
217
|
+
*
|
|
218
|
+
* @param session - The session id.
|
|
219
|
+
* @param id - The operation id.
|
|
220
|
+
* @returns False when it was not running.
|
|
221
|
+
*/
|
|
222
|
+
requeue(session: string, id: string): boolean;
|
|
223
|
+
/**
|
|
224
|
+
* Settle a queued or running operation.
|
|
225
|
+
*
|
|
226
|
+
* @param session - The session id.
|
|
227
|
+
* @param id - The operation id.
|
|
228
|
+
* @param outcome - How it ended.
|
|
229
|
+
* @returns False when it had already settled or does not exist.
|
|
230
|
+
*/
|
|
231
|
+
settle(session: string, id: string, outcome: OperationOutcome): boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Delete one operation, whatever its status. Its id may be enqueued
|
|
234
|
+
* again. For replacing a record imported from elsewhere; a harness that
|
|
235
|
+
* deletes an open operation must stop running it itself.
|
|
236
|
+
*
|
|
237
|
+
* @param session - The session id.
|
|
238
|
+
* @param id - The operation id.
|
|
239
|
+
* @returns False when there was no such operation.
|
|
240
|
+
*/
|
|
241
|
+
deleteOperation(session: string, id: string): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Forget a session's settled operations, as when the session starts
|
|
244
|
+
* over. Open operations are kept. A forgotten operation's id may be
|
|
245
|
+
* enqueued again.
|
|
246
|
+
*
|
|
247
|
+
* @param session - The session id.
|
|
248
|
+
* @returns How many operations were deleted.
|
|
249
|
+
*/
|
|
250
|
+
deleteSettled(session: string): number;
|
|
251
|
+
/**
|
|
252
|
+
* Replace an operation's harness-owned bookkeeping.
|
|
253
|
+
*
|
|
254
|
+
* @param session - The session id.
|
|
255
|
+
* @param id - The operation id.
|
|
256
|
+
* @param meta - The new value.
|
|
257
|
+
* @returns False when there is no such operation.
|
|
258
|
+
*/
|
|
259
|
+
setOperationMeta(session: string, id: string, meta: JsonValue): boolean;
|
|
260
|
+
/**
|
|
261
|
+
* Append entries to a session's log. An entry with an id that is already
|
|
262
|
+
* in the log replaces that entry in place, keeping its position.
|
|
263
|
+
*
|
|
264
|
+
* @param session - The session id.
|
|
265
|
+
* @param log - The log's name, such as `"messages"`.
|
|
266
|
+
* @param entries - The entries, in order.
|
|
267
|
+
* @returns The position of the last entry written, or of the log's end.
|
|
268
|
+
*/
|
|
269
|
+
append(session: string, log: string, entries: readonly LogAppend[]): number;
|
|
270
|
+
/**
|
|
271
|
+
* Read a session's log in order.
|
|
272
|
+
*
|
|
273
|
+
* @param session - The session id.
|
|
274
|
+
* @param log - The log's name.
|
|
275
|
+
* @param range - Entries after position `after` (exclusive), at most `limit`.
|
|
276
|
+
* @returns The entries.
|
|
277
|
+
*/
|
|
278
|
+
read(
|
|
279
|
+
session: string,
|
|
280
|
+
log: string,
|
|
281
|
+
range?: {
|
|
282
|
+
readonly after?: number;
|
|
283
|
+
readonly limit?: number;
|
|
284
|
+
}
|
|
285
|
+
): LogEntry[];
|
|
286
|
+
/**
|
|
287
|
+
* One entry by id.
|
|
288
|
+
*
|
|
289
|
+
* @param session - The session id.
|
|
290
|
+
* @param log - The log's name.
|
|
291
|
+
* @param id - The entry id.
|
|
292
|
+
* @returns The entry, or `undefined`.
|
|
293
|
+
*/
|
|
294
|
+
entry(session: string, log: string, id: string): LogEntry | undefined;
|
|
295
|
+
/**
|
|
296
|
+
* The position of a log's last entry.
|
|
297
|
+
*
|
|
298
|
+
* @param session - The session id.
|
|
299
|
+
* @param log - The log's name.
|
|
300
|
+
* @returns The position, or 0 for an empty log.
|
|
301
|
+
*/
|
|
302
|
+
end(session: string, log: string): number;
|
|
303
|
+
/**
|
|
304
|
+
* Empty a session's log.
|
|
305
|
+
*
|
|
306
|
+
* @param session - The session id.
|
|
307
|
+
* @param log - The log's name.
|
|
308
|
+
*/
|
|
309
|
+
clear(session: string, log: string): void;
|
|
310
|
+
/**
|
|
311
|
+
* Copy one session's log onto the end of another's, keeping entry ids.
|
|
312
|
+
*
|
|
313
|
+
* @param from - The source session.
|
|
314
|
+
* @param to - The target session.
|
|
315
|
+
* @param log - The log's name.
|
|
316
|
+
*/
|
|
317
|
+
copy(from: string, to: string, log: string): void;
|
|
318
|
+
}
|
|
319
|
+
//#endregion
|
|
320
|
+
export {
|
|
321
|
+
LogEntry as a,
|
|
322
|
+
OperationStatus as c,
|
|
323
|
+
LogAppend as i,
|
|
324
|
+
SessionRecord as l,
|
|
325
|
+
HarnessStoreOptions as n,
|
|
326
|
+
OperationOutcome as o,
|
|
327
|
+
JsonValue as r,
|
|
328
|
+
OperationRecord as s,
|
|
329
|
+
HarnessStore as t,
|
|
330
|
+
openHarnessStore as u
|
|
331
|
+
};
|
|
332
|
+
//# sourceMappingURL=store-BKM4Xm9M.d.ts.map
|