agents 0.23.0 → 0.25.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.
- package/dist/{agent-routing-DE5zmCQ8.d.ts → agent-routing-0DR0Tk2v.d.ts} +270 -190
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-routing.js.map +1 -1
- package/dist/agent-tool-types.d.ts +13 -7
- package/dist/agent-tool-types.js +23 -1
- package/dist/agent-tool-types.js.map +1 -1
- package/dist/{agent-tools-y7zLfw4Q.js → agent-tools-CdJC-8Dc.js} +72 -9
- package/dist/agent-tools-CdJC-8Dc.js.map +1 -0
- package/dist/{agent-tools-DtXMTDGM.d.ts → agent-tools-YrATZJu2.d.ts} +29 -15
- package/dist/agent-tools.d.ts +6 -4
- package/dist/agent-tools.js.map +1 -1
- package/dist/ai-fm7xx1BK.js +312 -0
- package/dist/ai-fm7xx1BK.js.map +1 -0
- package/dist/base64-redaction-C1Uu29t5.js +59 -0
- package/dist/base64-redaction-C1Uu29t5.js.map +1 -0
- package/dist/browser/ai-sdk.d.ts +90 -0
- package/dist/browser/ai-sdk.js +51 -0
- package/dist/browser/ai-sdk.js.map +1 -0
- package/dist/browser/ai.d.ts +4 -4
- package/dist/browser/ai.js +1 -347
- package/dist/browser/index.d.ts +81 -79
- package/dist/browser/index.js +361 -2
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/tanstack-ai.d.ts +96 -1
- package/dist/browser/tanstack-ai.js +62 -2
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/browser-DE3d-RLk.d.ts +275 -0
- package/dist/browser-tool-CQ10e6Kj.js +480 -0
- package/dist/browser-tool-CQ10e6Kj.js.map +1 -0
- package/dist/browser-tool-DuNdlwZ9.d.ts +73 -0
- package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
- package/dist/capability-B4WbF81e.js.map +1 -0
- package/dist/{capability-runner-Be_-PLR1.d.ts → capability-runner-DBAnGr6L.d.ts} +45 -15
- package/dist/chat/index.d.ts +341 -232
- package/dist/chat/index.js +602 -72
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +42 -2
- package/dist/chat/react.js +191 -35
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/transport.d.ts +1 -1
- package/dist/chat/transport.js +1 -1
- package/dist/chat-sdk/index.d.ts +17 -3
- package/dist/chat-sdk/index.js +28 -2
- package/dist/chat-sdk/index.js.map +1 -1
- package/dist/client-4OlMIJx-.js +591 -0
- package/dist/client-4OlMIJx-.js.map +1 -0
- package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
- package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
- package/dist/client-je3koD9Z.d.ts +446 -0
- package/dist/client.d.ts +29 -283
- package/dist/client.js +2 -274
- package/dist/{connector-CptFKzRh.js → connector-58XDGvc3.js} +251 -120
- package/dist/connector-58XDGvc3.js.map +1 -0
- package/dist/{connector-CkQD4MK3.d.ts → connector-Cl-VqPmA.d.ts} +244 -146
- package/dist/context/index.d.ts +29 -0
- package/dist/context/index.js +64 -17
- package/dist/context/index.js.map +1 -1
- package/dist/current-agent-DhoDkSnH.js.map +1 -1
- package/dist/current-agent-vAk_5JMt.d.ts +93 -0
- package/dist/{current-agent-Da_C9a3b.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +26 -127
- package/dist/harnesses/pi/index.d.ts +348 -0
- package/dist/harnesses/pi/index.js +754 -0
- package/dist/harnesses/pi/index.js.map +1 -0
- package/dist/images-iiTDSyRu.js +1631 -0
- package/dist/images-iiTDSyRu.js.map +1 -0
- package/dist/index-BreL0YrQ.d.ts +78 -0
- package/dist/index-kbSUIer3.d.ts +343 -0
- package/dist/index.d.ts +43 -41
- package/dist/index.js +2 -2
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +19 -17
- package/dist/lifecycle/index.js +2 -2
- package/dist/{lifecycle-CMRGjZdw.js → lifecycle-CFu6OV03.js} +73 -29
- package/dist/lifecycle-CFu6OV03.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/client/x402.js +21 -19
- package/dist/mcp/client/x402.js.map +1 -1
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +2 -2
- package/dist/{message-builder-BymO4N_D.js → message-builder-DAl9AhHH.js} +89 -4
- package/dist/message-builder-DAl9AhHH.js.map +1 -0
- package/dist/models/ai-sdk/index.d.ts +353 -0
- package/dist/models/ai-sdk/index.js +2443 -0
- package/dist/models/ai-sdk/index.js.map +1 -0
- package/dist/models/pi-ai/index.d.ts +313 -0
- package/dist/models/pi-ai/index.js +1795 -0
- package/dist/models/pi-ai/index.js.map +1 -0
- package/dist/observability/ai/index.js +20 -4
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/{protocol-B0nh6KNf.js → protocol-kHsr-f0X.js} +26 -12
- package/dist/{protocol-B0nh6KNf.js.map → protocol-kHsr-f0X.js.map} +1 -1
- package/dist/queue/index.d.ts +123 -0
- package/dist/queue/index.js +2 -0
- package/dist/queue-BB-zfIYF.js +406 -0
- package/dist/queue-BB-zfIYF.js.map +1 -0
- package/dist/react.d.ts +31 -13
- package/dist/react.js +190 -71
- package/dist/react.js.map +1 -1
- package/dist/{retries-D9Ds-1lz.d.ts → retries-CV6AkXmQ.d.ts} +23 -8
- package/dist/retries.d.ts +12 -8
- package/dist/retries.js +19 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +2 -2
- package/dist/routing/index.js +1 -1
- package/dist/{scheduler-DD9NdYbF.js → scheduler-DZ3xQfeK.js} +4 -11
- package/dist/scheduler-DZ3xQfeK.js.map +1 -0
- package/dist/{scheduler-Dwh85ZGl.d.ts → scheduler-RdACNXDP.d.ts} +6 -6
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +70 -3
- package/dist/sessions/index.js +276 -67
- package/dist/sessions/index.js.map +1 -1
- package/dist/skills/index.d.ts +29 -314
- package/dist/socket-address.d.ts +16 -0
- package/dist/socket-address.js +19 -0
- package/dist/socket-address.js.map +1 -0
- package/dist/{src-DlSHshb2.js → src-BZeu-SCD.js} +541 -500
- package/dist/src-BZeu-SCD.js.map +1 -0
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.js +137 -0
- package/dist/state/index.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-D6tJ0NN9.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
- package/dist/{streams-DZKgAj9b.js → streams-DW-UCOUc.js} +9 -2
- package/dist/streams-DW-UCOUc.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-ylZgBjhj.js → tasks-68DESxf0.js} +3 -3
- package/dist/{tasks-ylZgBjhj.js.map → tasks-68DESxf0.js.map} +1 -1
- package/dist/{tasks-BRJ5zgya.d.ts → tasks-9YxYtByh.d.ts} +5 -5
- package/dist/transport-PfFnt-Ld.d.ts +525 -0
- package/dist/transport-protocol-BxyngQ_R.js +25 -0
- package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
- package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
- package/dist/types-BA4anrjI.d.ts +70 -0
- package/dist/voice/index.d.ts +2 -2
- package/dist/websockets/index.d.ts +195 -69
- package/dist/websockets/index.js +3 -2
- package/dist/websockets-Ci-LZFHH.js +1030 -0
- package/dist/websockets-Ci-LZFHH.js.map +1 -0
- package/dist/wire-types-CnMt6_HR.js.map +1 -1
- package/dist/wire-types-CrjKvDyi.d.ts +300 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +21 -21
- package/dist/workflows.js +1 -1
- package/dist/{ws-chat-transport-UNRIS2xl.d.ts → ws-chat-transport-BbT1A5Lc.d.ts} +51 -2
- package/dist/{ws-chat-transport-rWwta645.js → ws-chat-transport-cqMKq3IY.js} +143 -16
- package/dist/ws-chat-transport-cqMKq3IY.js.map +1 -0
- package/docs/agent-class.md +2 -2
- package/docs/agent-tools.md +29 -4
- package/docs/browse-the-web.md +87 -5
- package/docs/chat-agents.md +124 -16
- package/docs/client-sdk.md +30 -0
- package/docs/context.md +27 -0
- package/docs/durable-execution.md +2 -0
- package/docs/harnesses/pi.md +91 -0
- package/docs/human-in-the-loop.md +4 -0
- package/docs/index.md +4 -1
- package/docs/lifecycle.md +114 -23
- package/docs/models-pi-ai.md +325 -0
- package/docs/models.md +504 -0
- package/docs/queue.md +135 -241
- package/docs/resumable-streaming.md +2 -0
- package/docs/retries.md +1 -1
- package/docs/routing.md +13 -13
- package/docs/sessions.md +36 -1
- package/docs/state.md +1 -0
- package/docs/sub-agents.md +12 -3
- package/package.json +51 -3
- package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
- package/dist/browser/ai.js.map +0 -1
- package/dist/capability-BjSKYpzg.js.map +0 -1
- package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
- package/dist/client.js.map +0 -1
- package/dist/connector-CptFKzRh.js.map +0 -1
- package/dist/lifecycle-CMRGjZdw.js.map +0 -1
- package/dist/message-builder-BymO4N_D.js.map +0 -1
- package/dist/scheduler-DD9NdYbF.js.map +0 -1
- package/dist/src-DlSHshb2.js.map +0 -1
- package/dist/streams-DZKgAj9b.js.map +0 -1
- package/dist/websockets-DUfRHPRq.js +0 -502
- package/dist/websockets-DUfRHPRq.js.map +0 -1
- 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/)
|