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
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { getAgentByName, routeAgentRequest } from "../agent-routing.js";
|
|
2
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js";
|
|
3
|
+
import { t as LifecycleCapability } from "../capability-BjSKYpzg.js";
|
|
4
|
+
import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
5
|
+
//#region src/routing/routed-agents.ts
|
|
6
|
+
/**
|
|
7
|
+
* Catalog of the entries an owning Durable Object has created, keyed by
|
|
8
|
+
* route so several namespaces can share one owner. `WITHOUT ROWID` keeps
|
|
9
|
+
* an insert at one billed row; `agent_name` is a random UUID, so it needs
|
|
10
|
+
* no unique index of its own.
|
|
11
|
+
*/
|
|
12
|
+
const TABLE = "cf_agents_routed_agents";
|
|
13
|
+
/**
|
|
14
|
+
* Derives the next per-route sequence number from a `MAX(seq)` read
|
|
15
|
+
* instead of a maintained counter row. Breaks ties between equal
|
|
16
|
+
* `Date.now()` values deterministically by write order, which a random
|
|
17
|
+
* entry `id` cannot: DO SQLite millisecond timestamps collide easily
|
|
18
|
+
* under rapid same-route writes.
|
|
19
|
+
*
|
|
20
|
+
* This scans every row for the route on each create()/setMetadata() —
|
|
21
|
+
* intentionally, not a missed index. DO SQLite bills roughly 1000
|
|
22
|
+
* writes for the cost of 1000 reads, so a maintained counter row (an
|
|
23
|
+
* extra write on every call) only wins at deep four-figure entries per
|
|
24
|
+
* route; a `(route, seq)` index would cost an extra write on every call
|
|
25
|
+
* too, since `seq` changes on every write it would index. `RoutedAgents`
|
|
26
|
+
* targets one owner's own catalog (chats, documents, sessions) — for a
|
|
27
|
+
* route expected to hold thousands of entries, benchmark before relying
|
|
28
|
+
* on this ordering; it is not built for that scale.
|
|
29
|
+
*/
|
|
30
|
+
const NEXT_SEQ = `(SELECT COALESCE(MAX(seq), 0) + 1 FROM ${TABLE} WHERE route = ?)`;
|
|
31
|
+
function encodeMetadata(value) {
|
|
32
|
+
const encoded = JSON.stringify(value ?? null);
|
|
33
|
+
if (encoded === void 0) throw new TypeError("RoutedAgents metadata must be JSON-serializable");
|
|
34
|
+
return encoded;
|
|
35
|
+
}
|
|
36
|
+
var _namespace = /* @__PURE__ */ new WeakMap();
|
|
37
|
+
var _route = /* @__PURE__ */ new WeakMap();
|
|
38
|
+
var _RoutedAgents_brand = /* @__PURE__ */ new WeakSet();
|
|
39
|
+
/**
|
|
40
|
+
* A durable, routed collection of independent top-level Agents.
|
|
41
|
+
*
|
|
42
|
+
* Install this on the owning Durable Object, typically a per-user hub. It
|
|
43
|
+
* maps public entry IDs to opaque physical Agent names, handles catalog
|
|
44
|
+
* CRUD without waking any target, and forwards matching HTTP requests and
|
|
45
|
+
* WebSocket upgrades to the selected Agent. After an upgrade the target
|
|
46
|
+
* owns the socket, so ordinary frames never wake the owner. The target
|
|
47
|
+
* Agent needs no matching capability. Destroying the owner condemns every
|
|
48
|
+
* remaining entry with a few retries so targets don't casually outlive
|
|
49
|
+
* their catalog — this is best-effort, not a durability guarantee; see
|
|
50
|
+
* {@link RoutedAgents.dispose}.
|
|
51
|
+
*
|
|
52
|
+
* Pick a `route` that cannot appear as a literal path segment elsewhere
|
|
53
|
+
* under the owner (its own name, another route, or a path the owner's own
|
|
54
|
+
* `onRequest` handles) — forwarding matches every occurrence of the route
|
|
55
|
+
* segment in the path, so a coincidental match with no active entry
|
|
56
|
+
* behind it is answered `404` instead of reaching the owner.
|
|
57
|
+
*
|
|
58
|
+
* A forwarded suffix is not searched for a `/sub/{class}/{name}` dynamic
|
|
59
|
+
* agents marker: `Agent.fetch()` resolves that marker against the OWNER's
|
|
60
|
+
* exported classes before this capability's `onRequest` ever runs, so a
|
|
61
|
+
* matching marker is served as a facet of the owner, not forwarded to the
|
|
62
|
+
* target. Address a target's own dynamic agents through a direct
|
|
63
|
+
* connection to that target, not through the owner's route.
|
|
64
|
+
*
|
|
65
|
+
* @experimental The API surface may change before stabilizing.
|
|
66
|
+
*/
|
|
67
|
+
var RoutedAgents = class extends LifecycleCapability {
|
|
68
|
+
/**
|
|
69
|
+
* @param options - Target binding and the route segment this capability
|
|
70
|
+
* claims. Install with `this.lifecycle.use()` before startup.
|
|
71
|
+
*/
|
|
72
|
+
constructor(options) {
|
|
73
|
+
const route = options.route.replace(/^\/+|\/+$/g, "");
|
|
74
|
+
if (!/^[A-Za-z0-9_-]+$/.test(route)) throw new Error("RoutedAgents route must be one non-empty URL-safe path segment");
|
|
75
|
+
super(`routed-agents:${route}`);
|
|
76
|
+
_classPrivateMethodInitSpec(this, _RoutedAgents_brand);
|
|
77
|
+
_classPrivateFieldInitSpec(this, _namespace, void 0);
|
|
78
|
+
_classPrivateFieldInitSpec(this, _route, void 0);
|
|
79
|
+
_classPrivateFieldSet2(_namespace, this, options.namespace);
|
|
80
|
+
_classPrivateFieldSet2(_route, this, route);
|
|
81
|
+
}
|
|
82
|
+
/** Create an entry without waking the target Agent. */
|
|
83
|
+
async create(options) {
|
|
84
|
+
await this.lifecycle.ready();
|
|
85
|
+
const id = crypto.randomUUID();
|
|
86
|
+
const encoded = encodeMetadata(options?.metadata ?? null);
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
_assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `INSERT INTO ${TABLE} (route, id, agent_name, status, metadata, created_at, updated_at, seq)
|
|
89
|
+
VALUES (?, ?, ?, 'active', ?, ?, ?, ${NEXT_SEQ})`, _classPrivateFieldGet2(_route, this), id, crypto.randomUUID(), encoded, now, now, _classPrivateFieldGet2(_route, this));
|
|
90
|
+
return {
|
|
91
|
+
id,
|
|
92
|
+
metadata: JSON.parse(encoded),
|
|
93
|
+
createdAt: now,
|
|
94
|
+
updatedAt: now
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** Resolve an active entry to an initialized, typed Agent stub. */
|
|
98
|
+
async get(id) {
|
|
99
|
+
await this.lifecycle.ready();
|
|
100
|
+
const agentName = _assertClassBrand(_RoutedAgents_brand, this, _agentName).call(this, id, "active");
|
|
101
|
+
return agentName ? _assertClassBrand(_RoutedAgents_brand, this, _stub).call(this, agentName) : null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* List active entries, most recently updated first. Entries whose
|
|
105
|
+
* `updatedAt` ties are ordered by actual write order, not by the
|
|
106
|
+
* random entry `id`.
|
|
107
|
+
*/
|
|
108
|
+
async list() {
|
|
109
|
+
await this.lifecycle.ready();
|
|
110
|
+
return _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `SELECT id, metadata, created_at AS createdAt, updated_at AS updatedAt
|
|
111
|
+
FROM ${TABLE} WHERE route = ? AND status = 'active'
|
|
112
|
+
ORDER BY updated_at DESC, seq DESC, id ASC`, _classPrivateFieldGet2(_route, this)).map((row) => ({
|
|
113
|
+
...row,
|
|
114
|
+
metadata: JSON.parse(row.metadata)
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
/** Replace an active entry's metadata. Returns false for unknown IDs. */
|
|
118
|
+
async setMetadata(id, metadata) {
|
|
119
|
+
await this.lifecycle.ready();
|
|
120
|
+
return _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `UPDATE ${TABLE} SET metadata = ?, updated_at = ?, seq = ${NEXT_SEQ}
|
|
121
|
+
WHERE route = ? AND id = ? AND status = 'active' RETURNING id`, encodeMetadata(metadata), Date.now(), _classPrivateFieldGet2(_route, this), _classPrivateFieldGet2(_route, this), id).length > 0;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Make an entry unreachable, condemn its Agent, then remove the row.
|
|
125
|
+
* Returns false for unknown IDs.
|
|
126
|
+
*
|
|
127
|
+
* The target is condemned through Agent's deferred teardown, which
|
|
128
|
+
* durably marks it and returns without aborting the isolate; its storage
|
|
129
|
+
* is wiped on its own next wake, moments later, and the marker survives
|
|
130
|
+
* interruption. A failed RPC leaves a hidden `deleting` row so a
|
|
131
|
+
* repeated call retries.
|
|
132
|
+
*/
|
|
133
|
+
async delete(id) {
|
|
134
|
+
await this.lifecycle.ready();
|
|
135
|
+
const agentName = _assertClassBrand(_RoutedAgents_brand, this, _agentName).call(this, id);
|
|
136
|
+
if (!agentName) return false;
|
|
137
|
+
_assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `UPDATE ${TABLE} SET status = 'deleting', updated_at = ?
|
|
138
|
+
WHERE route = ? AND id = ?`, Date.now(), _classPrivateFieldGet2(_route, this), id);
|
|
139
|
+
await _classPrivateFieldGet2(_namespace, this).get(_classPrivateFieldGet2(_namespace, this).idFromName(agentName))._cf_scheduleDestroy();
|
|
140
|
+
_assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `DELETE FROM ${TABLE} WHERE route = ? AND id = ?`, _classPrivateFieldGet2(_route, this), id);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
onStart() {
|
|
144
|
+
_assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
145
|
+
route TEXT NOT NULL,
|
|
146
|
+
id TEXT NOT NULL,
|
|
147
|
+
agent_name TEXT NOT NULL,
|
|
148
|
+
status TEXT NOT NULL CHECK (status IN ('active', 'deleting')),
|
|
149
|
+
metadata TEXT NOT NULL,
|
|
150
|
+
created_at INTEGER NOT NULL,
|
|
151
|
+
updated_at INTEGER NOT NULL,
|
|
152
|
+
seq INTEGER NOT NULL DEFAULT 0,
|
|
153
|
+
PRIMARY KEY (route, id)
|
|
154
|
+
) WITHOUT ROWID`);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Condemn every remaining entry (including one already `deleting`, in
|
|
158
|
+
* case its own condemnation RPC never landed) when the owner itself is
|
|
159
|
+
* destroyed.
|
|
160
|
+
*
|
|
161
|
+
* `Agent.destroy()` disposes capabilities before it wipes its own
|
|
162
|
+
* storage, so the catalog is still readable here — without this, the
|
|
163
|
+
* catalog would vanish with the owner while every target it named kept
|
|
164
|
+
* running and billing storage, unreachable forever.
|
|
165
|
+
*
|
|
166
|
+
* This is best-effort, not a durability guarantee: `Agent.destroy()`
|
|
167
|
+
* wipes the owner's storage immediately after disposal regardless of
|
|
168
|
+
* whether any capability's `dispose()` reports failure, so a target
|
|
169
|
+
* that is still unreachable after retries here is orphaned for good —
|
|
170
|
+
* there is no later "repeated call retries" for a catalog row that no
|
|
171
|
+
* longer exists. Retrying briefly here converts the common transient
|
|
172
|
+
* failure into a condemned target instead of an orphan; it cannot
|
|
173
|
+
* convert a target that is durably unreachable.
|
|
174
|
+
*/
|
|
175
|
+
async dispose() {
|
|
176
|
+
const entries = _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `SELECT agent_name AS agentName FROM ${TABLE}
|
|
177
|
+
WHERE route = ? AND status IN ('active', 'deleting')`, _classPrivateFieldGet2(_route, this));
|
|
178
|
+
await Promise.all(entries.map(({ agentName }) => _assertClassBrand(_RoutedAgents_brand, this, _condemnWithRetry).call(this, agentName)));
|
|
179
|
+
}
|
|
180
|
+
/** Forward a matching HTTP request to the selected Agent. */
|
|
181
|
+
onRequest({ request }) {
|
|
182
|
+
return _assertClassBrand(_RoutedAgents_brand, this, _forward).call(this, request);
|
|
183
|
+
}
|
|
184
|
+
/** Forward a matching upgrade so the selected Agent owns the WebSocket. */
|
|
185
|
+
onWebSocketUpgrade({ request }) {
|
|
186
|
+
return _assertClassBrand(_RoutedAgents_brand, this, _forward).call(this, request);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
async function _condemnWithRetry(agentName, attempts = 3) {
|
|
190
|
+
for (let attempt = 1; attempt <= attempts; attempt++) try {
|
|
191
|
+
await _classPrivateFieldGet2(_namespace, this).get(_classPrivateFieldGet2(_namespace, this).idFromName(agentName))._cf_scheduleDestroy();
|
|
192
|
+
return;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (attempt === attempts) {
|
|
195
|
+
console.error(`RoutedAgents "${_classPrivateFieldGet2(_route, this)}" could not condemn ${agentName} on owner disposal after ${attempts} attempts; its storage will leak, since the owner's catalog — the only record of it — is wiped immediately after disposal`, error);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 50));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The route segment may also appear as the owner's own name or inside
|
|
203
|
+
* the forwarded suffix, so every `/{route}/{id}` occurrence is tried
|
|
204
|
+
* against the catalog and the first active entry wins. A route match
|
|
205
|
+
* with no active entry is a 404; no match at all lets the request
|
|
206
|
+
* continue to the owner's other capabilities.
|
|
207
|
+
*/
|
|
208
|
+
async function _forward(request) {
|
|
209
|
+
const url = new URL(request.url);
|
|
210
|
+
const segments = url.pathname.split("/");
|
|
211
|
+
let matched = false;
|
|
212
|
+
for (let i = 1; i < segments.length - 1; i++) {
|
|
213
|
+
if (segments[i] !== _classPrivateFieldGet2(_route, this) || segments[i + 1] === "") continue;
|
|
214
|
+
matched = true;
|
|
215
|
+
const agentName = _assertClassBrand(_RoutedAgents_brand, this, _agentName).call(this, decode(segments[i + 1]), "active");
|
|
216
|
+
if (!agentName) continue;
|
|
217
|
+
url.pathname = `/${segments.slice(i + 2).join("/")}`;
|
|
218
|
+
return _classPrivateFieldGet2(_namespace, this).get(_classPrivateFieldGet2(_namespace, this).idFromName(agentName)).fetch(new Request(url, request));
|
|
219
|
+
}
|
|
220
|
+
return matched ? new Response("Agent not found", { status: 404 }) : void 0;
|
|
221
|
+
}
|
|
222
|
+
/** Initialized stub; the explicit generics keep inference shallow. */
|
|
223
|
+
function _stub(agentName) {
|
|
224
|
+
return getAgentByName(_classPrivateFieldGet2(_namespace, this), agentName);
|
|
225
|
+
}
|
|
226
|
+
function _agentName(id, status) {
|
|
227
|
+
const [row] = _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `SELECT agent_name AS agentName FROM ${TABLE}
|
|
228
|
+
WHERE route = ? AND id = ? AND status = COALESCE(?, status)`, _classPrivateFieldGet2(_route, this), id, status ?? null);
|
|
229
|
+
return row?.agentName;
|
|
230
|
+
}
|
|
231
|
+
function _sql(query, ...values) {
|
|
232
|
+
return this.lifecycle.storage.sql.exec(query, ...values).toArray();
|
|
233
|
+
}
|
|
234
|
+
function decode(segment) {
|
|
235
|
+
try {
|
|
236
|
+
return decodeURIComponent(segment);
|
|
237
|
+
} catch {
|
|
238
|
+
return segment;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
export { RoutedAgents, getAgentByName, routeAgentRequest };
|
|
243
|
+
|
|
244
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/routing/routed-agents.ts"],"sourcesContent":["import { getAgentByName } from \"../agent-routing\";\nimport type { Agent } from \"../index\";\nimport { LifecycleCapability } from \"../lifecycle/capability\";\nimport type {\n CapabilityRequestContext,\n CapabilityWebSocketUpgradeContext\n} from \"../lifecycle/capability-runner\";\n\n/**\n * Catalog of the entries an owning Durable Object has created, keyed by\n * route so several namespaces can share one owner. `WITHOUT ROWID` keeps\n * an insert at one billed row; `agent_name` is a random UUID, so it needs\n * no unique index of its own.\n */\nconst TABLE = \"cf_agents_routed_agents\";\n\n/**\n * Derives the next per-route sequence number from a `MAX(seq)` read\n * instead of a maintained counter row. Breaks ties between equal\n * `Date.now()` values deterministically by write order, which a random\n * entry `id` cannot: DO SQLite millisecond timestamps collide easily\n * under rapid same-route writes.\n *\n * This scans every row for the route on each create()/setMetadata() —\n * intentionally, not a missed index. DO SQLite bills roughly 1000\n * writes for the cost of 1000 reads, so a maintained counter row (an\n * extra write on every call) only wins at deep four-figure entries per\n * route; a `(route, seq)` index would cost an extra write on every call\n * too, since `seq` changes on every write it would index. `RoutedAgents`\n * targets one owner's own catalog (chats, documents, sessions) — for a\n * route expected to hold thousands of entries, benchmark before relying\n * on this ordering; it is not built for that scale.\n */\nconst NEXT_SEQ = `(SELECT COALESCE(MAX(seq), 0) + 1 FROM ${TABLE} WHERE route = ?)`;\n\n/** A public entry in an {@link RoutedAgents}. */\nexport type RoutedAgentEntry<Metadata = unknown> = {\n /** Stable application-facing identifier used in routes. */\n readonly id: string;\n /** Application-owned metadata stored with the entry. */\n readonly metadata: Metadata | null;\n /** Creation time, as Unix milliseconds. */\n readonly createdAt: number;\n /** Time the entry or its metadata last changed, as Unix milliseconds. */\n readonly updatedAt: number;\n};\n\n/** Options for creating an entry in an {@link RoutedAgents}. */\nexport type RoutedAgentCreateOptions<Metadata = unknown> = {\n /** Initial application-owned metadata. */\n readonly metadata?: Metadata;\n};\n\n/** Configuration for an {@link RoutedAgents}. */\nexport type RoutedAgentsOptions<TAgent extends Agent> = {\n /** Top-level Durable Object namespace the entries are created in. */\n readonly namespace: DurableObjectNamespace<TAgent>;\n /** One URL-safe path segment under the owning Durable Object. */\n readonly route: string;\n};\n\ntype EntryRow = {\n id: string;\n metadata: string;\n createdAt: number;\n updatedAt: number;\n};\n\nfunction encodeMetadata(value: unknown): string {\n const encoded = JSON.stringify(value ?? null);\n if (encoded === undefined) {\n throw new TypeError(\"RoutedAgents metadata must be JSON-serializable\");\n }\n return encoded;\n}\n\n/**\n * A durable, routed collection of independent top-level Agents.\n *\n * Install this on the owning Durable Object, typically a per-user hub. It\n * maps public entry IDs to opaque physical Agent names, handles catalog\n * CRUD without waking any target, and forwards matching HTTP requests and\n * WebSocket upgrades to the selected Agent. After an upgrade the target\n * owns the socket, so ordinary frames never wake the owner. The target\n * Agent needs no matching capability. Destroying the owner condemns every\n * remaining entry with a few retries so targets don't casually outlive\n * their catalog — this is best-effort, not a durability guarantee; see\n * {@link RoutedAgents.dispose}.\n *\n * Pick a `route` that cannot appear as a literal path segment elsewhere\n * under the owner (its own name, another route, or a path the owner's own\n * `onRequest` handles) — forwarding matches every occurrence of the route\n * segment in the path, so a coincidental match with no active entry\n * behind it is answered `404` instead of reaching the owner.\n *\n * A forwarded suffix is not searched for a `/sub/{class}/{name}` dynamic\n * agents marker: `Agent.fetch()` resolves that marker against the OWNER's\n * exported classes before this capability's `onRequest` ever runs, so a\n * matching marker is served as a facet of the owner, not forwarded to the\n * target. Address a target's own dynamic agents through a direct\n * connection to that target, not through the owner's route.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport class RoutedAgents<\n TAgent extends Agent = Agent,\n Metadata = unknown\n> extends LifecycleCapability {\n readonly #namespace: DurableObjectNamespace<TAgent>;\n readonly #route: string;\n\n /**\n * @param options - Target binding and the route segment this capability\n * claims. Install with `this.lifecycle.use()` before startup.\n */\n constructor(options: RoutedAgentsOptions<TAgent>) {\n const route = options.route.replace(/^\\/+|\\/+$/g, \"\");\n if (!/^[A-Za-z0-9_-]+$/.test(route)) {\n throw new Error(\n \"RoutedAgents route must be one non-empty URL-safe path segment\"\n );\n }\n super(`routed-agents:${route}`);\n this.#namespace = options.namespace;\n this.#route = route;\n }\n\n /** Create an entry without waking the target Agent. */\n async create(\n options?: RoutedAgentCreateOptions<Metadata>\n ): Promise<RoutedAgentEntry<Metadata>> {\n await this.lifecycle.ready();\n const id = crypto.randomUUID();\n const encoded = encodeMetadata(options?.metadata ?? null);\n const now = Date.now();\n this.#sql(\n `INSERT INTO ${TABLE} (route, id, agent_name, status, metadata, created_at, updated_at, seq)\n VALUES (?, ?, ?, 'active', ?, ?, ?, ${NEXT_SEQ})`,\n this.#route,\n id,\n crypto.randomUUID(),\n encoded,\n now,\n now,\n this.#route\n );\n // Round-trip through JSON so this agrees with list()'s decoded copy —\n // returning the caller's object verbatim would diverge for values JSON\n // can't represent exactly (undefined fields, NaN, non-plain objects).\n return {\n id,\n metadata: JSON.parse(encoded) as Metadata | null,\n createdAt: now,\n updatedAt: now\n };\n }\n\n /** Resolve an active entry to an initialized, typed Agent stub. */\n async get(id: string): Promise<DurableObjectStub<TAgent> | null> {\n await this.lifecycle.ready();\n const agentName = this.#agentName(id, \"active\");\n return agentName ? this.#stub(agentName) : null;\n }\n\n /**\n * List active entries, most recently updated first. Entries whose\n * `updatedAt` ties are ordered by actual write order, not by the\n * random entry `id`.\n */\n async list(): Promise<ReadonlyArray<RoutedAgentEntry<Metadata>>> {\n await this.lifecycle.ready();\n return this.#sql<EntryRow>(\n `SELECT id, metadata, created_at AS createdAt, updated_at AS updatedAt\n FROM ${TABLE} WHERE route = ? AND status = 'active'\n ORDER BY updated_at DESC, seq DESC, id ASC`,\n this.#route\n ).map((row) => ({\n ...row,\n // SAFETY: written by create() or setMetadata() from a Metadata value.\n // Changing Metadata for an existing namespace needs an application\n // migration; JSON carries no type to recover.\n metadata: JSON.parse(row.metadata) as Metadata | null\n }));\n }\n\n /** Replace an active entry's metadata. Returns false for unknown IDs. */\n async setMetadata(id: string, metadata: Metadata | null): Promise<boolean> {\n await this.lifecycle.ready();\n return (\n this.#sql(\n `UPDATE ${TABLE} SET metadata = ?, updated_at = ?, seq = ${NEXT_SEQ}\n WHERE route = ? AND id = ? AND status = 'active' RETURNING id`,\n encodeMetadata(metadata),\n Date.now(),\n this.#route,\n this.#route,\n id\n ).length > 0\n );\n }\n\n /**\n * Make an entry unreachable, condemn its Agent, then remove the row.\n * Returns false for unknown IDs.\n *\n * The target is condemned through Agent's deferred teardown, which\n * durably marks it and returns without aborting the isolate; its storage\n * is wiped on its own next wake, moments later, and the marker survives\n * interruption. A failed RPC leaves a hidden `deleting` row so a\n * repeated call retries.\n */\n async delete(id: string): Promise<boolean> {\n await this.lifecycle.ready();\n const agentName = this.#agentName(id);\n if (!agentName) return false;\n this.#sql(\n `UPDATE ${TABLE} SET status = 'deleting', updated_at = ?\n WHERE route = ? AND id = ?`,\n Date.now(),\n this.#route,\n id\n );\n await this.#namespace\n .get(this.#namespace.idFromName(agentName))\n ._cf_scheduleDestroy();\n this.#sql(\n `DELETE FROM ${TABLE} WHERE route = ? AND id = ?`,\n this.#route,\n id\n );\n return true;\n }\n\n override onStart(): void {\n this.#sql(`CREATE TABLE IF NOT EXISTS ${TABLE} (\n route TEXT NOT NULL,\n id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('active', 'deleting')),\n metadata TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n seq INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (route, id)\n ) WITHOUT ROWID`);\n }\n\n /**\n * Condemn every remaining entry (including one already `deleting`, in\n * case its own condemnation RPC never landed) when the owner itself is\n * destroyed.\n *\n * `Agent.destroy()` disposes capabilities before it wipes its own\n * storage, so the catalog is still readable here — without this, the\n * catalog would vanish with the owner while every target it named kept\n * running and billing storage, unreachable forever.\n *\n * This is best-effort, not a durability guarantee: `Agent.destroy()`\n * wipes the owner's storage immediately after disposal regardless of\n * whether any capability's `dispose()` reports failure, so a target\n * that is still unreachable after retries here is orphaned for good —\n * there is no later \"repeated call retries\" for a catalog row that no\n * longer exists. Retrying briefly here converts the common transient\n * failure into a condemned target instead of an orphan; it cannot\n * convert a target that is durably unreachable.\n */\n async dispose(): Promise<void> {\n const entries = this.#sql<{ agentName: string }>(\n `SELECT agent_name AS agentName FROM ${TABLE}\n WHERE route = ? AND status IN ('active', 'deleting')`,\n this.#route\n );\n await Promise.all(\n entries.map(({ agentName }) => this.#condemnWithRetry(agentName))\n );\n }\n\n async #condemnWithRetry(agentName: string, attempts = 3): Promise<void> {\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n await this.#namespace\n .get(this.#namespace.idFromName(agentName))\n ._cf_scheduleDestroy();\n return;\n } catch (error) {\n if (attempt === attempts) {\n console.error(\n `RoutedAgents \"${this.#route}\" could not condemn ${agentName} on owner disposal after ${attempts} attempts; its storage will leak, since the owner's catalog — the only record of it — is wiped immediately after disposal`,\n error\n );\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, attempt * 50));\n }\n }\n }\n\n /** Forward a matching HTTP request to the selected Agent. */\n onRequest({\n request\n }: CapabilityRequestContext): Promise<Response | undefined> {\n return this.#forward(request);\n }\n\n /** Forward a matching upgrade so the selected Agent owns the WebSocket. */\n onWebSocketUpgrade({\n request\n }: CapabilityWebSocketUpgradeContext): Promise<Response | undefined> {\n return this.#forward(request);\n }\n\n /**\n * The route segment may also appear as the owner's own name or inside\n * the forwarded suffix, so every `/{route}/{id}` occurrence is tried\n * against the catalog and the first active entry wins. A route match\n * with no active entry is a 404; no match at all lets the request\n * continue to the owner's other capabilities.\n */\n async #forward(request: Request): Promise<Response | undefined> {\n const url = new URL(request.url);\n const segments = url.pathname.split(\"/\");\n let matched = false;\n for (let i = 1; i < segments.length - 1; i++) {\n if (segments[i] !== this.#route || segments[i + 1] === \"\") continue;\n matched = true;\n const agentName = this.#agentName(decode(segments[i + 1]), \"active\");\n if (!agentName) continue;\n url.pathname = `/${segments.slice(i + 2).join(\"/\")}`;\n return this.#namespace\n .get(this.#namespace.idFromName(agentName))\n .fetch(new Request(url, request));\n }\n return matched\n ? new Response(\"Agent not found\", { status: 404 })\n : undefined;\n }\n\n /** Initialized stub; the explicit generics keep inference shallow. */\n #stub(agentName: string): Promise<DurableObjectStub<TAgent>> {\n return getAgentByName<Cloudflare.Env, TAgent>(this.#namespace, agentName);\n }\n\n #agentName(id: string, status?: \"active\"): string | undefined {\n const [row] = this.#sql<{ agentName: string }>(\n `SELECT agent_name AS agentName FROM ${TABLE}\n WHERE route = ? AND id = ? AND status = COALESCE(?, status)`,\n this.#route,\n id,\n status ?? null\n );\n return row?.agentName;\n }\n\n #sql<Row extends Record<string, SqlStorageValue>>(\n query: string,\n ...values: SqlStorageValue[]\n ): Row[] {\n return this.lifecycle.storage.sql.exec<Row>(query, ...values).toArray();\n }\n}\n\nfunction decode(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//#region src/sessions/sanitize.ts
|
|
2
|
+
/**
|
|
3
|
+
* Message hygiene shared by every Sessions write: UTF-8 measurement without a
|
|
4
|
+
* full encoded copy, and removal of ephemeral provider metadata that must not
|
|
5
|
+
* be stored or replayed.
|
|
6
|
+
*/
|
|
7
|
+
const textEncoder = new TextEncoder();
|
|
8
|
+
const BYTE_LENGTH_BUFFER_BYTES = 16 * 1024;
|
|
9
|
+
const BYTE_LENGTH_WINDOW_CHARS = 16 * 1024;
|
|
10
|
+
/**
|
|
11
|
+
* Measure UTF-8 byte length without allocating a complete encoded copy.
|
|
12
|
+
* Memory stays bounded by a 16 KiB buffer even for near-row-limit strings.
|
|
13
|
+
*/
|
|
14
|
+
function byteLength(s) {
|
|
15
|
+
const buffer = new Uint8Array(BYTE_LENGTH_BUFFER_BYTES);
|
|
16
|
+
let offset = 0;
|
|
17
|
+
let bytes = 0;
|
|
18
|
+
while (offset < s.length) {
|
|
19
|
+
let end = Math.min(s.length, offset + BYTE_LENGTH_WINDOW_CHARS);
|
|
20
|
+
if (end < s.length && end > offset && isHighSurrogate(s.charCodeAt(end - 1))) end--;
|
|
21
|
+
const { read, written } = textEncoder.encodeInto(s.slice(offset, end), buffer);
|
|
22
|
+
if (read === 0) break;
|
|
23
|
+
offset += read;
|
|
24
|
+
bytes += written;
|
|
25
|
+
}
|
|
26
|
+
return bytes;
|
|
27
|
+
}
|
|
28
|
+
function isHighSurrogate(code) {
|
|
29
|
+
return code >= 55296 && code <= 56319;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Sanitize a message for persistence by removing ephemeral provider-specific
|
|
33
|
+
* data that should not be stored or sent back in subsequent requests.
|
|
34
|
+
*
|
|
35
|
+
* 1. Strips OpenAI ephemeral fields (itemId, reasoningEncryptedContent)
|
|
36
|
+
* 2. Filters truly empty reasoning parts (no text, no remaining providerMetadata)
|
|
37
|
+
*/
|
|
38
|
+
function sanitizeMessage(message) {
|
|
39
|
+
const parts = message.parts.flatMap((part) => {
|
|
40
|
+
let sanitized = part;
|
|
41
|
+
for (const key of ["providerMetadata", "callProviderMetadata"]) {
|
|
42
|
+
const metadata = sanitized[key];
|
|
43
|
+
if (metadata && typeof metadata === "object" && "openai" in metadata) sanitized = stripOpenAIMetadata(sanitized, key);
|
|
44
|
+
}
|
|
45
|
+
if (sanitized.type === "reasoning") {
|
|
46
|
+
const text = sanitized.text;
|
|
47
|
+
if (typeof text !== "string" || text.trim() === "") {
|
|
48
|
+
const metadata = sanitized.providerMetadata;
|
|
49
|
+
if (!(metadata && typeof metadata === "object" && Object.keys(metadata).length > 0)) return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return [sanitized];
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
...message,
|
|
56
|
+
parts
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function stripOpenAIMetadata(part, metadataKey) {
|
|
60
|
+
const metadata = part[metadataKey];
|
|
61
|
+
if (!metadata?.openai) return part;
|
|
62
|
+
const { itemId: _itemId, reasoningEncryptedContent: _rec, ...restOpenai } = metadata.openai;
|
|
63
|
+
const { openai: _openai, ...restMetadata } = metadata;
|
|
64
|
+
let next;
|
|
65
|
+
if (Object.keys(restOpenai).length > 0) next = {
|
|
66
|
+
...restMetadata,
|
|
67
|
+
openai: restOpenai
|
|
68
|
+
};
|
|
69
|
+
else if (Object.keys(restMetadata).length > 0) next = restMetadata;
|
|
70
|
+
const { [metadataKey]: _old, ...rest } = part;
|
|
71
|
+
return next ? {
|
|
72
|
+
...rest,
|
|
73
|
+
[metadataKey]: next
|
|
74
|
+
} : rest;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { sanitizeMessage as n, byteLength as t };
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=sanitize-D9TujEK8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize-D9TujEK8.js","names":[],"sources":["../src/sessions/sanitize.ts"],"sourcesContent":["/**\n * Message hygiene shared by every Sessions write: UTF-8 measurement without a\n * full encoded copy, and removal of ephemeral provider metadata that must not\n * be stored or replayed.\n */\n\nconst textEncoder = new TextEncoder();\nconst BYTE_LENGTH_BUFFER_BYTES = 16 * 1024;\nconst BYTE_LENGTH_WINDOW_CHARS = 16 * 1024;\n\n/**\n * Measure UTF-8 byte length without allocating a complete encoded copy.\n * Memory stays bounded by a 16 KiB buffer even for near-row-limit strings.\n */\nexport function byteLength(s: string): number {\n const buffer = new Uint8Array(BYTE_LENGTH_BUFFER_BYTES);\n let offset = 0;\n let bytes = 0;\n while (offset < s.length) {\n let end = Math.min(s.length, offset + BYTE_LENGTH_WINDOW_CHARS);\n if (\n end < s.length &&\n end > offset &&\n isHighSurrogate(s.charCodeAt(end - 1))\n ) {\n end--;\n }\n const { read, written } = textEncoder.encodeInto(\n s.slice(offset, end),\n buffer\n );\n if (read === 0) break;\n offset += read;\n bytes += written;\n }\n return bytes;\n}\n\nfunction isHighSurrogate(code: number): boolean {\n return code >= 0xd800 && code <= 0xdbff;\n}\n\ntype PartRecord = Record<string, unknown> & { type: string };\n\n/**\n * Sanitize a message for persistence by removing ephemeral provider-specific\n * data that should not be stored or sent back in subsequent requests.\n *\n * 1. Strips OpenAI ephemeral fields (itemId, reasoningEncryptedContent)\n * 2. Filters truly empty reasoning parts (no text, no remaining providerMetadata)\n */\nexport function sanitizeMessage<M extends { parts: readonly object[] }>(\n message: M\n): M {\n const parts = (message.parts as readonly PartRecord[]).flatMap((part) => {\n let sanitized = part;\n for (const key of [\"providerMetadata\", \"callProviderMetadata\"] as const) {\n const metadata = sanitized[key];\n if (\n metadata &&\n typeof metadata === \"object\" &&\n \"openai\" in (metadata as Record<string, unknown>)\n ) {\n sanitized = stripOpenAIMetadata(sanitized, key);\n }\n }\n if (sanitized.type === \"reasoning\") {\n const text = sanitized.text;\n if (typeof text !== \"string\" || text.trim() === \"\") {\n const metadata = sanitized.providerMetadata;\n const keep =\n metadata &&\n typeof metadata === \"object\" &&\n Object.keys(metadata as object).length > 0;\n if (!keep) return [];\n }\n }\n return [sanitized];\n });\n return { ...message, parts };\n}\n\nfunction stripOpenAIMetadata(\n part: PartRecord,\n metadataKey: \"providerMetadata\" | \"callProviderMetadata\"\n): PartRecord {\n const metadata = part[metadataKey] as {\n openai?: Record<string, unknown>;\n [key: string]: unknown;\n };\n if (!metadata?.openai) return part;\n\n const {\n itemId: _itemId,\n reasoningEncryptedContent: _rec,\n ...restOpenai\n } = metadata.openai;\n const { openai: _openai, ...restMetadata } = metadata;\n\n let next: Record<string, unknown> | undefined;\n if (Object.keys(restOpenai).length > 0) {\n next = { ...restMetadata, openai: restOpenai };\n } else if (Object.keys(restMetadata).length > 0) {\n next = restMetadata;\n }\n\n const { [metadataKey]: _old, ...rest } = part;\n return next\n ? ({ ...rest, [metadataKey]: next } as PartRecord)\n : (rest as PartRecord);\n}\n"],"mappings":";;;;;;AAMA,MAAM,cAAc,IAAI,YAAY;AACpC,MAAM,2BAA2B,KAAK;AACtC,MAAM,2BAA2B,KAAK;;;;;AAMtC,SAAgB,WAAW,GAAmB;CAC5C,MAAM,SAAS,IAAI,WAAW,wBAAwB;CACtD,IAAI,SAAS;CACb,IAAI,QAAQ;CACZ,OAAO,SAAS,EAAE,QAAQ;EACxB,IAAI,MAAM,KAAK,IAAI,EAAE,QAAQ,SAAS,wBAAwB;EAC9D,IACE,MAAM,EAAE,UACR,MAAM,UACN,gBAAgB,EAAE,WAAW,MAAM,CAAC,CAAC,GAErC;EAEF,MAAM,EAAE,MAAM,YAAY,YAAY,WACpC,EAAE,MAAM,QAAQ,GAAG,GACnB,MACF;EACA,IAAI,SAAS,GAAG;EAChB,UAAU;EACV,SAAS;CACX;CACA,OAAO;AACT;AAEA,SAAS,gBAAgB,MAAuB;CAC9C,OAAO,QAAQ,SAAU,QAAQ;AACnC;;;;;;;;AAWA,SAAgB,gBACd,SACG;CACH,MAAM,QAAS,QAAQ,MAAgC,SAAS,SAAS;EACvE,IAAI,YAAY;EAChB,KAAK,MAAM,OAAO,CAAC,oBAAoB,sBAAsB,GAAY;GACvE,MAAM,WAAW,UAAU;GAC3B,IACE,YACA,OAAO,aAAa,YACpB,YAAa,UAEb,YAAY,oBAAoB,WAAW,GAAG;EAElD;EACA,IAAI,UAAU,SAAS,aAAa;GAClC,MAAM,OAAO,UAAU;GACvB,IAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,IAAI;IAClD,MAAM,WAAW,UAAU;IAK3B,IAAI,EAHF,YACA,OAAO,aAAa,YACpB,OAAO,KAAK,QAAkB,CAAC,CAAC,SAAS,IAChC,OAAO,CAAC;GACrB;EACF;EACA,OAAO,CAAC,SAAS;CACnB,CAAC;CACD,OAAO;EAAE,GAAG;EAAS;CAAM;AAC7B;AAEA,SAAS,oBACP,MACA,aACY;CACZ,MAAM,WAAW,KAAK;CAItB,IAAI,CAAC,UAAU,QAAQ,OAAO;CAE9B,MAAM,EACJ,QAAQ,SACR,2BAA2B,MAC3B,GAAG,eACD,SAAS;CACb,MAAM,EAAE,QAAQ,SAAS,GAAG,iBAAiB;CAE7C,IAAI;CACJ,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS,GACnC,OAAO;EAAE,GAAG;EAAc,QAAQ;CAAW;MACxC,IAAI,OAAO,KAAK,YAAY,CAAC,CAAC,SAAS,GAC5C,OAAO;CAGT,MAAM,GAAG,cAAc,MAAM,GAAG,SAAS;CACzC,OAAO,OACF;EAAE,GAAG;GAAO,cAAc;CAAK,IAC/B;AACP"}
|
package/dist/schedule.d.ts
CHANGED
|
@@ -1,130 +1,61 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ParsedSchedule,
|
|
3
|
+
getSchedulePrompt,
|
|
4
|
+
scheduleSchema
|
|
5
|
+
} from "./schedules/parser.js";
|
|
2
6
|
|
|
3
7
|
//#region src/schedule.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Get the schedule prompt for a given event
|
|
6
|
-
* @param event - The event to get the schedule prompt for
|
|
7
|
-
* @returns The schedule prompt
|
|
8
|
-
*/
|
|
9
|
-
declare function getSchedulePrompt(event: { date: Date }): string;
|
|
10
8
|
/**
|
|
11
9
|
* @deprecated this has been renamed to getSchedulePrompt, and unstable_getSchedulePrompt will be removed in the next major version
|
|
12
10
|
* @param event - The event to get the schedule prompt for
|
|
13
11
|
* @returns The schedule prompt
|
|
14
12
|
*/
|
|
15
13
|
declare function unstable_getSchedulePrompt(event: { date: Date }): string;
|
|
16
|
-
/**
|
|
17
|
-
* The schema for parsing natural language scheduling requests.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* import { generateObject } from "ai";
|
|
22
|
-
* import { scheduleSchema, getSchedulePrompt } from "agents/schedule";
|
|
23
|
-
*
|
|
24
|
-
* const result = await generateObject({
|
|
25
|
-
* model,
|
|
26
|
-
* prompt: `${getSchedulePrompt({ date: new Date() })} Input: "${userInput}"`,
|
|
27
|
-
* schema: scheduleSchema,
|
|
28
|
-
* // Required for OpenAI to avoid strict JSON schema validation errors
|
|
29
|
-
* providerOptions: {
|
|
30
|
-
* openai: { strictJsonSchema: false }
|
|
31
|
-
* }
|
|
32
|
-
* });
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* @remarks
|
|
36
|
-
* When using this schema with OpenAI models via the AI SDK, you must pass
|
|
37
|
-
* `providerOptions: { openai: { strictJsonSchema: false } }` to `generateObject`.
|
|
38
|
-
* This is because the schema uses a discriminated union which is not compatible
|
|
39
|
-
* with OpenAI's strict structured outputs mode.
|
|
40
|
-
*/
|
|
41
|
-
declare const scheduleSchema: z.ZodObject<
|
|
42
|
-
{
|
|
43
|
-
description: z.ZodString;
|
|
44
|
-
when: z.ZodDiscriminatedUnion<
|
|
45
|
-
[
|
|
46
|
-
z.ZodObject<
|
|
47
|
-
{
|
|
48
|
-
type: z.ZodLiteral<"scheduled">;
|
|
49
|
-
date: z.ZodString;
|
|
50
|
-
},
|
|
51
|
-
z.core.$strip
|
|
52
|
-
>,
|
|
53
|
-
z.ZodObject<
|
|
54
|
-
{
|
|
55
|
-
type: z.ZodLiteral<"delayed">;
|
|
56
|
-
delayInSeconds: z.ZodNumber;
|
|
57
|
-
},
|
|
58
|
-
z.core.$strip
|
|
59
|
-
>,
|
|
60
|
-
z.ZodObject<
|
|
61
|
-
{
|
|
62
|
-
type: z.ZodLiteral<"cron">;
|
|
63
|
-
cron: z.ZodString;
|
|
64
|
-
},
|
|
65
|
-
z.core.$strip
|
|
66
|
-
>,
|
|
67
|
-
z.ZodObject<
|
|
68
|
-
{
|
|
69
|
-
type: z.ZodLiteral<"no-schedule">;
|
|
70
|
-
},
|
|
71
|
-
z.core.$strip
|
|
72
|
-
>
|
|
73
|
-
],
|
|
74
|
-
"type"
|
|
75
|
-
>;
|
|
76
|
-
},
|
|
77
|
-
z.core.$strip
|
|
78
|
-
>;
|
|
79
|
-
/**
|
|
80
|
-
* The type for the schedule prompt
|
|
81
|
-
*/
|
|
82
|
-
type Schedule = z.infer<typeof scheduleSchema>;
|
|
83
14
|
/**
|
|
84
15
|
* @deprecated this has been renamed to scheduleSchema, and unstable_scheduleSchema will be removed in the next major version
|
|
85
16
|
* @returns The schedule schema
|
|
86
17
|
*/
|
|
87
|
-
declare const unstable_scheduleSchema:
|
|
18
|
+
declare const unstable_scheduleSchema: import("zod").ZodObject<
|
|
88
19
|
{
|
|
89
|
-
description:
|
|
90
|
-
when:
|
|
20
|
+
description: import("zod").ZodString;
|
|
21
|
+
when: import("zod").ZodDiscriminatedUnion<
|
|
91
22
|
[
|
|
92
|
-
|
|
23
|
+
import("zod").ZodObject<
|
|
93
24
|
{
|
|
94
|
-
type:
|
|
95
|
-
date:
|
|
25
|
+
type: import("zod").ZodLiteral<"scheduled">;
|
|
26
|
+
date: import("zod").ZodString;
|
|
96
27
|
},
|
|
97
|
-
|
|
28
|
+
import("zod/v4/core").$strip
|
|
98
29
|
>,
|
|
99
|
-
|
|
30
|
+
import("zod").ZodObject<
|
|
100
31
|
{
|
|
101
|
-
type:
|
|
102
|
-
delayInSeconds:
|
|
32
|
+
type: import("zod").ZodLiteral<"delayed">;
|
|
33
|
+
delayInSeconds: import("zod").ZodNumber;
|
|
103
34
|
},
|
|
104
|
-
|
|
35
|
+
import("zod/v4/core").$strip
|
|
105
36
|
>,
|
|
106
|
-
|
|
37
|
+
import("zod").ZodObject<
|
|
107
38
|
{
|
|
108
|
-
type:
|
|
109
|
-
cron:
|
|
39
|
+
type: import("zod").ZodLiteral<"cron">;
|
|
40
|
+
cron: import("zod").ZodString;
|
|
110
41
|
},
|
|
111
|
-
|
|
42
|
+
import("zod/v4/core").$strip
|
|
112
43
|
>,
|
|
113
|
-
|
|
44
|
+
import("zod").ZodObject<
|
|
114
45
|
{
|
|
115
|
-
type:
|
|
46
|
+
type: import("zod").ZodLiteral<"no-schedule">;
|
|
116
47
|
},
|
|
117
|
-
|
|
48
|
+
import("zod/v4/core").$strip
|
|
118
49
|
>
|
|
119
50
|
],
|
|
120
51
|
"type"
|
|
121
52
|
>;
|
|
122
53
|
},
|
|
123
|
-
|
|
54
|
+
import("zod/v4/core").$strip
|
|
124
55
|
>;
|
|
125
56
|
//#endregion
|
|
126
57
|
export {
|
|
127
|
-
Schedule,
|
|
58
|
+
type ParsedSchedule as Schedule,
|
|
128
59
|
getSchedulePrompt,
|
|
129
60
|
scheduleSchema,
|
|
130
61
|
unstable_getSchedulePrompt,
|
package/dist/schedule.js
CHANGED
|
@@ -1,59 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getSchedulePrompt, scheduleSchema } from "./schedules/parser.js";
|
|
2
2
|
//#region src/schedule.ts
|
|
3
|
-
/**
|
|
4
|
-
* Get the schedule prompt for a given event
|
|
5
|
-
* @param event - The event to get the schedule prompt for
|
|
6
|
-
* @returns The schedule prompt
|
|
7
|
-
*/
|
|
8
|
-
function getSchedulePrompt(event) {
|
|
9
|
-
return `
|
|
10
|
-
[Schedule Parser Component]
|
|
11
|
-
|
|
12
|
-
Current time: ${event.date.toUTCString()}
|
|
13
|
-
|
|
14
|
-
This component parses natural language scheduling requests into a structured format. It extracts:
|
|
15
|
-
1. A clean task description (without timing information)
|
|
16
|
-
2. Scheduling details in one of these formats:
|
|
17
|
-
- scheduled: Specific date/time events
|
|
18
|
-
- delayed: Relative time delays (in seconds)
|
|
19
|
-
- cron: Recurring patterns
|
|
20
|
-
- no-schedule: Tasks without timing
|
|
21
|
-
|
|
22
|
-
Rules:
|
|
23
|
-
- Task descriptions should be clean and focused on the action
|
|
24
|
-
- Use numbers (0-6) for days in cron patterns (0=Sunday)
|
|
25
|
-
- For recurring tasks, use standard cron syntax
|
|
26
|
-
- For relative times, convert to seconds
|
|
27
|
-
- For specific dates, use the current time as reference
|
|
28
|
-
|
|
29
|
-
Example outputs:
|
|
30
|
-
{
|
|
31
|
-
"description": "meeting with team",
|
|
32
|
-
"when": {
|
|
33
|
-
"type": "scheduled",
|
|
34
|
-
"date": "tomorrow at 14:00"
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
{
|
|
39
|
-
"description": "backup database",
|
|
40
|
-
"when": {
|
|
41
|
-
"type": "cron",
|
|
42
|
-
"cron": "0 0 * * *"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
"description": "send report",
|
|
48
|
-
"when": {
|
|
49
|
-
"type": "delayed",
|
|
50
|
-
"delayInSeconds": 1800
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
[End Schedule Parser Component]
|
|
55
|
-
`;
|
|
56
|
-
}
|
|
57
3
|
let didWarnAboutUnstableGetSchedulePrompt = false;
|
|
58
4
|
/**
|
|
59
5
|
* @deprecated this has been renamed to getSchedulePrompt, and unstable_getSchedulePrompt will be removed in the next major version
|
|
@@ -68,49 +14,6 @@ function unstable_getSchedulePrompt(event) {
|
|
|
68
14
|
return getSchedulePrompt(event);
|
|
69
15
|
}
|
|
70
16
|
/**
|
|
71
|
-
* The schema for parsing natural language scheduling requests.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```typescript
|
|
75
|
-
* import { generateObject } from "ai";
|
|
76
|
-
* import { scheduleSchema, getSchedulePrompt } from "agents/schedule";
|
|
77
|
-
*
|
|
78
|
-
* const result = await generateObject({
|
|
79
|
-
* model,
|
|
80
|
-
* prompt: `${getSchedulePrompt({ date: new Date() })} Input: "${userInput}"`,
|
|
81
|
-
* schema: scheduleSchema,
|
|
82
|
-
* // Required for OpenAI to avoid strict JSON schema validation errors
|
|
83
|
-
* providerOptions: {
|
|
84
|
-
* openai: { strictJsonSchema: false }
|
|
85
|
-
* }
|
|
86
|
-
* });
|
|
87
|
-
* ```
|
|
88
|
-
*
|
|
89
|
-
* @remarks
|
|
90
|
-
* When using this schema with OpenAI models via the AI SDK, you must pass
|
|
91
|
-
* `providerOptions: { openai: { strictJsonSchema: false } }` to `generateObject`.
|
|
92
|
-
* This is because the schema uses a discriminated union which is not compatible
|
|
93
|
-
* with OpenAI's strict structured outputs mode.
|
|
94
|
-
*/
|
|
95
|
-
const scheduleSchema = z.object({
|
|
96
|
-
description: z.string().describe("A description of the task"),
|
|
97
|
-
when: z.discriminatedUnion("type", [
|
|
98
|
-
z.object({
|
|
99
|
-
type: z.literal("scheduled"),
|
|
100
|
-
date: z.string().describe("Execute task at the specified date and time in ISO 8601 format")
|
|
101
|
-
}),
|
|
102
|
-
z.object({
|
|
103
|
-
type: z.literal("delayed"),
|
|
104
|
-
delayInSeconds: z.number().describe("Execute task after a delay in seconds")
|
|
105
|
-
}),
|
|
106
|
-
z.object({
|
|
107
|
-
type: z.literal("cron"),
|
|
108
|
-
cron: z.string().describe("Execute task on a recurring interval specified as cron syntax")
|
|
109
|
-
}),
|
|
110
|
-
z.object({ type: z.literal("no-schedule") })
|
|
111
|
-
])
|
|
112
|
-
});
|
|
113
|
-
/**
|
|
114
17
|
* @deprecated this has been renamed to scheduleSchema, and unstable_scheduleSchema will be removed in the next major version
|
|
115
18
|
* @returns The schedule schema
|
|
116
19
|
*/
|
package/dist/schedule.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schedule.js","names":[],"sources":["../src/schedule.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"schedule.js","names":[],"sources":["../src/schedule.ts"],"sourcesContent":["/**\n * @deprecated Import schedule parsing helpers from `agents/schedules/parser`.\n * This compatibility entry point will be removed in a future major release.\n */\nexport { getSchedulePrompt, scheduleSchema } from \"./schedules/parser\";\n/**\n * @deprecated Import `ParsedSchedule` from `agents/schedules/parser`.\n */\nexport type { ParsedSchedule as Schedule } from \"./schedules/parser\";\n\nimport { getSchedulePrompt, scheduleSchema } from \"./schedules/parser\";\n\nlet didWarnAboutUnstableGetSchedulePrompt = false;\n\n/**\n * @deprecated this has been renamed to getSchedulePrompt, and unstable_getSchedulePrompt will be removed in the next major version\n * @param event - The event to get the schedule prompt for\n * @returns The schedule prompt\n */\nexport function unstable_getSchedulePrompt(event: { date: Date }) {\n if (!didWarnAboutUnstableGetSchedulePrompt) {\n didWarnAboutUnstableGetSchedulePrompt = true;\n console.warn(\n \"unstable_getSchedulePrompt is deprecated, use getSchedulePrompt instead. unstable_getSchedulePrompt will be removed in the next major version.\"\n );\n }\n return getSchedulePrompt(event);\n}\n\n/**\n * @deprecated this has been renamed to scheduleSchema, and unstable_scheduleSchema will be removed in the next major version\n * @returns The schedule schema\n */\nexport const unstable_scheduleSchema = scheduleSchema;\n"],"mappings":";;AAYA,IAAI,wCAAwC;;;;;;AAO5C,SAAgB,2BAA2B,OAAuB;CAChE,IAAI,CAAC,uCAAuC;EAC1C,wCAAwC;EACxC,QAAQ,KACN,gJACF;CACF;CACA,OAAO,kBAAkB,KAAK;AAChC;;;;;AAMA,MAAa,0BAA0B"}
|