agents 0.28.1 → 0.30.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-BMvWTFEp.d.ts → agent-routing-CDbpVjYq.d.ts} +7 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-ohoewW-B.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-C2iTAwUd.js → ai-sdk-DRPh2rRa.js} +185 -125
- 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 +7 -3
- package/dist/browser/tools/pi.js +9 -5
- 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 +53 -52
- package/dist/{client-Bp-JwL3p.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/harness/container/index.js +1 -1
- package/dist/harness/container/index.js.map +1 -1
- package/dist/harness/opencode/index.js +1 -1
- package/dist/harness/pi/index.d.ts +70 -40
- package/dist/harness/pi/index.js +266 -330
- package/dist/harness/pi/index.js.map +1 -1
- package/dist/{images-C4hiIexM.js → images-xaDv2Fqe.js} +636 -168
- 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-xBofjBKC.js → input-schema-B0Etuo-o.js} +2 -2
- package/dist/{input-schema-xBofjBKC.js.map → input-schema-B0Etuo-o.js.map} +1 -1
- package/dist/lifecycle/index.js +1 -1
- package/dist/{lifecycle-DGorpAmA.js → lifecycle-v1rKHV2C.js} +67 -19
- package/dist/lifecycle-v1rKHV2C.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 +1 -1
- package/dist/models/ai-sdk/index.d.ts +29 -352
- 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 +13 -14
- package/dist/models/pi-ai/index.js +87 -34
- 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-BjAiPd6Z.js → settings-Dk2il2Hf.js} +4 -1
- 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-DfLzdwlK.js} +3 -3
- package/dist/src-DfLzdwlK.js.map +1 -0
- package/dist/sub-routing.d.ts +1 -1
- package/dist/{tool-VF0qiO7q.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.d.ts +10 -6
- package/dist/web/fetch/tools/pi.js +12 -8
- 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.d.ts +10 -6
- package/dist/web/search/tools/pi.js +13 -9
- 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-CyoeosdO.js} +2 -2
- package/dist/{websockets-C3zy8YR0.js.map → websockets-CyoeosdO.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 +101 -38
- package/docs/lifecycle.md +7 -1
- package/docs/models-pi-ai.md +66 -20
- package/docs/models.md +197 -172
- package/package.json +12 -7
- package/dist/ai-sdk-C2iTAwUd.js.map +0 -1
- package/dist/browser-CEdNlsaD.js.map +0 -1
- package/dist/images-C4hiIexM.js.map +0 -1
- package/dist/lifecycle-DGorpAmA.js.map +0 -1
- package/dist/session-store-Do4xkDcf.js.map +0 -1
- package/dist/settings-BjAiPd6Z.js.map +0 -1
- package/dist/settings-DnD__xuE.d.ts +0 -349
- package/dist/src-CQxJFv4l.js.map +0 -1
- package/dist/tool-VF0qiO7q.js.map +0 -1
- package/dist/transport-DCyarnwm.d.ts +0 -200
package/docs/models.md
CHANGED
|
@@ -1,39 +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
|
-
autocompleted from `@cloudflare/workers-types
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
2. **`ai(model)` is any AI SDK v4 model**,
|
|
29
|
-
provider
|
|
30
|
-
errors; only the transport is
|
|
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.
|
|
31
33
|
3. **The gateway is an option, not a product.** Caching, logging, metadata,
|
|
32
34
|
timeouts and retries are options on the call; the `default` gateway is
|
|
33
35
|
created for you on first use.
|
|
34
|
-
4. **Unified billing means no vendor key.**
|
|
35
|
-
|
|
36
|
-
(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).
|
|
37
38
|
5. One factory: `createAI` — text, embeddings, images, speech, transcription
|
|
38
39
|
and reranking all come off it, and it is a full `ProviderV4`.
|
|
39
40
|
|
|
@@ -41,16 +42,15 @@ we hand its request to AI Gateway and hand the answer straight back to it.
|
|
|
41
42
|
|
|
42
43
|
Requirements: `ai@^7` and `@ai-sdk/provider@^4` must both be resolvable — they
|
|
43
44
|
are optional peers of `agents`, so install them alongside it. Add a vendor
|
|
44
|
-
provider only if you want that vendor:
|
|
45
|
+
provider only if you want to pass that vendor's model objects:
|
|
45
46
|
|
|
46
47
|
```sh
|
|
47
48
|
pnpm add ai @ai-sdk/provider
|
|
48
|
-
pnpm add @ai-sdk/anthropic # only
|
|
49
|
+
pnpm add @ai-sdk/anthropic # only for ai(anthropic("…"))
|
|
49
50
|
```
|
|
50
51
|
|
|
51
52
|
```ts
|
|
52
53
|
import { createAI } from "agents/models/ai-sdk";
|
|
53
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
54
54
|
import { generateText } from "ai";
|
|
55
55
|
|
|
56
56
|
export default {
|
|
@@ -58,20 +58,17 @@ export default {
|
|
|
58
58
|
// Inside a Worker: keyless, using the AI binding.
|
|
59
59
|
const ai = createAI({ binding: env.AI, id: "default", cacheTtl: 60 });
|
|
60
60
|
|
|
61
|
-
// The gateway holds the real key, so this one is a placeholder.
|
|
62
|
-
const anthropic = createAnthropic({ apiKey: "cloudflare" });
|
|
63
|
-
|
|
64
61
|
const workersAI = await generateText({
|
|
65
62
|
model: ai("@cf/zai-org/glm-4.7-flash"),
|
|
66
63
|
prompt: "Say hello in three words"
|
|
67
64
|
});
|
|
68
65
|
|
|
69
|
-
const
|
|
70
|
-
model: ai(anthropic
|
|
66
|
+
const claude = await generateText({
|
|
67
|
+
model: ai("anthropic/claude-opus-4.8"),
|
|
71
68
|
prompt: "Say hello in three words"
|
|
72
69
|
});
|
|
73
70
|
|
|
74
|
-
return Response.json({ workersAI: workersAI.text,
|
|
71
|
+
return Response.json({ workersAI: workersAI.text, claude: claude.text });
|
|
75
72
|
}
|
|
76
73
|
};
|
|
77
74
|
```
|
|
@@ -84,12 +81,12 @@ Your `wrangler.jsonc` needs the binding:
|
|
|
84
81
|
}
|
|
85
82
|
```
|
|
86
83
|
|
|
87
|
-
Streaming, tools, and structured output are ordinary AI SDK calls, on
|
|
84
|
+
Streaming, tools, and structured output are ordinary AI SDK calls, on every
|
|
88
85
|
form:
|
|
89
86
|
|
|
90
87
|
```ts
|
|
91
88
|
const result = streamText({
|
|
92
|
-
model: ai(
|
|
89
|
+
model: ai("openai/gpt-5.5"),
|
|
93
90
|
prompt: "Write a haiku about durable objects"
|
|
94
91
|
});
|
|
95
92
|
return result.toTextStreamResponse();
|
|
@@ -106,6 +103,30 @@ const { embeddings } = await embedMany({
|
|
|
106
103
|
});
|
|
107
104
|
```
|
|
108
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
|
+
|
|
109
130
|
## Registry and default provider
|
|
110
131
|
|
|
111
132
|
`createAI` returns a full `ProviderV4`, so it drops into both places the AI SDK
|
|
@@ -117,7 +138,7 @@ import { createProviderRegistry, generateText } from "ai";
|
|
|
117
138
|
// A registry: `cloudflare:` names this provider.
|
|
118
139
|
const registry = createProviderRegistry({ cloudflare: ai });
|
|
119
140
|
await generateText({
|
|
120
|
-
model: registry.languageModel("cloudflare
|
|
141
|
+
model: registry.languageModel("cloudflare:anthropic/claude-opus-4.8"),
|
|
121
142
|
prompt: "Hi"
|
|
122
143
|
});
|
|
123
144
|
|
|
@@ -126,116 +147,113 @@ globalThis.AI_SDK_DEFAULT_PROVIDER = createAI({ binding: env.AI });
|
|
|
126
147
|
await generateText({ model: "@cf/zai-org/glm-4.7-flash", prompt: "Hi" });
|
|
127
148
|
```
|
|
128
149
|
|
|
129
|
-
The string rule does not change in either place: a specifier is a `@cf/` id, so
|
|
130
|
-
a vendor model is still a model object (`ai(anthropic("claude-opus-4-8"))`) and
|
|
131
|
-
cannot be reached by a bare string.
|
|
132
|
-
|
|
133
150
|
Every modality is on the registry too (`registry.imageModel`,
|
|
134
151
|
`registry.textEmbeddingModel`, and so on), because each `ProviderV4` method is
|
|
135
152
|
implemented. The default provider is process-wide: set it once, where you build
|
|
136
153
|
the Worker's `env`-dependent state.
|
|
137
154
|
|
|
138
|
-
##
|
|
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:
|
|
139
193
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
+
```
|
|
146
200
|
|
|
147
|
-
|
|
148
|
-
`
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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.
|
|
152
206
|
|
|
153
207
|
```ts
|
|
208
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
154
209
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
155
210
|
|
|
211
|
+
const anthropic = createAnthropic({ apiKey: "cloudflare" }); // placeholder
|
|
156
212
|
const openai = createOpenAI({ apiKey: "cloudflare" });
|
|
157
213
|
|
|
158
|
-
ai(
|
|
159
|
-
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
|
|
160
217
|
```
|
|
161
218
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// Workers AI ids start with "@cf/" … install @ai-sdk/anthropic and pass the
|
|
168
|
-
// model object: ai(anthropic("claude-opus-4-8")).
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
A model whose provider hides its settings — one already wrapped in middleware,
|
|
172
|
-
or hand-written — is an error too. Wrap first, then apply middleware:
|
|
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:
|
|
173
224
|
|
|
174
225
|
```ts
|
|
175
226
|
wrapLanguageModel({ model: ai(anthropic("claude-opus-4-8")), middleware });
|
|
176
227
|
```
|
|
177
228
|
|
|
178
|
-
|
|
229
|
+
### Unified billing and BYOK
|
|
179
230
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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.
|
|
183
235
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
{ provider: "anthropic", endpoint: "v1/messages", headers, query },
|
|
187
|
-
{ gateway: { cacheTtl, skipCache, metadata, … }, extraHeaders, signal }
|
|
188
|
-
);
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
- `provider` comes from the request URL's host (`api.anthropic.com` →
|
|
192
|
-
`anthropic`, `api.openai.com` → `openai`, and so on for every gateway
|
|
193
|
-
provider). A host the gateway has no provider for is a `TypeError` naming it.
|
|
194
|
-
- `endpoint` is the path after the host (`v1/messages`, `v1/responses`).
|
|
195
|
-
- `query` is the vendor's JSON body, verbatim — including its own model id, in
|
|
196
|
-
the vendor's own spelling. `claude-opus-4.8` gets a 404 from Anthropic
|
|
197
|
-
("Did you mean claude-opus-4-8?"), because we do not rewrite ids.
|
|
198
|
-
- The vendor's credential header (`authorization`, `x-api-key`,
|
|
199
|
-
`x-goog-api-key`, `api-key`) is stripped; its protocol headers
|
|
200
|
-
(`anthropic-version`, `anthropic-beta`, …) travel.
|
|
201
|
-
- `stream: true` comes back as the vendor's own SSE. The gateway adds a small
|
|
202
|
-
`"p"` padding field to each data event, which every vendor parser ignores.
|
|
203
|
-
|
|
204
|
-
The universal request carries **JSON only**. A multipart body (a file upload,
|
|
205
|
-
an audio transcription posted as `FormData`) cannot be expressed in it and
|
|
206
|
-
raises a `TypeError` rather than being silently mangled.
|
|
207
|
-
|
|
208
|
-
## Unified billing and BYOK
|
|
209
|
-
|
|
210
|
-
Whether a vendor answers without a key of yours is the gateway's business, not
|
|
211
|
-
this package's:
|
|
212
|
-
|
|
213
|
-
- **Unified billing** covers Anthropic and OpenAI today: a placeholder
|
|
214
|
-
`apiKey` is enough, and Cloudflare bills you.
|
|
215
|
-
- **Everything else needs BYOK** — a provider key stored on the gateway. Google
|
|
216
|
-
AI Studio without one answers `403 "Method doesn't allow unregistered callers"`,
|
|
217
|
-
and that error is surfaced to you exactly as Google wrote it, because Google's
|
|
218
|
-
provider is the one that knows how to read it.
|
|
219
|
-
|
|
220
|
-
`zdr` turns Zero Data Retention on (`true`) or off (`false`) for one request,
|
|
221
|
-
whatever the gateway's own setting. It applies to a routed model on unified
|
|
222
|
-
billing and is sent as `cf-aig-zdr`.
|
|
223
|
-
|
|
224
|
-
To use a key you hold yourself rather than one stored on the gateway, give it
|
|
225
|
-
to the vendor's provider and set `vendorKey: "request"`, which forwards the
|
|
226
|
-
credential header instead of stripping it:
|
|
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:
|
|
227
238
|
|
|
228
239
|
```ts
|
|
229
240
|
const anthropic = createAnthropic({ apiKey: env.ANTHROPIC_API_KEY });
|
|
230
241
|
const model = ai(anthropic("claude-opus-4-8"), { vendorKey: "request" });
|
|
231
242
|
```
|
|
232
243
|
|
|
233
|
-
`vendorKey` belongs to the model it is set on
|
|
234
|
-
|
|
235
|
-
a model on your key with one on unified billing.
|
|
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.
|
|
236
246
|
|
|
237
|
-
|
|
238
|
-
|
|
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`.
|
|
239
257
|
|
|
240
258
|
## Dynamic routes
|
|
241
259
|
|
|
@@ -289,18 +307,6 @@ const { rerankedDocuments } = await rerank({
|
|
|
289
307
|
});
|
|
290
308
|
```
|
|
291
309
|
|
|
292
|
-
A vendor's own embedding, image, speech or transcription model can be routed
|
|
293
|
-
too, with `ai.routed(model)`: the same `fetch` swap, the same gateway options,
|
|
294
|
-
and `providerMetadata.cloudflare` on the result.
|
|
295
|
-
|
|
296
|
-
`ai.routed` takes the gateway options, `headers` and `sessionAffinity` — and
|
|
297
|
-
those only. `fallback` is not among them (a leg for one of those modalities
|
|
298
|
-
would have to be another vendor's model, which this provider cannot build), and
|
|
299
|
-
neither are `reasoningEffort` and `chatTemplateKwargs`, which are Workers AI
|
|
300
|
-
settings; passing one is a compile error rather than a silent drop. Sent per
|
|
301
|
-
call under `providerOptions.cloudflare` instead, the two Workers AI knobs come
|
|
302
|
-
back in the result's `warnings`.
|
|
303
|
-
|
|
304
310
|
Bytes stay bytes and base64 stays base64: some models answer with a raw body
|
|
305
311
|
(`image/png`, `audio/mpeg`, `audio/wav`) and some with JSON (`{ image }`,
|
|
306
312
|
`{ audio }`), the content type decides, and nothing is converted on the way
|
|
@@ -326,23 +332,23 @@ a `compatibility` warning.
|
|
|
326
332
|
The run path generates one image per call, so `maxImagesPerCall` is 1 and
|
|
327
333
|
`generateImage` fans a larger `n` out into parallel calls.
|
|
328
334
|
|
|
329
|
-
`@cf/deepgram/nova-3`
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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.
|
|
334
340
|
|
|
335
341
|
## Options
|
|
336
342
|
|
|
337
|
-
Options can be set in three places
|
|
338
|
-
|
|
343
|
+
Options can be set in three places. Precedence is per-call > per-model >
|
|
344
|
+
provider.
|
|
339
345
|
|
|
340
346
|
```ts
|
|
341
347
|
// Provider-wide
|
|
342
348
|
const ai = createAI({ binding: env.AI, id: "prod", metadata: { app: "demo" } });
|
|
343
349
|
|
|
344
350
|
// Per model
|
|
345
|
-
const model = ai(anthropic
|
|
351
|
+
const model = ai("anthropic/claude-opus-4.8", { cacheTtl: 3600 });
|
|
346
352
|
|
|
347
353
|
// Per call
|
|
348
354
|
await generateText({
|
|
@@ -366,9 +372,10 @@ await generateText({
|
|
|
366
372
|
| `eventId` | `string` | Correlates several requests into one gateway event. |
|
|
367
373
|
| `requestTimeoutMs` | `number` | Upstream request timeout. |
|
|
368
374
|
| `retries` | `{ maxAttempts, retryDelayMs, backoff }` | Server-side retry policy for the upstream provider call. |
|
|
369
|
-
| `
|
|
370
|
-
| `
|
|
371
|
-
| `
|
|
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. |
|
|
372
379
|
| `headers` | `Record<string, string>` | Extra request headers, merged last. |
|
|
373
380
|
| `sessionAffinity` | `string` | Pin same-key requests to one replica for prefix-cache hits. |
|
|
374
381
|
| `reasoningEffort` | `"low" \| "medium" \| "high" \| null` | Workers AI only: explicit effort; `null` turns reasoning off. |
|
|
@@ -379,28 +386,25 @@ The flat gateway keys are sugar: `{ cacheTtl: 60 }` and
|
|
|
379
386
|
layers above and below them (`metadata`, `retries` and `headers` merge field by
|
|
380
387
|
field).
|
|
381
388
|
|
|
382
|
-
`fallback` takes model objects only on
|
|
389
|
+
`fallback` takes model objects only on language models. On the modality
|
|
383
390
|
methods (`ai.embedding`, `ai.image`, `ai.speech`, `ai.transcription`,
|
|
384
391
|
`ai.reranking`) a leg is a Workers AI id, because those run on the Workers AI
|
|
385
392
|
run path, which resolves a leg by id and nothing else.
|
|
386
393
|
|
|
387
|
-
`reasoningEffort`
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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.
|
|
391
398
|
|
|
392
399
|
### `providerOptions.cloudflare`
|
|
393
400
|
|
|
394
|
-
Anything in the table above can be passed per call under the `cloudflare` key
|
|
395
|
-
It is read for gateway options and then **removed** before the call reaches a
|
|
396
|
-
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:
|
|
397
402
|
|
|
398
403
|
```ts
|
|
399
404
|
await generateText({
|
|
400
|
-
model: ai(anthropic
|
|
405
|
+
model: ai("anthropic/claude-opus-4.8"),
|
|
401
406
|
prompt: "hi",
|
|
402
407
|
providerOptions: {
|
|
403
|
-
anthropic: { sendReasoning: true },
|
|
404
408
|
cloudflare: { metadata: { userId: "u1" }, skipCache: true, cacheTtl: 300 }
|
|
405
409
|
}
|
|
406
410
|
});
|
|
@@ -414,28 +418,55 @@ then fails before its first output part — an `error` part, an error finish, or
|
|
|
414
418
|
a broken body — in which case the failed leg's preamble is dropped and the
|
|
415
419
|
answering leg's stream is delivered in its place. Once a leg has produced
|
|
416
420
|
output it is the answer, failures and all: by then the caller already has part
|
|
417
|
-
of it.
|
|
421
|
+
of it. A leg is any model id, or any AI SDK language model:
|
|
418
422
|
|
|
419
423
|
```ts
|
|
420
424
|
const model = ai(anthropic("claude-opus-4-8"), {
|
|
421
|
-
fallback: [
|
|
425
|
+
fallback: ["openai/gpt-5.5", "@cf/zai-org/glm-4.7-flash"]
|
|
422
426
|
});
|
|
423
427
|
```
|
|
424
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
|
+
|
|
425
433
|
A leg travels with the chain's gateway options: the chain's `id`, `cacheTtl`
|
|
426
434
|
and the rest win over a leg's own, `metadata` merges field by field, and
|
|
427
435
|
everything else a leg was built with — its `headers`, its Workers AI knobs —
|
|
428
|
-
stays the leg's
|
|
429
|
-
|
|
430
|
-
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`.
|
|
431
440
|
|
|
432
441
|
`providerMetadata.cloudflare.model` reports the model that actually answered.
|
|
433
442
|
When every leg fails, the thrown `CloudflareAIError` carries an `attempts`
|
|
434
443
|
array of `{ model, error }` and the last error as its `cause`.
|
|
435
444
|
|
|
436
|
-
This
|
|
437
|
-
|
|
438
|
-
|
|
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.
|
|
439
470
|
|
|
440
471
|
## How Workers AI stays OpenAI-shaped
|
|
441
472
|
|
|
@@ -460,20 +491,17 @@ is conformant needs no entry at all.
|
|
|
460
491
|
## Observability: `providerMetadata.cloudflare`
|
|
461
492
|
|
|
462
493
|
Every result and every stream `finish` part carries typed gateway metadata, so
|
|
463
|
-
you never have to walk response objects looking for a log id
|
|
464
|
-
vendor model it is merged alongside the vendor's own metadata block rather than
|
|
465
|
-
replacing it:
|
|
494
|
+
you never have to walk response objects looking for a log id:
|
|
466
495
|
|
|
467
496
|
```ts
|
|
468
497
|
const { providerMetadata } = await generateText({
|
|
469
|
-
model: ai(anthropic
|
|
498
|
+
model: ai("anthropic/claude-opus-4.8"),
|
|
470
499
|
prompt: "hi"
|
|
471
500
|
});
|
|
472
501
|
|
|
473
502
|
providerMetadata?.cloudflare;
|
|
474
503
|
// {
|
|
475
|
-
// model: "claude-opus-4
|
|
476
|
-
// provider: "anthropic",
|
|
504
|
+
// model: "anthropic/claude-opus-4.8",
|
|
477
505
|
// gateway: "prod",
|
|
478
506
|
// logId: "01M1KZWN069WWNPC18V05NKHSS",
|
|
479
507
|
// eventId: "7bfd3660-9d0f-4bf7-bf9b-fa90b860456f",
|
|
@@ -482,14 +510,13 @@ providerMetadata?.cloudflare;
|
|
|
482
510
|
// cacheStatus: "MISS",
|
|
483
511
|
// step: 0
|
|
484
512
|
// }
|
|
485
|
-
providerMetadata?.anthropic; // still Anthropic's own
|
|
486
513
|
```
|
|
487
514
|
|
|
488
515
|
| Field | Source header | Notes |
|
|
489
516
|
| ------------- | --------------------- | ---------------------------------------------- |
|
|
490
517
|
| `model` | — | The leg that answered, after any fallback. |
|
|
491
|
-
| `gateway` | — | The gateway id the request
|
|
492
|
-
| `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. |
|
|
493
520
|
| `logId` | `cf-aig-log-id` | Falls back to the binding's last log id. |
|
|
494
521
|
| `eventId` | `cf-aig-event-id` | Correlates the requests of one gateway event. |
|
|
495
522
|
| `requestId` | `cf-aig-request-id` | The gateway's id for this request. |
|
|
@@ -498,7 +525,7 @@ providerMetadata?.anthropic; // still Anthropic's own
|
|
|
498
525
|
| `cacheStatus` | `cf-aig-cache-status` | `HIT` / `MISS`. |
|
|
499
526
|
| `step` | `cf-aig-step` | Which step of a gateway route answered. |
|
|
500
527
|
|
|
501
|
-
Every field except `model`
|
|
528
|
+
Every field except `model` is optional, and which ones arrive
|
|
502
529
|
depends on the path and the call. Read the fields you find rather than assuming
|
|
503
530
|
any particular one is there. `response.headers` and `response.modelId` are
|
|
504
531
|
populated too.
|
|
@@ -527,10 +554,8 @@ try {
|
|
|
527
554
|
`code` is one of `"auth"`, `"rate-limit"`, `"not-found"`, `"bad-request"`,
|
|
528
555
|
`"provider-error"`, `"gateway-error"`, `"unknown"`.
|
|
529
556
|
|
|
530
|
-
On a
|
|
531
|
-
|
|
532
|
-
vendor's provider in its own error type, because it is the code that knows how
|
|
533
|
-
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.
|
|
534
559
|
|
|
535
560
|
## Experimental
|
|
536
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.30.0",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
18
18
|
"directory": "packages/agents",
|
|
@@ -42,10 +42,11 @@
|
|
|
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/
|
|
47
|
-
"@earendil-works/pi-
|
|
48
|
-
"@earendil-works/pi-
|
|
45
|
+
"@cloudflare/codemode": "^0.5.5",
|
|
46
|
+
"@earendil-works/chord": "~1.4.0",
|
|
47
|
+
"@earendil-works/pi-ai": "~1.4.0",
|
|
48
|
+
"@earendil-works/pi-durable": "~1.4.0",
|
|
49
|
+
"@earendil-works/pi-tui": "1.4.0",
|
|
49
50
|
"@modelcontextprotocol/client": "2.3.1",
|
|
50
51
|
"@modelcontextprotocol/conformance-v2": "npm:@modelcontextprotocol/conformance@0.2.0-alpha.12",
|
|
51
52
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
@@ -71,8 +72,9 @@
|
|
|
71
72
|
"@ai-sdk/provider": "^4.0.0",
|
|
72
73
|
"@ai-sdk/react": "^3.0.0 || ^4.0.0",
|
|
73
74
|
"@cloudflare/codemode": ">=0.5.0",
|
|
74
|
-
"@earendil-works/
|
|
75
|
-
"@earendil-works/pi-
|
|
75
|
+
"@earendil-works/chord": "~1.4.0",
|
|
76
|
+
"@earendil-works/pi-ai": "~1.4.0",
|
|
77
|
+
"@earendil-works/pi-durable": "~1.4.0",
|
|
76
78
|
"@modelcontextprotocol/client": "2.3.1",
|
|
77
79
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
78
80
|
"@modelcontextprotocol/server": "2.3.1",
|
|
@@ -98,6 +100,9 @@
|
|
|
98
100
|
"@cloudflare/codemode": {
|
|
99
101
|
"optional": true
|
|
100
102
|
},
|
|
103
|
+
"@earendil-works/chord": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
101
106
|
"@earendil-works/pi-ai": {
|
|
102
107
|
"optional": true
|
|
103
108
|
},
|