agents 0.20.1 → 0.22.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 (169) hide show
  1. package/README.md +51 -12
  2. package/dist/{agent-tool-types-BC-WFlsz.d.ts → agent-routing-CnkaHb-v.d.ts} +3305 -3138
  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-DeHe9Xov.d.ts → agent-tools-C0XJqWzB.d.ts} +7 -7
  8. package/dist/agent-tools.d.ts +44 -28
  9. package/dist/agent-tools.js +16 -7
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/browser/ai.d.ts +5 -3
  12. package/dist/browser/ai.js +88 -8
  13. package/dist/browser/ai.js.map +1 -1
  14. package/dist/browser/index.d.ts +1 -1
  15. package/dist/browser/index.js +1 -1
  16. package/dist/browser/tanstack-ai.js +13 -1
  17. package/dist/browser/tanstack-ai.js.map +1 -1
  18. package/dist/capability-BjSKYpzg.js +42 -0
  19. package/dist/capability-BjSKYpzg.js.map +1 -0
  20. package/dist/capability-runner-CvHGZqUu.d.ts +150 -0
  21. package/dist/chat/index.d.ts +51 -15
  22. package/dist/chat/index.js +59 -4
  23. package/dist/chat/index.js.map +1 -1
  24. package/dist/chat/react.d.ts +19 -180
  25. package/dist/chat/react.js +93 -600
  26. package/dist/chat/react.js.map +1 -1
  27. package/dist/chat/transport.d.ts +10 -0
  28. package/dist/chat/transport.js +2 -0
  29. package/dist/chat-sdk/index.d.ts +4 -4
  30. package/dist/chat-sdk/index.js +1 -1
  31. package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
  32. package/dist/client-jagG8a9_.js.map +1 -0
  33. package/dist/client.d.ts +1 -1
  34. package/dist/client.js +1 -1
  35. package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
  36. package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
  37. package/dist/{connector-v2M1zlZp.d.ts → connector-CkQD4MK3.d.ts} +20 -3
  38. package/dist/{connector-KEJnl6e5.js → connector-CptFKzRh.js} +158 -40
  39. package/dist/connector-CptFKzRh.js.map +1 -0
  40. package/dist/current-agent-CuMErtly.d.ts +283 -0
  41. package/dist/current-agent-DhoDkSnH.js +51 -0
  42. package/dist/current-agent-DhoDkSnH.js.map +1 -0
  43. package/dist/diagnostics-CaBjfz4J.js +41 -0
  44. package/dist/diagnostics-CaBjfz4J.js.map +1 -0
  45. package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
  46. package/dist/durable-object-lifecycle-D6nNQJJd.js +862 -0
  47. package/dist/durable-object-lifecycle-D6nNQJJd.js.map +1 -0
  48. package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
  49. package/dist/email-send.d.ts +15 -0
  50. package/dist/email-send.js +32 -0
  51. package/dist/email-send.js.map +1 -0
  52. package/dist/email.d.ts +14 -10
  53. package/dist/email.js.map +1 -1
  54. package/dist/experimental/memory/session/index.d.ts +3 -2
  55. package/dist/experimental/memory/session/index.js +15 -10
  56. package/dist/experimental/memory/session/index.js.map +1 -1
  57. package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
  58. package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
  59. package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
  60. package/dist/{index-BRnybD6X.d.ts → index-DcSAZKsB.d.ts} +19 -31
  61. package/dist/index.d.ts +102 -91
  62. package/dist/index.js +12 -7235
  63. package/dist/internal_context-BlxFEWfn.d.ts +19 -0
  64. package/dist/internal_context.d.ts +10 -4
  65. package/dist/internal_context.js +1 -10
  66. package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
  67. package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
  68. package/dist/invoker-CHMnoxIA.js.map +1 -0
  69. package/dist/lifecycle/index.d.ts +48 -0
  70. package/dist/lifecycle/index.js +4 -0
  71. package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
  72. package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
  73. package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
  74. package/dist/mcp/client/index.d.ts +42 -0
  75. package/dist/mcp/{client.js → client/index.js} +1 -1
  76. package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
  77. package/dist/mcp/{x402.js → client/x402.js} +2 -2
  78. package/dist/mcp/client/x402.js.map +1 -0
  79. package/dist/mcp/index.d.ts +36 -36
  80. package/dist/mcp/index.js +14 -16
  81. package/dist/mcp/index.js.map +1 -1
  82. package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
  83. package/dist/mcp/{server.js → server/index.js} +1 -1
  84. package/dist/observability/ai/index.d.ts +1 -130
  85. package/dist/observability/ai/index.js +88 -433
  86. package/dist/observability/ai/index.js.map +1 -1
  87. package/dist/observability/index.d.ts +4 -4
  88. package/dist/observability/index.js +3 -50
  89. package/dist/observability/index.js.map +1 -1
  90. package/dist/{wire-types-CU9rLoeS.js → protocol-B0nh6KNf.js} +20 -66
  91. package/dist/protocol-B0nh6KNf.js.map +1 -0
  92. package/dist/react.d.ts +4 -4
  93. package/dist/react.js +6 -9
  94. package/dist/react.js.map +1 -1
  95. package/dist/schedule.d.ts +25 -94
  96. package/dist/schedule.js +1 -98
  97. package/dist/schedule.js.map +1 -1
  98. package/dist/scheduler-CR9RHGos.js +857 -0
  99. package/dist/scheduler-CR9RHGos.js.map +1 -0
  100. package/dist/scheduler-DQoTGoAW.d.ts +224 -0
  101. package/dist/schedules/index.d.ts +22 -0
  102. package/dist/schedules/index.js +2 -0
  103. package/dist/schedules/parser.d.ts +79 -0
  104. package/dist/schedules/parser.js +103 -0
  105. package/dist/schedules/parser.js.map +1 -0
  106. package/dist/serializable.d.ts +1 -1
  107. package/dist/sql-error-CPY-GXyI.d.ts +12 -0
  108. package/dist/sql-error.d.ts +2 -0
  109. package/dist/sql-error.js +16 -0
  110. package/dist/sql-error.js.map +1 -0
  111. package/dist/src-5W6JNKVb.js +6613 -0
  112. package/dist/src-5W6JNKVb.js.map +1 -0
  113. package/dist/sub-routing.d.ts +18 -6
  114. package/dist/sub-routing.js +92 -2
  115. package/dist/sub-routing.js.map +1 -1
  116. package/dist/wire-types-CnMt6_HR.js +47 -0
  117. package/dist/wire-types-CnMt6_HR.js.map +1 -0
  118. package/dist/workflow-types.d.ts +25 -25
  119. package/dist/workflow-types.js.map +1 -1
  120. package/dist/workflows.d.ts +22 -22
  121. package/dist/workflows.js +2 -1
  122. package/dist/workflows.js.map +1 -1
  123. package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
  124. package/dist/ws-chat-transport-rWwta645.js +698 -0
  125. package/dist/ws-chat-transport-rWwta645.js.map +1 -0
  126. package/docs/adding-to-existing-project.md +4 -2
  127. package/docs/agent-class.md +29 -87
  128. package/docs/agent-tools.md +29 -0
  129. package/docs/browse-the-web.md +16 -1
  130. package/docs/chat-agents.md +16 -13
  131. package/docs/client-sdk.md +12 -8
  132. package/docs/configuration.md +7 -1
  133. package/docs/cross-domain-authentication.md +7 -35
  134. package/docs/durable-execution.md +1 -1
  135. package/docs/email.md +2 -13
  136. package/docs/http-websockets.md +1 -11
  137. package/docs/human-in-the-loop.md +16 -13
  138. package/docs/index.md +1 -0
  139. package/docs/lifecycle.md +323 -0
  140. package/docs/long-running-agents.md +13 -15
  141. package/docs/mcp-client.md +55 -0
  142. package/docs/mcp-servers.md +6 -0
  143. package/docs/mcp-transports.md +18 -12
  144. package/docs/migration-to-ai-sdk-v5.md +2 -2
  145. package/docs/migration-to-ai-sdk-v6.md +5 -1
  146. package/docs/observability.md +33 -77
  147. package/docs/resumable-streaming.md +1 -1
  148. package/docs/routing.md +27 -0
  149. package/docs/scheduling.md +175 -15
  150. package/docs/server-driven-messages.md +1 -1
  151. package/docs/sub-agents.md +47 -2
  152. package/docs/webhooks.md +109 -136
  153. package/docs/workflows.md +9 -1
  154. package/package.json +41 -23
  155. package/dist/cli/index.d.ts +0 -1
  156. package/dist/cli/index.js +0 -26
  157. package/dist/cli/index.js.map +0 -1
  158. package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
  159. package/dist/client-zqKcsyFa.js.map +0 -1
  160. package/dist/connector-KEJnl6e5.js.map +0 -1
  161. package/dist/handler-stateless-CIkKPETH.js.map +0 -1
  162. package/dist/index.js.map +0 -1
  163. package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
  164. package/dist/internal_context.js.map +0 -1
  165. package/dist/mcp/client.d.ts +0 -42
  166. package/dist/mcp/do-oauth-client-provider.js.map +0 -1
  167. package/dist/mcp/x402.js.map +0 -1
  168. package/dist/wire-types-CU9rLoeS.js.map +0 -1
  169. package/dist/workflow-types-Baz_PO5v.d.ts +0 -280
@@ -457,7 +457,7 @@ Pre-aborted signals short-circuit before any model work runs.
457
457
  ### Limitations
458
458
 
459
459
  - **Signals cannot cross Durable Object boundaries.** `AbortSignal` is not an RPC-serializable type. Construct the controller inside the DO that calls `saveMessages`. For Think child-agent orchestration, use [Agent Tools](./agent-tools.md); `runAgentTool()` bridges parent aborts into the child run. For lower-level custom RPC, return a `ReadableStream` from the child and let the parent cancel it — workerd propagates the cancel back to the source's `cancel` callback.
460
- - **Hibernation drops the listener.** The signal lives in memory. If the DO hibernates mid-turn and `chatRecovery` is enabled, the recovered turn usually calls `continueLastTurn()` internally without the original signal an abort fired after restart has no effect on the recovered turn. For pre-stream interruptions, recovery can instead retry the latest unanswered user message automatically. This is true for top-level agents and sub-agents; sub-agent recovery still works, but the original caller's in-memory signal is gone. Override `onChatRecovery` (Think) or set `chatRecovery = false` for callers that need stronger guarantees.
460
+ - **Hibernation drops the listener.** The signal lives in memory. After the DO restarts, durable recovery usually calls `continueLastTurn()` without the original signal, so an abort fired after restart has no effect. For pre-stream interruptions, recovery can instead retry the latest unanswered user message automatically. This is true for top-level agents and sub-agents. If cancellation must survive restart, persist the cancellation intent in agent state or SQL, check it in `onChatRecovery()`, and return `{ continue: false }`. Durable recovery cannot be disabled.
461
461
 
462
462
  This is the integration point for agent-tool orchestration where the parent's AI SDK abort signal needs to propagate into a child DO's `saveMessages` call. See [`cloudflare/agents#1406`](https://github.com/cloudflare/agents/issues/1406) for the original use case.
463
463
 
@@ -273,7 +273,52 @@ const chat = useAgent({
273
273
 
274
274
  Every other `useAgent` feature works as usual: `state` sync, `stub.method()` calls, `@callable` RPCs, `useAgentChat` on top of the returned socket.
275
275
 
276
- ### Direct HTTP / custom routing
276
+ ### Direct HTTP and WebSocket URLs
277
+
278
+ Use `buildAgentPath()` to turn a root-first Agent identity into the canonical URL pathname used by both HTTP requests and WebSocket connections:
279
+
280
+ ```typescript
281
+ import { buildAgentPath } from "agents";
282
+
283
+ const path = buildAgentPath(
284
+ [
285
+ { className: "Inbox", name: userId },
286
+ { className: "Chat", name: chatId }
287
+ ],
288
+ { leafPath: "/callbacks/job" }
289
+ );
290
+
291
+ // /agents/inbox/{userId}/sub/chat/{chatId}/callbacks/job
292
+ ```
293
+
294
+ Inside an Agent, pass `this.selfPath` directly. If the root Durable Object binding name differs from its class name, also pass `rootBinding` in the options. `buildAgentUrl()` adds a public origin, which is useful when registering callbacks, webhooks, approval URLs, or asynchronous job-completion URLs with an external system:
295
+
296
+ ```typescript
297
+ import { buildAgentUrl } from "agents";
298
+
299
+ export class Chat extends Agent<Env> {
300
+ callbackUrl() {
301
+ return buildAgentUrl(this.env.PUBLIC_ORIGIN, this.selfPath, {
302
+ leafPath: "/callbacks/job"
303
+ });
304
+ }
305
+
306
+ override async onRequest(request: Request) {
307
+ if (new URL(request.url).pathname === "/callbacks/job") {
308
+ return this.handleJobCallback(request);
309
+ }
310
+ return new Response("Not found", { status: 404 });
311
+ }
312
+ }
313
+ ```
314
+
315
+ The Worker must pass the incoming request to `routeAgentRequest()`. Each ancestor's `onBeforeSubAgent` hook runs before the destination receives the request. For a sub-agent destination, the nested `/sub/...` routing segments are removed during forwarding, so its pathname is the `leafPath` suffix.
316
+
317
+ `buildAgentUrl()` accepts an HTTP(S) or WS(S) origin without a pathname, query, fragment, or credentials. Set callback query parameters through the returned URL's `searchParams`. If you use a custom routing prefix, pass the same value to both `buildAgentPath()` and `routeAgentRequest()`.
318
+
319
+ Root Agent names follow `routeAgentRequest`'s raw pathname-segment behavior and must already be externally routable. The `sub` segment is reserved in routing prefixes, class and binding names, and root Agent names. Descendant names are URL-encoded by the helper, so names containing spaces, Unicode, `/`, or URL-reserved characters round-trip safely.
320
+
321
+ ### Custom routing
277
322
 
278
323
  For fetch handlers that do their own top-level URL parsing, use `routeSubAgentRequest` to dispatch a request into a sub-agent from an already-resolved parent stub:
279
324
 
@@ -293,7 +338,7 @@ export default {
293
338
  };
294
339
  ```
295
340
 
296
- `fromPath` takes the sub-agent tail (something like `/sub/chat/chat-abc/...`). The helper parses it, runs the parent's `onBeforeSubAgent` hook, and forwards into the facet.
341
+ `fromPath` takes any pathname containing the sub-agent tail (something like `/sub/chat/chat-abc/...`). When the destination is already represented as a root-first Agent path, pass the result of `buildAgentPath()` directly. The helper parses the first child hop, runs the parent's `onBeforeSubAgent` hook, and forwards into the facet.
297
342
 
298
343
  ### External typed RPC
299
344
 
package/docs/webhooks.md CHANGED
@@ -7,56 +7,54 @@ Receive webhook events from external services and route them to dedicated agent
7
7
  ```typescript
8
8
  import { Agent, getAgentByName, routeAgentRequest } from "agents";
9
9
 
10
- // Agent that handles webhooks for a specific entity
10
+ type GitHubWebhookPayload = {
11
+ repository?: { full_name?: string };
12
+ };
13
+
14
+ async function verifyGitHubWebhook(
15
+ rawBody: string,
16
+ signature: string | null,
17
+ secret: string
18
+ ): Promise<boolean> {
19
+ if (!signature || !/^sha256=[0-9a-f]{64}$/i.test(signature)) return false;
20
+
21
+ const encoder = new TextEncoder();
22
+ const key = await crypto.subtle.importKey(
23
+ "raw",
24
+ encoder.encode(secret),
25
+ { name: "HMAC", hash: "SHA-256" },
26
+ false,
27
+ ["verify"]
28
+ );
29
+ const signatureBytes = Uint8Array.from(
30
+ signature.slice("sha256=".length).match(/.{2}/g) ?? [],
31
+ (byte) => Number.parseInt(byte, 16)
32
+ );
33
+
34
+ return crypto.subtle.verify(
35
+ "HMAC",
36
+ key,
37
+ signatureBytes,
38
+ encoder.encode(rawBody)
39
+ );
40
+ }
41
+
11
42
  export class WebhookAgent extends Agent<Env> {
12
43
  async onRequest(request: Request): Promise<Response> {
13
44
  if (request.method !== "POST") {
14
45
  return new Response("Method not allowed", { status: 405 });
15
46
  }
16
47
 
17
- // Verify the webhook signature
48
+ const rawBody = await request.text();
18
49
  const signature = request.headers.get("X-Hub-Signature-256");
19
- const body = await request.text();
20
-
21
50
  if (
22
- !(await this.verifySignature(body, signature, this.env.WEBHOOK_SECRET))
51
+ !(await verifyGitHubWebhook(rawBody, signature, this.env.WEBHOOK_SECRET))
23
52
  ) {
24
53
  return new Response("Invalid signature", { status: 401 });
25
54
  }
26
55
 
27
- // Process the webhook payload
28
- const payload = JSON.parse(body);
29
- await this.processEvent(payload);
30
-
31
- return new Response("OK", { status: 200 });
32
- }
33
-
34
- private async verifySignature(
35
- payload: string,
36
- signature: string | null,
37
- secret: string
38
- ): Promise<boolean> {
39
- if (!signature) return false;
40
-
41
- const encoder = new TextEncoder();
42
- const key = await crypto.subtle.importKey(
43
- "raw",
44
- encoder.encode(secret),
45
- { name: "HMAC", hash: "SHA-256" },
46
- false,
47
- ["sign"]
48
- );
49
-
50
- const signatureBytes = await crypto.subtle.sign(
51
- "HMAC",
52
- key,
53
- encoder.encode(payload)
54
- );
55
- const expected = `sha256=${Array.from(new Uint8Array(signatureBytes))
56
- .map((b) => b.toString(16).padStart(2, "0"))
57
- .join("")}`;
58
-
59
- return signature === expected;
56
+ await this.processEvent(JSON.parse(rawBody));
57
+ return new Response("OK");
60
58
  }
61
59
 
62
60
  private async processEvent(payload: unknown) {
@@ -64,19 +62,30 @@ export class WebhookAgent extends Agent<Env> {
64
62
  }
65
63
  }
66
64
 
67
- // Route webhooks to the right agent instance
68
65
  export default {
69
66
  async fetch(request: Request, env: Env): Promise<Response> {
70
67
  const url = new URL(request.url);
71
68
 
72
- // Webhook endpoint: POST /webhooks/:entityId
73
- if (url.pathname.startsWith("/webhooks/") && request.method === "POST") {
74
- const entityId = url.pathname.split("/")[2];
75
- const agent = await getAgentByName(env.WebhookAgent, entityId);
69
+ if (url.pathname === "/webhooks/github" && request.method === "POST") {
70
+ const rawBody = await request.clone().text();
71
+ const signature = request.headers.get("X-Hub-Signature-256");
72
+ if (
73
+ !(await verifyGitHubWebhook(rawBody, signature, env.WEBHOOK_SECRET))
74
+ ) {
75
+ return new Response("Invalid signature", { status: 401 });
76
+ }
77
+
78
+ const payload = JSON.parse(rawBody) as GitHubWebhookPayload;
79
+ const repository = payload.repository?.full_name;
80
+ if (!repository) {
81
+ return new Response("Missing repository", { status: 400 });
82
+ }
83
+
84
+ const agentName = repository.toLowerCase().replace(/\//g, "-");
85
+ const agent = await getAgentByName(env.WebhookAgent, agentName);
76
86
  return agent.fetch(request);
77
87
  }
78
88
 
79
- // Default routing for WebSocket connections
80
89
  return (
81
90
  (await routeAgentRequest(request, env)) ||
82
91
  new Response("Not found", { status: 404 })
@@ -131,7 +140,7 @@ Webhooks combined with agents enable powerful patterns where each external entit
131
140
 
132
141
  ## Routing Webhooks to Agents
133
142
 
134
- The key pattern is extracting an entity identifier from the webhook and using `getAgentByName()` to route to a dedicated agent instance.
143
+ The key pattern is verifying the raw request before parsing it, then deriving the Agent identity from authenticated payload data. A body signature does not authenticate an unrelated URL segment or arbitrary header.
135
144
 
136
145
  ### Extract Entity from Payload
137
146
 
@@ -140,19 +149,23 @@ Most webhooks include an identifier in the payload:
140
149
  ```typescript
141
150
  export default {
142
151
  async fetch(request: Request, env: Env): Promise<Response> {
152
+ const url = new URL(request.url);
143
153
  if (request.method === "POST" && url.pathname === "/webhooks/github") {
144
- const payload = await request.clone().json();
154
+ const rawBody = await request.clone().text();
155
+ const signature = request.headers.get("X-Hub-Signature-256");
156
+ if (
157
+ !(await verifyGitHubWebhook(rawBody, signature, env.WEBHOOK_SECRET))
158
+ ) {
159
+ return new Response("Invalid signature", { status: 401 });
160
+ }
145
161
 
146
- // Extract entity ID from payload
147
- const repoFullName = payload.repository?.full_name;
148
- if (!repoFullName) {
162
+ const payload = JSON.parse(rawBody) as GitHubWebhookPayload;
163
+ const repository = payload.repository?.full_name;
164
+ if (!repository) {
149
165
  return new Response("Missing repository", { status: 400 });
150
166
  }
151
167
 
152
- // Sanitize for use as agent name
153
- const agentName = repoFullName.toLowerCase().replace(/\//g, "-");
154
-
155
- // Route to dedicated agent
168
+ const agentName = repository.toLowerCase().replace(/\//g, "-");
156
169
  const agent = await getAgentByName(env.RepoAgent, agentName);
157
170
  return agent.fetch(request);
158
171
  }
@@ -160,69 +173,21 @@ export default {
160
173
  };
161
174
  ```
162
175
 
163
- ### Extract Entity from URL
164
-
165
- Alternatively, include the entity ID in the webhook URL:
166
-
167
- ```typescript
168
- // Webhook URL: https://your-worker.dev/webhooks/stripe/cus_123456
169
- if (url.pathname.startsWith("/webhooks/stripe/")) {
170
- const customerId = url.pathname.split("/")[3]; // "cus_123456"
171
- const agent = await getAgentByName(env.StripeAgent, customerId);
172
- return agent.fetch(request);
173
- }
174
- ```
176
+ ### Validate Entity IDs in URLs
175
177
 
176
- ### Extract Entity from Headers
178
+ A provider's body signature does not authenticate the webhook URL. If the URL includes an entity ID, compare it with the corresponding identity from the verified provider payload and reject a mismatch before calling `getAgentByName()`.
177
179
 
178
- Some services include identifiers in headers:
180
+ ### Derive Slack Identity from the Verified Body
179
181
 
180
- ```typescript
181
- // Slack sends workspace info in headers
182
- const teamId = request.headers.get("X-Slack-Team-Id");
183
- if (teamId) {
184
- const agent = await getAgentByName(env.SlackAgent, teamId);
185
- return agent.fetch(request);
186
- }
187
- ```
182
+ Slack does not send an authenticated `X-Slack-Team-Id` routing header. Validate Slack's timestamped signature and replay window against the raw body, then read `team_id` from the verified event or form body.
188
183
 
189
184
  ## Signature Verification
190
185
 
191
- Always verify webhook signatures to ensure requests are authentic. Most providers use HMAC-SHA256.
186
+ Always verify webhook signatures before trusting or processing the payload.
192
187
 
193
- ### HMAC-SHA256 Pattern
188
+ ### GitHub HMAC-SHA256 Pattern
194
189
 
195
- ```typescript
196
- async function verifySignature(
197
- payload: string,
198
- signature: string | null,
199
- secret: string
200
- ): Promise<boolean> {
201
- if (!signature) return false;
202
-
203
- const encoder = new TextEncoder();
204
- const key = await crypto.subtle.importKey(
205
- "raw",
206
- encoder.encode(secret),
207
- { name: "HMAC", hash: "SHA-256" },
208
- false,
209
- ["sign"]
210
- );
211
-
212
- const signatureBytes = await crypto.subtle.sign(
213
- "HMAC",
214
- key,
215
- encoder.encode(payload)
216
- );
217
-
218
- const expected = `sha256=${Array.from(new Uint8Array(signatureBytes))
219
- .map((b) => b.toString(16).padStart(2, "0"))
220
- .join("")}`;
221
-
222
- // Use timing-safe comparison in production
223
- return signature === expected;
224
- }
225
- ```
190
+ The Quick Start's `verifyGitHubWebhook()` helper verifies GitHub's `sha256=<hex>` signature over the raw body with `crypto.subtle.verify()`. This format is GitHub-specific. Other providers use different signature encodings, signed inputs, timestamp checks, and replay protections; follow the provider documentation linked under [Common Webhook Providers](#common-webhook-providers).
226
191
 
227
192
  ### Provider-Specific Headers
228
193
 
@@ -238,7 +203,7 @@ async function verifySignature(
238
203
 
239
204
  ### The onRequest Handler
240
205
 
241
- Use `onRequest()` to handle incoming webhooks in your agent:
206
+ Use `onRequest()` to handle incoming webhooks in your agent. If the Worker has not already verified the request, verify it before parsing the body. This example reuses the Quick Start's `verifyGitHubWebhook()` helper:
242
207
 
243
208
  ```typescript
244
209
  export class WebhookAgent extends Agent<Env, MyState> {
@@ -248,14 +213,16 @@ export class WebhookAgent extends Agent<Env, MyState> {
248
213
  return new Response("Method not allowed", { status: 405 });
249
214
  }
250
215
 
251
- // 2. Get event type from headers
252
- const eventType = request.headers.get("X-Event-Type");
216
+ // 2. Get the GitHub event type
217
+ const eventType = request.headers.get("X-GitHub-Event") ?? "unknown";
253
218
 
254
- // 3. Verify signature
255
- const signature = request.headers.get("X-Signature");
219
+ // 3. Verify the GitHub signature
220
+ const signature = request.headers.get("X-Hub-Signature-256");
256
221
  const body = await request.text();
257
222
 
258
- if (!(await this.verifySignature(body, signature))) {
223
+ if (
224
+ !(await verifyGitHubWebhook(body, signature, this.env.WEBHOOK_SECRET))
225
+ ) {
259
226
  return new Response("Invalid signature", { status: 401 });
260
227
  }
261
228
 
@@ -462,35 +429,41 @@ fibers retain status, dedupe provider retries, and let `onFiberRecovered()` or
462
429
 
463
430
  ### Multi-Provider Routing
464
431
 
465
- Handle webhooks from multiple services in one worker:
432
+ Keep provider-specific verification and parsing behind one typed seam. Its implementation must validate the raw request according to the provider documentation linked under [Common Webhook Providers](#common-webhook-providers), then derive `agentName` only from the verified body.
466
433
 
467
434
  ```typescript
435
+ type VerifiedWebhook =
436
+ | { provider: "github"; agentName: string }
437
+ | { provider: "stripe"; agentName: string }
438
+ | { provider: "slack"; agentName: string };
439
+
440
+ declare function verifyAndParseWebhook(
441
+ request: Request,
442
+ env: Env
443
+ ): Promise<VerifiedWebhook | null>;
444
+
468
445
  export default {
469
446
  async fetch(request: Request, env: Env): Promise<Response> {
470
447
  const url = new URL(request.url);
471
-
472
- if (request.method === "POST") {
473
- // GitHub webhooks
474
- if (url.pathname.startsWith("/webhooks/github/")) {
475
- const payload = await request.clone().json();
476
- const repoName = payload.repository?.full_name?.replace("/", "-");
477
- const agent = await getAgentByName(env.GitHubAgent, repoName);
478
- return agent.fetch(request);
479
- }
480
-
481
- // Stripe webhooks
482
- if (url.pathname.startsWith("/webhooks/stripe/")) {
483
- const payload = await request.clone().json();
484
- const customerId = payload.data?.object?.customer;
485
- const agent = await getAgentByName(env.StripeAgent, customerId);
486
- return agent.fetch(request);
448
+ if (request.method === "POST" && url.pathname.startsWith("/webhooks/")) {
449
+ const verified = await verifyAndParseWebhook(request.clone(), env);
450
+ if (!verified) {
451
+ return new Response("Invalid signature", { status: 401 });
487
452
  }
488
453
 
489
- // Slack webhooks
490
- if (url.pathname === "/webhooks/slack") {
491
- const teamId = request.headers.get("X-Slack-Team-Id");
492
- const agent = await getAgentByName(env.SlackAgent, teamId);
493
- return agent.fetch(request);
454
+ switch (verified.provider) {
455
+ case "github":
456
+ return (
457
+ await getAgentByName(env.GitHubAgent, verified.agentName)
458
+ ).fetch(request);
459
+ case "stripe":
460
+ return (
461
+ await getAgentByName(env.StripeAgent, verified.agentName)
462
+ ).fetch(request);
463
+ case "slack":
464
+ return (
465
+ await getAgentByName(env.SlackAgent, verified.agentName)
466
+ ).fetch(request);
494
467
  }
495
468
  }
496
469
 
package/docs/workflows.md CHANGED
@@ -246,7 +246,11 @@ const instanceId = await this.runWorkflow(
246
246
  {
247
247
  id: "custom-id", // optional - auto-generated if not provided
248
248
  metadata: { userId: "user-456", priority: "high" }, // optional - for querying
249
- agentBinding: "MyAgent" // optional - auto-detected from class name if not provided
249
+ agentBinding: "MyAgent", // optional - auto-detected from class name if not provided
250
+ retention: {
251
+ successRetention: "1 day",
252
+ errorRetention: "7 days"
253
+ }
250
254
  }
251
255
  );
252
256
  ```
@@ -258,6 +262,10 @@ const instanceId = await this.runWorkflow(
258
262
  - `options.id` - Custom workflow ID (auto-generated if not provided)
259
263
  - `options.metadata` - Optional metadata stored for querying (not passed to workflow)
260
264
  - `options.agentBinding` - Agent binding name (auto-detected from class name if not provided). When called from a sub-agent, this is the root Agent binding name.
265
+ - [`options.retention`](https://developers.cloudflare.com/workflows/build/workers-api/#workflowinstancecreateoptions) - Workflow retention passed unchanged to
266
+ `Workflow.create()`.
267
+ Use `successRetention` for successful instances and `errorRetention` for
268
+ errored or terminated instances.
261
269
 
262
270
  **Returns:** Workflow instance ID
263
271
 
package/package.json CHANGED
@@ -9,16 +9,13 @@
9
9
  "durable objects"
10
10
  ],
11
11
  "type": "module",
12
- "version": "0.20.1",
12
+ "version": "0.22.0",
13
13
  "license": "MIT",
14
14
  "repository": {
15
15
  "directory": "packages/agents",
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/cloudflare/agents.git"
18
18
  },
19
- "bin": {
20
- "agents": "dist/cli/index.js"
21
- },
22
19
  "author": "Cloudflare Inc.",
23
20
  "bugs": {
24
21
  "url": "https://github.com/cloudflare/agents/issues"
@@ -31,13 +28,12 @@
31
28
  "esbuild": "^0.28.1",
32
29
  "mimetext": "^3.0.28",
33
30
  "nanoid": "^5.1.16",
34
- "partyserver": "^0.5.8",
35
31
  "partysocket": "1.3.0",
36
- "yaml": "^2.9.0",
37
- "yargs": "^18.0.0"
32
+ "yaml": "^2.9.0"
38
33
  },
39
34
  "devDependencies": {
40
35
  "@ai-sdk/react": "^4.0.0",
36
+ "@ai-sdk/valibot": "3.0.6",
41
37
  "@cloudflare/codemode": "^0.5.1",
42
38
  "@modelcontextprotocol/client": "2.0.0",
43
39
  "@modelcontextprotocol/conformance-v2": "npm:@modelcontextprotocol/conformance@0.2.0-alpha.10",
@@ -45,8 +41,7 @@
45
41
  "@modelcontextprotocol/server": "2.0.0",
46
42
  "@tanstack/ai": "0.38.0",
47
43
  "@types/react": "^19.2.17",
48
- "@types/yargs": "^17.0.35",
49
- "@vitest/browser-playwright": "^4.1.9",
44
+ "@valibot/to-json-schema": "1.3.0",
50
45
  "@x402/core": "^2.17.0",
51
46
  "@x402/evm": "^2.17.0",
52
47
  "ai": "^7.0.0",
@@ -54,7 +49,7 @@
54
49
  "glob": "^13.0.6",
55
50
  "just-bash": "^3.0.2",
56
51
  "react": "^19.2.7",
57
- "vitest-browser-react": "^2.2.0",
52
+ "valibot": "1.4.1",
58
53
  "zod": "^4.4.3"
59
54
  },
60
55
  "peerDependencies": {
@@ -98,6 +93,9 @@
98
93
  "just-bash": {
99
94
  "optional": true
100
95
  },
96
+ "react": {
97
+ "optional": true
98
+ },
101
99
  "vite": {
102
100
  "optional": true
103
101
  }
@@ -119,6 +117,11 @@
119
117
  "import": "./dist/client.js",
120
118
  "require": "./dist/client.js"
121
119
  },
120
+ "./lifecycle": {
121
+ "types": "./dist/lifecycle/index.d.ts",
122
+ "import": "./dist/lifecycle/index.js",
123
+ "require": "./dist/lifecycle/index.js"
124
+ },
122
125
  "./agent-tools": {
123
126
  "types": "./dist/agent-tools.d.ts",
124
127
  "import": "./dist/agent-tools.js",
@@ -135,19 +138,19 @@
135
138
  "require": "./dist/mcp/index.js"
136
139
  },
137
140
  "./mcp/client": {
138
- "types": "./dist/mcp/client.d.ts",
139
- "import": "./dist/mcp/client.js",
140
- "require": "./dist/mcp/client.js"
141
+ "types": "./dist/mcp/client/index.d.ts",
142
+ "import": "./dist/mcp/client/index.js",
143
+ "require": "./dist/mcp/client/index.js"
141
144
  },
142
145
  "./mcp/server": {
143
- "types": "./dist/mcp/server.d.ts",
144
- "import": "./dist/mcp/server.js",
145
- "require": "./dist/mcp/server.js"
146
+ "types": "./dist/mcp/server/index.d.ts",
147
+ "import": "./dist/mcp/server/index.js",
148
+ "require": "./dist/mcp/server/index.js"
146
149
  },
147
150
  "./mcp/do-oauth-client-provider": {
148
- "types": "./dist/mcp/do-oauth-client-provider.d.ts",
149
- "import": "./dist/mcp/do-oauth-client-provider.js",
150
- "require": "./dist/mcp/do-oauth-client-provider.js"
151
+ "types": "./dist/mcp/client/do-oauth-client-provider.d.ts",
152
+ "import": "./dist/mcp/client/do-oauth-client-provider.js",
153
+ "require": "./dist/mcp/client/do-oauth-client-provider.js"
151
154
  },
152
155
  "./observability": {
153
156
  "types": "./dist/observability/index.d.ts",
@@ -169,6 +172,16 @@
169
172
  "import": "./dist/schedule.js",
170
173
  "require": "./dist/schedule.js"
171
174
  },
175
+ "./schedules": {
176
+ "types": "./dist/schedules/index.d.ts",
177
+ "import": "./dist/schedules/index.js",
178
+ "require": "./dist/schedules/index.js"
179
+ },
180
+ "./schedules/parser": {
181
+ "types": "./dist/schedules/parser.d.ts",
182
+ "import": "./dist/schedules/parser.js",
183
+ "require": "./dist/schedules/parser.js"
184
+ },
172
185
  "./workflows": {
173
186
  "types": "./dist/workflows.d.ts",
174
187
  "import": "./dist/workflows.js",
@@ -190,15 +203,20 @@
190
203
  "require": "./dist/experimental/webmcp.js"
191
204
  },
192
205
  "./x402": {
193
- "types": "./dist/mcp/x402.d.ts",
194
- "import": "./dist/mcp/x402.js",
195
- "require": "./dist/mcp/x402.js"
206
+ "types": "./dist/mcp/client/x402.d.ts",
207
+ "import": "./dist/mcp/client/x402.js",
208
+ "require": "./dist/mcp/client/x402.js"
196
209
  },
197
210
  "./chat": {
198
211
  "types": "./dist/chat/index.d.ts",
199
212
  "import": "./dist/chat/index.js",
200
213
  "require": "./dist/chat/index.js"
201
214
  },
215
+ "./chat/transport": {
216
+ "types": "./dist/chat/transport.d.ts",
217
+ "import": "./dist/chat/transport.js",
218
+ "require": "./dist/chat/transport.js"
219
+ },
202
220
  "./chat/react": {
203
221
  "types": "./dist/chat/react.d.ts",
204
222
  "import": "./dist/chat/react.js",
@@ -299,7 +317,7 @@
299
317
  "build": "tsx ./scripts/build.ts",
300
318
  "evals": "(cd evals; evalite)",
301
319
  "test": "vitest --run",
302
- "test:cli": "vitest --project cli",
320
+ "test:node": "vitest --project node",
303
321
  "test:react": "vitest --project react",
304
322
  "test:browser": "vitest run --config src/browser-tests/vitest.config.ts",
305
323
  "test:workers": "vitest --project workers",
@@ -1 +0,0 @@
1
- export {};
package/dist/cli/index.js DELETED
@@ -1,26 +0,0 @@
1
- #!/usr/bin/env node
2
- import yargs from "yargs";
3
- import { hideBin } from "yargs/helpers";
4
- //#region src/cli/create.ts
5
- function createCli(argv = process.argv) {
6
- return yargs(hideBin(argv)).scriptName("agents").usage("$0 <command> [options]").command(["init", "create"], "Initialize an agents project", (cmd) => cmd, async () => {
7
- console.log("agents init: not implemented yet");
8
- process.exit(0);
9
- }).command("dev", "Start development server", (cmd) => cmd, async () => {
10
- console.log("agents dev: not implemented yet");
11
- process.exit(0);
12
- }).command("deploy", "Deploy agents to Cloudflare", (cmd) => cmd, async () => {
13
- console.log("agents deploy: not implemented yet");
14
- process.exit(0);
15
- }).command("mcp", "The agents mcp server", (cmd) => cmd, async () => {
16
- console.log("agents mcp: not implemented yet");
17
- process.exit(0);
18
- }).demandCommand(1, "Please provide a command").strict().help();
19
- }
20
- //#endregion
21
- //#region src/cli/index.ts
22
- createCli().parse();
23
- //#endregion
24
- export {};
25
-
26
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/cli/create.ts","../../src/cli/index.ts"],"sourcesContent":["import yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\n\nexport function createCli(argv = process.argv) {\n return yargs(hideBin(argv))\n .scriptName(\"agents\")\n .usage(\"$0 <command> [options]\")\n .command(\n [\"init\", \"create\"],\n \"Initialize an agents project\",\n (cmd) => cmd,\n async () => {\n console.log(\"agents init: not implemented yet\");\n process.exit(0);\n }\n )\n .command(\n \"dev\",\n \"Start development server\",\n (cmd) => cmd,\n async () => {\n console.log(\"agents dev: not implemented yet\");\n process.exit(0);\n }\n )\n .command(\n \"deploy\",\n \"Deploy agents to Cloudflare\",\n (cmd) => cmd,\n async () => {\n console.log(\"agents deploy: not implemented yet\");\n process.exit(0);\n }\n )\n .command(\n \"mcp\",\n \"The agents mcp server\",\n (cmd) => cmd,\n async () => {\n console.log(\"agents mcp: not implemented yet\");\n process.exit(0);\n }\n )\n .demandCommand(1, \"Please provide a command\")\n .strict()\n .help();\n}\n","#!/usr/bin/env node\n\nimport { createCli } from \"./create\";\n\nvoid createCli().parse();\n"],"mappings":";;;;AAGA,SAAgB,UAAU,OAAO,QAAQ,MAAM;CAC7C,OAAO,MAAM,QAAQ,IAAI,CAAC,CAAC,CACxB,WAAW,QAAQ,CAAC,CACpB,MAAM,wBAAwB,CAAC,CAC/B,QACC,CAAC,QAAQ,QAAQ,GACjB,iCACC,QAAQ,KACT,YAAY;EACV,QAAQ,IAAI,kCAAkC;EAC9C,QAAQ,KAAK,CAAC;CAChB,CACF,CAAC,CACA,QACC,OACA,6BACC,QAAQ,KACT,YAAY;EACV,QAAQ,IAAI,iCAAiC;EAC7C,QAAQ,KAAK,CAAC;CAChB,CACF,CAAC,CACA,QACC,UACA,gCACC,QAAQ,KACT,YAAY;EACV,QAAQ,IAAI,oCAAoC;EAChD,QAAQ,KAAK,CAAC;CAChB,CACF,CAAC,CACA,QACC,OACA,0BACC,QAAQ,KACT,YAAY;EACV,QAAQ,IAAI,iCAAiC;EAC7C,QAAQ,KAAK,CAAC;CAChB,CACF,CAAC,CACA,cAAc,GAAG,0BAA0B,CAAC,CAC5C,OAAO,CAAC,CACR,KAAK;AACV;;;AC1CK,UAAU,CAAC,CAAC,MAAM"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-invoker-VNZ7X0nn.js","names":[],"sources":["../src/mcp/client-invoker.ts"],"sourcesContent":["import type {\n CallToolRequest,\n CallToolRequestOptions,\n CallToolResult,\n Client,\n ListToolsResult\n} from \"@modelcontextprotocol/client\";\nimport type { Client as LegacyClient } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type {\n CallToolResult as LegacyCallToolResult,\n CallToolResultSchema,\n CompatibilityCallToolResultSchema,\n ListToolsRequest\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { RequestOptions as LegacyRequestOptions } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\n\nexport type LegacyCallToolResultSchema =\n | typeof CallToolResultSchema\n | typeof CompatibilityCallToolResultSchema;\n\nexport type CallToolSchemaOrOptions =\n | LegacyCallToolResultSchema\n | CallToolRequestOptions;\n\nexport type CompatibleMcpClient = Client | LegacyClient;\n\nexport interface BoundMcpClientInvoker {\n listTools(\n params?: ListToolsRequest[\"params\"],\n options?: CallToolRequestOptions\n ): Promise<ListToolsResult>;\n callTool(\n params: CallToolRequest[\"params\"],\n schemaOrOptions?: CallToolSchemaOrOptions,\n options?: CallToolRequestOptions\n ): Promise<LegacyCallToolResult>;\n}\n\nfunction isLegacyResultSchema(\n value: CallToolSchemaOrOptions | undefined\n): value is LegacyCallToolResultSchema {\n return (\n typeof (value as { parse?: unknown } | undefined)?.parse === \"function\"\n );\n}\n\nfunction normalizeCallToolArguments(\n schemaOrOptions?: CallToolSchemaOrOptions,\n options?: CallToolRequestOptions\n): {\n schema?: LegacyCallToolResultSchema;\n options?: CallToolRequestOptions;\n} {\n return isLegacyResultSchema(schemaOrOptions)\n ? { schema: schemaOrOptions, options }\n : { options: schemaOrOptions ?? options };\n}\n\n/**\n * Invoke SDK v2 while preserving the deprecated v1 result-schema overload.\n * The v2 request funnel still owns decoding and automatic MRTR.\n */\nexport function callV2Tool(\n client: Client,\n params: CallToolRequest[\"params\"],\n schemaOrOptions?: CallToolSchemaOrOptions,\n options?: CallToolRequestOptions\n): Promise<CallToolResult> {\n const normalized = normalizeCallToolArguments(schemaOrOptions, options);\n if (normalized.schema) {\n return client.request(\n { method: \"tools/call\", params },\n normalized.schema as never,\n normalized.options\n ) as Promise<CallToolResult>;\n }\n return client.callTool(params, normalized.options);\n}\n\n/**\n * Bind the original methods before decorators such as x402 replace them. All\n * generation-specific calling conventions stay behind this small interface.\n */\nexport function bindMcpClient(\n client: CompatibleMcpClient\n): BoundMcpClientInvoker {\n const isV2 =\n \"getProtocolEra\" in client &&\n typeof (client as { getProtocolEra?: unknown }).getProtocolEra ===\n \"function\";\n\n if (isV2) {\n const v2Client = client as Client;\n const listTools = v2Client.listTools.bind(v2Client);\n const callTool = v2Client.callTool.bind(v2Client);\n return {\n listTools: (params, options) => listTools(params, options),\n callTool: (params, schemaOrOptions, options) => {\n const normalized = normalizeCallToolArguments(schemaOrOptions, options);\n const pending = normalized.schema\n ? v2Client.request(\n { method: \"tools/call\", params },\n normalized.schema as never,\n normalized.options\n )\n : callTool(params, normalized.options);\n return pending as Promise<LegacyCallToolResult>;\n }\n };\n }\n\n const legacy = client as LegacyClient;\n const listTools = legacy.listTools.bind(legacy);\n const callTool = legacy.callTool.bind(legacy);\n return {\n listTools: (params, options) =>\n listTools(\n params,\n options as LegacyRequestOptions\n ) as Promise<ListToolsResult>,\n callTool: (params, schemaOrOptions, options) => {\n const normalized = normalizeCallToolArguments(schemaOrOptions, options);\n return callTool(\n params as Parameters<LegacyClient[\"callTool\"]>[0],\n normalized.schema,\n normalized.options as LegacyRequestOptions\n ) as Promise<LegacyCallToolResult>;\n }\n };\n}\n"],"mappings":";AAsCA,SAAS,qBACP,OACqC;CACrC,OACE,OAAQ,OAA2C,UAAU;AAEjE;AAEA,SAAS,2BACP,iBACA,SAIA;CACA,OAAO,qBAAqB,eAAe,IACvC;EAAE,QAAQ;EAAiB;CAAQ,IACnC,EAAE,SAAS,mBAAmB,QAAQ;AAC5C;;;;;AAMA,SAAgB,WACd,QACA,QACA,iBACA,SACyB;CACzB,MAAM,aAAa,2BAA2B,iBAAiB,OAAO;CACtE,IAAI,WAAW,QACb,OAAO,OAAO,QACZ;EAAE,QAAQ;EAAc;CAAO,GAC/B,WAAW,QACX,WAAW,OACb;CAEF,OAAO,OAAO,SAAS,QAAQ,WAAW,OAAO;AACnD;;;;;AAMA,SAAgB,cACd,QACuB;CAMvB,IAJE,oBAAoB,UACpB,OAAQ,OAAwC,mBAC9C,YAEM;EACR,MAAM,WAAW;EACjB,MAAM,YAAY,SAAS,UAAU,KAAK,QAAQ;EAClD,MAAM,WAAW,SAAS,SAAS,KAAK,QAAQ;EAChD,OAAO;GACL,YAAY,QAAQ,YAAY,UAAU,QAAQ,OAAO;GACzD,WAAW,QAAQ,iBAAiB,YAAY;IAC9C,MAAM,aAAa,2BAA2B,iBAAiB,OAAO;IAQtE,OAPgB,WAAW,SACvB,SAAS,QACP;KAAE,QAAQ;KAAc;IAAO,GAC/B,WAAW,QACX,WAAW,OACb,IACA,SAAS,QAAQ,WAAW,OAAO;GAEzC;EACF;CACF;CAEA,MAAM,SAAS;CACf,MAAM,YAAY,OAAO,UAAU,KAAK,MAAM;CAC9C,MAAM,WAAW,OAAO,SAAS,KAAK,MAAM;CAC5C,OAAO;EACL,YAAY,QAAQ,YAClB,UACE,QACA,OACF;EACF,WAAW,QAAQ,iBAAiB,YAAY;GAC9C,MAAM,aAAa,2BAA2B,iBAAiB,OAAO;GACtE,OAAO,SACL,QACA,WAAW,QACX,WAAW,OACb;EACF;CACF;AACF"}