agents 0.16.1 → 0.17.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 (127) hide show
  1. package/README.md +11 -8
  2. package/dist/{agent-tool-types-NofdbL9X.d.ts → agent-tool-types-Cd1TZPfB.d.ts} +595 -137
  3. package/dist/agent-tool-types.d.ts +34 -18
  4. package/dist/agent-tool-types.js +20 -1
  5. package/dist/agent-tool-types.js.map +1 -0
  6. package/dist/agent-tools-BXlsuX0d.js +304 -0
  7. package/dist/agent-tools-BXlsuX0d.js.map +1 -0
  8. package/dist/agent-tools-_E8wxUIK.d.ts +119 -0
  9. package/dist/agent-tools.d.ts +24 -18
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/ai-chat-agent.d.ts +1 -1
  12. package/dist/ai-chat-agent.js +1 -2
  13. package/dist/ai-chat-agent.js.map +1 -1
  14. package/dist/ai-chat-v5-migration.d.ts +1 -1
  15. package/dist/ai-chat-v5-migration.js +1 -2
  16. package/dist/ai-chat-v5-migration.js.map +1 -1
  17. package/dist/ai-react.d.ts +1 -1
  18. package/dist/ai-react.js +1 -2
  19. package/dist/ai-react.js.map +1 -1
  20. package/dist/ai-types.d.ts +1 -7
  21. package/dist/ai-types.js +2 -8
  22. package/dist/ai-types.js.map +1 -1
  23. package/dist/browser/ai.js +1 -1
  24. package/dist/browser/index.js +1 -1
  25. package/dist/chat/index.d.ts +1923 -76
  26. package/dist/chat/index.js +1784 -278
  27. package/dist/chat/index.js.map +1 -1
  28. package/dist/chat/react.d.ts +602 -0
  29. package/dist/chat/react.js +1506 -0
  30. package/dist/chat/react.js.map +1 -0
  31. package/dist/chat-sdk/index.d.ts +4 -4
  32. package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
  33. package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
  34. package/dist/{client-FUizKzj2.js → client-BZ-B3NhC.js} +19 -3
  35. package/dist/client-BZ-B3NhC.js.map +1 -0
  36. package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
  37. package/dist/client.d.ts +76 -57
  38. package/dist/client.js +33 -5
  39. package/dist/client.js.map +1 -1
  40. package/dist/{compaction-helpers-DVcu5lPN.d.ts → compaction-helpers-wUz6M3us.d.ts} +20 -1
  41. package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
  42. package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
  43. package/dist/email.js +1 -1
  44. package/dist/email.js.map +1 -1
  45. package/dist/experimental/memory/session/index.d.ts +1 -1
  46. package/dist/experimental/memory/session/index.js +20 -2
  47. package/dist/experimental/memory/session/index.js.map +1 -1
  48. package/dist/experimental/memory/utils/index.d.ts +1 -1
  49. package/dist/{index-B7IbEeze.d.ts → index-CcbnKkNh.d.ts} +188 -14
  50. package/dist/index.d.ts +91 -71
  51. package/dist/index.js +562 -24
  52. package/dist/index.js.map +1 -1
  53. package/dist/mcp/client.d.ts +18 -14
  54. package/dist/mcp/client.js +1 -1
  55. package/dist/mcp/index.d.ts +30 -30
  56. package/dist/mcp/index.js +7 -7
  57. package/dist/mcp/index.js.map +1 -1
  58. package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
  59. package/dist/message-builder-BymO4N_D.js.map +1 -0
  60. package/dist/observability/index.d.ts +1 -1
  61. package/dist/observability/index.js +4 -2
  62. package/dist/observability/index.js.map +1 -1
  63. package/dist/react.d.ts +123 -110
  64. package/dist/react.js +44 -11
  65. package/dist/react.js.map +1 -1
  66. package/dist/serializable.d.ts +1 -1
  67. package/dist/skills/compile.js +1 -1
  68. package/dist/skills/compile.js.map +1 -1
  69. package/dist/skills/index.js +5 -5
  70. package/dist/skills/index.js.map +1 -1
  71. package/dist/sub-routing.d.ts +6 -6
  72. package/dist/utils.js +1 -1
  73. package/dist/utils.js.map +1 -1
  74. package/dist/vite.js +5 -5
  75. package/dist/vite.js.map +1 -1
  76. package/dist/wire-types-nflOzNuU.js +240 -0
  77. package/dist/wire-types-nflOzNuU.js.map +1 -0
  78. package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
  79. package/dist/workflow-types.d.ts +25 -21
  80. package/dist/workflow-types.js.map +1 -1
  81. package/dist/workflows.d.ts +22 -21
  82. package/dist/workflows.js +31 -7
  83. package/dist/workflows.js.map +1 -1
  84. package/docs/adding-to-existing-project.md +450 -0
  85. package/docs/agent-class.md +503 -0
  86. package/docs/agent-tools.md +552 -0
  87. package/docs/browse-the-web.md +430 -0
  88. package/docs/callable-methods.md +627 -0
  89. package/docs/chat-agents.md +1687 -0
  90. package/docs/chat-sdk.md +181 -0
  91. package/docs/client-sdk.md +520 -0
  92. package/docs/client-tools-continuation.md +177 -0
  93. package/docs/codemode.md +440 -0
  94. package/docs/configuration.md +775 -0
  95. package/docs/cross-domain-authentication.md +171 -0
  96. package/docs/durable-execution.md +537 -0
  97. package/docs/email.md +663 -0
  98. package/docs/get-current-agent.md +204 -0
  99. package/docs/getting-started.md +305 -0
  100. package/docs/http-websockets.md +668 -0
  101. package/docs/human-in-the-loop.md +661 -0
  102. package/docs/index.md +151 -0
  103. package/docs/long-running-agents.md +730 -0
  104. package/docs/mcp-client.md +620 -0
  105. package/docs/mcp-servers.md +526 -0
  106. package/docs/mcp-transports.md +308 -0
  107. package/docs/migration-to-ai-sdk-v5.md +96 -0
  108. package/docs/migration-to-ai-sdk-v6.md +163 -0
  109. package/docs/observability.md +261 -0
  110. package/docs/push-notifications.md +367 -0
  111. package/docs/queue.md +329 -0
  112. package/docs/readonly-connections.md +278 -0
  113. package/docs/resumable-streaming.md +127 -0
  114. package/docs/retries.md +444 -0
  115. package/docs/routing.md +749 -0
  116. package/docs/scheduling.md +898 -0
  117. package/docs/securing-mcp-servers.md +359 -0
  118. package/docs/server-driven-messages.md +477 -0
  119. package/docs/sessions.md +1024 -0
  120. package/docs/state.md +512 -0
  121. package/docs/sub-agents.md +389 -0
  122. package/docs/webhooks.md +604 -0
  123. package/docs/workflows.md +877 -0
  124. package/package.json +47 -15
  125. package/dist/agent-tools-3zLG7MgA.js.map +0 -1
  126. package/dist/agent-tools-DLquv-dp.d.ts +0 -14
  127. package/dist/client-FUizKzj2.js.map +0 -1
@@ -0,0 +1,749 @@
1
+ # Routing
2
+
3
+ This guide explains how requests are routed to agents, how naming works, and patterns for organizing your agents.
4
+
5
+ ---
6
+
7
+ ## How Routing Works
8
+
9
+ When a request comes in, `routeAgentRequest()` examines the URL and routes it to the appropriate agent instance:
10
+
11
+ ```
12
+ https://your-worker.dev/agents/{agent-name}/{instance-name}
13
+ └─────┬─────┘ └─────┬──────┘
14
+ Class name Unique instance ID
15
+ (kebab-case)
16
+ ```
17
+
18
+ **Example URLs:**
19
+
20
+ | URL | Agent Class | Instance |
21
+ | -------------------------- | ----------- | ---------- |
22
+ | `/agents/counter/user-123` | `Counter` | `user-123` |
23
+ | `/agents/chat-room/lobby` | `ChatRoom` | `lobby` |
24
+ | `/agents/my-agent/default` | `MyAgent` | `default` |
25
+
26
+ ---
27
+
28
+ ## Name Resolution
29
+
30
+ Agent class names are automatically converted to kebab-case for URLs:
31
+
32
+ | Class Name | URL Path |
33
+ | ------------- | -------------------------- |
34
+ | `Counter` | `/agents/counter/...` |
35
+ | `MyAgent` | `/agents/my-agent/...` |
36
+ | `ChatRoom` | `/agents/chat-room/...` |
37
+ | `AIAssistant` | `/agents/ai-assistant/...` |
38
+
39
+ The router matches both the original name and kebab-case version, so these all work:
40
+
41
+ - `useAgent({ agent: "Counter" })` → `/agents/counter/...`
42
+ - `useAgent({ agent: "counter" })` → `/agents/counter/...`
43
+
44
+ ---
45
+
46
+ ## Basic Usage
47
+
48
+ ### `routeAgentRequest()`
49
+
50
+ The main entry point for agent routing. Handles both HTTP requests and WebSocket upgrades:
51
+
52
+ ```typescript
53
+ import { routeAgentRequest } from "agents";
54
+
55
+ export default {
56
+ async fetch(request: Request, env: Env, ctx: ExecutionContext) {
57
+ // Route to agents - returns Response or undefined
58
+ const agentResponse = await routeAgentRequest(request, env);
59
+
60
+ if (agentResponse) {
61
+ return agentResponse;
62
+ }
63
+
64
+ // No agent matched - handle other routes
65
+ return new Response("Not found", { status: 404 });
66
+ }
67
+ };
68
+ ```
69
+
70
+ ### `getAgentByName()`
71
+
72
+ Get a specific agent instance for server-side RPC calls or request forwarding:
73
+
74
+ ```typescript
75
+ import { getAgentByName, routeAgentRequest } from "agents";
76
+
77
+ export default {
78
+ async fetch(request: Request, env: Env) {
79
+ const url = new URL(request.url);
80
+
81
+ // API endpoint that interacts with an agent
82
+ if (url.pathname === "/api/increment") {
83
+ const counter = await getAgentByName(env.Counter, "global-counter");
84
+ const newCount = await counter.increment();
85
+ return Response.json({ count: newCount });
86
+ }
87
+
88
+ // Regular agent routing
89
+ return (
90
+ (await routeAgentRequest(request, env)) ??
91
+ new Response("Not found", { status: 404 })
92
+ );
93
+ }
94
+ };
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Instance Naming Patterns
100
+
101
+ The instance name (the last part of the URL) determines which agent instance handles the request. Each unique name gets its own isolated agent with its own state.
102
+
103
+ ### Per-User Agents
104
+
105
+ Each user gets their own agent instance:
106
+
107
+ ```typescript
108
+ // Client
109
+ const agent = useAgent({
110
+ agent: "UserProfile",
111
+ name: `user-${userId}` // e.g., "user-abc123"
112
+ });
113
+ ```
114
+
115
+ ```
116
+ /agents/user-profile/user-abc123 → User abc123's agent
117
+ /agents/user-profile/user-xyz789 → User xyz789's agent (separate instance)
118
+ ```
119
+
120
+ ### Shared Rooms
121
+
122
+ Multiple users share the same agent instance:
123
+
124
+ ```typescript
125
+ // Client
126
+ const agent = useAgent({
127
+ agent: "ChatRoom",
128
+ name: roomId // e.g., "general" or "room-42"
129
+ });
130
+ ```
131
+
132
+ ```
133
+ /agents/chat-room/general → All users in "general" share this agent
134
+ ```
135
+
136
+ ### Global Singleton
137
+
138
+ A single instance for the entire application:
139
+
140
+ ```typescript
141
+ // Client
142
+ const agent = useAgent({
143
+ agent: "AppConfig",
144
+ name: "default" // Or any consistent name
145
+ });
146
+ ```
147
+
148
+ ### Dynamic Naming
149
+
150
+ Generate instance names based on context:
151
+
152
+ ```typescript
153
+ // Per-session
154
+ const agent = useAgent({
155
+ agent: "Session",
156
+ name: sessionId
157
+ });
158
+
159
+ // Per-document
160
+ const agent = useAgent({
161
+ agent: "Document",
162
+ name: `doc-${documentId}`
163
+ });
164
+
165
+ // Per-game
166
+ const agent = useAgent({
167
+ agent: "Game",
168
+ name: `game-${gameId}-${Date.now()}`
169
+ });
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Routing Options
175
+
176
+ Both `routeAgentRequest()` and `getAgentByName()` accept options for customizing routing behavior.
177
+
178
+ ### CORS
179
+
180
+ For cross-origin requests (common when your frontend is on a different domain):
181
+
182
+ ```typescript
183
+ const response = await routeAgentRequest(request, env, {
184
+ cors: true // Enable default CORS headers
185
+ });
186
+ ```
187
+
188
+ Or with custom CORS headers:
189
+
190
+ ```typescript
191
+ const response = await routeAgentRequest(request, env, {
192
+ cors: {
193
+ "Access-Control-Allow-Origin": "https://myapp.com",
194
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
195
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
196
+ }
197
+ });
198
+ ```
199
+
200
+ ### Location Hints
201
+
202
+ For latency-sensitive applications, hint where the agent should run:
203
+
204
+ ```typescript
205
+ // With getAgentByName
206
+ const agent = await getAgentByName(env.MyAgent, "instance-name", {
207
+ locationHint: "enam" // Eastern North America
208
+ });
209
+
210
+ // With routeAgentRequest (applies to all matched agents)
211
+ const response = await routeAgentRequest(request, env, {
212
+ locationHint: "enam"
213
+ });
214
+ ```
215
+
216
+ Available location hints: `wnam`, `enam`, `sam`, `weur`, `eeur`, `apac`, `oc`, `afr`, `me`
217
+
218
+ ### Jurisdiction
219
+
220
+ For data residency requirements:
221
+
222
+ ```typescript
223
+ // With getAgentByName
224
+ const agent = await getAgentByName(env.MyAgent, "instance-name", {
225
+ jurisdiction: "eu" // EU jurisdiction
226
+ });
227
+
228
+ // With routeAgentRequest (applies to all matched agents)
229
+ const response = await routeAgentRequest(request, env, {
230
+ jurisdiction: "eu"
231
+ });
232
+ ```
233
+
234
+ ### Props
235
+
236
+ Since agents are instantiated by the runtime rather than constructed directly, `props` provides a way to pass initialization arguments:
237
+
238
+ ```typescript
239
+ const agent = await getAgentByName(env.MyAgent, "instance-name", {
240
+ props: {
241
+ userId: session.userId,
242
+ config: { maxRetries: 3 }
243
+ }
244
+ });
245
+ ```
246
+
247
+ Props are passed to the agent's `onStart` lifecycle method:
248
+
249
+ ```typescript
250
+ class MyAgent extends Agent<Env, State> {
251
+ private userId?: string;
252
+ private config?: { maxRetries: number };
253
+
254
+ async onStart(props?: { userId: string; config: { maxRetries: number } }) {
255
+ this.userId = props?.userId;
256
+ this.config = props?.config;
257
+ }
258
+ }
259
+ ```
260
+
261
+ When using `props` with `routeAgentRequest`, the same props are passed to whichever agent matches the URL. This works well for universal context like authentication:
262
+
263
+ ```typescript
264
+ export default {
265
+ async fetch(request, env) {
266
+ const session = await getSession(request);
267
+ return routeAgentRequest(request, env, {
268
+ props: { userId: session.userId, role: session.role }
269
+ });
270
+ }
271
+ };
272
+ ```
273
+
274
+ For agent-specific initialization, use `getAgentByName` instead where you control exactly which agent receives the props.
275
+
276
+ > **Note:** For `McpAgent`, props are automatically stored and accessible via `this.props`. See [MCP Servers](/mcp-servers) for details.
277
+
278
+ ### Hooks
279
+
280
+ `routeAgentRequest` supports hooks for intercepting requests before they reach agents:
281
+
282
+ ```typescript
283
+ const response = await routeAgentRequest(request, env, {
284
+ onBeforeConnect: (req, lobby) => {
285
+ // Called before WebSocket connections
286
+ // Return a Response to reject, Request to modify, or void to continue
287
+ },
288
+ onBeforeRequest: (req, lobby) => {
289
+ // Called before HTTP requests
290
+ // Return a Response to reject, Request to modify, or void to continue
291
+ }
292
+ });
293
+ ```
294
+
295
+ These hooks are useful for authentication and validation. See [Securing Agents](/securing-agents) for detailed examples.
296
+
297
+ ---
298
+
299
+ ## Custom URL Routing
300
+
301
+ For advanced use cases where you need control over the URL structure, you can bypass the default `/agents/{agent}/{name}` pattern.
302
+
303
+ ### Using `basePath` (Client-Side)
304
+
305
+ The `basePath` option lets clients connect to any URL path:
306
+
307
+ ```typescript
308
+ // Client connects to /user instead of /agents/user-agent/...
309
+ const agent = useAgent({
310
+ agent: "UserAgent", // Required but ignored when basePath is set
311
+ basePath: "user" // → connects to /user
312
+ });
313
+ ```
314
+
315
+ This is useful when:
316
+
317
+ - You want clean URLs without the `/agents/` prefix
318
+ - The instance name is determined server-side (e.g., from auth/session)
319
+ - You're integrating with an existing URL structure
320
+
321
+ ### Server-Side Instance Selection
322
+
323
+ When using `basePath`, the server must handle routing. Use `getAgentByName()` to get the agent instance, then forward the request with `fetch()`:
324
+
325
+ ```typescript
326
+ export default {
327
+ async fetch(request: Request, env: Env) {
328
+ const url = new URL(request.url);
329
+
330
+ // Custom routing - server determines instance from session
331
+ if (url.pathname === "/user") {
332
+ const session = await getSession(request);
333
+ const agent = await getAgentByName(env.UserAgent, session.userId);
334
+ return agent.fetch(request); // Forward request directly to agent
335
+ }
336
+
337
+ // Default routing for standard /agents/... paths
338
+ return (
339
+ (await routeAgentRequest(request, env)) ??
340
+ new Response("Not found", { status: 404 })
341
+ );
342
+ }
343
+ };
344
+ ```
345
+
346
+ ### Custom Path with Dynamic Instance
347
+
348
+ Route different paths to different instances:
349
+
350
+ ```typescript
351
+ // Route /chat/{room} to ChatRoom agent
352
+ if (url.pathname.startsWith("/chat/")) {
353
+ const roomId = url.pathname.replace("/chat/", "");
354
+ const agent = await getAgentByName(env.ChatRoom, roomId);
355
+ return agent.fetch(request);
356
+ }
357
+
358
+ // Route /doc/{id} to Document agent
359
+ if (url.pathname.startsWith("/doc/")) {
360
+ const docId = url.pathname.replace("/doc/", "");
361
+ const agent = await getAgentByName(env.Document, docId);
362
+ return agent.fetch(request);
363
+ }
364
+ ```
365
+
366
+ ### Receiving the Instance Identity (Client-Side)
367
+
368
+ When using `basePath`, the client doesn't know which instance it connected to until the server tells it. The agent automatically sends its identity on connection:
369
+
370
+ ```typescript
371
+ const agent = useAgent({
372
+ agent: "UserAgent",
373
+ basePath: "user",
374
+ onIdentity: (name, agentType) => {
375
+ console.log(`Connected to ${agentType} instance: ${name}`);
376
+ // e.g., "Connected to user-agent instance: user-123"
377
+ }
378
+ });
379
+
380
+ // Reactive state - re-renders when identity is received
381
+ return (
382
+ <div>
383
+ {agent.identified ? `Connected to: ${agent.name}` : "Connecting..."}
384
+ </div>
385
+ );
386
+ ```
387
+
388
+ For `AgentClient`:
389
+
390
+ ```typescript
391
+ const agent = new AgentClient({
392
+ agent: "UserAgent",
393
+ basePath: "user",
394
+ host: "example.com",
395
+ onIdentity: (name, agentType) => {
396
+ // Update UI with actual instance name
397
+ setInstanceName(name);
398
+ }
399
+ });
400
+
401
+ // Wait for identity before proceeding
402
+ await agent.ready;
403
+ console.log(agent.name); // Now has the server-determined name
404
+ ```
405
+
406
+ ### Handling Identity Changes on Reconnect
407
+
408
+ If the identity changes on reconnect (e.g., session expired and user logs in as someone else), you can handle it with `onIdentityChange`:
409
+
410
+ ```typescript
411
+ const agent = useAgent({
412
+ agent: "UserAgent",
413
+ basePath: "user",
414
+ onIdentityChange: (oldName, newName, oldAgent, newAgent) => {
415
+ console.log(`Session changed: ${oldName} → ${newName}`);
416
+ // Refresh state, show notification, etc.
417
+ }
418
+ });
419
+ ```
420
+
421
+ If `onIdentityChange` is not provided and identity changes, a warning is logged to help catch unexpected session changes.
422
+
423
+ ### Sub-Paths with `path` Option
424
+
425
+ Append additional path segments to the URL:
426
+
427
+ ```typescript
428
+ // With basePath: /user/settings
429
+ useAgent({ agent: "UserAgent", basePath: "user", path: "settings" });
430
+
431
+ // Standard routing: /agents/my-agent/room/settings
432
+ useAgent({ agent: "MyAgent", name: "room", path: "settings" });
433
+ ```
434
+
435
+ ### Disabling Identity for Security
436
+
437
+ If your instance names contain sensitive data (session IDs, internal user IDs), you can disable identity sending:
438
+
439
+ ```typescript
440
+ class SecureAgent extends Agent {
441
+ // Don't expose instance names to clients
442
+ static options = { sendIdentityOnConnect: false };
443
+ }
444
+ ```
445
+
446
+ When identity is disabled:
447
+
448
+ - `agent.identified` stays `false`
449
+ - `agent.ready` never resolves (use state updates instead)
450
+ - `onIdentity` and `onIdentityChange` are never called
451
+
452
+ ### When to Use Custom Routing
453
+
454
+ | Scenario | Approach |
455
+ | --------------------------------- | --------------------------------------- |
456
+ | Standard agent access | Default `/agents/{agent}/{name}` |
457
+ | Instance from auth/session | `basePath` + `getAgentByName` + `fetch` |
458
+ | Clean URLs (no `/agents/` prefix) | `basePath` + custom routing |
459
+ | Legacy URL structure | `basePath` + custom routing |
460
+ | Complex routing logic | Custom routing in Worker |
461
+
462
+ ### Request Flow with Custom Routing
463
+
464
+ ```
465
+ ┌─────────────────┐
466
+ │ /user request │
467
+ │ (basePath) │
468
+ └────────┬────────┘
469
+
470
+
471
+ ┌─────────────────┐
472
+ │ Worker fetch │
473
+ │ getSession() │
474
+ └────────┬────────┘
475
+
476
+
477
+ ┌─────────────────┐
478
+ │ getAgentByName │
479
+ │ (session.userId)│
480
+ └────────┬────────┘
481
+ │ agent.fetch(request)
482
+
483
+ ┌─────────────────┐
484
+ │ Agent Instance │
485
+ │ onConnect() or │
486
+ │ onRequest() │
487
+ └─────────────────┘
488
+ ```
489
+
490
+ ---
491
+
492
+ ## Sub-Paths and HTTP Methods
493
+
494
+ Requests can include sub-paths after the instance name. These are passed to your agent's `onRequest()` handler:
495
+
496
+ ```
497
+ /agents/api/v1/users → agent: "api", instance: "v1", path: "/users"
498
+ /agents/api/v1/users/123 → agent: "api", instance: "v1", path: "/users/123"
499
+ ```
500
+
501
+ Handle sub-paths in your agent:
502
+
503
+ ```typescript
504
+ export class API extends Agent<Env> {
505
+ async onRequest(request: Request): Promise<Response> {
506
+ const url = new URL(request.url);
507
+
508
+ // url.pathname contains the full path including /agents/api/v1/...
509
+ // Extract the sub-path after your agent's base path
510
+ const path = url.pathname.replace(/^\/agents\/api\/[^/]+/, "");
511
+
512
+ if (request.method === "GET" && path === "/users") {
513
+ return Response.json(await this.getUsers());
514
+ }
515
+
516
+ if (request.method === "POST" && path === "/users") {
517
+ const data = await request.json();
518
+ return Response.json(await this.createUser(data));
519
+ }
520
+
521
+ return new Response("Not found", { status: 404 });
522
+ }
523
+ }
524
+ ```
525
+
526
+ ---
527
+
528
+ ## Multiple Agents
529
+
530
+ You can have multiple agent classes in one project. Each gets its own namespace:
531
+
532
+ ```typescript
533
+ // server.ts
534
+ export { Counter } from "./agents/counter";
535
+ export { ChatRoom } from "./agents/chat-room";
536
+ export { UserProfile } from "./agents/user-profile";
537
+
538
+ export default {
539
+ async fetch(request: Request, env: Env) {
540
+ return (
541
+ (await routeAgentRequest(request, env)) ??
542
+ new Response("Not found", { status: 404 })
543
+ );
544
+ }
545
+ };
546
+ ```
547
+
548
+ ```jsonc
549
+ // wrangler.jsonc
550
+ {
551
+ "durable_objects": {
552
+ "bindings": [
553
+ { "name": "Counter", "class_name": "Counter" },
554
+ { "name": "ChatRoom", "class_name": "ChatRoom" },
555
+ { "name": "UserProfile", "class_name": "UserProfile" }
556
+ ]
557
+ },
558
+ "migrations": [
559
+ {
560
+ "tag": "v1",
561
+ "new_sqlite_classes": ["Counter", "ChatRoom", "UserProfile"]
562
+ }
563
+ ]
564
+ }
565
+ ```
566
+
567
+ Each agent is accessed via its own path:
568
+
569
+ ```
570
+ /agents/counter/...
571
+ /agents/chat-room/...
572
+ /agents/user-profile/...
573
+ ```
574
+
575
+ ---
576
+
577
+ ## Routing with Authentication
578
+
579
+ Check authentication before routing to agents:
580
+
581
+ ```typescript
582
+ export default {
583
+ async fetch(request: Request, env: Env) {
584
+ const url = new URL(request.url);
585
+
586
+ // Protect agent routes
587
+ if (url.pathname.startsWith("/agents/")) {
588
+ const user = await authenticate(request, env);
589
+ if (!user) {
590
+ return new Response("Unauthorized", { status: 401 });
591
+ }
592
+
593
+ // Optionally, enforce that users can only access their own agents
594
+ const instanceName = url.pathname.split("/")[3];
595
+ if (instanceName !== `user-${user.id}`) {
596
+ return new Response("Forbidden", { status: 403 });
597
+ }
598
+ }
599
+
600
+ return (
601
+ (await routeAgentRequest(request, env)) ??
602
+ new Response("Not found", { status: 404 })
603
+ );
604
+ }
605
+ };
606
+ ```
607
+
608
+ ---
609
+
610
+ ## Request Flow
611
+
612
+ Here's how a request flows through the system:
613
+
614
+ ```
615
+ ┌─────────────────┐
616
+ │ HTTP Request │
617
+ │ or WebSocket │
618
+ └────────┬────────┘
619
+
620
+
621
+ ┌─────────────────┐
622
+ │ routeAgentRequest()
623
+ │ Parse URL path │
624
+ └────────┬────────┘
625
+
626
+
627
+ ┌─────────────────┐
628
+ │ Find binding in │
629
+ │ env by name │
630
+ └────────┬────────┘
631
+
632
+
633
+ ┌─────────────────┐
634
+ │ Get/create DO │
635
+ │ by instance ID │
636
+ └────────┬────────┘
637
+
638
+
639
+ ┌─────────────────────────────────────┐
640
+ │ Agent Instance │
641
+ ├─────────────────────────────────────┤
642
+ │ WebSocket? → onConnect(), onMessage()
643
+ │ HTTP? → onRequest() │
644
+ └─────────────────────────────────────┘
645
+ ```
646
+
647
+ ---
648
+
649
+ ## Troubleshooting
650
+
651
+ ### "Agent namespace not found"
652
+
653
+ The error message lists available agents. Check:
654
+
655
+ 1. Agent class is exported from your entry point
656
+ 2. Class name in code matches `class_name` in `wrangler.jsonc`
657
+ 3. URL uses correct kebab-case name
658
+
659
+ ### Request returns 404
660
+
661
+ 1. Verify the URL pattern: `/agents/{agent-name}/{instance-name}`
662
+ 2. Check that `routeAgentRequest()` is called before your 404 handler
663
+ 3. Ensure the response from `routeAgentRequest()` is returned (not just called)
664
+
665
+ ### WebSocket won't connect
666
+
667
+ 1. Don't modify the response from `routeAgentRequest()` for WebSocket upgrades
668
+ 2. Ensure CORS is enabled if connecting from a different origin
669
+ 3. Check browser dev tools for the actual error
670
+
671
+ ### `basePath` not working
672
+
673
+ 1. Ensure your Worker handles the custom path and forwards to the agent
674
+ 2. Use `getAgentByName()` + `agent.fetch(request)` to forward requests
675
+ 3. The `agent` parameter is still required but ignored when `basePath` is set
676
+ 4. Check that the server-side route matches the client's `basePath`
677
+
678
+ ---
679
+
680
+ ## API Reference
681
+
682
+ ### `routeAgentRequest(request, env, options?)`
683
+
684
+ Routes a request to the appropriate agent.
685
+
686
+ | Parameter | Type | Description |
687
+ | ------------------------- | ------------------------- | --------------------------------------------------- |
688
+ | `request` | `Request` | The incoming request |
689
+ | `env` | `Env` | Environment with agent bindings |
690
+ | `options.cors` | `boolean \| HeadersInit` | Enable CORS headers |
691
+ | `options.props` | `Record<string, unknown>` | Props passed to whichever agent handles the request |
692
+ | `options.locationHint` | `string` | Preferred location for agent instances |
693
+ | `options.jurisdiction` | `string` | Data jurisdiction for agent instances |
694
+ | `options.onBeforeConnect` | `Function` | Callback before WebSocket connections |
695
+ | `options.onBeforeRequest` | `Function` | Callback before HTTP requests |
696
+
697
+ **Returns:** `Promise<Response \| undefined>` - Response if matched, undefined if no agent route
698
+
699
+ ### `getAgentByName(namespace, name, options?)`
700
+
701
+ Get an agent instance by name for server-side RPC or request forwarding.
702
+
703
+ | Parameter | Type | Description |
704
+ | ---------------------- | --------------------------- | --------------------------------------- |
705
+ | `namespace` | `DurableObjectNamespace<T>` | Agent binding from env |
706
+ | `name` | `string` | Instance name |
707
+ | `options.locationHint` | `string` | Preferred location |
708
+ | `options.jurisdiction` | `string` | Data jurisdiction |
709
+ | `options.props` | `Record<string, unknown>` | Initialization properties for `onStart` |
710
+
711
+ **Returns:** `Promise<DurableObjectStub<T>>` - Typed stub for calling agent methods or forwarding requests
712
+
713
+ ### `useAgent(options)` / `AgentClient` Options
714
+
715
+ Client connection options:
716
+
717
+ | Option | Type | Description |
718
+ | ------------------ | ------------------------------------------------ | ---------------------------------------------------- |
719
+ | `agent` | `string` | Agent class name (required) |
720
+ | `name` | `string` | Instance name (default: `"default"`) |
721
+ | `basePath` | `string` | Full URL path - bypasses agent/name URL construction |
722
+ | `path` | `string` | Additional path to append to the URL |
723
+ | `onIdentity` | `(name, agent) => void` | Called when server sends identity |
724
+ | `onIdentityChange` | `(oldName, newName, oldAgent, newAgent) => void` | Called when identity changes on reconnect |
725
+
726
+ **Return value properties (React hook):**
727
+
728
+ | Property | Type | Description |
729
+ | ------------ | --------------- | --------------------------------------------- |
730
+ | `name` | `string` | Current instance name (reactive) |
731
+ | `agent` | `string` | Current agent class name (reactive) |
732
+ | `identified` | `boolean` | Whether identity has been received (reactive) |
733
+ | `ready` | `Promise<void>` | Resolves when identity is received |
734
+
735
+ ### `Agent.options` (Server)
736
+
737
+ Static options for agent configuration:
738
+
739
+ | Option | Type | Default | Description |
740
+ | ---------------------------- | --------- | ------- | ---------------------------------------------------- |
741
+ | `hibernate` | `boolean` | `true` | Whether the agent should hibernate when inactive |
742
+ | `sendIdentityOnConnect` | `boolean` | `true` | Whether to send identity to clients on connect |
743
+ | `hungScheduleTimeoutSeconds` | `number` | `30` | Timeout before a running schedule is considered hung |
744
+
745
+ ```typescript
746
+ class SecureAgent extends Agent {
747
+ static options = { sendIdentityOnConnect: false };
748
+ }
749
+ ```