agents 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-routing-B2XLNMxq.d.ts → agent-routing-0DR0Tk2v.d.ts} +205 -132
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-routing.js.map +1 -1
- package/dist/agent-tool-types.d.ts +28 -22
- 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-Dj5Mzlw7.d.ts → agent-tools-YrATZJu2.d.ts} +34 -20
- package/dist/agent-tools.d.ts +20 -18
- 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-B4WbF81e.js.map +1 -1
- package/dist/{capability-runner-BUBa6Ake.d.ts → capability-runner-DBAnGr6L.d.ts} +7 -2
- package/dist/chat/index.d.ts +340 -234
- package/dist/chat/index.js +592 -66
- 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-C9DAjZIY.js → client-4OlMIJx-.js} +102 -29
- package/dist/client-4OlMIJx-.js.map +1 -0
- package/dist/{client-BxdkCuVy.d.ts → client-je3koD9Z.d.ts} +45 -10
- package/dist/client.d.ts +7 -5
- package/dist/client.js +2 -2
- 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-C4w86kcN.d.ts → current-agent-vAk_5JMt.d.ts} +4 -4
- package/dist/{durable-object-lifecycle-ei7M7lqB.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +15 -7
- package/dist/harness/pi/index.d.ts +344 -0
- package/dist/harness/pi/index.js +873 -0
- package/dist/harness/pi/index.js.map +1 -0
- package/dist/images-iiTDSyRu.js +1631 -0
- package/dist/images-iiTDSyRu.js.map +1 -0
- package/dist/{index-Dlgtd3Mj.d.ts → index-BreL0YrQ.d.ts} +2 -2
- package/dist/index-kbSUIer3.d.ts +343 -0
- package/dist/index.d.ts +46 -44
- package/dist/index.js +1 -1
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +3 -3
- package/dist/lifecycle/index.js +1 -1
- package/dist/{lifecycle-Mm_jQh7r.js → lifecycle-CFu6OV03.js} +39 -10
- package/dist/{lifecycle-Mm_jQh7r.js.map → lifecycle-CFu6OV03.js.map} +1 -1
- package/dist/mcp/client/index.d.ts +20 -20
- 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 +1 -1
- 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 +3 -3
- package/dist/queue/index.js +1 -1
- package/dist/{queue-BC9F5cUo.js → queue-BB-zfIYF.js} +2 -2
- package/dist/{queue-BC9F5cUo.js.map → queue-BB-zfIYF.js.map} +1 -1
- package/dist/react.d.ts +6 -6
- package/dist/react.js +160 -64
- package/dist/react.js.map +1 -1
- package/dist/{retries-D9B2UCq3.d.ts → retries-CV6AkXmQ.d.ts} +18 -9
- package/dist/retries.d.ts +11 -9
- package/dist/retries.js +11 -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-D_KHqxBa.js → scheduler-DZ3xQfeK.js} +2 -2
- package/dist/{scheduler-D_KHqxBa.js.map → scheduler-DZ3xQfeK.js.map} +1 -1
- package/dist/{scheduler-BGq6M5Kd.d.ts → scheduler-RdACNXDP.d.ts} +3 -3
- 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-BNU3ZiJM.js → src-BZeu-SCD.js} +423 -115
- package/dist/src-BZeu-SCD.js.map +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.js +1 -1
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-BcE8JP2X.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
- package/dist/{streams-CCPRV6dt.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-D4nLqVSI.js → tasks-68DESxf0.js} +2 -2
- package/dist/{tasks-D4nLqVSI.js.map → tasks-68DESxf0.js.map} +1 -1
- package/dist/{tasks-V2lCT8ZV.d.ts → tasks-9YxYtByh.d.ts} +2 -2
- package/dist/transport-PfFnt-Ld.d.ts +525 -0
- package/dist/{types-DCD6BNZH.d.ts → types-BA4anrjI.d.ts} +2 -2
- package/dist/voice/index.d.ts +2 -2
- package/dist/websockets/index.d.ts +23 -6
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-D7IzWbZb.js → websockets-Ci-LZFHH.js} +45 -20
- package/dist/{websockets-D7IzWbZb.js.map → websockets-Ci-LZFHH.js.map} +1 -1
- 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-tools.md +29 -4
- package/docs/browse-the-web.md +87 -5
- package/docs/chat-agents.md +124 -16
- package/docs/context.md +27 -0
- package/docs/durable-execution.md +2 -0
- package/docs/harnesses/pi.md +173 -0
- package/docs/human-in-the-loop.md +4 -0
- package/docs/index.md +3 -0
- package/docs/lifecycle.md +20 -4
- package/docs/models-pi-ai.md +325 -0
- package/docs/models.md +504 -0
- package/docs/resumable-streaming.md +2 -0
- package/docs/routing.md +13 -13
- package/docs/sessions.md +36 -1
- package/docs/state.md +1 -0
- package/docs/sub-agents.md +9 -0
- package/package.json +40 -2
- package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
- package/dist/browser/ai.js.map +0 -1
- package/dist/client-C9DAjZIY.js.map +0 -1
- package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
- package/dist/connector-CptFKzRh.js.map +0 -1
- package/dist/message-builder-BymO4N_D.js.map +0 -1
- package/dist/src-BNU3ZiJM.js.map +0 -1
- package/dist/streams-CCPRV6dt.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/)
|
|
@@ -84,6 +84,8 @@ function Chat() {
|
|
|
84
84
|
|
|
85
85
|
Replayed chunks include `replay: true` to distinguish them from live chunks. The client uses this to batch-apply all replayed chunks before rendering, which prevents intermediate states (like reasoning "Thinking..." indicators) from flashing briefly during replay. During a live stream, chunks arrive gradually and React renders each intermediate state naturally.
|
|
86
86
|
|
|
87
|
+
Live and replayed chunks also carry a `seq` field: the chunk's position in the stream, starting at `0`. A continuation stream (for example, a tool continuation) appends to an assistant message that the client already holds, so replaying it from the start would apply the same text twice. The client remembers the highest `seq` it has applied for each request and skips replayed continuation chunks at or below it. Replays of non-continuation streams rebuild the message from the first chunk and are not filtered.
|
|
88
|
+
|
|
87
89
|
## Durable client cleanup
|
|
88
90
|
|
|
89
91
|
`resume: true` controls whether the client tries to reconnect to an active stream. `cancelOnClientAbort: false` is the default cancellation behavior: generic client stream abort/cleanup is local-only, while explicit `stop()` still cancels the server turn.
|
package/docs/routing.md
CHANGED
|
@@ -777,9 +777,9 @@ The error message lists available agents. Check:
|
|
|
777
777
|
2. Check that `routeAgentRequest()` is called before your 404 handler
|
|
778
778
|
3. Ensure the response from `routeAgentRequest()` is returned (not just called)
|
|
779
779
|
|
|
780
|
-
### WebSocket
|
|
780
|
+
### WebSocket will not connect
|
|
781
781
|
|
|
782
|
-
1.
|
|
782
|
+
1. Do not modify the response from `routeAgentRequest()` for WebSocket upgrades
|
|
783
783
|
2. Ensure CORS is enabled if connecting from a different origin
|
|
784
784
|
3. Check browser dev tools for the actual error
|
|
785
785
|
|
|
@@ -798,16 +798,16 @@ The error message lists available agents. Check:
|
|
|
798
798
|
|
|
799
799
|
Routes a request to the appropriate agent.
|
|
800
800
|
|
|
801
|
-
| Parameter | Type
|
|
802
|
-
| ------------------------- |
|
|
803
|
-
| `request` | `Request`
|
|
804
|
-
| `env` | `Env`
|
|
805
|
-
| `options.cors` | `boolean \| HeadersInit`
|
|
806
|
-
| `options.props` | `
|
|
807
|
-
| `options.locationHint` | `string`
|
|
808
|
-
| `options.jurisdiction` | `string`
|
|
809
|
-
| `options.onBeforeConnect` | `Function`
|
|
810
|
-
| `options.onBeforeRequest` | `Function`
|
|
801
|
+
| Parameter | Type | Description |
|
|
802
|
+
| ------------------------- | ------------------------ | ------------------------------------------------------------------------------------ |
|
|
803
|
+
| `request` | `Request` | The incoming request |
|
|
804
|
+
| `env` | `Env` | Environment with agent bindings |
|
|
805
|
+
| `options.cors` | `boolean \| HeadersInit` | Enable CORS headers |
|
|
806
|
+
| `options.props` | `object` | Props passed to whichever agent handles the request. Sent as JSON, so use plain data |
|
|
807
|
+
| `options.locationHint` | `string` | Preferred location for agent instances |
|
|
808
|
+
| `options.jurisdiction` | `string` | Data jurisdiction for agent instances |
|
|
809
|
+
| `options.onBeforeConnect` | `Function` | Callback before WebSocket connections |
|
|
810
|
+
| `options.onBeforeRequest` | `Function` | Callback before HTTP requests |
|
|
811
811
|
|
|
812
812
|
**Returns:** `Promise<Response \| undefined>` - Response if matched, undefined if no agent route
|
|
813
813
|
|
|
@@ -821,7 +821,7 @@ Get an agent instance by name for server-side RPC or request forwarding.
|
|
|
821
821
|
| `name` | `string` | Instance name |
|
|
822
822
|
| `options.locationHint` | `string` | Preferred location |
|
|
823
823
|
| `options.jurisdiction` | `string` | Data jurisdiction |
|
|
824
|
-
| `options.props` | `
|
|
824
|
+
| `options.props` | `object` | Initialization properties for `onStart` |
|
|
825
825
|
|
|
826
826
|
**Returns:** `Promise<DurableObjectStub<T>>` - Typed stub for calling agent methods or forwarding requests
|
|
827
827
|
|
package/docs/sessions.md
CHANGED
|
@@ -242,7 +242,23 @@ await session.addCompaction(summary, fromMessageId, toMessageId);
|
|
|
242
242
|
|
|
243
243
|
Sessions stamps each message with a token estimate when the row is written. `compactAfter()` gates on that O(1) aggregate and never reads the transcript to decide whether to compact. Auto-compaction failures are non-fatal: they log, emit `session:error`, and leave the transcript alone.
|
|
244
244
|
|
|
245
|
-
To trim a transcript before handing it to a model, `truncateOlderMessages` is exported from [`agents/chat`](./chat-agents.md), not from `agents/sessions`.
|
|
245
|
+
To trim a transcript before handing it to a model, `truncateOlderMessages` is exported from [`agents/chat`](./chat-agents.md), not from `agents/sessions`. If your tools define `toModelOutput`, a truncated tool output may no longer match the tool's output schema. In that case, pass `toolOutputs: false` to `truncateOlderMessages` and call `truncateOlderToolResults` on the result of `convertToModelMessages`, which truncates what the model reads after `toModelOutput` has run. Provider-executed tool outputs are never truncated.
|
|
246
|
+
|
|
247
|
+
With the default `keepRecent`, the truncation cutoff moves forward by one message whenever a message is added, so every turn rewrites a message near the end of the prompt and invalidates the provider's prompt cache from that point. To keep the prefix stable, move the cutoff in steps and pass the same `keepRecent` to both functions:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const step = 8;
|
|
251
|
+
const keepRecent = history.length <= 4 ? 4 : 4 + ((history.length - 4) % step);
|
|
252
|
+
const truncated = truncateOlderMessages(history, {
|
|
253
|
+
keepRecent,
|
|
254
|
+
toolOutputs: false
|
|
255
|
+
});
|
|
256
|
+
const modelMessages = truncateOlderToolResults(
|
|
257
|
+
await convertToModelMessages(truncated, { tools }),
|
|
258
|
+
truncated,
|
|
259
|
+
{ keepRecent }
|
|
260
|
+
);
|
|
261
|
+
```
|
|
246
262
|
|
|
247
263
|
## Large messages
|
|
248
264
|
|
|
@@ -345,6 +361,25 @@ const unsubscribe = sessions.subscribe(async (event) => {
|
|
|
345
361
|
|
|
346
362
|
`import` fires once per `importMessage()` and carries the row; it is deliberately not an `append`, so a projection does not patch itself per imported row during a migration — it marks itself stale and re-derives once. `compaction` fires when an overlay is stored directly through `addCompaction()`; `compact()` reports its own overlay as `compact`.
|
|
347
363
|
|
|
364
|
+
A host that keeps an in-memory transcript does not need to write that reduction itself. `session.mirror()` subscribes for one session and patches an array in place: an append or update replaces the entry with the same id (or pushes a new row), a delete filters, and a clear empties it:
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
const stop = sessions.session("").mirror<UIMessage>({
|
|
368
|
+
get: () => this.messages,
|
|
369
|
+
set: (messages) => {
|
|
370
|
+
this.messages = messages;
|
|
371
|
+
},
|
|
372
|
+
transform: (message) => toCachedMessage(message),
|
|
373
|
+
intercept: async (event) => {
|
|
374
|
+
if (event.type !== "compact") return false;
|
|
375
|
+
this.messages = await reloadFromStorage();
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
`get()` is read on every event, so reassigning the array is safe. `intercept` handles an event instead of the default reduction. Use it for changes an in-place patch cannot express, such as a branch append or a compaction. `onApplied` runs after an append or update was written, with the entry it replaced.
|
|
382
|
+
|
|
348
383
|
This is a local cache-coherence feed, not a cross-object event log. Capability diagnostics are also emitted through Lifecycle observability.
|
|
349
384
|
|
|
350
385
|
## Errors
|
package/docs/state.md
CHANGED
package/docs/sub-agents.md
CHANGED
|
@@ -246,6 +246,15 @@ The hook receives the **original** request with its URL intact — including the
|
|
|
246
246
|
|
|
247
247
|
WebSocket upgrade requests flow through this hook the same way as plain HTTP. If you return a mutated `Request`, keep the original `Upgrade: websocket` and `Sec-WebSocket-*` headers — cloning via `new Headers(req.headers)` and only adding or replacing entries is the safest recipe.
|
|
248
248
|
|
|
249
|
+
When the hook returns a `Response` for a WebSocket upgrade, the framework accepts the socket and immediately closes it, because browsers hide the status of a failed handshake and reconnecting clients would retry it forever. The close code carries the status:
|
|
250
|
+
|
|
251
|
+
| Returned status | Close code | Client behavior |
|
|
252
|
+
| --------------- | --------------- | --------------------------------------------------------------------- |
|
|
253
|
+
| `4xx` | `4000 + status` | Terminal: `useAgent` and `AgentClient` stop and set `connectionError` |
|
|
254
|
+
| anything else | `1011` | Retried with backoff |
|
|
255
|
+
|
|
256
|
+
The close reason is `Sub-agent connection rejected (<status>)`, so a `404` arrives as code `4404`. At the first `/sub/` hop, a returned `Response` that already carries a `webSocket` is passed through unchanged; deeper hops always close with the mapped code.
|
|
257
|
+
|
|
249
258
|
### `this.parentPath` and `this.selfPath`
|
|
250
259
|
|
|
251
260
|
Root-first ancestor chains. `parentPath` covers strict ancestors; `selfPath` includes the current agent.
|