agents 0.20.0 → 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 (87) hide show
  1. package/README.md +50 -12
  2. package/dist/{agent-tool-types-Btk9ETS-.d.ts → agent-tool-types-CzGGB-20.d.ts} +403 -115
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-UuScsJg3.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/cloudflare-BduZwmYK.js +204 -0
  26. package/dist/cloudflare-BduZwmYK.js.map +1 -0
  27. package/dist/{connector-v2M1zlZp.d.ts → connector-CkQD4MK3.d.ts} +20 -3
  28. package/dist/{connector-KEJnl6e5.js → connector-CptFKzRh.js} +158 -40
  29. package/dist/connector-CptFKzRh.js.map +1 -0
  30. package/dist/{handler-stateless-8hQN_kC3.js → handler-stateless-CIkKPETH.js} +20 -69
  31. package/dist/handler-stateless-CIkKPETH.js.map +1 -0
  32. package/dist/index.d.ts +21 -11
  33. package/dist/index.js +31 -21
  34. package/dist/index.js.map +1 -1
  35. package/dist/mcp/client.d.ts +20 -20
  36. package/dist/mcp/index.d.ts +35 -35
  37. package/dist/mcp/index.js +32 -98
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/mcp/server.js +1 -1
  40. package/dist/observability/ai/index.d.ts +10 -131
  41. package/dist/observability/ai/index.js +209 -444
  42. package/dist/observability/ai/index.js.map +1 -1
  43. package/dist/{wire-types-CU9rLoeS.js → protocol-Dqc2MQxo.js} +2 -46
  44. package/dist/protocol-Dqc2MQxo.js.map +1 -0
  45. package/dist/react.d.ts +1 -1
  46. package/dist/react.js +5 -8
  47. package/dist/react.js.map +1 -1
  48. package/dist/serializable.d.ts +1 -1
  49. package/dist/sub-routing.d.ts +18 -6
  50. package/dist/sub-routing.js +92 -2
  51. package/dist/sub-routing.js.map +1 -1
  52. package/dist/wire-types-CnMt6_HR.js +47 -0
  53. package/dist/wire-types-CnMt6_HR.js.map +1 -0
  54. package/dist/workflow-types.d.ts +25 -25
  55. package/dist/workflow-types.js.map +1 -1
  56. package/dist/workflows.d.ts +22 -22
  57. package/dist/ws-chat-transport-CIoOBbO7.js +561 -0
  58. package/dist/ws-chat-transport-CIoOBbO7.js.map +1 -0
  59. package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
  60. package/docs/adding-to-existing-project.md +4 -2
  61. package/docs/agent-class.md +1 -1
  62. package/docs/agent-tools.md +29 -0
  63. package/docs/browse-the-web.md +16 -1
  64. package/docs/chat-agents.md +3 -1
  65. package/docs/client-sdk.md +12 -8
  66. package/docs/configuration.md +7 -1
  67. package/docs/cross-domain-authentication.md +7 -35
  68. package/docs/email.md +2 -13
  69. package/docs/human-in-the-loop.md +15 -12
  70. package/docs/long-running-agents.md +11 -11
  71. package/docs/mcp-client.md +1 -1
  72. package/docs/mcp-servers.md +7 -1
  73. package/docs/mcp-transports.md +18 -12
  74. package/docs/migration-to-ai-sdk-v5.md +2 -2
  75. package/docs/migration-to-ai-sdk-v6.md +5 -1
  76. package/docs/observability.md +24 -67
  77. package/docs/routing.md +27 -0
  78. package/docs/sub-agents.md +47 -2
  79. package/docs/webhooks.md +109 -136
  80. package/docs/workflows.md +9 -1
  81. package/package.json +19 -10
  82. package/dist/cloudflare-BldFV0Pa.js +0 -117
  83. package/dist/cloudflare-BldFV0Pa.js.map +0 -1
  84. package/dist/connector-KEJnl6e5.js.map +0 -1
  85. package/dist/handler-stateless-8hQN_kC3.js.map +0 -1
  86. package/dist/wire-types-CU9rLoeS.js.map +0 -1
  87. package/dist/workflow-types-Baz_PO5v.d.ts +0 -280
@@ -0,0 +1,184 @@
1
+ import { ChatTransport, UIMessage, UIMessageChunk } from "ai";
2
+
3
+ //#region src/chat/ws-chat-transport.d.ts
4
+ /**
5
+ * Agent-like interface for sending/receiving WebSocket messages.
6
+ * Matches `AgentClient` from `agents/client` and the connection returned by
7
+ * `useAgent` from `agents/react`.
8
+ */
9
+ interface AgentConnection {
10
+ send: (data: string) => void;
11
+ addEventListener: (
12
+ type: string,
13
+ listener: (event: MessageEvent) => void,
14
+ options?: {
15
+ signal?: AbortSignal;
16
+ }
17
+ ) => void;
18
+ removeEventListener: (
19
+ type: string,
20
+ listener: (event: MessageEvent) => void
21
+ ) => void;
22
+ }
23
+ type WebSocketChatTransportOptions<ChatMessage extends UIMessage = UIMessage> =
24
+ {
25
+ /** The framework-neutral AgentClient or useAgent connection. */ agent: AgentConnection;
26
+ /**
27
+ * Callback to add custom fields to the request body before sending.
28
+ */
29
+ prepareBody?: (options: {
30
+ messages: ChatMessage[];
31
+ trigger: "submit-message" | "regenerate-message";
32
+ messageId?: string;
33
+ }) => Promise<Record<string, unknown>> | Record<string, unknown>;
34
+ /**
35
+ * Optional set to track active request IDs.
36
+ * IDs are added when a request starts and removed when it completes.
37
+ * Used by the onAgentMessage handler to skip messages already handled by the transport.
38
+ */
39
+ activeRequestIds?: Set<string>;
40
+ /**
41
+ * Whether generic client-side abort/cancel lifecycle should cancel the
42
+ * server turn. Explicit cancellation via cancelActiveServerTurn() always
43
+ * sends CF_AGENT_CHAT_REQUEST_CANCEL.
44
+ * @default false
45
+ */
46
+ cancelOnClientAbort?: boolean;
47
+ };
48
+ /**
49
+ * ChatTransport that sends messages over WebSocket and returns a
50
+ * ReadableStream<UIMessageChunk> that the AI SDK's useChat consumes directly.
51
+ *
52
+ * This low-level transport handles new and regenerated request streams plus
53
+ * cancellation. Higher-level protocol coordination such as automatic reconnect
54
+ * resume, cross-tab transcript synchronization, and client-tool continuations
55
+ * is provided by integrations such as `useAgentChat`.
56
+ */
57
+ declare class WebSocketChatTransport<
58
+ ChatMessage extends UIMessage = UIMessage
59
+ > implements ChatTransport<ChatMessage> {
60
+ agent: AgentConnection;
61
+ private prepareBody?;
62
+ private activeRequestIds?;
63
+ private cancelOnClientAbort;
64
+ private _resumeResolver;
65
+ private _resumeNoneResolver;
66
+ private _onStreamPending;
67
+ private _retryResumeProbe;
68
+ private _expectToolContinuation;
69
+ private _abortToolContinuation;
70
+ private _activeServerTurnId;
71
+ private _cancelAttachedStream;
72
+ private _detachResumeStream;
73
+ constructor(options: WebSocketChatTransportOptions<ChatMessage>);
74
+ /**
75
+ * Point the singleton transport at a new Agent connection. A pending resolver
76
+ * belongs to the old Chat/socket generation and must settle before messages
77
+ * from the replacement connection can be consumed (#1914 review).
78
+ */
79
+ setAgent(agent: AgentConnection): void;
80
+ setCancelOnClientAbort(cancelOnClientAbort: boolean): void;
81
+ /**
82
+ * Explicitly cancel the active server turn, if any.
83
+ * This is separate from generic client-side abort/cancel lifecycle so
84
+ * clients can detach locally without stopping server work.
85
+ */
86
+ cancelActiveServerTurn(): boolean;
87
+ private sendCancelFrame;
88
+ private setActiveServerTurn;
89
+ private clearActiveServerTurn;
90
+ /**
91
+ * Mark that the next reconnectToStream() call should attach to a
92
+ * server-initiated tool continuation rather than a page-load resume.
93
+ */
94
+ expectToolContinuation(): void;
95
+ /**
96
+ * Abort the active client-side tool continuation stream, if one is attached
97
+ * to a server request id.
98
+ */
99
+ abortActiveToolContinuation(): boolean;
100
+ /**
101
+ * True when the transport is waiting for a resume handshake.
102
+ */
103
+ isAwaitingResume(): boolean;
104
+ /**
105
+ * Settle and detach the current handshake without interpreting it as a
106
+ * server-idle response. Used when the owning hook/agent generation changes.
107
+ */
108
+ cancelPendingResume(): boolean;
109
+ /**
110
+ * Invalidate all client-side resume state for an obsolete hook/agent
111
+ * generation without cancelling its durable server turn.
112
+ */
113
+ resetResumeState(): void;
114
+ /**
115
+ * Re-send the active handshake request on the latest socket generation. This
116
+ * preserves one AI SDK resume operation while recovering a request/reply lost
117
+ * with the previous WebSocket.
118
+ */
119
+ retryPendingResume(): boolean;
120
+ /**
121
+ * Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUMING.
122
+ * If reconnectToStream is waiting, this handles the resume handshake
123
+ * (ACK + stream creation) and returns true. Otherwise returns false
124
+ * so the caller can use its own fallback path.
125
+ */
126
+ handleStreamResuming(data: { id: string }): boolean;
127
+ /**
128
+ * Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUME_NONE.
129
+ * If reconnectToStream is waiting, resolves the promise with null
130
+ * immediately (no 5-second timeout). Returns true if handled.
131
+ */
132
+ handleStreamResumeNone(data?: { probeId?: string }): boolean;
133
+ /**
134
+ * Called by onAgentMessage when it receives CF_AGENT_STREAM_PENDING (#1784):
135
+ * the server accepted a turn but its stream has not started yet. If a resume
136
+ * path is awaiting, extend its probe timeout (so it keeps waiting for the
137
+ * eventual STREAM_RESUMING / STREAM_RESUME_NONE instead of resolving null
138
+ * after the short window). Returns true if a waiting path consumed it.
139
+ */
140
+ handleStreamPending(): boolean;
141
+ /**
142
+ * Called by the hook's shared message handler when a server turn finishes
143
+ * outside the currently attached transport stream, such as after local-only
144
+ * client cleanup.
145
+ */
146
+ handleServerTurnCompleted(requestId: string): void;
147
+ /**
148
+ * Register a server turn that is being rendered outside a transport-owned
149
+ * stream, such as the hook's fallback cross-tab/resume observer path.
150
+ */
151
+ observeServerTurn(requestId: string): void;
152
+ sendMessages(options: {
153
+ chatId: string;
154
+ messages: ChatMessage[];
155
+ abortSignal: AbortSignal | undefined;
156
+ trigger: "submit-message" | "regenerate-message";
157
+ messageId?: string;
158
+ body?: object;
159
+ headers?: Record<string, string> | Headers;
160
+ metadata?: unknown;
161
+ }): Promise<ReadableStream<UIMessageChunk>>;
162
+ reconnectToStream(_options: {
163
+ chatId: string;
164
+ }): Promise<ReadableStream<UIMessageChunk> | null>;
165
+ /**
166
+ * Creates a deferred ReadableStream for client-side tool continuations.
167
+ * The stream is returned immediately so AI SDK status becomes "submitted"
168
+ * right after addToolOutput()/addToolApprovalResponse(), then it waits for
169
+ * the server to announce the continuation via STREAM_RESUMING.
170
+ */
171
+ private _createToolContinuationStream;
172
+ /**
173
+ * Creates a ReadableStream that receives resumed stream chunks
174
+ * and forwards them to useChat as UIMessageChunk objects.
175
+ */
176
+ private _createResumeStream;
177
+ }
178
+ //#endregion
179
+ export {
180
+ WebSocketChatTransport as n,
181
+ WebSocketChatTransportOptions as r,
182
+ AgentConnection as t
183
+ };
184
+ //# sourceMappingURL=ws-chat-transport-UNRIS2xl.d.ts.map
@@ -32,7 +32,7 @@ npm install agents hono-agents
32
32
  Create a new file for your agent (e.g., `src/agents/counter.ts`):
33
33
 
34
34
  ```typescript
35
- import { Agent } from "agents";
35
+ import { Agent, callable } from "agents";
36
36
 
37
37
  type CounterState = {
38
38
  count: number;
@@ -41,11 +41,13 @@ type CounterState = {
41
41
  export class Counter extends Agent<Env, CounterState> {
42
42
  initialState: CounterState = { count: 0 };
43
43
 
44
+ @callable()
44
45
  increment() {
45
46
  this.setState({ count: this.state.count + 1 });
46
47
  return this.state.count;
47
48
  }
48
49
 
50
+ @callable()
49
51
  decrement() {
50
52
  this.setState({ count: this.state.count - 1 });
51
53
  return this.state.count;
@@ -371,7 +373,7 @@ By default, agents are routed at `/agents/{agent-name}/{instance-name}`. You can
371
373
  import { routeAgentRequest } from "agents";
372
374
 
373
375
  const agentResponse = await routeAgentRequest(request, env, {
374
- prefix: "/api/agents" // Now routes at /api/agents/{agent-name}/{instance-name}
376
+ prefix: "api/agents" // Now routes at /api/agents/{agent-name}/{instance-name}
375
377
  });
376
378
  ```
377
379
 
@@ -250,7 +250,7 @@ class MyAgent extends Agent {
250
250
 
251
251
  ### RPC and Callable Methods
252
252
 
253
- `agents` take Durable Objects RPC one step forward by implementing RPC through WebSockets, so clients can also call methods on the Agent directly. To make a method callable through WS, developers can use the `@callable` decorator. Methods can return a serializable value or a stream (when using `@callable({ stream: true })`).
253
+ `agents` take Durable Objects RPC one step forward by implementing RPC through WebSockets, so clients can also call methods on the Agent directly. To make a method callable through WS, developers can use the `@callable` decorator. Methods can return a serializable value or stream chunks (when using `@callable({ streaming: true })`).
254
254
 
255
255
  ```ts
256
256
  class MyAgent extends Agent {
@@ -48,6 +48,35 @@ export class Assistant extends Think<Env> {
48
48
  }
49
49
  ```
50
50
 
51
+ ### Use Valibot or another schema library
52
+
53
+ `agentTool()` accepts the AI SDK's flexible schema format. For Valibot, wrap the
54
+ schema with `valibotSchema()` so the AI SDK receives both runtime validation and
55
+ the JSON Schema required by the model. Use `@ai-sdk/valibot` v2 with AI SDK 6
56
+ and v3 with AI SDK 7:
57
+
58
+ ```ts
59
+ import { valibotSchema } from "@ai-sdk/valibot";
60
+ import { agentTool } from "agents/agent-tools";
61
+ import * as v from "valibot";
62
+
63
+ const researchInput = valibotSchema(
64
+ v.object({
65
+ query: v.pipe(v.string(), v.minLength(3))
66
+ })
67
+ );
68
+
69
+ const research = agentTool(Researcher, {
70
+ description: "Research one topic in depth.",
71
+ inputSchema: researchInput
72
+ });
73
+ ```
74
+
75
+ You can also provide a Zod schema, a Standard JSON Schema-compatible schema, or
76
+ a raw JSON Schema wrapped with `jsonSchema()` from `ai`. A validation-only
77
+ Standard Schema is not sufficient for a tool input because it does not provide
78
+ the JSON Schema sent to the model.
79
+
51
80
  The child can also be an `AIChatAgent`:
52
81
 
53
82
  ```ts
@@ -244,7 +244,7 @@ const md = await browserMarkdown(this.env.BROWSER, { url });
244
244
  const data = await browserExtract<{ price: number }>(this.env.BROWSER, {
245
245
  url,
246
246
  prompt: "the product price",
247
- response_format: { type: "json_schema", schema: priceSchema }
247
+ response_format: { type: "json_schema", json_schema: priceSchema }
248
248
  });
249
249
  ```
250
250
 
@@ -279,6 +279,21 @@ class Researcher extends Think<Env> {
279
279
 
280
280
  Quick Actions require a Worker `compatibility_date` of `2026-03-24` or later and `remote: true` on the browser binding for local `wrangler dev`.
281
281
 
282
+ ## Using Kitesurf
283
+
284
+ To use Kitesurf with the CDP-based `browser_execute` tool, select it in the session options:
285
+
286
+ ```ts
287
+ const tools = createBrowserTools({
288
+ ctx: this.ctx,
289
+ browser: this.env.BROWSER,
290
+ loader: this.env.LOADER,
291
+ session: { browser: "kitesurf" }
292
+ });
293
+ ```
294
+
295
+ A Kitesurf browser is scoped to its CDP WebSocket. It therefore supports only one-shot execution. Session reuse, pause and resume, Live View, recording, `keepAliveMs`, protocol discovery, and Kitesurf-backed Quick Actions are not available.
296
+
282
297
  ## Live View and human-in-the-loop
283
298
 
284
299
  [Live View](https://developers.cloudflare.com/browser-run/features/live-view/) lets a human open a URL and watch — or take control of — a running browser session in real time. It is the building block for human-in-the-loop steps such as logging in, solving a CAPTCHA, completing MFA, or entering data you do not want to pass through an automation script.
@@ -25,7 +25,7 @@ Built on the [AI SDK](https://ai-sdk.dev) and Cloudflare Durable Objects, you ge
25
25
  ### Install
26
26
 
27
27
  ```sh
28
- npm install @cloudflare/ai-chat agents ai workers-ai-provider
28
+ npm install @cloudflare/ai-chat agents ai @ai-sdk/react workers-ai-provider
29
29
  ```
30
30
 
31
31
  ### Server
@@ -956,6 +956,8 @@ async onChatMessage() {
956
956
  }
957
957
  ```
958
958
 
959
+ The `inputSchema` accepts the AI SDK's flexible schema format, so you are not limited to Zod. You can also use Valibot, a Standard JSON Schema-compatible schema, or a raw JSON Schema wrapped with `jsonSchema()` from `ai`. See [Use Valibot or another schema library](./agent-tools.md#use-valibot-or-another-schema-library) for details.
960
+
959
961
  ### Client-Side Tools
960
962
 
961
963
  Define a tool on the server without `execute`, then handle it on the client with `onToolCall`. Use this for tools that need browser APIs:
@@ -264,17 +264,21 @@ const result = await agent.stub.processData({ input: "test" });
264
264
 
265
265
  ### Streaming Responses
266
266
 
267
- For methods that return `StreamingResponse`, handle chunks as they arrive:
267
+ Mark a callable method as streaming. The framework passes a
268
+ `StreamingResponse` as its first argument:
268
269
 
269
270
  ```typescript
270
- // Agent-side:
271
- @callable()
272
- async generateText(prompt: string) {
273
- return new StreamingResponse(async (stream) => {
274
- for await (const chunk of llm.stream(prompt)) {
275
- await stream.write(chunk);
271
+ // Agent-side
272
+ import { Agent, callable, type StreamingResponse } from "agents";
273
+
274
+ export class AIAgent extends Agent {
275
+ @callable({ streaming: true })
276
+ async generateText(stream: StreamingResponse, prompt: string) {
277
+ for (const chunk of ["Generated response for ", prompt]) {
278
+ stream.send(chunk);
276
279
  }
277
- });
280
+ stream.end();
281
+ }
278
282
  }
279
283
 
280
284
  // Client-side:
@@ -617,12 +617,18 @@ Define environments in wrangler.jsonc:
617
617
  "env": {
618
618
  "staging": {
619
619
  "name": "my-agent-staging",
620
+ "durable_objects": {
621
+ "bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
622
+ },
620
623
  "vars": {
621
624
  "ENVIRONMENT": "staging"
622
625
  }
623
626
  },
624
627
  "production": {
625
628
  "name": "my-agent-production",
629
+ "durable_objects": {
630
+ "bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
631
+ },
626
632
  "vars": {
627
633
  "ENVIRONMENT": "production"
628
634
  }
@@ -647,7 +653,7 @@ npx wrangler secret put OPENAI_API_KEY --env production
647
653
 
648
654
  ### Separate Durable Objects
649
655
 
650
- Each environment gets its own Durable Objects. Staging agents don't share state with production agents.
656
+ Durable Object bindings are not inherited by named environments, so repeat them in every environment as shown above. Each environment gets its own Durable Objects. Staging agents do not share state with production agents.
651
657
 
652
658
  To explicitly separate:
653
659
 
@@ -24,7 +24,7 @@ If the client and server share the origin, the browser will send cookies during
24
24
 
25
25
  ### Cross origin
26
26
 
27
- Cookies do not help across origins. Pass credentials in the URL query, then verify on the server.
27
+ Cross-origin cookie behavior depends on the cookie's domain and `SameSite` attributes, whether the two origins are same-site, and browser third-party cookie policy. If you cannot rely on a cookie, pass a short-lived credential in the URL query and verify it on the server.
28
28
 
29
29
  ## Usage examples
30
30
 
@@ -79,50 +79,22 @@ function ChatComponent() {
79
79
 
80
80
  ### JWT refresh pattern
81
81
 
82
- Refresh the token when the connection fails due to authentication error.
82
+ `useAgent` resolves an async query before connecting and reevaluates it when reconnecting. Return a fresh, short-lived application token each time:
83
83
 
84
84
  ```ts
85
85
  import { useAgent } from "agents/react";
86
- import { useCallback, useEffect } from "react";
87
-
88
- const validateToken = async (token: string) => {
89
- // An example of how you might implement this
90
- const res = await fetch(`${API_HOST}/api/users/me`, {
91
- headers: {
92
- Authorization: `Bearer ${token}`
93
- }
94
- });
95
-
96
- return res.ok;
97
- };
86
+ import { useCallback } from "react";
98
87
 
99
- const refreshToken = () => {
100
- // Depends on implementation:
101
- // - You could use a longer-lived token to refresh the expired token
102
- // - De-auth the app and prompt the user to log in manually
103
- // - ...
104
- };
88
+ declare function getShortLivedAccessToken(): Promise<string>;
105
89
 
106
90
  function useJWTAgent(agentName: string) {
107
91
  const asyncQuery = useCallback(async () => {
108
- let token = localStorage.getItem("jwt");
109
-
110
- // If no token OR the token is no longer valid
111
- // request a fresh token
112
- if (!token && !(await validateToken(token))) {
113
- token = await refreshToken();
114
- localStorage.setItem("jwt", token);
115
- }
116
-
117
- return {
118
- token
119
- };
92
+ return { token: await getShortLivedAccessToken() };
120
93
  }, []);
121
94
 
122
- const agent = useAgent({
95
+ return useAgent({
123
96
  agent: agentName,
124
- query: asyncQuery,
125
- queryDeps: [] // Run on mount
97
+ query: asyncQuery
126
98
  });
127
99
  }
128
100
  ```
package/docs/email.md CHANGED
@@ -419,21 +419,10 @@ When your agent sends emails and expects replies, use secure reply routing to pr
419
419
 
420
420
  ### Setup
421
421
 
422
- 1. Add a secret to your `wrangler.jsonc`:
423
-
424
- ```jsonc
425
- // wrangler.jsonc
426
- {
427
- "vars": {
428
- "EMAIL_SECRET": "change-me-in-production"
429
- }
430
- }
431
- ```
432
-
433
- For production, use Wrangler secrets instead:
422
+ 1. Store the signing key as a Wrangler secret. Do not put it in `vars` or commit it to source control:
434
423
 
435
424
  ```bash
436
- wrangler secret put EMAIL_SECRET
425
+ pnpm exec wrangler secret put EMAIL_SECRET
437
426
  ```
438
427
 
439
428
  2. Use the combined resolver pattern:
@@ -196,7 +196,7 @@ Define tools with `needsApproval` to require human confirmation:
196
196
  ```typescript
197
197
  import { AIChatAgent } from "@cloudflare/ai-chat";
198
198
  import { createWorkersAI } from "workers-ai-provider";
199
- import { streamText, tool, convertToModelMessages } from "ai";
199
+ import { streamText, tool, convertToModelMessages, stepCountIs } from "ai";
200
200
  import { z } from "zod";
201
201
 
202
202
  export class MyAgent extends AIChatAgent {
@@ -238,7 +238,7 @@ export class MyAgent extends AIChatAgent {
238
238
  execute: async ({ city }) => fetchWeather(city)
239
239
  })
240
240
  },
241
- maxSteps: 5
241
+ stopWhen: stepCountIs(5)
242
242
  });
243
243
 
244
244
  return result.toUIMessageStreamResponse();
@@ -246,6 +246,8 @@ export class MyAgent extends AIChatAgent {
246
246
  }
247
247
  ```
248
248
 
249
+ The `inputSchema` accepts the AI SDK's flexible schema format, so you are not limited to Zod. You can also use Valibot, a Standard JSON Schema-compatible schema, or a raw JSON Schema wrapped with `jsonSchema()` from `ai`. See [Use Valibot or another schema library](./agent-tools.md#use-valibot-or-another-schema-library) for details.
250
+
249
251
  ### Client
250
252
 
251
253
  Handle approval requests with `addToolApprovalResponse`:
@@ -380,7 +382,7 @@ export class MyAgent extends AIChatAgent {
380
382
  inputSchema: z.object({})
381
383
  })
382
384
  },
383
- maxSteps: 3
385
+ stopWhen: stepCountIs(3)
384
386
  });
385
387
 
386
388
  return result.toUIMessageStreamResponse();
@@ -410,17 +412,18 @@ const { messages, sendMessage } = useAgentChat({
410
412
  });
411
413
  ```
412
414
 
413
- The server receives the tool output via `CF_AGENT_TOOL_RESULT` and can auto-continue the conversation (with `maxSteps > 1`), letting the LLM respond to the location data in the same turn.
415
+ The server receives the tool output via `CF_AGENT_TOOL_RESULT` and can auto-continue the conversation when `stopWhen` allows another step, letting the LLM respond to the location data in the same turn.
414
416
 
415
417
  ### OpenAI Agents SDK Pattern
416
418
 
417
419
  When using the [OpenAI Agents SDK](https://openai.github.io/openai-agents-js/), use the `needsApproval` function for conditional approval:
418
420
 
419
421
  ```typescript
420
- import { Agent } from "agents";
421
- import { tool, run } from "@openai/agents";
422
+ import { Agent as CloudflareAgent } from "agents";
423
+ import { Agent as OpenAIAgent, tool, run } from "@openai/agents";
424
+ import { z } from "zod";
422
425
 
423
- export class WeatherAgent extends Agent<Env, AgentState> {
426
+ export class WeatherAgent extends CloudflareAgent<Env> {
424
427
  async processQuery(query: string) {
425
428
  const weatherTool = tool({
426
429
  name: "get_weather",
@@ -438,13 +441,13 @@ export class WeatherAgent extends Agent<Env, AgentState> {
438
441
  }
439
442
  });
440
443
 
441
- const result = await run(this.openai, {
442
- model: "gpt-4o",
443
- tools: [weatherTool],
444
- input: query
444
+ const openaiAgent = new OpenAIAgent({
445
+ name: "Weather assistant",
446
+ instructions: "Help the user check the weather.",
447
+ tools: [weatherTool]
445
448
  });
446
449
 
447
- return result;
450
+ return run(openaiAgent, query);
448
451
  }
449
452
  }
450
453
  ```
@@ -86,7 +86,7 @@ type Task = {
86
86
  externalJobId?: string;
87
87
  };
88
88
 
89
- export class ProjectManager extends Agent<ProjectState> {
89
+ export class ProjectManager extends Agent<Env, ProjectState> {
90
90
  initialState: ProjectState = {
91
91
  name: "",
92
92
  status: "planning",
@@ -115,7 +115,7 @@ The pattern extends naturally to any event source that can reach a Worker — an
115
115
  The agent does not need to be "started" or "deployed" separately for each wake source — they all route to the same Durable Object instance. The agent's identity (its name) is the routing key.
116
116
 
117
117
  ```typescript
118
- export class ProjectManager extends Agent<ProjectState> {
118
+ export class ProjectManager extends Agent<Env, ProjectState> {
119
119
  async onStart() {
120
120
  // Daily deadline check at 9am UTC — idempotent, safe across restarts
121
121
  await this.schedule(
@@ -163,7 +163,7 @@ Sometimes an agent needs to do work that takes longer than the idle eviction win
163
163
  `keepAlive()` prevents this by creating a heartbeat that resets the inactivity timer:
164
164
 
165
165
  ```typescript
166
- export class ProjectManager extends Agent<ProjectState> {
166
+ export class ProjectManager extends Agent<Env, ProjectState> {
167
167
  async generateProjectPlan(goal: string) {
168
168
  const result = await this.keepAliveWhile(async () => {
169
169
  const plan = await this.callLLM(`Create a project plan for: ${goal}`);
@@ -223,7 +223,7 @@ side effects. If recovery succeeds, return a recovery result from
223
223
  aborted, failed, or intentionally left interrupted.
224
224
 
225
225
  ```typescript
226
- export class ProjectManager extends Agent<ProjectState> {
226
+ export class ProjectManager extends Agent<Env, ProjectState> {
227
227
  async executeTask(task: Task) {
228
228
  await this.runFiber(`task:${task.id}`, async (ctx) => {
229
229
  const resources = await this.gatherResources(task);
@@ -268,7 +268,7 @@ The project manager frequently kicks off work that takes far longer than any sin
268
268
  The project manager starts a CI pipeline for a task. The pipeline takes 20 minutes. Rather than holding a connection open, the agent registers its own URL as the callback and goes to sleep:
269
269
 
270
270
  ```typescript
271
- export class ProjectManager extends Agent<ProjectState> {
271
+ export class ProjectManager extends Agent<Env, ProjectState> {
272
272
  async startCIPipeline(task: Task) {
273
273
  const response = await fetch("https://ci.example.com/api/pipelines", {
274
274
  method: "POST",
@@ -307,7 +307,7 @@ export class ProjectManager extends Agent<ProjectState> {
307
307
  Not every external service supports callbacks. When the project manager submits a video asset for generation, it needs to check back periodically until the job completes:
308
308
 
309
309
  ```typescript
310
- export class ProjectManager extends Agent<ProjectState> {
310
+ export class ProjectManager extends Agent<Env, ProjectState> {
311
311
  async startVideoGeneration(task: Task) {
312
312
  const response = await fetch("https://video-api.example.com/generate", {
313
313
  method: "POST",
@@ -354,7 +354,7 @@ export class ProjectManager extends Agent<ProjectState> {
354
354
  A production deployment involves multiple steps that must each retry independently — build, test, stage, promote. The project manager should not manage these steps internally; it delegates to a [Workflow](./workflows.md) that handles retries and step sequencing:
355
355
 
356
356
  ```typescript
357
- export class ProjectManager extends Agent<ProjectState> {
357
+ export class ProjectManager extends Agent<Env, ProjectState> {
358
358
  async startDeployment(task: Task) {
359
359
  const instanceId = await this.runWorkflow("DEPLOY_WORKFLOW", {
360
360
  taskId: task.id,
@@ -422,7 +422,7 @@ type PlanStep = {
422
422
  result?: unknown;
423
423
  };
424
424
 
425
- export class ProjectManager extends Agent<ProjectState> {
425
+ export class ProjectManager extends Agent<Env, ProjectState> {
426
426
  async createPlan(goal: string) {
427
427
  const steps = await this.keepAliveWhile(async () => {
428
428
  return this.callLLM(`
@@ -508,7 +508,7 @@ This pattern has several advantages for long-running agents:
508
508
  A project manager does not do everything itself. It delegates specialized work to sub-agents — child Durable Objects (facets) spawned under the parent. Each facet has its own isolated SQLite state and runs in parallel, but stays colocated on the same machine as the parent.
509
509
 
510
510
  ```typescript
511
- export class ProjectManager extends Agent<ProjectState> {
511
+ export class ProjectManager extends Agent<Env, ProjectState> {
512
512
  async delegateTask(task: Task) {
513
513
  // Get a stub to a specialized agent (same DO namespace, unique name)
514
514
  const researcher = await this.subAgent(
@@ -586,7 +586,7 @@ An agent that runs for months accumulates data: conversation history, timeline e
586
586
  Schedule periodic cleanup to prune old data and archive completed work:
587
587
 
588
588
  ```typescript
589
- export class ProjectManager extends Agent<ProjectState> {
589
+ export class ProjectManager extends Agent<Env, ProjectState> {
590
590
  async onStart() {
591
591
  await this.schedule("0 0 * * *", "housekeeping", {}, { idempotent: true });
592
592
  }
@@ -634,7 +634,7 @@ For simpler cases: keep only the last N messages in the active context (sliding
634
634
  A long-running agent eventually completes its purpose. The project ships, the investigation concludes, the monitoring window closes. Clean up explicitly:
635
635
 
636
636
  ```typescript
637
- export class ProjectManager extends Agent<ProjectState> {
637
+ export class ProjectManager extends Agent<Env, ProjectState> {
638
638
  async completeProject() {
639
639
  // Cancel remaining schedules
640
640
  const schedules = await this.listSchedules();
@@ -18,7 +18,7 @@ The MCP client capability lets your agent:
18
18
  Install the exact MCP client peer used by this Agents release:
19
19
 
20
20
  ```sh
21
- pnpm add agents @modelcontextprotocol/client@2.0.0-beta.5
21
+ pnpm add agents @modelcontextprotocol/client@2.0.0
22
22
  ```
23
23
 
24
24
  ```typescript
@@ -14,12 +14,18 @@ This guide covers the different ways to create MCP servers with the Agents SDK a
14
14
  - **`McpAgent`** is a retained, feature-frozen SDK v1 path for existing stateful deployments. New servers should use `createMcpHandler()`.
15
15
  - **Raw transport** gives you low-level control if the standard handler lifecycle is not suitable.
16
16
 
17
+ `McpAgent` and the other retained SDK v1 registration APIs use the upstream
18
+ Zod-based schema contract. Define their tool input and output schemas as Zod
19
+ shapes; AI SDK flexible-schema adapters are not accepted. For a new MCP server
20
+ that needs Standard Schema support, use `@modelcontextprotocol/server` v2 with
21
+ `createMcpHandler()`.
22
+
17
23
  ## Stateless MCP Server with `createMcpHandler()`
18
24
 
19
25
  The simplest way to create an MCP server. Install the exact SDK v2 server peer, then use the isolated server entry point so legacy Agents transports and MCP clients stay out of your Worker bundle:
20
26
 
21
27
  ```sh
22
- pnpm add agents @modelcontextprotocol/server@2.0.0-beta.5 zod
28
+ pnpm add agents @modelcontextprotocol/server@2.0.0 zod
23
29
  ```
24
30
 
25
31
  ```typescript