agents 0.28.0 → 0.29.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-QDcchcQP.d.ts → agent-routing-CDbpVjYq.d.ts} +18 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-DHL2XI7u.d.ts → agent-tools-C8DH8Yii.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/{ai-ahruGw7e.d.ts → ai-iXw0uOGj.d.ts} +3 -3
- package/dist/{ai-sdk-Bdsf6mSA.js → ai-sdk-DRPh2rRa.js} +218 -132
- package/dist/ai-sdk-DRPh2rRa.js.map +1 -0
- package/dist/browser/ai.d.ts +1 -1
- package/dist/browser/ai.js +2 -2
- package/dist/browser/index.d.ts +8 -8
- package/dist/browser/index.js +3 -3
- package/dist/browser/tools/ai-sdk.d.ts +2 -2
- package/dist/browser/tools/ai-sdk.js +1 -1
- package/dist/browser/tools/pi.d.ts +1 -1
- package/dist/browser/tools/pi.js +3 -3
- package/dist/browser/tools/pi.js.map +1 -1
- package/dist/browser/tools/tanstack-ai.d.ts +2 -2
- package/dist/browser/tools/tanstack-ai.js +1 -1
- package/dist/{browser-CEdNlsaD.js → browser-DccPw_wW.js} +130 -79
- package/dist/browser-DccPw_wW.js.map +1 -0
- package/dist/{browser-D30VlpcA.d.ts → browser-DlXhSAdf.d.ts} +41 -37
- package/dist/{browser-tool-4iomavs_.js → browser-tool-B1mhifin.js} +3 -3
- package/dist/{browser-tool-4iomavs_.js.map → browser-tool-B1mhifin.js.map} +1 -1
- package/dist/{browser-tool-vtrPOV8e.d.ts → browser-tool-Cxjk5sgG.d.ts} +2 -2
- package/dist/chat/index.d.ts +143 -71
- package/dist/chat/index.js +269 -110
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +33 -33
- package/dist/{client-BGIQKJhX.d.ts → client-C27CtRZr.d.ts} +2 -2
- package/dist/client.d.ts +1 -1
- package/dist/{connector-DEXzVoaY.d.ts → connector-20DJ6p4k.d.ts} +2 -2
- package/dist/{connector-CtZylXdk.js → connector-DAXBOIIi.js} +2 -2
- package/dist/{connector-CtZylXdk.js.map → connector-DAXBOIIi.js.map} +1 -1
- package/dist/experimental/channels/web/index.js +1 -1
- package/dist/{handler-stateless-VvrWSAVA.js → handler-stateless-Bly_nSH3.js} +12 -3
- package/dist/handler-stateless-Bly_nSH3.js.map +1 -0
- package/dist/harness/container/index.js +1 -1
- package/dist/harness/container/index.js.map +1 -1
- package/dist/harness/opencode/index.d.ts +182 -6
- package/dist/harness/opencode/index.js +354 -23
- package/dist/harness/opencode/index.js.map +1 -1
- package/dist/harness/pi/index.d.ts +13 -5
- package/dist/harness/pi/index.js +8 -214
- package/dist/harness/pi/index.js.map +1 -1
- package/dist/{images-Ce9SCu3X.js → images-xaDv2Fqe.js} +662 -152
- package/dist/images-xaDv2Fqe.js.map +1 -0
- package/dist/index-DCxk3OSJ.d.ts +378 -0
- package/dist/{index-kbSUIer3.d.ts → index-iv9FTXX9.d.ts} +80 -112
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{input-schema-D2QAtHZH.js → input-schema-B0Etuo-o.js} +2 -2
- package/dist/{input-schema-D2QAtHZH.js.map → input-schema-B0Etuo-o.js.map} +1 -1
- package/dist/lifecycle/index.js +1 -1
- package/dist/{lifecycle-DGorpAmA.js → lifecycle-ClT4DmF2.js} +11 -3
- package/dist/lifecycle-ClT4DmF2.js.map +1 -0
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +17 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server/index.js +1 -1
- package/dist/models/ai-sdk/index.d.ts +31 -326
- package/dist/models/ai-sdk/index.js +1 -1
- package/dist/models/opencode/index.d.ts +25 -13
- package/dist/models/opencode/index.js +16 -11
- package/dist/models/opencode/index.js.map +1 -1
- package/dist/models/pi-ai/index.d.ts +17 -13
- package/dist/models/pi-ai/index.js +89 -36
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/{session-store-DoPY12xM.d.ts → session-store-_IK6dV-w.d.ts} +14 -2
- package/dist/{session-store-Do4xkDcf.js → session-store-yvwq1f9x.js} +55 -2
- package/dist/session-store-yvwq1f9x.js.map +1 -0
- package/dist/{settings-C45hDliX.js → settings-Dk2il2Hf.js} +10 -4
- package/dist/settings-Dk2il2Hf.js.map +1 -0
- package/dist/skills/index.d.ts +22 -28
- package/dist/skills/index.js +183 -143
- package/dist/skills/index.js.map +1 -1
- package/dist/{src-CQxJFv4l.js → src-6lr7MtM_.js} +3 -3
- package/dist/src-6lr7MtM_.js.map +1 -0
- package/dist/sub-routing.d.ts +1 -1
- package/dist/{tool-cQRkwSZZ.js → tool-DkT_DJBm.js} +2 -2
- package/dist/tool-DkT_DJBm.js.map +1 -0
- package/dist/transport-CPh7YGiw.d.ts +623 -0
- package/dist/voice/index.d.ts +1 -1
- package/dist/web/fetch/tools/pi.js +2 -2
- package/dist/web/fetch/tools/pi.js.map +1 -1
- package/dist/web/search/index.js +1 -1
- package/dist/web/search/tools/ai-sdk.js +2 -2
- package/dist/web/search/tools/pi.js +3 -3
- package/dist/web/search/tools/pi.js.map +1 -1
- package/dist/web/search/tools/tanstack-ai.js +2 -2
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-C3zy8YR0.js → websockets-CqxnntmE.js} +2 -2
- package/dist/{websockets-C3zy8YR0.js.map → websockets-CqxnntmE.js.map} +1 -1
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +1 -1
- package/docs/browse-the-web.md +1 -1
- package/docs/chat-agents.md +19 -18
- package/docs/harnesses/pi.md +47 -18
- package/docs/mcp-client.md +1 -1
- package/docs/mcp-servers.md +1 -1
- package/docs/models-pi-ai.md +70 -17
- package/docs/models.md +219 -152
- package/package.json +14 -14
- package/dist/ai-sdk-Bdsf6mSA.js.map +0 -1
- package/dist/browser-CEdNlsaD.js.map +0 -1
- package/dist/handler-stateless-VvrWSAVA.js.map +0 -1
- package/dist/images-Ce9SCu3X.js.map +0 -1
- package/dist/lifecycle-DGorpAmA.js.map +0 -1
- package/dist/session-store-Do4xkDcf.js.map +0 -1
- package/dist/settings-C45hDliX.js.map +0 -1
- package/dist/settings-NgGwF7Qt.d.ts +0 -331
- package/dist/src-CQxJFv4l.js.map +0 -1
- package/dist/tool-cQRkwSZZ.js.map +0 -1
- package/dist/transport-k2mFrtL8.d.ts +0 -200
package/docs/models.md
CHANGED
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
# Models (Experimental)
|
|
2
2
|
|
|
3
|
-
`agents/models/ai-sdk` is Cloudflare's AI SDK provider.
|
|
4
|
-
|
|
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:
|
|
3
|
+
`agents/models/ai-sdk` is Cloudflare's AI SDK provider. It takes two call
|
|
4
|
+
forms, through the `AI` binding or
|
|
5
|
+
[an account id and API token](#account-id-and-api-token):
|
|
10
6
|
|
|
11
7
|
```ts
|
|
12
|
-
|
|
13
|
-
ai(
|
|
8
|
+
// A string runs on env.AI.run.
|
|
9
|
+
ai("@cf/zai-org/glm-4.7-flash"); // Workers AI
|
|
10
|
+
ai("anthropic/claude-opus-4.8"); // the AI Gateway catalog
|
|
11
|
+
ai("dynamic/support"); // a dynamic route on your gateway
|
|
12
|
+
|
|
13
|
+
// A vendor's own model goes to AI Gateway's provider-native path.
|
|
14
|
+
ai(anthropic("claude-opus-4-8"));
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
A string needs no vendor package and no vendor key: AI Gateway's catalog names
|
|
18
|
+
the model, picks the provider, converts the request, and bills through Unified
|
|
19
|
+
Billing or a key stored on the gateway. A model object keeps the vendor's own
|
|
20
|
+
request and parser, so every vendor feature its package supports works.
|
|
20
21
|
|
|
21
22
|
## The canon, in five lines
|
|
22
23
|
|
|
23
|
-
1.
|
|
24
|
-
from `@cloudflare/workers-types
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
1. **A string is Cloudflare's.** `@cf/<author>/<model>` is Workers AI,
|
|
25
|
+
autocompleted from `@cloudflare/workers-types`; `<author>/<model>` is the
|
|
26
|
+
AI Gateway catalog, in the gateway's spelling; `dynamic/<route>` is a
|
|
27
|
+
[dynamic route](#dynamic-routes). All three run on `env.AI.run`, and any
|
|
28
|
+
other string is a `TypeError` naming the three forms.
|
|
29
|
+
2. **`ai(model)` is any AI SDK v4 model**, sent to AI Gateway's
|
|
30
|
+
[provider-native path](#model-objects). Its provider builds the request,
|
|
31
|
+
parses the response, and handles its own errors; only the transport is
|
|
32
|
+
ours.
|
|
29
33
|
3. **The gateway is an option, not a product.** Caching, logging, metadata,
|
|
30
34
|
timeouts and retries are options on the call; the `default` gateway is
|
|
31
35
|
created for you on first use.
|
|
32
|
-
4. **Unified billing means no vendor key.**
|
|
33
|
-
|
|
34
|
-
(BYOK).
|
|
36
|
+
4. **Unified billing means no vendor key.** The gateway holds the credential:
|
|
37
|
+
Cloudflare's, or a key you store on the gateway (BYOK).
|
|
35
38
|
5. One factory: `createAI` — text, embeddings, images, speech, transcription
|
|
36
39
|
and reranking all come off it, and it is a full `ProviderV4`.
|
|
37
40
|
|
|
@@ -39,16 +42,15 @@ we hand its request to AI Gateway and hand the answer straight back to it.
|
|
|
39
42
|
|
|
40
43
|
Requirements: `ai@^7` and `@ai-sdk/provider@^4` must both be resolvable — they
|
|
41
44
|
are optional peers of `agents`, so install them alongside it. Add a vendor
|
|
42
|
-
provider only if you want that vendor:
|
|
45
|
+
provider only if you want to pass that vendor's model objects:
|
|
43
46
|
|
|
44
47
|
```sh
|
|
45
48
|
pnpm add ai @ai-sdk/provider
|
|
46
|
-
pnpm add @ai-sdk/anthropic # only
|
|
49
|
+
pnpm add @ai-sdk/anthropic # only for ai(anthropic("…"))
|
|
47
50
|
```
|
|
48
51
|
|
|
49
52
|
```ts
|
|
50
53
|
import { createAI } from "agents/models/ai-sdk";
|
|
51
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
52
54
|
import { generateText } from "ai";
|
|
53
55
|
|
|
54
56
|
export default {
|
|
@@ -56,20 +58,17 @@ export default {
|
|
|
56
58
|
// Inside a Worker: keyless, using the AI binding.
|
|
57
59
|
const ai = createAI({ binding: env.AI, id: "default", cacheTtl: 60 });
|
|
58
60
|
|
|
59
|
-
// The gateway holds the real key, so this one is a placeholder.
|
|
60
|
-
const anthropic = createAnthropic({ apiKey: "cloudflare" });
|
|
61
|
-
|
|
62
61
|
const workersAI = await generateText({
|
|
63
62
|
model: ai("@cf/zai-org/glm-4.7-flash"),
|
|
64
63
|
prompt: "Say hello in three words"
|
|
65
64
|
});
|
|
66
65
|
|
|
67
|
-
const
|
|
68
|
-
model: ai(anthropic
|
|
66
|
+
const claude = await generateText({
|
|
67
|
+
model: ai("anthropic/claude-opus-4.8"),
|
|
69
68
|
prompt: "Say hello in three words"
|
|
70
69
|
});
|
|
71
70
|
|
|
72
|
-
return Response.json({ workersAI: workersAI.text,
|
|
71
|
+
return Response.json({ workersAI: workersAI.text, claude: claude.text });
|
|
73
72
|
}
|
|
74
73
|
};
|
|
75
74
|
```
|
|
@@ -82,12 +81,12 @@ Your `wrangler.jsonc` needs the binding:
|
|
|
82
81
|
}
|
|
83
82
|
```
|
|
84
83
|
|
|
85
|
-
Streaming, tools, and structured output are ordinary AI SDK calls, on
|
|
84
|
+
Streaming, tools, and structured output are ordinary AI SDK calls, on every
|
|
86
85
|
form:
|
|
87
86
|
|
|
88
87
|
```ts
|
|
89
88
|
const result = streamText({
|
|
90
|
-
model: ai(
|
|
89
|
+
model: ai("openai/gpt-5.5"),
|
|
91
90
|
prompt: "Write a haiku about durable objects"
|
|
92
91
|
});
|
|
93
92
|
return result.toTextStreamResponse();
|
|
@@ -104,6 +103,30 @@ const { embeddings } = await embedMany({
|
|
|
104
103
|
});
|
|
105
104
|
```
|
|
106
105
|
|
|
106
|
+
### Account id and API token
|
|
107
|
+
|
|
108
|
+
To use a gateway in another account, or to call from outside a Worker, pass
|
|
109
|
+
`accountId` and `apiToken` instead of `binding`:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const ai = createAI({
|
|
113
|
+
accountId: "27b146402af2103944379f33841b6234",
|
|
114
|
+
apiToken: env.CLOUDFLARE_API_TOKEN,
|
|
115
|
+
gateway: "project-gateway"
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The call forms and options are the same, and the gateway still defaults to
|
|
120
|
+
`default`. A string goes to AI Gateway's
|
|
121
|
+
[REST API](https://developers.cloudflare.com/ai-gateway/usage/rest-api/)
|
|
122
|
+
(`api.cloudflare.com/client/v4/accounts/{accountId}/ai/...`), which runs it on
|
|
123
|
+
`/run` as `env.AI.run` does. A model object goes to the provider-native path,
|
|
124
|
+
`gateway.ai.cloudflare.com/v1/{accountId}/{gateway}/{provider}/...`, with the
|
|
125
|
+
token as `cf-aig-authorization`. The token is sent to those two hosts only.
|
|
126
|
+
|
|
127
|
+
Create the token with **Workers AI Read** for strings and **AI Gateway Run**
|
|
128
|
+
for model objects.
|
|
129
|
+
|
|
107
130
|
## Registry and default provider
|
|
108
131
|
|
|
109
132
|
`createAI` returns a full `ProviderV4`, so it drops into both places the AI SDK
|
|
@@ -115,7 +138,7 @@ import { createProviderRegistry, generateText } from "ai";
|
|
|
115
138
|
// A registry: `cloudflare:` names this provider.
|
|
116
139
|
const registry = createProviderRegistry({ cloudflare: ai });
|
|
117
140
|
await generateText({
|
|
118
|
-
model: registry.languageModel("cloudflare
|
|
141
|
+
model: registry.languageModel("cloudflare:anthropic/claude-opus-4.8"),
|
|
119
142
|
prompt: "Hi"
|
|
120
143
|
});
|
|
121
144
|
|
|
@@ -124,99 +147,134 @@ globalThis.AI_SDK_DEFAULT_PROVIDER = createAI({ binding: env.AI });
|
|
|
124
147
|
await generateText({ model: "@cf/zai-org/glm-4.7-flash", prompt: "Hi" });
|
|
125
148
|
```
|
|
126
149
|
|
|
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
150
|
Every modality is on the registry too (`registry.imageModel`,
|
|
132
151
|
`registry.textEmbeddingModel`, and so on), because each `ProviderV4` method is
|
|
133
152
|
implemented. The default provider is process-wide: set it once, where you build
|
|
134
153
|
the Worker's `env`-dependent state.
|
|
135
154
|
|
|
136
|
-
##
|
|
155
|
+
## Catalog models
|
|
156
|
+
|
|
157
|
+
A catalog id is `<author>/<model>` in the
|
|
158
|
+
[AI Gateway catalog](https://developers.cloudflare.com/ai/models/)'s own
|
|
159
|
+
spelling — `anthropic/claude-opus-4.8`, `openai/gpt-5.5`, `cloudflare/auto`. The spelling is the gateway's, not the vendor's:
|
|
160
|
+
`anthropic/claude-opus-4.8`, not `claude-opus-4-8`.
|
|
161
|
+
|
|
162
|
+
The request goes through `env.AI.run(id, body, { gateway, openaiEndpoint })`
|
|
163
|
+
to AI Gateway's `/run`. The body is chat completions and the answer comes back
|
|
164
|
+
as chat completions, whichever provider served it: `/run` converts to and from
|
|
165
|
+
the provider's format. The gateway owns the rest:
|
|
166
|
+
|
|
167
|
+
- **Credentials.** Unified Billing bills the model to your Cloudflare account,
|
|
168
|
+
so there is no vendor key. A provider key stored on the gateway (BYOK) is
|
|
169
|
+
used instead when one is configured.
|
|
170
|
+
- **Providers.** A catalog model can have several providers behind it; `/run`
|
|
171
|
+
picks one and retries across them.
|
|
172
|
+
- **Errors.** A failure — an unknown model, an unpaid account, an upstream
|
|
173
|
+
error — comes back as a `CloudflareAIError`, the same type as a Workers AI
|
|
174
|
+
failure.
|
|
175
|
+
|
|
176
|
+
The request is built as strict OpenAI. `reasoningEffort` travels as
|
|
177
|
+
`reasoning_effort`, the output cap as `max_completion_tokens`, and
|
|
178
|
+
`chatTemplateKwargs`, which only Workers AI takes, is dropped with a warning.
|
|
179
|
+
|
|
180
|
+
A streamed answer from a model whose native format is Anthropic Messages (the
|
|
181
|
+
Claude models) comes back from `/run` in Anthropic events rather than chat
|
|
182
|
+
completions. This provider reads those events as chat completions (text,
|
|
183
|
+
reasoning, tool calls, finish reason and usage), so the stream looks the same
|
|
184
|
+
whatever model served it. `byokAlias` picks a stored key by alias; it is sent
|
|
185
|
+
as `cf-aig-byok-alias`, which `/run` reads.
|
|
186
|
+
|
|
187
|
+
## Model objects
|
|
188
|
+
|
|
189
|
+
A vendor's own AI SDK model goes to AI Gateway's **provider-native path**,
|
|
190
|
+
unchanged. `ai(model)` clones the model per call and swaps the `fetch` its
|
|
191
|
+
provider would have used for one that sends the same request (method,
|
|
192
|
+
headers, body) through the binding:
|
|
137
193
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
194
|
+
```ts
|
|
195
|
+
env.AI.fetch(
|
|
196
|
+
`https://workers-binding.ai/ai-gateway/gateways/${gateway}/anthropic/v1/messages`,
|
|
197
|
+
{ method, headers, body } // the vendor's own, plus cf-aig-* gateway headers
|
|
198
|
+
);
|
|
199
|
+
```
|
|
144
200
|
|
|
145
|
-
|
|
146
|
-
`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
201
|
+
The provider slug and path come from the request URL's host (`api.anthropic.com`
|
|
202
|
+
is `anthropic`, `api.openai.com` is `openai`, and so on for every gateway
|
|
203
|
+
provider). A host the gateway has no provider for is a `TypeError` naming it.
|
|
204
|
+
What comes back is the vendor's own response, parsed by the vendor's own code,
|
|
205
|
+
streams included. Your model object is never mutated.
|
|
150
206
|
|
|
151
207
|
```ts
|
|
208
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
152
209
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
153
210
|
|
|
211
|
+
const anthropic = createAnthropic({ apiKey: "cloudflare" }); // placeholder
|
|
154
212
|
const openai = createOpenAI({ apiKey: "cloudflare" });
|
|
155
213
|
|
|
156
|
-
ai(
|
|
157
|
-
ai(
|
|
214
|
+
ai(anthropic("claude-opus-4-8")); // Anthropic Messages, the vendor's own id
|
|
215
|
+
ai(openai("gpt-5-mini")); // OpenAI Responses
|
|
216
|
+
ai.routed(openai.transcription("whisper-1")); // multipart bodies travel too
|
|
158
217
|
```
|
|
159
218
|
|
|
160
|
-
|
|
219
|
+
The vendor's id is the vendor's spelling (`claude-opus-4-8`), because its
|
|
220
|
+
package picks capabilities from it. Gateway options travel as `cf-aig-*`
|
|
221
|
+
headers. A model whose provider hides its settings (one already wrapped in
|
|
222
|
+
middleware, or hand-written) is a `TypeError`; wrap first, then apply
|
|
223
|
+
middleware:
|
|
161
224
|
|
|
162
225
|
```ts
|
|
163
|
-
ai("
|
|
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")).
|
|
226
|
+
wrapLanguageModel({ model: ai(anthropic("claude-opus-4-8")), middleware });
|
|
167
227
|
```
|
|
168
228
|
|
|
169
|
-
|
|
170
|
-
|
|
229
|
+
### Unified billing and BYOK
|
|
230
|
+
|
|
231
|
+
The vendor's credential header (`authorization`, `x-api-key`,
|
|
232
|
+
`x-goog-api-key`, `api-key`) is stripped by default, so the gateway supplies
|
|
233
|
+
the key: Unified Billing, or a key stored on the gateway. Unified Billing needs
|
|
234
|
+
authentication turned on for the gateway.
|
|
235
|
+
|
|
236
|
+
To use a key you hold yourself, give it to the vendor's provider and set
|
|
237
|
+
`vendorKey: "request"`, which forwards the credential instead of stripping it:
|
|
171
238
|
|
|
172
239
|
```ts
|
|
173
|
-
|
|
240
|
+
const anthropic = createAnthropic({ apiKey: env.ANTHROPIC_API_KEY });
|
|
241
|
+
const model = ai(anthropic("claude-opus-4-8"), { vendorKey: "request" });
|
|
174
242
|
```
|
|
175
243
|
|
|
176
|
-
|
|
244
|
+
`vendorKey` belongs to the model it is set on. A fallback leg does not inherit
|
|
245
|
+
it, so a chain can mix a model on your key with one on Unified Billing.
|
|
246
|
+
|
|
247
|
+
`byokAlias` picks a stored key by alias when the gateway holds several for one
|
|
248
|
+
provider, and `zdr` turns Zero Data Retention on (`true`) or off (`false`) for
|
|
249
|
+
a Unified Billing request, whatever the gateway's own setting. On a model
|
|
250
|
+
object both travel as headers (`cf-aig-byok-alias`, `cf-aig-zdr`). `zdr`
|
|
251
|
+
applies to model objects only: `/run` does not read it.
|
|
252
|
+
|
|
253
|
+
A vendor's own error, such as a bad model id or a rejected parameter, stays the
|
|
254
|
+
vendor's, raised by its provider in its own error type. Only the gateway's own
|
|
255
|
+
failures, such as no such gateway or an unpaid account, become a
|
|
256
|
+
`CloudflareAIError`.
|
|
177
257
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
258
|
+
## Dynamic routes
|
|
259
|
+
|
|
260
|
+
An [AI Gateway dynamic route](https://developers.cloudflare.com/ai-gateway/features/dynamic-routing/)
|
|
261
|
+
is called by name, as `dynamic/<route>`. The route configured on the gateway
|
|
262
|
+
picks the model (conditions, percentages, rate and budget limits, its own
|
|
263
|
+
fallbacks), and the call goes through `env.AI.run` like a Workers AI model:
|
|
181
264
|
|
|
182
265
|
```ts
|
|
183
|
-
env.AI
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
);
|
|
266
|
+
const ai = createAI({ binding: env.AI, id: "prod" });
|
|
267
|
+
|
|
268
|
+
await generateText({ model: ai("dynamic/support"), prompt: "Hi" });
|
|
269
|
+
ai("@cf/zai-org/glm-4.7-flash", { fallback: ["dynamic/support"] });
|
|
187
270
|
```
|
|
188
271
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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.
|
|
272
|
+
The route belongs to a gateway, so set the gateway `id` it was created on. It
|
|
273
|
+
answers in the OpenAI chat-completions shape whatever model served it, so the
|
|
274
|
+
request is built as strict OpenAI: `chatTemplateKwargs` is dropped with a
|
|
275
|
+
warning, because the route may not land on Workers AI. A dynamic route is a
|
|
276
|
+
language model only, and the modality methods refuse it, including as a
|
|
277
|
+
fallback leg.
|
|
220
278
|
|
|
221
279
|
## Other modalities
|
|
222
280
|
|
|
@@ -249,18 +307,6 @@ const { rerankedDocuments } = await rerank({
|
|
|
249
307
|
});
|
|
250
308
|
```
|
|
251
309
|
|
|
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
310
|
Bytes stay bytes and base64 stays base64: some models answer with a raw body
|
|
265
311
|
(`image/png`, `audio/mpeg`, `audio/wav`) and some with JSON (`{ image }`,
|
|
266
312
|
`{ audio }`), the content type decides, and nothing is converted on the way
|
|
@@ -286,23 +332,23 @@ a `compatibility` warning.
|
|
|
286
332
|
The run path generates one image per call, so `maxImagesPerCall` is 1 and
|
|
287
333
|
`generateImage` fans a larger `n` out into parallel calls.
|
|
288
334
|
|
|
289
|
-
`@cf/deepgram/nova-3`
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
335
|
+
`@cf/deepgram/nova-3` takes its audio as a raw body (`audio: { body,
|
|
336
|
+
contentType }`), and the provider sends it as one. Of the transcription options
|
|
337
|
+
it takes `language`; the whisper-only ones come back in `warnings`.
|
|
338
|
+
`@cf/deepgram/flux` takes websocket connections only, so it throws a
|
|
339
|
+
`CloudflareAIError` before any request is made.
|
|
294
340
|
|
|
295
341
|
## Options
|
|
296
342
|
|
|
297
|
-
Options can be set in three places
|
|
298
|
-
|
|
343
|
+
Options can be set in three places. Precedence is per-call > per-model >
|
|
344
|
+
provider.
|
|
299
345
|
|
|
300
346
|
```ts
|
|
301
347
|
// Provider-wide
|
|
302
348
|
const ai = createAI({ binding: env.AI, id: "prod", metadata: { app: "demo" } });
|
|
303
349
|
|
|
304
350
|
// Per model
|
|
305
|
-
const model = ai(anthropic
|
|
351
|
+
const model = ai("anthropic/claude-opus-4.8", { cacheTtl: 3600 });
|
|
306
352
|
|
|
307
353
|
// Per call
|
|
308
354
|
await generateText({
|
|
@@ -326,7 +372,10 @@ await generateText({
|
|
|
326
372
|
| `eventId` | `string` | Correlates several requests into one gateway event. |
|
|
327
373
|
| `requestTimeoutMs` | `number` | Upstream request timeout. |
|
|
328
374
|
| `retries` | `{ maxAttempts, retryDelayMs, backoff }` | Server-side retry policy for the upstream provider call. |
|
|
329
|
-
| `
|
|
375
|
+
| `byokAlias` | `string` | A stored BYOK key, by alias. Catalog models and model objects. |
|
|
376
|
+
| `zdr` | `boolean` | Zero Data Retention on or off (Unified Billing). Model objects only. |
|
|
377
|
+
| `vendorKey` | `"gateway" \| "request"` | Model objects only: forward the vendor key on the request. Per model. |
|
|
378
|
+
| `fallback` | `(CloudflareLanguageModelId \| LanguageModelV4)[]` | Models to try, in order, if the primary fails before producing output. |
|
|
330
379
|
| `headers` | `Record<string, string>` | Extra request headers, merged last. |
|
|
331
380
|
| `sessionAffinity` | `string` | Pin same-key requests to one replica for prefix-cache hits. |
|
|
332
381
|
| `reasoningEffort` | `"low" \| "medium" \| "high" \| null` | Workers AI only: explicit effort; `null` turns reasoning off. |
|
|
@@ -337,28 +386,25 @@ The flat gateway keys are sugar: `{ cacheTtl: 60 }` and
|
|
|
337
386
|
layers above and below them (`metadata`, `retries` and `headers` merge field by
|
|
338
387
|
field).
|
|
339
388
|
|
|
340
|
-
`fallback` takes model objects only on
|
|
389
|
+
`fallback` takes model objects only on language models. On the modality
|
|
341
390
|
methods (`ai.embedding`, `ai.image`, `ai.speech`, `ai.transcription`,
|
|
342
391
|
`ai.reranking`) a leg is a Workers AI id, because those run on the Workers AI
|
|
343
392
|
run path, which resolves a leg by id and nothing else.
|
|
344
393
|
|
|
345
|
-
`reasoningEffort`
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
394
|
+
`reasoningEffort` maps onto `reasoning_effort` for every model, and the AI
|
|
395
|
+
SDK's unified `reasoning` call option maps onto it when it is not set.
|
|
396
|
+
`chatTemplateKwargs` is a Workers AI setting: a catalog model or a dynamic
|
|
397
|
+
route drops it with a warning.
|
|
349
398
|
|
|
350
399
|
### `providerOptions.cloudflare`
|
|
351
400
|
|
|
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:
|
|
401
|
+
Anything in the table above can be passed per call under the `cloudflare` key:
|
|
355
402
|
|
|
356
403
|
```ts
|
|
357
404
|
await generateText({
|
|
358
|
-
model: ai(anthropic
|
|
405
|
+
model: ai("anthropic/claude-opus-4.8"),
|
|
359
406
|
prompt: "hi",
|
|
360
407
|
providerOptions: {
|
|
361
|
-
anthropic: { sendReasoning: true },
|
|
362
408
|
cloudflare: { metadata: { userId: "u1" }, skipCache: true, cacheTtl: 300 }
|
|
363
409
|
}
|
|
364
410
|
});
|
|
@@ -372,28 +418,55 @@ then fails before its first output part — an `error` part, an error finish, or
|
|
|
372
418
|
a broken body — in which case the failed leg's preamble is dropped and the
|
|
373
419
|
answering leg's stream is delivered in its place. Once a leg has produced
|
|
374
420
|
output it is the answer, failures and all: by then the caller already has part
|
|
375
|
-
of it.
|
|
421
|
+
of it. A leg is any model id, or any AI SDK language model:
|
|
376
422
|
|
|
377
423
|
```ts
|
|
378
424
|
const model = ai(anthropic("claude-opus-4-8"), {
|
|
379
|
-
fallback: [
|
|
425
|
+
fallback: ["openai/gpt-5.5", "@cf/zai-org/glm-4.7-flash"]
|
|
380
426
|
});
|
|
381
427
|
```
|
|
382
428
|
|
|
429
|
+
A model object in a chain is routed like `ai(model)`, through the
|
|
430
|
+
provider-native path, so a chain can mix catalog ids, Workers AI and a vendor's
|
|
431
|
+
own models.
|
|
432
|
+
|
|
383
433
|
A leg travels with the chain's gateway options: the chain's `id`, `cacheTtl`
|
|
384
434
|
and the rest win over a leg's own, `metadata` merges field by field, and
|
|
385
435
|
everything else a leg was built with — its `headers`, its Workers AI knobs —
|
|
386
|
-
stays the leg's
|
|
387
|
-
|
|
388
|
-
written on the model or per call under
|
|
436
|
+
stays the leg's, and so does its `vendorKey`. A leg written as a string follows the same rule as `ai("…")`:
|
|
437
|
+
anything that is not one of the three id forms is the same `TypeError`,
|
|
438
|
+
whether it was written on the model or per call under
|
|
439
|
+
`providerOptions.cloudflare`.
|
|
389
440
|
|
|
390
441
|
`providerMetadata.cloudflare.model` reports the model that actually answered.
|
|
391
442
|
When every leg fails, the thrown `CloudflareAIError` carries an `attempts`
|
|
392
443
|
array of `{ model, error }` and the last error as its `cause`.
|
|
393
444
|
|
|
394
|
-
This
|
|
395
|
-
|
|
396
|
-
|
|
445
|
+
This loop runs across models. Within one catalog model, `/run` already retries
|
|
446
|
+
across that model's providers, and a [dynamic route](#dynamic-routes) runs its
|
|
447
|
+
own fallback on the gateway.
|
|
448
|
+
|
|
449
|
+
## Stream resume
|
|
450
|
+
|
|
451
|
+
A streamed answer from AI Gateway's `/run` through the binding (a catalog
|
|
452
|
+
model or a dynamic route) carries `cf-aig-run-id`, and the gateway keeps every event of the run.
|
|
453
|
+
If the connection drops mid-stream, the provider reconnects through the
|
|
454
|
+
binding and replays the run from the last complete event, so the stream you
|
|
455
|
+
read continues with nothing lost or repeated. There is nothing to configure:
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
const result = streamText({ model: ai("anthropic/claude-opus-4.8"), prompt });
|
|
459
|
+
// A dropped connection is resumed; result.textStream just keeps going.
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
The run keeps going on the gateway after the connection is gone, so a resume
|
|
463
|
+
continues the same answer rather than starting a new one. It gives up, with a
|
|
464
|
+
`CloudflareAIError`, after five drops in one stream or once the gateway no
|
|
465
|
+
longer holds the run (a few minutes after it ends). Aborting the call never
|
|
466
|
+
resumes. The run id is in `providerMetadata.cloudflare.runId`.
|
|
467
|
+
|
|
468
|
+
Workers AI `@cf/` streams and model objects on the provider-native path have
|
|
469
|
+
no run id, so they are not resumed.
|
|
397
470
|
|
|
398
471
|
## How Workers AI stays OpenAI-shaped
|
|
399
472
|
|
|
@@ -418,20 +491,17 @@ is conformant needs no entry at all.
|
|
|
418
491
|
## Observability: `providerMetadata.cloudflare`
|
|
419
492
|
|
|
420
493
|
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
|
|
422
|
-
vendor model it is merged alongside the vendor's own metadata block rather than
|
|
423
|
-
replacing it:
|
|
494
|
+
you never have to walk response objects looking for a log id:
|
|
424
495
|
|
|
425
496
|
```ts
|
|
426
497
|
const { providerMetadata } = await generateText({
|
|
427
|
-
model: ai(anthropic
|
|
498
|
+
model: ai("anthropic/claude-opus-4.8"),
|
|
428
499
|
prompt: "hi"
|
|
429
500
|
});
|
|
430
501
|
|
|
431
502
|
providerMetadata?.cloudflare;
|
|
432
503
|
// {
|
|
433
|
-
// model: "claude-opus-4
|
|
434
|
-
// provider: "anthropic",
|
|
504
|
+
// model: "anthropic/claude-opus-4.8",
|
|
435
505
|
// gateway: "prod",
|
|
436
506
|
// logId: "01M1KZWN069WWNPC18V05NKHSS",
|
|
437
507
|
// eventId: "7bfd3660-9d0f-4bf7-bf9b-fa90b860456f",
|
|
@@ -440,14 +510,13 @@ providerMetadata?.cloudflare;
|
|
|
440
510
|
// cacheStatus: "MISS",
|
|
441
511
|
// step: 0
|
|
442
512
|
// }
|
|
443
|
-
providerMetadata?.anthropic; // still Anthropic's own
|
|
444
513
|
```
|
|
445
514
|
|
|
446
515
|
| Field | Source header | Notes |
|
|
447
516
|
| ------------- | --------------------- | ---------------------------------------------- |
|
|
448
517
|
| `model` | — | The leg that answered, after any fallback. |
|
|
449
|
-
| `gateway` | — | The gateway id the request
|
|
450
|
-
| `provider` | — | The gateway provider slug, for a
|
|
518
|
+
| `gateway` | — | The gateway id, when one handled the request. |
|
|
519
|
+
| `provider` | — | The gateway provider slug, for a model object. |
|
|
451
520
|
| `logId` | `cf-aig-log-id` | Falls back to the binding's last log id. |
|
|
452
521
|
| `eventId` | `cf-aig-event-id` | Correlates the requests of one gateway event. |
|
|
453
522
|
| `requestId` | `cf-aig-request-id` | The gateway's id for this request. |
|
|
@@ -456,7 +525,7 @@ providerMetadata?.anthropic; // still Anthropic's own
|
|
|
456
525
|
| `cacheStatus` | `cf-aig-cache-status` | `HIT` / `MISS`. |
|
|
457
526
|
| `step` | `cf-aig-step` | Which step of a gateway route answered. |
|
|
458
527
|
|
|
459
|
-
Every field except `model`
|
|
528
|
+
Every field except `model` is optional, and which ones arrive
|
|
460
529
|
depends on the path and the call. Read the fields you find rather than assuming
|
|
461
530
|
any particular one is there. `response.headers` and `response.modelId` are
|
|
462
531
|
populated too.
|
|
@@ -485,10 +554,8 @@ try {
|
|
|
485
554
|
`code` is one of `"auth"`, `"rate-limit"`, `"not-found"`, `"bad-request"`,
|
|
486
555
|
`"provider-error"`, `"gateway-error"`, `"unknown"`.
|
|
487
556
|
|
|
488
|
-
On a
|
|
489
|
-
|
|
490
|
-
vendor's provider in its own error type, because it is the code that knows how
|
|
491
|
-
to read it.
|
|
557
|
+
On a model object this covers the gateway's own failures; a vendor's own error
|
|
558
|
+
is raised by that vendor's provider in its own error type.
|
|
492
559
|
|
|
493
560
|
## Experimental
|
|
494
561
|
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"bin": {
|
|
13
13
|
"agents": "./dist/cli.js"
|
|
14
14
|
},
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.29.0",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
18
18
|
"directory": "packages/agents",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"@ai-sdk/provider": "^4.0.0",
|
|
43
43
|
"@ai-sdk/react": "^4.0.0",
|
|
44
44
|
"@ai-sdk/valibot": "3.0.6",
|
|
45
|
-
"@cloudflare/codemode": "^0.5.
|
|
46
|
-
"@earendil-works/pi-ai": "
|
|
47
|
-
"@earendil-works/pi-durable": "
|
|
48
|
-
"@earendil-works/pi-tui": "1.
|
|
49
|
-
"@modelcontextprotocol/client": "2.
|
|
50
|
-
"@modelcontextprotocol/conformance-v2": "npm:@modelcontextprotocol/conformance@0.2.0-alpha.
|
|
51
|
-
"@modelcontextprotocol/sdk": "1.
|
|
52
|
-
"@modelcontextprotocol/server": "2.
|
|
45
|
+
"@cloudflare/codemode": "^0.5.5",
|
|
46
|
+
"@earendil-works/pi-ai": "~1.3.0",
|
|
47
|
+
"@earendil-works/pi-durable": "~1.3.0",
|
|
48
|
+
"@earendil-works/pi-tui": "1.3.0",
|
|
49
|
+
"@modelcontextprotocol/client": "2.3.1",
|
|
50
|
+
"@modelcontextprotocol/conformance-v2": "npm:@modelcontextprotocol/conformance@0.2.0-alpha.12",
|
|
51
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
52
|
+
"@modelcontextprotocol/server": "2.3.1",
|
|
53
53
|
"@opencode/plugin": "2.0.24",
|
|
54
54
|
"@opencode/sdk": "2.0.24",
|
|
55
55
|
"@tanstack/ai": "0.38.0",
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
"@ai-sdk/provider": "^4.0.0",
|
|
72
72
|
"@ai-sdk/react": "^3.0.0 || ^4.0.0",
|
|
73
73
|
"@cloudflare/codemode": ">=0.5.0",
|
|
74
|
-
"@earendil-works/pi-ai": "
|
|
75
|
-
"@earendil-works/pi-durable": "
|
|
76
|
-
"@modelcontextprotocol/client": "2.
|
|
77
|
-
"@modelcontextprotocol/sdk": "1.
|
|
78
|
-
"@modelcontextprotocol/server": "2.
|
|
74
|
+
"@earendil-works/pi-ai": "~1.3.0",
|
|
75
|
+
"@earendil-works/pi-durable": "~1.3.0",
|
|
76
|
+
"@modelcontextprotocol/client": "2.3.1",
|
|
77
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
78
|
+
"@modelcontextprotocol/server": "2.3.1",
|
|
79
79
|
"@opencode/plugin": "^2.0.24",
|
|
80
80
|
"@opencode/sdk": "^2.0.24",
|
|
81
81
|
"@tanstack/ai": ">=0.10.2 <1.0.0",
|