agents 0.20.1 → 0.21.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 (76) hide show
  1. package/README.md +50 -12
  2. package/dist/{agent-tool-types-BC-WFlsz.d.ts → agent-tool-types-CzGGB-20.d.ts} +375 -72
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-DeHe9Xov.d.ts → agent-tools-zR2d5uij.d.ts} +2 -2
  5. package/dist/agent-tools.d.ts +24 -8
  6. package/dist/agent-tools.js +14 -6
  7. package/dist/agent-tools.js.map +1 -1
  8. package/dist/browser/ai.d.ts +5 -3
  9. package/dist/browser/ai.js +86 -7
  10. package/dist/browser/ai.js.map +1 -1
  11. package/dist/browser/index.d.ts +1 -1
  12. package/dist/browser/index.js +1 -1
  13. package/dist/browser/tanstack-ai.js +13 -1
  14. package/dist/browser/tanstack-ai.js.map +1 -1
  15. package/dist/chat/index.d.ts +31 -2
  16. package/dist/chat/index.js +57 -2
  17. package/dist/chat/index.js.map +1 -1
  18. package/dist/chat/react.d.ts +5 -179
  19. package/dist/chat/react.js +18 -555
  20. package/dist/chat/react.js.map +1 -1
  21. package/dist/chat/transport.d.ts +10 -0
  22. package/dist/chat/transport.js +2 -0
  23. package/dist/chat-sdk/index.d.ts +1 -1
  24. package/dist/client.d.ts +1 -1
  25. package/dist/{connector-v2M1zlZp.d.ts → connector-CkQD4MK3.d.ts} +20 -3
  26. package/dist/{connector-KEJnl6e5.js → connector-CptFKzRh.js} +158 -40
  27. package/dist/connector-CptFKzRh.js.map +1 -0
  28. package/dist/index.d.ts +20 -12
  29. package/dist/index.js +5 -4
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp/client.d.ts +20 -20
  32. package/dist/mcp/index.d.ts +35 -35
  33. package/dist/observability/ai/index.d.ts +1 -130
  34. package/dist/observability/ai/index.js +39 -399
  35. package/dist/observability/ai/index.js.map +1 -1
  36. package/dist/{wire-types-CU9rLoeS.js → protocol-Dqc2MQxo.js} +2 -46
  37. package/dist/protocol-Dqc2MQxo.js.map +1 -0
  38. package/dist/react.d.ts +1 -1
  39. package/dist/react.js +5 -8
  40. package/dist/react.js.map +1 -1
  41. package/dist/serializable.d.ts +1 -1
  42. package/dist/sub-routing.d.ts +18 -6
  43. package/dist/sub-routing.js +92 -2
  44. package/dist/sub-routing.js.map +1 -1
  45. package/dist/wire-types-CnMt6_HR.js +47 -0
  46. package/dist/wire-types-CnMt6_HR.js.map +1 -0
  47. package/dist/workflow-types.d.ts +25 -25
  48. package/dist/workflow-types.js.map +1 -1
  49. package/dist/workflows.d.ts +22 -22
  50. package/dist/ws-chat-transport-CIoOBbO7.js +561 -0
  51. package/dist/ws-chat-transport-CIoOBbO7.js.map +1 -0
  52. package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
  53. package/docs/adding-to-existing-project.md +4 -2
  54. package/docs/agent-class.md +1 -1
  55. package/docs/agent-tools.md +29 -0
  56. package/docs/browse-the-web.md +16 -1
  57. package/docs/chat-agents.md +3 -1
  58. package/docs/client-sdk.md +12 -8
  59. package/docs/configuration.md +7 -1
  60. package/docs/cross-domain-authentication.md +7 -35
  61. package/docs/email.md +2 -13
  62. package/docs/human-in-the-loop.md +15 -12
  63. package/docs/long-running-agents.md +11 -11
  64. package/docs/mcp-servers.md +6 -0
  65. package/docs/mcp-transports.md +18 -12
  66. package/docs/migration-to-ai-sdk-v5.md +2 -2
  67. package/docs/migration-to-ai-sdk-v6.md +5 -1
  68. package/docs/observability.md +22 -66
  69. package/docs/routing.md +27 -0
  70. package/docs/sub-agents.md +47 -2
  71. package/docs/webhooks.md +109 -136
  72. package/docs/workflows.md +9 -1
  73. package/package.json +13 -4
  74. package/dist/connector-KEJnl6e5.js.map +0 -1
  75. package/dist/wire-types-CU9rLoeS.js.map +0 -1
  76. package/dist/workflow-types-Baz_PO5v.d.ts +0 -280
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,7 +9,7 @@
9
9
  "durable objects"
10
10
  ],
11
11
  "type": "module",
12
- "version": "0.20.1",
12
+ "version": "0.21.0",
13
13
  "license": "MIT",
14
14
  "repository": {
15
15
  "directory": "packages/agents",
@@ -31,13 +31,14 @@
31
31
  "esbuild": "^0.28.1",
32
32
  "mimetext": "^3.0.28",
33
33
  "nanoid": "^5.1.16",
34
- "partyserver": "^0.5.8",
34
+ "partyserver": "^0.5.9",
35
35
  "partysocket": "1.3.0",
36
36
  "yaml": "^2.9.0",
37
37
  "yargs": "^18.0.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@ai-sdk/react": "^4.0.0",
41
+ "@ai-sdk/valibot": "3.0.6",
41
42
  "@cloudflare/codemode": "^0.5.1",
42
43
  "@modelcontextprotocol/client": "2.0.0",
43
44
  "@modelcontextprotocol/conformance-v2": "npm:@modelcontextprotocol/conformance@0.2.0-alpha.10",
@@ -46,7 +47,7 @@
46
47
  "@tanstack/ai": "0.38.0",
47
48
  "@types/react": "^19.2.17",
48
49
  "@types/yargs": "^17.0.35",
49
- "@vitest/browser-playwright": "^4.1.9",
50
+ "@valibot/to-json-schema": "1.3.0",
50
51
  "@x402/core": "^2.17.0",
51
52
  "@x402/evm": "^2.17.0",
52
53
  "ai": "^7.0.0",
@@ -54,7 +55,7 @@
54
55
  "glob": "^13.0.6",
55
56
  "just-bash": "^3.0.2",
56
57
  "react": "^19.2.7",
57
- "vitest-browser-react": "^2.2.0",
58
+ "valibot": "1.4.1",
58
59
  "zod": "^4.4.3"
59
60
  },
60
61
  "peerDependencies": {
@@ -98,6 +99,9 @@
98
99
  "just-bash": {
99
100
  "optional": true
100
101
  },
102
+ "react": {
103
+ "optional": true
104
+ },
101
105
  "vite": {
102
106
  "optional": true
103
107
  }
@@ -199,6 +203,11 @@
199
203
  "import": "./dist/chat/index.js",
200
204
  "require": "./dist/chat/index.js"
201
205
  },
206
+ "./chat/transport": {
207
+ "types": "./dist/chat/transport.d.ts",
208
+ "import": "./dist/chat/transport.js",
209
+ "require": "./dist/chat/transport.js"
210
+ },
202
211
  "./chat/react": {
203
212
  "types": "./dist/chat/react.d.ts",
204
213
  "import": "./dist/chat/react.js",