agents 0.24.0 → 0.26.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 (170) hide show
  1. package/dist/{agent-routing-B2XLNMxq.d.ts → agent-routing-0DR0Tk2v.d.ts} +205 -132
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-routing.js.map +1 -1
  4. package/dist/agent-tool-types.d.ts +28 -22
  5. package/dist/agent-tool-types.js +23 -1
  6. package/dist/agent-tool-types.js.map +1 -1
  7. package/dist/{agent-tools-y7zLfw4Q.js → agent-tools-CdJC-8Dc.js} +72 -9
  8. package/dist/agent-tools-CdJC-8Dc.js.map +1 -0
  9. package/dist/{agent-tools-Dj5Mzlw7.d.ts → agent-tools-YrATZJu2.d.ts} +34 -20
  10. package/dist/agent-tools.d.ts +20 -18
  11. package/dist/agent-tools.js.map +1 -1
  12. package/dist/ai-fm7xx1BK.js +312 -0
  13. package/dist/ai-fm7xx1BK.js.map +1 -0
  14. package/dist/base64-redaction-C1Uu29t5.js +59 -0
  15. package/dist/base64-redaction-C1Uu29t5.js.map +1 -0
  16. package/dist/browser/ai-sdk.d.ts +90 -0
  17. package/dist/browser/ai-sdk.js +51 -0
  18. package/dist/browser/ai-sdk.js.map +1 -0
  19. package/dist/browser/ai.d.ts +4 -4
  20. package/dist/browser/ai.js +1 -347
  21. package/dist/browser/index.d.ts +81 -79
  22. package/dist/browser/index.js +361 -2
  23. package/dist/browser/index.js.map +1 -0
  24. package/dist/browser/tanstack-ai.d.ts +96 -1
  25. package/dist/browser/tanstack-ai.js +62 -2
  26. package/dist/browser/tanstack-ai.js.map +1 -1
  27. package/dist/browser-DE3d-RLk.d.ts +275 -0
  28. package/dist/browser-tool-CQ10e6Kj.js +480 -0
  29. package/dist/browser-tool-CQ10e6Kj.js.map +1 -0
  30. package/dist/browser-tool-DuNdlwZ9.d.ts +73 -0
  31. package/dist/capability-B4WbF81e.js.map +1 -1
  32. package/dist/{capability-runner-BUBa6Ake.d.ts → capability-runner-DBAnGr6L.d.ts} +7 -2
  33. package/dist/chat/index.d.ts +340 -234
  34. package/dist/chat/index.js +592 -66
  35. package/dist/chat/index.js.map +1 -1
  36. package/dist/chat/react.d.ts +42 -2
  37. package/dist/chat/react.js +191 -35
  38. package/dist/chat/react.js.map +1 -1
  39. package/dist/chat/transport.d.ts +1 -1
  40. package/dist/chat/transport.js +1 -1
  41. package/dist/chat-sdk/index.d.ts +17 -3
  42. package/dist/chat-sdk/index.js +28 -2
  43. package/dist/chat-sdk/index.js.map +1 -1
  44. package/dist/{client-C9DAjZIY.js → client-4OlMIJx-.js} +102 -29
  45. package/dist/client-4OlMIJx-.js.map +1 -0
  46. package/dist/{client-BxdkCuVy.d.ts → client-je3koD9Z.d.ts} +45 -10
  47. package/dist/client.d.ts +7 -5
  48. package/dist/client.js +2 -2
  49. package/dist/{connector-CptFKzRh.js → connector-58XDGvc3.js} +251 -120
  50. package/dist/connector-58XDGvc3.js.map +1 -0
  51. package/dist/{connector-CkQD4MK3.d.ts → connector-Cl-VqPmA.d.ts} +244 -146
  52. package/dist/context/index.d.ts +29 -0
  53. package/dist/context/index.js +64 -17
  54. package/dist/context/index.js.map +1 -1
  55. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  56. package/dist/{current-agent-C4w86kcN.d.ts → current-agent-vAk_5JMt.d.ts} +4 -4
  57. package/dist/{durable-object-lifecycle-ei7M7lqB.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +15 -7
  58. package/dist/harness/pi/index.d.ts +344 -0
  59. package/dist/harness/pi/index.js +873 -0
  60. package/dist/harness/pi/index.js.map +1 -0
  61. package/dist/images-iiTDSyRu.js +1631 -0
  62. package/dist/images-iiTDSyRu.js.map +1 -0
  63. package/dist/{index-Dlgtd3Mj.d.ts → index-BreL0YrQ.d.ts} +2 -2
  64. package/dist/index-kbSUIer3.d.ts +343 -0
  65. package/dist/index.d.ts +46 -44
  66. package/dist/index.js +1 -1
  67. package/dist/internal_context.d.ts +1 -1
  68. package/dist/lifecycle/index.d.ts +3 -3
  69. package/dist/lifecycle/index.js +1 -1
  70. package/dist/{lifecycle-Mm_jQh7r.js → lifecycle-CFu6OV03.js} +39 -10
  71. package/dist/{lifecycle-Mm_jQh7r.js.map → lifecycle-CFu6OV03.js.map} +1 -1
  72. package/dist/mcp/client/index.d.ts +20 -20
  73. package/dist/mcp/client/x402.js +21 -19
  74. package/dist/mcp/client/x402.js.map +1 -1
  75. package/dist/mcp/index.d.ts +35 -35
  76. package/dist/mcp/index.js +1 -1
  77. package/dist/{message-builder-BymO4N_D.js → message-builder-DAl9AhHH.js} +89 -4
  78. package/dist/message-builder-DAl9AhHH.js.map +1 -0
  79. package/dist/models/ai-sdk/index.d.ts +353 -0
  80. package/dist/models/ai-sdk/index.js +2443 -0
  81. package/dist/models/ai-sdk/index.js.map +1 -0
  82. package/dist/models/pi-ai/index.d.ts +313 -0
  83. package/dist/models/pi-ai/index.js +1795 -0
  84. package/dist/models/pi-ai/index.js.map +1 -0
  85. package/dist/observability/ai/index.js +20 -4
  86. package/dist/observability/ai/index.js.map +1 -1
  87. package/dist/{protocol-B0nh6KNf.js → protocol-kHsr-f0X.js} +26 -12
  88. package/dist/{protocol-B0nh6KNf.js.map → protocol-kHsr-f0X.js.map} +1 -1
  89. package/dist/queue/index.d.ts +3 -3
  90. package/dist/queue/index.js +1 -1
  91. package/dist/{queue-BC9F5cUo.js → queue-BB-zfIYF.js} +2 -2
  92. package/dist/{queue-BC9F5cUo.js.map → queue-BB-zfIYF.js.map} +1 -1
  93. package/dist/react.d.ts +6 -6
  94. package/dist/react.js +160 -64
  95. package/dist/react.js.map +1 -1
  96. package/dist/{retries-D9B2UCq3.d.ts → retries-CV6AkXmQ.d.ts} +18 -9
  97. package/dist/retries.d.ts +11 -9
  98. package/dist/retries.js +11 -1
  99. package/dist/retries.js.map +1 -1
  100. package/dist/routing/index.d.ts +2 -2
  101. package/dist/routing/index.js +1 -1
  102. package/dist/{scheduler-D_KHqxBa.js → scheduler-DZ3xQfeK.js} +2 -2
  103. package/dist/{scheduler-D_KHqxBa.js.map → scheduler-DZ3xQfeK.js.map} +1 -1
  104. package/dist/{scheduler-BGq6M5Kd.d.ts → scheduler-RdACNXDP.d.ts} +3 -3
  105. package/dist/schedules/index.d.ts +1 -1
  106. package/dist/schedules/index.js +1 -1
  107. package/dist/serializable.d.ts +1 -1
  108. package/dist/sessions/index.d.ts +70 -3
  109. package/dist/sessions/index.js +276 -67
  110. package/dist/sessions/index.js.map +1 -1
  111. package/dist/skills/index.d.ts +29 -314
  112. package/dist/socket-address.d.ts +16 -0
  113. package/dist/socket-address.js +19 -0
  114. package/dist/socket-address.js.map +1 -0
  115. package/dist/{src-BNU3ZiJM.js → src-BZeu-SCD.js} +423 -115
  116. package/dist/src-BZeu-SCD.js.map +1 -0
  117. package/dist/state/index.d.ts +1 -1
  118. package/dist/state/index.js +1 -1
  119. package/dist/streams/index.d.ts +1 -1
  120. package/dist/streams/index.js +1 -1
  121. package/dist/{streams-BcE8JP2X.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
  122. package/dist/{streams-CCPRV6dt.js → streams-DW-UCOUc.js} +9 -2
  123. package/dist/streams-DW-UCOUc.js.map +1 -0
  124. package/dist/sub-routing.d.ts +12 -12
  125. package/dist/tasks/index.d.ts +1 -1
  126. package/dist/tasks/index.js +1 -1
  127. package/dist/{tasks-D4nLqVSI.js → tasks-68DESxf0.js} +2 -2
  128. package/dist/{tasks-D4nLqVSI.js.map → tasks-68DESxf0.js.map} +1 -1
  129. package/dist/{tasks-V2lCT8ZV.d.ts → tasks-9YxYtByh.d.ts} +2 -2
  130. package/dist/transport-PfFnt-Ld.d.ts +525 -0
  131. package/dist/{types-DCD6BNZH.d.ts → types-BA4anrjI.d.ts} +2 -2
  132. package/dist/voice/index.d.ts +2 -2
  133. package/dist/websockets/index.d.ts +23 -6
  134. package/dist/websockets/index.js +1 -1
  135. package/dist/{websockets-D7IzWbZb.js → websockets-Ci-LZFHH.js} +45 -20
  136. package/dist/{websockets-D7IzWbZb.js.map → websockets-Ci-LZFHH.js.map} +1 -1
  137. package/dist/wire-types-CnMt6_HR.js.map +1 -1
  138. package/dist/wire-types-CrjKvDyi.d.ts +300 -0
  139. package/dist/workflow-types.d.ts +25 -25
  140. package/dist/workflows.d.ts +21 -21
  141. package/dist/workflows.js +1 -1
  142. package/dist/{ws-chat-transport-UNRIS2xl.d.ts → ws-chat-transport-BbT1A5Lc.d.ts} +51 -2
  143. package/dist/{ws-chat-transport-rWwta645.js → ws-chat-transport-cqMKq3IY.js} +143 -16
  144. package/dist/ws-chat-transport-cqMKq3IY.js.map +1 -0
  145. package/docs/agent-tools.md +29 -4
  146. package/docs/browse-the-web.md +87 -5
  147. package/docs/chat-agents.md +124 -16
  148. package/docs/context.md +27 -0
  149. package/docs/durable-execution.md +2 -0
  150. package/docs/harnesses/pi.md +173 -0
  151. package/docs/human-in-the-loop.md +4 -0
  152. package/docs/index.md +3 -0
  153. package/docs/lifecycle.md +20 -4
  154. package/docs/models-pi-ai.md +325 -0
  155. package/docs/models.md +504 -0
  156. package/docs/resumable-streaming.md +2 -0
  157. package/docs/routing.md +13 -13
  158. package/docs/sessions.md +36 -1
  159. package/docs/state.md +1 -0
  160. package/docs/sub-agents.md +9 -0
  161. package/package.json +40 -2
  162. package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
  163. package/dist/browser/ai.js.map +0 -1
  164. package/dist/client-C9DAjZIY.js.map +0 -1
  165. package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
  166. package/dist/connector-CptFKzRh.js.map +0 -1
  167. package/dist/message-builder-BymO4N_D.js.map +0 -1
  168. package/dist/src-BNU3ZiJM.js.map +0 -1
  169. package/dist/streams-CCPRV6dt.js.map +0 -1
  170. package/dist/ws-chat-transport-rWwta645.js.map +0 -1
package/docs/models.md ADDED
@@ -0,0 +1,504 @@
1
+ # Models (Experimental)
2
+
3
+ `agents/models/ai-sdk` is Cloudflare's AI SDK provider. One rule decides
4
+ everything in it:
5
+
6
+ > **Workers AI is the one catalog we keep up with. Every other vendor brings
7
+ > its own provider, and we route it.**
8
+
9
+ So there are two call forms, and no third:
10
+
11
+ ```ts
12
+ ai("@cf/zai-org/glm-4.7-flash"); // Workers AI — our model, our compat layer
13
+ ai(anthropic("claude-opus-4-8")); // the vendor's model, our transport
14
+ ```
15
+
16
+ No vendor ids, wire formats, thinking rules or model metadata live in this
17
+ package, and `agents` takes no vendor package as a dependency — not even an
18
+ optional one. You build the model with the vendor's own `@ai-sdk/*` provider;
19
+ we hand its request to AI Gateway and hand the answer straight back to it.
20
+
21
+ ## The canon, in five lines
22
+
23
+ 1. **`ai(string)` is a Workers AI id.** `@cf/<author>/<model>`, autocompleted
24
+ from `@cloudflare/workers-types`. Any other string is a `TypeError` that
25
+ tells you which provider package to install.
26
+ 2. **`ai(model)` is any AI SDK v4 model**, routed through AI Gateway. Its
27
+ provider builds the request, parses the response, and handles its own
28
+ errors; only the transport is ours.
29
+ 3. **The gateway is an option, not a product.** Caching, logging, metadata,
30
+ timeouts and retries are options on the call; the `default` gateway is
31
+ created for you on first use.
32
+ 4. **Unified billing means no vendor key.** Anthropic and OpenAI answer with a
33
+ placeholder key today; anything else needs a key stored on the gateway
34
+ (BYOK).
35
+ 5. One factory: `createAI` — text, embeddings, images, speech, transcription
36
+ and reranking all come off it, and it is a full `ProviderV4`.
37
+
38
+ ## Quick start
39
+
40
+ Requirements: `ai@^7` and `@ai-sdk/provider@^4` must both be resolvable — they
41
+ are optional peers of `agents`, so install them alongside it. Add a vendor
42
+ provider only if you want that vendor:
43
+
44
+ ```sh
45
+ pnpm add ai @ai-sdk/provider
46
+ pnpm add @ai-sdk/anthropic # only if you call Anthropic
47
+ ```
48
+
49
+ ```ts
50
+ import { createAI } from "agents/models/ai-sdk";
51
+ import { createAnthropic } from "@ai-sdk/anthropic";
52
+ import { generateText } from "ai";
53
+
54
+ export default {
55
+ async fetch(request: Request, env: Env) {
56
+ // Inside a Worker: keyless, using the AI binding.
57
+ const ai = createAI({ binding: env.AI, id: "default", cacheTtl: 60 });
58
+
59
+ // The gateway holds the real key, so this one is a placeholder.
60
+ const anthropic = createAnthropic({ apiKey: "cloudflare" });
61
+
62
+ const workersAI = await generateText({
63
+ model: ai("@cf/zai-org/glm-4.7-flash"),
64
+ prompt: "Say hello in three words"
65
+ });
66
+
67
+ const vendor = await generateText({
68
+ model: ai(anthropic("claude-opus-4-8")),
69
+ prompt: "Say hello in three words"
70
+ });
71
+
72
+ return Response.json({ workersAI: workersAI.text, vendor: vendor.text });
73
+ }
74
+ };
75
+ ```
76
+
77
+ Your `wrangler.jsonc` needs the binding:
78
+
79
+ ```jsonc
80
+ {
81
+ "ai": { "binding": "AI" }
82
+ }
83
+ ```
84
+
85
+ Streaming, tools, and structured output are ordinary AI SDK calls, on either
86
+ form:
87
+
88
+ ```ts
89
+ const result = streamText({
90
+ model: ai(anthropic("claude-opus-4-8")),
91
+ prompt: "Write a haiku about durable objects"
92
+ });
93
+ return result.toTextStreamResponse();
94
+ ```
95
+
96
+ Embeddings come from the same provider:
97
+
98
+ ```ts
99
+ import { embedMany } from "ai";
100
+
101
+ const { embeddings } = await embedMany({
102
+ model: ai.embedding("@cf/baai/bge-base-en-v1.5"),
103
+ values: ["hello", "world"]
104
+ });
105
+ ```
106
+
107
+ ## Registry and default provider
108
+
109
+ `createAI` returns a full `ProviderV4`, so it drops into both places the AI SDK
110
+ resolves a model from a string alone:
111
+
112
+ ```ts
113
+ import { createProviderRegistry, generateText } from "ai";
114
+
115
+ // A registry: `cloudflare:` names this provider.
116
+ const registry = createProviderRegistry({ cloudflare: ai });
117
+ await generateText({
118
+ model: registry.languageModel("cloudflare:@cf/zai-org/glm-4.7-flash"),
119
+ prompt: "Hi"
120
+ });
121
+
122
+ // Or install it globally: bare strings then resolve through this provider.
123
+ globalThis.AI_SDK_DEFAULT_PROVIDER = createAI({ binding: env.AI });
124
+ await generateText({ model: "@cf/zai-org/glm-4.7-flash", prompt: "Hi" });
125
+ ```
126
+
127
+ The string rule does not change in either place: a specifier is a `@cf/` id, so
128
+ a vendor model is still a model object (`ai(anthropic("claude-opus-4-8"))`) and
129
+ cannot be reached by a bare string.
130
+
131
+ Every modality is on the registry too (`registry.imageModel`,
132
+ `registry.textEmbeddingModel`, and so on), because each `ProviderV4` method is
133
+ implemented. The default provider is process-wide: set it once, where you build
134
+ the Worker's `env`-dependent state.
135
+
136
+ ## Why a model object and not an id
137
+
138
+ An id is only useful if someone keeps its catalog current. We keep exactly one:
139
+ Workers AI. Every other vendor already ships a maintained catalog inside its
140
+ own `@ai-sdk/*` package — ids, wire format, thinking levels, tool shapes, error
141
+ types — and that package is updated the day the vendor ships something. Copying
142
+ any of it here would mean a second, slower copy that is wrong every time a
143
+ vendor moves.
144
+
145
+ So `ai(model)` takes what the vendor built, clones it per call, swaps the
146
+ `fetch` its provider would have used, and sends the very same bytes to AI
147
+ Gateway. What comes back is the vendor's own response, parsed by the vendor's
148
+ own code. Your model object is never mutated — two concurrent calls never see
149
+ each other's transport.
150
+
151
+ ```ts
152
+ import { createOpenAI } from "@ai-sdk/openai";
153
+
154
+ const openai = createOpenAI({ apiKey: "cloudflare" });
155
+
156
+ ai(openai.responses("gpt-5-mini")); // OpenAI Responses API, routed
157
+ ai(anthropic("claude-opus-4-8")); // Anthropic Messages API, routed
158
+ ```
159
+
160
+ Passing a third-party id as a string is an error, on purpose:
161
+
162
+ ```ts
163
+ ai("anthropic/claude-opus-4-8");
164
+ // TypeError: "anthropic/claude-opus-4-8" is not a Workers AI model id.
165
+ // Workers AI ids start with "@cf/" … install @ai-sdk/anthropic and pass the
166
+ // model object: ai(anthropic("claude-opus-4-8")).
167
+ ```
168
+
169
+ A model whose provider hides its settings — one already wrapped in middleware,
170
+ or hand-written — is an error too. Wrap first, then apply middleware:
171
+
172
+ ```ts
173
+ wrapLanguageModel({ model: ai(anthropic("claude-opus-4-8")), middleware });
174
+ ```
175
+
176
+ ## The universal gateway request
177
+
178
+ A routed vendor model does not go down the Workers AI run path. It goes through
179
+ AI Gateway's **universal request**, which carries the vendor's own body
180
+ untouched:
181
+
182
+ ```ts
183
+ env.AI.gateway(gatewayId).run(
184
+ { provider: "anthropic", endpoint: "v1/messages", headers, query },
185
+ { gateway: { cacheTtl, skipCache, metadata, … }, extraHeaders, signal }
186
+ );
187
+ ```
188
+
189
+ - `provider` comes from the request URL's host (`api.anthropic.com` →
190
+ `anthropic`, `api.openai.com` → `openai`, and so on for every gateway
191
+ provider). A host the gateway has no provider for is a `TypeError` naming it.
192
+ - `endpoint` is the path after the host (`v1/messages`, `v1/responses`).
193
+ - `query` is the vendor's JSON body, verbatim — including its own model id, in
194
+ the vendor's own spelling. `claude-opus-4.8` gets a 404 from Anthropic
195
+ ("Did you mean claude-opus-4-8?"), because we do not rewrite ids.
196
+ - The vendor's credential header (`authorization`, `x-api-key`,
197
+ `x-goog-api-key`, `api-key`) is stripped; its protocol headers
198
+ (`anthropic-version`, `anthropic-beta`, …) travel.
199
+ - `stream: true` comes back as the vendor's own SSE. The gateway adds a small
200
+ `"p"` padding field to each data event, which every vendor parser ignores.
201
+
202
+ The universal request carries **JSON only**. A multipart body (a file upload,
203
+ an audio transcription posted as `FormData`) cannot be expressed in it and
204
+ raises a `TypeError` rather than being silently mangled.
205
+
206
+ ## Unified billing and BYOK
207
+
208
+ Whether a vendor answers without a key of yours is the gateway's business, not
209
+ this package's:
210
+
211
+ - **Unified billing** covers Anthropic and OpenAI today: a placeholder
212
+ `apiKey` is enough, and Cloudflare bills you.
213
+ - **Everything else needs BYOK** — a provider key stored on the gateway. Google
214
+ AI Studio without one answers `403 "Method doesn't allow unregistered callers"`,
215
+ and that error is surfaced to you exactly as Google wrote it, because Google's
216
+ provider is the one that knows how to read it.
217
+
218
+ Only the gateway's _own_ failures — no such gateway, an unpaid account — become
219
+ a `CloudflareAIError`. A vendor error stays the vendor's.
220
+
221
+ ## Other modalities
222
+
223
+ Images, speech, transcription and reranking come off the same provider, the
224
+ same Workers AI catalog, and the same options. Each has a short name and the
225
+ `ProviderV4` alias (`ai.image` / `ai.imageModel`, and so on).
226
+
227
+ ```ts
228
+ import { generateImage, generateSpeech, rerank, transcribe } from "ai";
229
+
230
+ const { image } = await generateImage({
231
+ model: ai.image("@cf/black-forest-labs/flux-1-schnell"),
232
+ prompt: "A cyberpunk lizard"
233
+ });
234
+
235
+ const { audio } = await generateSpeech({
236
+ model: ai.speech("@cf/deepgram/aura-1"),
237
+ text: "Hello from Cloudflare."
238
+ });
239
+
240
+ const { text } = await transcribe({
241
+ model: ai.transcription("@cf/openai/whisper-large-v3-turbo"),
242
+ audio: audio.uint8Array
243
+ });
244
+
245
+ const { rerankedDocuments } = await rerank({
246
+ model: ai.reranking("@cf/baai/bge-reranker-base"),
247
+ documents: ["a cyberpunk lizard", "a rainy Tuesday"],
248
+ query: "Which one is cooler?"
249
+ });
250
+ ```
251
+
252
+ A vendor's own embedding, image, speech or transcription model can be routed
253
+ too, with `ai.routed(model)`: the same `fetch` swap, the same gateway options,
254
+ and `providerMetadata.cloudflare` on the result.
255
+
256
+ `ai.routed` takes the gateway options, `headers` and `sessionAffinity` — and
257
+ those only. `fallback` is not among them (a leg for one of those modalities
258
+ would have to be another vendor's model, which this provider cannot build), and
259
+ neither are `reasoningEffort` and `chatTemplateKwargs`, which are Workers AI
260
+ settings; passing one is a compile error rather than a silent drop. Sent per
261
+ call under `providerOptions.cloudflare` instead, the two Workers AI knobs come
262
+ back in the result's `warnings`.
263
+
264
+ Bytes stay bytes and base64 stays base64: some models answer with a raw body
265
+ (`image/png`, `audio/mpeg`, `audio/wav`) and some with JSON (`{ image }`,
266
+ `{ audio }`), the content type decides, and nothing is converted on the way
267
+ through. The media type comes off the bytes rather than the model name —
268
+ `flux-1-schnell` answers with JPEG and MeloTTS with WAV.
269
+
270
+ Per-call knobs go under `providerOptions.cloudflare`, next to the gateway
271
+ options:
272
+
273
+ | Modality | Call options used | `providerOptions.cloudflare` |
274
+ | ------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
275
+ | Image | `prompt`, `n`, `size`, `seed`, `files`, `mask` | `steps`, `guidance`, `negativePrompt`, `strength` |
276
+ | Speech | `text`, `voice` → `speaker`, `outputFormat` → `encoding`, `language` → `lang` (MeloTTS) | — |
277
+ | Transcription | `audio`, `mediaType` | `language`, `task`, `initialPrompt`, `prefix`, `vadFilter` |
278
+ | Reranking | `query`, `documents`, `topN` → `top_k` | — |
279
+
280
+ Anything a model cannot take comes back in `warnings` rather than being sent:
281
+ `aspectRatio` everywhere, `size`/`seed`/`guidance` on `flux-1-schnell` (which
282
+ takes only a prompt and steps), `speed` and `instructions` on every speech
283
+ model, `voice` on MeloTTS. Object documents are stringified for reranking, with
284
+ a `compatibility` warning.
285
+
286
+ The run path generates one image per call, so `maxImagesPerCall` is 1 and
287
+ `generateImage` fans a larger `n` out into parallel calls.
288
+
289
+ `@cf/deepgram/nova-3` and `@cf/deepgram/flux` are not supported: they want the
290
+ audio as an object body (`audio: { body, contentType }`) that the JSON run path
291
+ cannot carry, and every documented encoding of it answers `400` live. They
292
+ throw a `CloudflareAIError` before any request is made, pointing at
293
+ `@cf/openai/whisper-large-v3-turbo`.
294
+
295
+ ## Options
296
+
297
+ Options can be set in three places, on both call forms. Precedence is
298
+ per-call > per-model > provider.
299
+
300
+ ```ts
301
+ // Provider-wide
302
+ const ai = createAI({ binding: env.AI, id: "prod", metadata: { app: "demo" } });
303
+
304
+ // Per model
305
+ const model = ai(anthropic("claude-opus-4-8"), { cacheTtl: 3600 });
306
+
307
+ // Per call
308
+ await generateText({
309
+ model,
310
+ prompt: "hi",
311
+ providerOptions: {
312
+ cloudflare: { skipCache: true, metadata: { userId: "u1" } }
313
+ }
314
+ });
315
+ ```
316
+
317
+ | Option | Type | What it does |
318
+ | -------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------- |
319
+ | `id` | `string` | Gateway id. Defaults to `"default"`, created on first use. |
320
+ | `gateway` | `string \| GatewayOptions` | The same options nested, if you prefer that spelling. |
321
+ | `skipCache` | `boolean` | Bypass the gateway cache for this request. |
322
+ | `cacheTtl` | `number` | Cache lifetime in seconds. |
323
+ | `cacheKey` | `string` | Explicit cache key instead of the request-derived one. |
324
+ | `collectLog` | `boolean` | Whether the gateway stores the request/response in its log. |
325
+ | `metadata` | `Record<string, string \| number \| boolean \| null>` | Key/values attached to the gateway log entry. |
326
+ | `eventId` | `string` | Correlates several requests into one gateway event. |
327
+ | `requestTimeoutMs` | `number` | Upstream request timeout. |
328
+ | `retries` | `{ maxAttempts, retryDelayMs, backoff }` | Server-side retry policy for the upstream provider call. |
329
+ | `fallback` | `(WorkersAIModelId \| LanguageModelV4)[]` | Models to try, in order, if the primary fails before producing output. |
330
+ | `headers` | `Record<string, string>` | Extra request headers, merged last. |
331
+ | `sessionAffinity` | `string` | Pin same-key requests to one replica for prefix-cache hits. |
332
+ | `reasoningEffort` | `"low" \| "medium" \| "high" \| null` | Workers AI only: explicit effort; `null` turns reasoning off. |
333
+ | `chatTemplateKwargs` | `Record<string, unknown>` | Workers AI only: forwarded verbatim, e.g. `{ enable_thinking: false }`. |
334
+
335
+ The flat gateway keys are sugar: `{ cacheTtl: 60 }` and
336
+ `{ gateway: { cacheTtl: 60 } }` mean the same thing, and both merge with the
337
+ layers above and below them (`metadata`, `retries` and `headers` merge field by
338
+ field).
339
+
340
+ `fallback` takes model objects only on the language forms. On the modality
341
+ methods (`ai.embedding`, `ai.image`, `ai.speech`, `ai.transcription`,
342
+ `ai.reranking`) a leg is a Workers AI id, because those run on the Workers AI
343
+ run path, which resolves a leg by id and nothing else.
344
+
345
+ `reasoningEffort` and `chatTemplateKwargs` are Workers AI settings. On a routed
346
+ vendor model, reasoning is the vendor's own call option — use the AI SDK's
347
+ unified `reasoning`, or that provider's `providerOptions`, and the vendor's
348
+ rules apply because the vendor's code is what runs.
349
+
350
+ ### `providerOptions.cloudflare`
351
+
352
+ Anything in the table above can be passed per call under the `cloudflare` key.
353
+ It is read for gateway options and then **removed** before the call reaches a
354
+ routed vendor model, so the vendor never sees a key it does not understand:
355
+
356
+ ```ts
357
+ await generateText({
358
+ model: ai(anthropic("claude-opus-4-8")),
359
+ prompt: "hi",
360
+ providerOptions: {
361
+ anthropic: { sendReasoning: true },
362
+ cloudflare: { metadata: { userId: "u1" }, skipCache: true, cacheTtl: 300 }
363
+ }
364
+ });
365
+ ```
366
+
367
+ ## Fallback
368
+
369
+ `fallback` tries each leg in order when the primary fails _before_ producing
370
+ any output. That covers a rejected request and also a stream that opens and
371
+ then fails before its first output part — an `error` part, an error finish, or
372
+ a broken body — in which case the failed leg's preamble is dropped and the
373
+ answering leg's stream is delivered in its place. Once a leg has produced
374
+ output it is the answer, failures and all: by then the caller already has part
375
+ of it. Legs may be Workers AI ids or model objects, in any mix:
376
+
377
+ ```ts
378
+ const model = ai(anthropic("claude-opus-4-8"), {
379
+ fallback: [ai("@cf/zai-org/glm-4.7-flash")]
380
+ });
381
+ ```
382
+
383
+ A leg travels with the chain's gateway options: the chain's `id`, `cacheTtl`
384
+ and the rest win over a leg's own, `metadata` merges field by field, and
385
+ everything else a leg was built with — its `headers`, its Workers AI knobs —
386
+ stays the leg's. Legs written as strings are Workers AI ids, in a chain exactly
387
+ as in `ai("…")`: a vendor id there is the same `TypeError`, whether it was
388
+ written on the model or per call under `providerOptions.cloudflare`.
389
+
390
+ `providerMetadata.cloudflare.model` reports the model that actually answered.
391
+ When every leg fails, the thrown `CloudflareAIError` carries an `attempts`
392
+ array of `{ model, error }` and the last error as its `cause`.
393
+
394
+ This is a client-side loop today. AI Gateway also accepts an **array** of
395
+ universal requests as a server-side fallback chain; when we adopt it, the
396
+ option shape will not change.
397
+
398
+ ## How Workers AI stays OpenAI-shaped
399
+
400
+ Every `@cf/...` model answers in the OpenAI chat-completions shape, but the run
401
+ path adds a few things of its own: a per-chunk usage on every streamed delta, a
402
+ `choices: []` heartbeat, a native `{ response: "", usage }` tail with the
403
+ cumulative usage, reasoning under either `reasoning` or `reasoning_content`, and
404
+ a handful of per-family limits (one family cannot turn reasoning off, another
405
+ rejects `response_format`, one older id rejects `tool_choice: "required"`, one
406
+ wants nine-character tool-call ids, one streams its answer labelled as reasoning
407
+ once thinking is off).
408
+
409
+ None of that reaches this provider's parsers. A framework-neutral compat layer
410
+ in the package core rewrites a strict OpenAI request into what the model accepts
411
+ and normalizes responses and streams back into strict OpenAI chunks, keyed by
412
+ model family from live conformance captures. The same layer serves the
413
+ [pi-ai provider](./models-pi-ai.md), so both frameworks see one behaviour. Where
414
+ a feature had to be dropped or changed, the call's `warnings` say so (for
415
+ example `reasoning-off` or `tool-choice-required`). A new Workers AI model that
416
+ is conformant needs no entry at all.
417
+
418
+ ## Observability: `providerMetadata.cloudflare`
419
+
420
+ Every result and every stream `finish` part carries typed gateway metadata, so
421
+ you never have to walk response objects looking for a log id. On a routed
422
+ vendor model it is merged alongside the vendor's own metadata block rather than
423
+ replacing it:
424
+
425
+ ```ts
426
+ const { providerMetadata } = await generateText({
427
+ model: ai(anthropic("claude-opus-4-8")),
428
+ prompt: "hi"
429
+ });
430
+
431
+ providerMetadata?.cloudflare;
432
+ // {
433
+ // model: "claude-opus-4-8",
434
+ // provider: "anthropic",
435
+ // gateway: "prod",
436
+ // logId: "01M1KZWN069WWNPC18V05NKHSS",
437
+ // eventId: "7bfd3660-9d0f-4bf7-bf9b-fa90b860456f",
438
+ // requestId: "7bfd3660-9d0f-4bf7-bf9b-fa90b860456f",
439
+ // traceId: "2babd9bbb1984dfc90417e513c60a714",
440
+ // cacheStatus: "MISS",
441
+ // step: 0
442
+ // }
443
+ providerMetadata?.anthropic; // still Anthropic's own
444
+ ```
445
+
446
+ | Field | Source header | Notes |
447
+ | ------------- | --------------------- | ---------------------------------------------- |
448
+ | `model` | — | The leg that answered, after any fallback. |
449
+ | `gateway` | — | The gateway id the request went through. |
450
+ | `provider` | — | The gateway provider slug, for a routed model. |
451
+ | `logId` | `cf-aig-log-id` | Falls back to the binding's last log id. |
452
+ | `eventId` | `cf-aig-event-id` | Correlates the requests of one gateway event. |
453
+ | `requestId` | `cf-aig-request-id` | The gateway's id for this request. |
454
+ | `traceId` | `cf-aig-trace-id` | The trace this request belongs to. |
455
+ | `runId` | `cf-aig-run-id` | Present only when the gateway sends one. |
456
+ | `cacheStatus` | `cf-aig-cache-status` | `HIT` / `MISS`. |
457
+ | `step` | `cf-aig-step` | Which step of a gateway route answered. |
458
+
459
+ Every field except `model` and `gateway` is optional, and which ones arrive
460
+ depends on the path and the call. Read the fields you find rather than assuming
461
+ any particular one is there. `response.headers` and `response.modelId` are
462
+ populated too.
463
+
464
+ ## Errors
465
+
466
+ Failures throw `CloudflareAIError`, which extends the AI SDK's `APICallError`,
467
+ so `ai`'s built-in retry loop honours `isRetryable` without extra wiring.
468
+
469
+ ```ts
470
+ import { CloudflareAIError } from "agents/models/ai-sdk";
471
+
472
+ try {
473
+ await generateText({ model: ai("@cf/zai-org/glm-4.7-flash"), prompt: "hi" });
474
+ } catch (error) {
475
+ if (error instanceof CloudflareAIError) {
476
+ error.status; // 402
477
+ error.code; // "gateway-error"
478
+ error.logId; // for the gateway log
479
+ error.model; // which model failed
480
+ error.attempts; // every fallback leg, when there was a chain
481
+ }
482
+ }
483
+ ```
484
+
485
+ `code` is one of `"auth"`, `"rate-limit"`, `"not-found"`, `"bad-request"`,
486
+ `"provider-error"`, `"gateway-error"`, `"unknown"`.
487
+
488
+ On a routed vendor model this covers the gateway's own failures. A vendor's own
489
+ error — a bad model id, a rejected parameter, a rate limit — is raised by that
490
+ vendor's provider in its own error type, because it is the code that knows how
491
+ to read it.
492
+
493
+ ## Experimental
494
+
495
+ This module is experimental. The surface may change in a minor release, and
496
+ every exported symbol is tagged `@experimental`. It ships from `agents` first;
497
+ the plan is for `cloudflare/ai` to re-export it and deprecate the older
498
+ `workers-ai-provider` / `ai-gateway-provider` packages.
499
+
500
+ ## See also
501
+
502
+ - [Models for pi-ai](./models-pi-ai.md) — the same factory for pi-ai and pi-durable
503
+ - [Cloudflare model catalog](https://developers.cloudflare.com/ai/models/)
504
+ - [Worker binding methods](https://developers.cloudflare.com/ai-gateway/usage/worker-binding-methods/)
@@ -84,6 +84,8 @@ function Chat() {
84
84
 
85
85
  Replayed chunks include `replay: true` to distinguish them from live chunks. The client uses this to batch-apply all replayed chunks before rendering, which prevents intermediate states (like reasoning "Thinking..." indicators) from flashing briefly during replay. During a live stream, chunks arrive gradually and React renders each intermediate state naturally.
86
86
 
87
+ Live and replayed chunks also carry a `seq` field: the chunk's position in the stream, starting at `0`. A continuation stream (for example, a tool continuation) appends to an assistant message that the client already holds, so replaying it from the start would apply the same text twice. The client remembers the highest `seq` it has applied for each request and skips replayed continuation chunks at or below it. Replays of non-continuation streams rebuild the message from the first chunk and are not filtered.
88
+
87
89
  ## Durable client cleanup
88
90
 
89
91
  `resume: true` controls whether the client tries to reconnect to an active stream. `cancelOnClientAbort: false` is the default cancellation behavior: generic client stream abort/cleanup is local-only, while explicit `stop()` still cancels the server turn.
package/docs/routing.md CHANGED
@@ -777,9 +777,9 @@ The error message lists available agents. Check:
777
777
  2. Check that `routeAgentRequest()` is called before your 404 handler
778
778
  3. Ensure the response from `routeAgentRequest()` is returned (not just called)
779
779
 
780
- ### WebSocket won't connect
780
+ ### WebSocket will not connect
781
781
 
782
- 1. Don't modify the response from `routeAgentRequest()` for WebSocket upgrades
782
+ 1. Do not modify the response from `routeAgentRequest()` for WebSocket upgrades
783
783
  2. Ensure CORS is enabled if connecting from a different origin
784
784
  3. Check browser dev tools for the actual error
785
785
 
@@ -798,16 +798,16 @@ The error message lists available agents. Check:
798
798
 
799
799
  Routes a request to the appropriate agent.
800
800
 
801
- | Parameter | Type | Description |
802
- | ------------------------- | ------------------------- | --------------------------------------------------- |
803
- | `request` | `Request` | The incoming request |
804
- | `env` | `Env` | Environment with agent bindings |
805
- | `options.cors` | `boolean \| HeadersInit` | Enable CORS headers |
806
- | `options.props` | `Record<string, unknown>` | Props passed to whichever agent handles the request |
807
- | `options.locationHint` | `string` | Preferred location for agent instances |
808
- | `options.jurisdiction` | `string` | Data jurisdiction for agent instances |
809
- | `options.onBeforeConnect` | `Function` | Callback before WebSocket connections |
810
- | `options.onBeforeRequest` | `Function` | Callback before HTTP requests |
801
+ | Parameter | Type | Description |
802
+ | ------------------------- | ------------------------ | ------------------------------------------------------------------------------------ |
803
+ | `request` | `Request` | The incoming request |
804
+ | `env` | `Env` | Environment with agent bindings |
805
+ | `options.cors` | `boolean \| HeadersInit` | Enable CORS headers |
806
+ | `options.props` | `object` | Props passed to whichever agent handles the request. Sent as JSON, so use plain data |
807
+ | `options.locationHint` | `string` | Preferred location for agent instances |
808
+ | `options.jurisdiction` | `string` | Data jurisdiction for agent instances |
809
+ | `options.onBeforeConnect` | `Function` | Callback before WebSocket connections |
810
+ | `options.onBeforeRequest` | `Function` | Callback before HTTP requests |
811
811
 
812
812
  **Returns:** `Promise<Response \| undefined>` - Response if matched, undefined if no agent route
813
813
 
@@ -821,7 +821,7 @@ Get an agent instance by name for server-side RPC or request forwarding.
821
821
  | `name` | `string` | Instance name |
822
822
  | `options.locationHint` | `string` | Preferred location |
823
823
  | `options.jurisdiction` | `string` | Data jurisdiction |
824
- | `options.props` | `Record<string, unknown>` | Initialization properties for `onStart` |
824
+ | `options.props` | `object` | Initialization properties for `onStart` |
825
825
 
826
826
  **Returns:** `Promise<DurableObjectStub<T>>` - Typed stub for calling agent methods or forwarding requests
827
827
 
package/docs/sessions.md CHANGED
@@ -242,7 +242,23 @@ await session.addCompaction(summary, fromMessageId, toMessageId);
242
242
 
243
243
  Sessions stamps each message with a token estimate when the row is written. `compactAfter()` gates on that O(1) aggregate and never reads the transcript to decide whether to compact. Auto-compaction failures are non-fatal: they log, emit `session:error`, and leave the transcript alone.
244
244
 
245
- To trim a transcript before handing it to a model, `truncateOlderMessages` is exported from [`agents/chat`](./chat-agents.md), not from `agents/sessions`.
245
+ To trim a transcript before handing it to a model, `truncateOlderMessages` is exported from [`agents/chat`](./chat-agents.md), not from `agents/sessions`. If your tools define `toModelOutput`, a truncated tool output may no longer match the tool's output schema. In that case, pass `toolOutputs: false` to `truncateOlderMessages` and call `truncateOlderToolResults` on the result of `convertToModelMessages`, which truncates what the model reads after `toModelOutput` has run. Provider-executed tool outputs are never truncated.
246
+
247
+ With the default `keepRecent`, the truncation cutoff moves forward by one message whenever a message is added, so every turn rewrites a message near the end of the prompt and invalidates the provider's prompt cache from that point. To keep the prefix stable, move the cutoff in steps and pass the same `keepRecent` to both functions:
248
+
249
+ ```ts
250
+ const step = 8;
251
+ const keepRecent = history.length <= 4 ? 4 : 4 + ((history.length - 4) % step);
252
+ const truncated = truncateOlderMessages(history, {
253
+ keepRecent,
254
+ toolOutputs: false
255
+ });
256
+ const modelMessages = truncateOlderToolResults(
257
+ await convertToModelMessages(truncated, { tools }),
258
+ truncated,
259
+ { keepRecent }
260
+ );
261
+ ```
246
262
 
247
263
  ## Large messages
248
264
 
@@ -345,6 +361,25 @@ const unsubscribe = sessions.subscribe(async (event) => {
345
361
 
346
362
  `import` fires once per `importMessage()` and carries the row; it is deliberately not an `append`, so a projection does not patch itself per imported row during a migration — it marks itself stale and re-derives once. `compaction` fires when an overlay is stored directly through `addCompaction()`; `compact()` reports its own overlay as `compact`.
347
363
 
364
+ A host that keeps an in-memory transcript does not need to write that reduction itself. `session.mirror()` subscribes for one session and patches an array in place: an append or update replaces the entry with the same id (or pushes a new row), a delete filters, and a clear empties it:
365
+
366
+ ```ts
367
+ const stop = sessions.session("").mirror<UIMessage>({
368
+ get: () => this.messages,
369
+ set: (messages) => {
370
+ this.messages = messages;
371
+ },
372
+ transform: (message) => toCachedMessage(message),
373
+ intercept: async (event) => {
374
+ if (event.type !== "compact") return false;
375
+ this.messages = await reloadFromStorage();
376
+ return true;
377
+ }
378
+ });
379
+ ```
380
+
381
+ `get()` is read on every event, so reassigning the array is safe. `intercept` handles an event instead of the default reduction. Use it for changes an in-place patch cannot express, such as a branch append or a compaction. `onApplied` runs after an append or update was written, with the entry it replaced.
382
+
348
383
  This is a local cache-coherence feed, not a cross-object event log. Capability diagnostics are also emitted through Lifecycle observability.
349
384
 
350
385
  ## Errors
package/docs/state.md CHANGED
@@ -301,6 +301,7 @@ const client = new AgentClient({
301
301
  host: "your-worker.workers.dev"
302
302
  });
303
303
 
304
+ // Resolves once the agent's stored state has arrived, if it has any
304
305
  await client.ready;
305
306
 
306
307
  // Read state directly
@@ -246,6 +246,15 @@ The hook receives the **original** request with its URL intact — including the
246
246
 
247
247
  WebSocket upgrade requests flow through this hook the same way as plain HTTP. If you return a mutated `Request`, keep the original `Upgrade: websocket` and `Sec-WebSocket-*` headers — cloning via `new Headers(req.headers)` and only adding or replacing entries is the safest recipe.
248
248
 
249
+ When the hook returns a `Response` for a WebSocket upgrade, the framework accepts the socket and immediately closes it, because browsers hide the status of a failed handshake and reconnecting clients would retry it forever. The close code carries the status:
250
+
251
+ | Returned status | Close code | Client behavior |
252
+ | --------------- | --------------- | --------------------------------------------------------------------- |
253
+ | `4xx` | `4000 + status` | Terminal: `useAgent` and `AgentClient` stop and set `connectionError` |
254
+ | anything else | `1011` | Retried with backoff |
255
+
256
+ The close reason is `Sub-agent connection rejected (<status>)`, so a `404` arrives as code `4404`. At the first `/sub/` hop, a returned `Response` that already carries a `webSocket` is passed through unchanged; deeper hops always close with the mapped code.
257
+
249
258
  ### `this.parentPath` and `this.selfPath`
250
259
 
251
260
  Root-first ancestor chains. `parentPath` covers strict ancestors; `selfPath` includes the current agent.