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.
Files changed (258) hide show
  1. package/README.md +21 -1
  2. package/dist/{agent-tool-types-CzGGB-20.d.ts → agent-routing-DE5zmCQ8.d.ts} +1844 -1336
  3. package/dist/agent-routing.d.ts +14 -0
  4. package/dist/agent-routing.js +187 -0
  5. package/dist/agent-routing.js.map +1 -0
  6. package/dist/agent-tool-types.d.ts +26 -26
  7. package/dist/{agent-tools-zR2d5uij.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
  8. package/dist/agent-tools.d.ts +21 -21
  9. package/dist/agent-tools.js +2 -1
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/browser/ai.js +6 -2
  12. package/dist/browser/ai.js.map +1 -1
  13. package/dist/browser/tanstack-ai.js.map +1 -1
  14. package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
  15. package/dist/callable-decorator.d.ts +18 -0
  16. package/dist/callable-decorator.js +71 -0
  17. package/dist/callable-decorator.js.map +1 -0
  18. package/dist/capability-BjSKYpzg.js +42 -0
  19. package/dist/capability-BjSKYpzg.js.map +1 -0
  20. package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
  21. package/dist/channel-Bnm4S7T2.d.ts +491 -0
  22. package/dist/channels/ai-sdk.d.ts +46 -0
  23. package/dist/channels/ai-sdk.js +120 -0
  24. package/dist/channels/ai-sdk.js.map +1 -0
  25. package/dist/channels/email.d.ts +95 -0
  26. package/dist/channels/email.js +323 -0
  27. package/dist/channels/email.js.map +1 -0
  28. package/dist/channels/index.d.ts +233 -0
  29. package/dist/channels/index.js +608 -0
  30. package/dist/channels/index.js.map +1 -0
  31. package/dist/channels/slack.d.ts +140 -0
  32. package/dist/channels/slack.js +614 -0
  33. package/dist/channels/slack.js.map +1 -0
  34. package/dist/channels/tanstack-ai.d.ts +39 -0
  35. package/dist/channels/tanstack-ai.js +17 -0
  36. package/dist/channels/tanstack-ai.js.map +1 -0
  37. package/dist/channels/telegram.d.ts +106 -0
  38. package/dist/channels/telegram.js +427 -0
  39. package/dist/channels/telegram.js.map +1 -0
  40. package/dist/channels/voice.d.ts +45 -0
  41. package/dist/channels/voice.js +122 -0
  42. package/dist/channels/voice.js.map +1 -0
  43. package/dist/chat/index.d.ts +2328 -2015
  44. package/dist/chat/index.js +891 -521
  45. package/dist/chat/index.js.map +1 -1
  46. package/dist/chat/react.d.ts +14 -1
  47. package/dist/chat/react.js +82 -52
  48. package/dist/chat/react.js.map +1 -1
  49. package/dist/chat/transport.js +1 -1
  50. package/dist/chat-sdk/index.d.ts +7 -7
  51. package/dist/chat-sdk/index.js +1 -1
  52. package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
  53. package/dist/client-jagG8a9_.js.map +1 -0
  54. package/dist/client.d.ts +1 -1
  55. package/dist/client.js +1 -1
  56. package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
  57. package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
  58. package/dist/context/index.d.ts +216 -0
  59. package/dist/context/index.js +454 -0
  60. package/dist/context/index.js.map +1 -0
  61. package/dist/current-agent-Da_C9a3b.d.ts +266 -0
  62. package/dist/current-agent-DhoDkSnH.js +51 -0
  63. package/dist/current-agent-DhoDkSnH.js.map +1 -0
  64. package/dist/diagnostics-BzvaX2UT.js +45 -0
  65. package/dist/diagnostics-BzvaX2UT.js.map +1 -0
  66. package/dist/diagnostics-C4jcz3VK.js +360 -0
  67. package/dist/diagnostics-C4jcz3VK.js.map +1 -0
  68. package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
  69. package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
  70. package/dist/email-send.d.ts +15 -0
  71. package/dist/email-send.js +32 -0
  72. package/dist/email-send.js.map +1 -0
  73. package/dist/email.d.ts +14 -10
  74. package/dist/email.js.map +1 -1
  75. package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
  76. package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
  77. package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
  78. package/dist/index-BB0kqhIz.d.ts +101 -0
  79. package/dist/index-XDkuQ7zm.d.ts +89 -0
  80. package/dist/{index-BRnybD6X.d.ts → index-YSKgfgg9.d.ts} +21 -31
  81. package/dist/index.d.ts +113 -101
  82. package/dist/index.js +11 -7234
  83. package/dist/ingress-BfetZbMO.js +83 -0
  84. package/dist/ingress-BfetZbMO.js.map +1 -0
  85. package/dist/internal-CYlgHl1l.js +59 -0
  86. package/dist/internal-CYlgHl1l.js.map +1 -0
  87. package/dist/internal_context-BlxFEWfn.d.ts +19 -0
  88. package/dist/internal_context.d.ts +10 -4
  89. package/dist/internal_context.js +1 -10
  90. package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
  91. package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
  92. package/dist/invoker-CHMnoxIA.js.map +1 -0
  93. package/dist/lifecycle/index.d.ts +66 -0
  94. package/dist/lifecycle/index.js +4 -0
  95. package/dist/lifecycle-CMRGjZdw.js +1299 -0
  96. package/dist/lifecycle-CMRGjZdw.js.map +1 -0
  97. package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
  98. package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
  99. package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
  100. package/dist/mcp/client/index.d.ts +42 -0
  101. package/dist/mcp/{client.js → client/index.js} +1 -1
  102. package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
  103. package/dist/mcp/{x402.js → client/x402.js} +2 -2
  104. package/dist/mcp/client/x402.js.map +1 -0
  105. package/dist/mcp/index.d.ts +36 -36
  106. package/dist/mcp/index.js +14 -16
  107. package/dist/mcp/index.js.map +1 -1
  108. package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
  109. package/dist/mcp/{server.js → server/index.js} +1 -1
  110. package/dist/observability/ai/index.js +50 -35
  111. package/dist/observability/ai/index.js.map +1 -1
  112. package/dist/observability/index.d.ts +4 -4
  113. package/dist/observability/index.js +3 -50
  114. package/dist/observability/index.js.map +1 -1
  115. package/dist/{protocol-Dqc2MQxo.js → protocol-B0nh6KNf.js} +19 -21
  116. package/dist/protocol-B0nh6KNf.js.map +1 -0
  117. package/dist/react.d.ts +4 -4
  118. package/dist/react.js +1 -1
  119. package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
  120. package/dist/retries.d.ts +8 -6
  121. package/dist/retries.js +13 -1
  122. package/dist/retries.js.map +1 -1
  123. package/dist/routing/index.d.ts +137 -0
  124. package/dist/routing/index.js +244 -0
  125. package/dist/routing/index.js.map +1 -0
  126. package/dist/sanitize-D9TujEK8.js +79 -0
  127. package/dist/sanitize-D9TujEK8.js.map +1 -0
  128. package/dist/schedule.d.ts +25 -94
  129. package/dist/schedule.js +1 -98
  130. package/dist/schedule.js.map +1 -1
  131. package/dist/scheduler-DD9NdYbF.js +665 -0
  132. package/dist/scheduler-DD9NdYbF.js.map +1 -0
  133. package/dist/scheduler-Dwh85ZGl.d.ts +223 -0
  134. package/dist/schedules/index.d.ts +22 -0
  135. package/dist/schedules/index.js +2 -0
  136. package/dist/schedules/parser.d.ts +79 -0
  137. package/dist/schedules/parser.js +103 -0
  138. package/dist/schedules/parser.js.map +1 -0
  139. package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
  140. package/dist/serializable.d.ts +1 -1
  141. package/dist/sessions/index.d.ts +441 -0
  142. package/dist/sessions/index.js +2063 -0
  143. package/dist/sessions/index.js.map +1 -0
  144. package/dist/skills/index.d.ts +99 -0
  145. package/dist/skills/index.js +254 -5
  146. package/dist/skills/index.js.map +1 -1
  147. package/dist/sql-error-CPY-GXyI.d.ts +12 -0
  148. package/dist/sql-error.d.ts +2 -0
  149. package/dist/sql-error.js +16 -0
  150. package/dist/sql-error.js.map +1 -0
  151. package/dist/src-DlSHshb2.js +6963 -0
  152. package/dist/src-DlSHshb2.js.map +1 -0
  153. package/dist/streams/index.d.ts +120 -0
  154. package/dist/streams/index.js +107 -0
  155. package/dist/streams/index.js.map +1 -0
  156. package/dist/streams-D6tJ0NN9.d.ts +370 -0
  157. package/dist/streams-DZKgAj9b.js +709 -0
  158. package/dist/streams-DZKgAj9b.js.map +1 -0
  159. package/dist/sub-routing.d.ts +12 -12
  160. package/dist/surface-bZZJqBka.js +17 -0
  161. package/dist/surface-bZZJqBka.js.map +1 -0
  162. package/dist/tasks/index.d.ts +64 -0
  163. package/dist/tasks/index.js +2 -0
  164. package/dist/tasks-BRJ5zgya.d.ts +517 -0
  165. package/dist/tasks-ylZgBjhj.js +1656 -0
  166. package/dist/tasks-ylZgBjhj.js.map +1 -0
  167. package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
  168. package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
  169. package/dist/text-stream-CpdiKrJB.js +272 -0
  170. package/dist/text-stream-CpdiKrJB.js.map +1 -0
  171. package/dist/tokens-nHAKcN6M.js +52 -0
  172. package/dist/tokens-nHAKcN6M.js.map +1 -0
  173. package/dist/tool-schema-CBjGPrsQ.js +31 -0
  174. package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
  175. package/dist/types-B7LojTe4.d.ts +202 -0
  176. package/dist/types-_Faxb570.d.ts +439 -0
  177. package/dist/voice/client.d.ts +226 -0
  178. package/dist/voice/client.js +932 -0
  179. package/dist/voice/client.js.map +1 -0
  180. package/dist/voice/errors.d.ts +43 -0
  181. package/dist/voice/errors.js +41 -0
  182. package/dist/voice/errors.js.map +1 -0
  183. package/dist/voice/index.d.ts +271 -0
  184. package/dist/voice/index.js +1812 -0
  185. package/dist/voice/index.js.map +1 -0
  186. package/dist/voice/react.d.ts +167 -0
  187. package/dist/voice/react.js +234 -0
  188. package/dist/voice/react.js.map +1 -0
  189. package/dist/voice/sfu.d.ts +71 -0
  190. package/dist/voice/sfu.js +157 -0
  191. package/dist/voice/sfu.js.map +1 -0
  192. package/dist/voice/text.d.ts +6 -0
  193. package/dist/voice/text.js +2 -0
  194. package/dist/voice/types.d.ts +58 -0
  195. package/dist/voice/types.js +18 -0
  196. package/dist/voice/types.js.map +1 -0
  197. package/dist/voice/workers-ai.d.ts +136 -0
  198. package/dist/voice/workers-ai.js +568 -0
  199. package/dist/voice/workers-ai.js.map +1 -0
  200. package/dist/websockets/index.d.ts +192 -0
  201. package/dist/websockets/index.js +2 -0
  202. package/dist/websockets-DUfRHPRq.js +502 -0
  203. package/dist/websockets-DUfRHPRq.js.map +1 -0
  204. package/dist/workflow-types.d.ts +25 -25
  205. package/dist/workflows.d.ts +22 -22
  206. package/dist/workflows.js +2 -1
  207. package/dist/workflows.js.map +1 -1
  208. package/dist/{ws-chat-transport-CIoOBbO7.js → ws-chat-transport-rWwta645.js} +152 -15
  209. package/dist/ws-chat-transport-rWwta645.js.map +1 -0
  210. package/docs/agent-class.md +29 -87
  211. package/docs/agent-tools.md +2 -1
  212. package/docs/channels.md +323 -0
  213. package/docs/chat-agents.md +19 -25
  214. package/docs/context.md +131 -0
  215. package/docs/durable-execution.md +1 -1
  216. package/docs/http-websockets.md +1 -11
  217. package/docs/human-in-the-loop.md +1 -1
  218. package/docs/index.md +16 -12
  219. package/docs/lifecycle.md +370 -0
  220. package/docs/long-running-agents.md +4 -6
  221. package/docs/mcp-client.md +55 -0
  222. package/docs/mcp-servers.md +5 -1
  223. package/docs/observability.md +11 -11
  224. package/docs/resumable-streaming.md +2 -2
  225. package/docs/routing.md +105 -0
  226. package/docs/scheduling.md +175 -15
  227. package/docs/server-driven-messages.md +1 -1
  228. package/docs/sessions.md +237 -871
  229. package/docs/streams.md +213 -0
  230. package/docs/sub-agents.md +185 -125
  231. package/docs/tasks.md +246 -0
  232. package/docs/voice.md +745 -0
  233. package/package.json +144 -33
  234. package/dist/cli/index.js +0 -26
  235. package/dist/cli/index.js.map +0 -1
  236. package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
  237. package/dist/client-zqKcsyFa.js.map +0 -1
  238. package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
  239. package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
  240. package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
  241. package/dist/experimental/memory/session/index.d.ts +0 -670
  242. package/dist/experimental/memory/session/index.js +0 -2374
  243. package/dist/experimental/memory/session/index.js.map +0 -1
  244. package/dist/experimental/memory/utils/index.d.ts +0 -96
  245. package/dist/experimental/memory/utils/index.js +0 -79
  246. package/dist/experimental/memory/utils/index.js.map +0 -1
  247. package/dist/handler-stateless-CIkKPETH.js.map +0 -1
  248. package/dist/index.js.map +0 -1
  249. package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
  250. package/dist/internal_context.js.map +0 -1
  251. package/dist/mcp/client.d.ts +0 -42
  252. package/dist/mcp/do-oauth-client-provider.js.map +0 -1
  253. package/dist/mcp/x402.js.map +0 -1
  254. package/dist/protocol-Dqc2MQxo.js.map +0 -1
  255. package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
  256. package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
  257. package/dist/ws-chat-transport-CIoOBbO7.js.map +0 -1
  258. /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"}
@@ -1,130 +1,61 @@
1
- import { z } from "zod";
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: z.ZodObject<
18
+ declare const unstable_scheduleSchema: import("zod").ZodObject<
88
19
  {
89
- description: z.ZodString;
90
- when: z.ZodDiscriminatedUnion<
20
+ description: import("zod").ZodString;
21
+ when: import("zod").ZodDiscriminatedUnion<
91
22
  [
92
- z.ZodObject<
23
+ import("zod").ZodObject<
93
24
  {
94
- type: z.ZodLiteral<"scheduled">;
95
- date: z.ZodString;
25
+ type: import("zod").ZodLiteral<"scheduled">;
26
+ date: import("zod").ZodString;
96
27
  },
97
- z.core.$strip
28
+ import("zod/v4/core").$strip
98
29
  >,
99
- z.ZodObject<
30
+ import("zod").ZodObject<
100
31
  {
101
- type: z.ZodLiteral<"delayed">;
102
- delayInSeconds: z.ZodNumber;
32
+ type: import("zod").ZodLiteral<"delayed">;
33
+ delayInSeconds: import("zod").ZodNumber;
103
34
  },
104
- z.core.$strip
35
+ import("zod/v4/core").$strip
105
36
  >,
106
- z.ZodObject<
37
+ import("zod").ZodObject<
107
38
  {
108
- type: z.ZodLiteral<"cron">;
109
- cron: z.ZodString;
39
+ type: import("zod").ZodLiteral<"cron">;
40
+ cron: import("zod").ZodString;
110
41
  },
111
- z.core.$strip
42
+ import("zod/v4/core").$strip
112
43
  >,
113
- z.ZodObject<
44
+ import("zod").ZodObject<
114
45
  {
115
- type: z.ZodLiteral<"no-schedule">;
46
+ type: import("zod").ZodLiteral<"no-schedule">;
116
47
  },
117
- z.core.$strip
48
+ import("zod/v4/core").$strip
118
49
  >
119
50
  ],
120
51
  "type"
121
52
  >;
122
53
  },
123
- z.core.$strip
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 { z } from "zod";
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
  */
@@ -1 +1 @@
1
- {"version":3,"file":"schedule.js","names":[],"sources":["../src/schedule.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * Get the schedule prompt for a given event\n * @param event - The event to get the schedule prompt for\n * @returns The schedule prompt\n */\nexport function getSchedulePrompt(event: { date: Date }) {\n return `\n[Schedule Parser Component]\n\nCurrent time: ${event.date.toUTCString()}\n\nThis component parses natural language scheduling requests into a structured format. It extracts:\n1. A clean task description (without timing information)\n2. Scheduling details in one of these formats:\n - scheduled: Specific date/time events\n - delayed: Relative time delays (in seconds)\n - cron: Recurring patterns\n - no-schedule: Tasks without timing\n\nRules:\n- Task descriptions should be clean and focused on the action\n- Use numbers (0-6) for days in cron patterns (0=Sunday)\n- For recurring tasks, use standard cron syntax\n- For relative times, convert to seconds\n- For specific dates, use the current time as reference\n\nExample outputs:\n{\n \"description\": \"meeting with team\",\n \"when\": {\n \"type\": \"scheduled\",\n \"date\": \"tomorrow at 14:00\"\n }\n}\n\n{\n \"description\": \"backup database\",\n \"when\": {\n \"type\": \"cron\",\n \"cron\": \"0 0 * * *\"\n }\n}\n\n{\n \"description\": \"send report\",\n \"when\": {\n \"type\": \"delayed\",\n \"delayInSeconds\": 1800\n }\n}\n\n[End Schedule Parser Component]\n`;\n}\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 * The schema for parsing natural language scheduling requests.\n *\n * @example\n * ```typescript\n * import { generateObject } from \"ai\";\n * import { scheduleSchema, getSchedulePrompt } from \"agents/schedule\";\n *\n * const result = await generateObject({\n * model,\n * prompt: `${getSchedulePrompt({ date: new Date() })} Input: \"${userInput}\"`,\n * schema: scheduleSchema,\n * // Required for OpenAI to avoid strict JSON schema validation errors\n * providerOptions: {\n * openai: { strictJsonSchema: false }\n * }\n * });\n * ```\n *\n * @remarks\n * When using this schema with OpenAI models via the AI SDK, you must pass\n * `providerOptions: { openai: { strictJsonSchema: false } }` to `generateObject`.\n * This is because the schema uses a discriminated union which is not compatible\n * with OpenAI's strict structured outputs mode.\n */\nexport const scheduleSchema = z.object({\n description: z.string().describe(\"A description of the task\"),\n when: z.discriminatedUnion(\"type\", [\n z.object({\n type: z.literal(\"scheduled\"),\n date: z\n .string()\n .describe(\n \"Execute task at the specified date and time in ISO 8601 format\"\n )\n }),\n z.object({\n type: z.literal(\"delayed\"),\n delayInSeconds: z\n .number()\n .describe(\"Execute task after a delay in seconds\")\n }),\n z.object({\n type: z.literal(\"cron\"),\n cron: z\n .string()\n .describe(\n \"Execute task on a recurring interval specified as cron syntax\"\n )\n }),\n z.object({\n type: z.literal(\"no-schedule\")\n })\n ])\n});\n\n/**\n * The type for the schedule prompt\n */\nexport type Schedule = z.infer<typeof scheduleSchema>;\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":";;;;;;;AAOA,SAAgB,kBAAkB,OAAuB;CACvD,OAAO;;;gBAGO,MAAM,KAAK,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CzC;AAEA,IAAI,wCAAwC;;;;;;AAO5C,SAAgB,2BAA2B,OAAuB;CAChE,IAAI,CAAC,uCAAuC;EAC1C,wCAAwC;EACxC,QAAQ,KACN,gJACF;CACF;CACA,OAAO,kBAAkB,KAAK;AAChC;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,iBAAiB,EAAE,OAAO;CACrC,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS,2BAA2B;CAC5D,MAAM,EAAE,mBAAmB,QAAQ;EACjC,EAAE,OAAO;GACP,MAAM,EAAE,QAAQ,WAAW;GAC3B,MAAM,EACH,OAAO,CAAC,CACR,SACC,gEACF;EACJ,CAAC;EACD,EAAE,OAAO;GACP,MAAM,EAAE,QAAQ,SAAS;GACzB,gBAAgB,EACb,OAAO,CAAC,CACR,SAAS,uCAAuC;EACrD,CAAC;EACD,EAAE,OAAO;GACP,MAAM,EAAE,QAAQ,MAAM;GACtB,MAAM,EACH,OAAO,CAAC,CACR,SACC,+DACF;EACJ,CAAC;EACD,EAAE,OAAO,EACP,MAAM,EAAE,QAAQ,aAAa,EAC/B,CAAC;CACH,CAAC;AACH,CAAC;;;;;AAWD,MAAa,0BAA0B"}
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"}