agents 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/{agent-routing-B2XLNMxq.d.ts → agent-routing-0DR0Tk2v.d.ts} +205 -132
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-routing.js.map +1 -1
  4. package/dist/agent-tool-types.d.ts +28 -22
  5. package/dist/agent-tool-types.js +23 -1
  6. package/dist/agent-tool-types.js.map +1 -1
  7. package/dist/{agent-tools-y7zLfw4Q.js → agent-tools-CdJC-8Dc.js} +72 -9
  8. package/dist/agent-tools-CdJC-8Dc.js.map +1 -0
  9. package/dist/{agent-tools-Dj5Mzlw7.d.ts → agent-tools-YrATZJu2.d.ts} +34 -20
  10. package/dist/agent-tools.d.ts +20 -18
  11. package/dist/agent-tools.js.map +1 -1
  12. package/dist/ai-fm7xx1BK.js +312 -0
  13. package/dist/ai-fm7xx1BK.js.map +1 -0
  14. package/dist/base64-redaction-C1Uu29t5.js +59 -0
  15. package/dist/base64-redaction-C1Uu29t5.js.map +1 -0
  16. package/dist/browser/ai-sdk.d.ts +90 -0
  17. package/dist/browser/ai-sdk.js +51 -0
  18. package/dist/browser/ai-sdk.js.map +1 -0
  19. package/dist/browser/ai.d.ts +4 -4
  20. package/dist/browser/ai.js +1 -347
  21. package/dist/browser/index.d.ts +81 -79
  22. package/dist/browser/index.js +361 -2
  23. package/dist/browser/index.js.map +1 -0
  24. package/dist/browser/tanstack-ai.d.ts +96 -1
  25. package/dist/browser/tanstack-ai.js +62 -2
  26. package/dist/browser/tanstack-ai.js.map +1 -1
  27. package/dist/browser-DE3d-RLk.d.ts +275 -0
  28. package/dist/browser-tool-CQ10e6Kj.js +480 -0
  29. package/dist/browser-tool-CQ10e6Kj.js.map +1 -0
  30. package/dist/browser-tool-DuNdlwZ9.d.ts +73 -0
  31. package/dist/capability-B4WbF81e.js.map +1 -1
  32. package/dist/{capability-runner-BUBa6Ake.d.ts → capability-runner-DBAnGr6L.d.ts} +7 -2
  33. package/dist/chat/index.d.ts +340 -234
  34. package/dist/chat/index.js +592 -66
  35. package/dist/chat/index.js.map +1 -1
  36. package/dist/chat/react.d.ts +42 -2
  37. package/dist/chat/react.js +191 -35
  38. package/dist/chat/react.js.map +1 -1
  39. package/dist/chat/transport.d.ts +1 -1
  40. package/dist/chat/transport.js +1 -1
  41. package/dist/chat-sdk/index.d.ts +17 -3
  42. package/dist/chat-sdk/index.js +28 -2
  43. package/dist/chat-sdk/index.js.map +1 -1
  44. package/dist/{client-C9DAjZIY.js → client-4OlMIJx-.js} +102 -29
  45. package/dist/client-4OlMIJx-.js.map +1 -0
  46. package/dist/{client-BxdkCuVy.d.ts → client-je3koD9Z.d.ts} +45 -10
  47. package/dist/client.d.ts +7 -5
  48. package/dist/client.js +2 -2
  49. package/dist/{connector-CptFKzRh.js → connector-58XDGvc3.js} +251 -120
  50. package/dist/connector-58XDGvc3.js.map +1 -0
  51. package/dist/{connector-CkQD4MK3.d.ts → connector-Cl-VqPmA.d.ts} +244 -146
  52. package/dist/context/index.d.ts +29 -0
  53. package/dist/context/index.js +64 -17
  54. package/dist/context/index.js.map +1 -1
  55. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  56. package/dist/{current-agent-C4w86kcN.d.ts → current-agent-vAk_5JMt.d.ts} +4 -4
  57. package/dist/{durable-object-lifecycle-ei7M7lqB.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +15 -7
  58. package/dist/harness/pi/index.d.ts +344 -0
  59. package/dist/harness/pi/index.js +873 -0
  60. package/dist/harness/pi/index.js.map +1 -0
  61. package/dist/images-iiTDSyRu.js +1631 -0
  62. package/dist/images-iiTDSyRu.js.map +1 -0
  63. package/dist/{index-Dlgtd3Mj.d.ts → index-BreL0YrQ.d.ts} +2 -2
  64. package/dist/index-kbSUIer3.d.ts +343 -0
  65. package/dist/index.d.ts +46 -44
  66. package/dist/index.js +1 -1
  67. package/dist/internal_context.d.ts +1 -1
  68. package/dist/lifecycle/index.d.ts +3 -3
  69. package/dist/lifecycle/index.js +1 -1
  70. package/dist/{lifecycle-Mm_jQh7r.js → lifecycle-CFu6OV03.js} +39 -10
  71. package/dist/{lifecycle-Mm_jQh7r.js.map → lifecycle-CFu6OV03.js.map} +1 -1
  72. package/dist/mcp/client/index.d.ts +20 -20
  73. package/dist/mcp/client/x402.js +21 -19
  74. package/dist/mcp/client/x402.js.map +1 -1
  75. package/dist/mcp/index.d.ts +35 -35
  76. package/dist/mcp/index.js +1 -1
  77. package/dist/{message-builder-BymO4N_D.js → message-builder-DAl9AhHH.js} +89 -4
  78. package/dist/message-builder-DAl9AhHH.js.map +1 -0
  79. package/dist/models/ai-sdk/index.d.ts +353 -0
  80. package/dist/models/ai-sdk/index.js +2443 -0
  81. package/dist/models/ai-sdk/index.js.map +1 -0
  82. package/dist/models/pi-ai/index.d.ts +313 -0
  83. package/dist/models/pi-ai/index.js +1795 -0
  84. package/dist/models/pi-ai/index.js.map +1 -0
  85. package/dist/observability/ai/index.js +20 -4
  86. package/dist/observability/ai/index.js.map +1 -1
  87. package/dist/{protocol-B0nh6KNf.js → protocol-kHsr-f0X.js} +26 -12
  88. package/dist/{protocol-B0nh6KNf.js.map → protocol-kHsr-f0X.js.map} +1 -1
  89. package/dist/queue/index.d.ts +3 -3
  90. package/dist/queue/index.js +1 -1
  91. package/dist/{queue-BC9F5cUo.js → queue-BB-zfIYF.js} +2 -2
  92. package/dist/{queue-BC9F5cUo.js.map → queue-BB-zfIYF.js.map} +1 -1
  93. package/dist/react.d.ts +6 -6
  94. package/dist/react.js +160 -64
  95. package/dist/react.js.map +1 -1
  96. package/dist/{retries-D9B2UCq3.d.ts → retries-CV6AkXmQ.d.ts} +18 -9
  97. package/dist/retries.d.ts +11 -9
  98. package/dist/retries.js +11 -1
  99. package/dist/retries.js.map +1 -1
  100. package/dist/routing/index.d.ts +2 -2
  101. package/dist/routing/index.js +1 -1
  102. package/dist/{scheduler-D_KHqxBa.js → scheduler-DZ3xQfeK.js} +2 -2
  103. package/dist/{scheduler-D_KHqxBa.js.map → scheduler-DZ3xQfeK.js.map} +1 -1
  104. package/dist/{scheduler-BGq6M5Kd.d.ts → scheduler-RdACNXDP.d.ts} +3 -3
  105. package/dist/schedules/index.d.ts +1 -1
  106. package/dist/schedules/index.js +1 -1
  107. package/dist/serializable.d.ts +1 -1
  108. package/dist/sessions/index.d.ts +70 -3
  109. package/dist/sessions/index.js +276 -67
  110. package/dist/sessions/index.js.map +1 -1
  111. package/dist/skills/index.d.ts +29 -314
  112. package/dist/socket-address.d.ts +16 -0
  113. package/dist/socket-address.js +19 -0
  114. package/dist/socket-address.js.map +1 -0
  115. package/dist/{src-BNU3ZiJM.js → src-BZeu-SCD.js} +423 -115
  116. package/dist/src-BZeu-SCD.js.map +1 -0
  117. package/dist/state/index.d.ts +1 -1
  118. package/dist/state/index.js +1 -1
  119. package/dist/streams/index.d.ts +1 -1
  120. package/dist/streams/index.js +1 -1
  121. package/dist/{streams-BcE8JP2X.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
  122. package/dist/{streams-CCPRV6dt.js → streams-DW-UCOUc.js} +9 -2
  123. package/dist/streams-DW-UCOUc.js.map +1 -0
  124. package/dist/sub-routing.d.ts +12 -12
  125. package/dist/tasks/index.d.ts +1 -1
  126. package/dist/tasks/index.js +1 -1
  127. package/dist/{tasks-D4nLqVSI.js → tasks-68DESxf0.js} +2 -2
  128. package/dist/{tasks-D4nLqVSI.js.map → tasks-68DESxf0.js.map} +1 -1
  129. package/dist/{tasks-V2lCT8ZV.d.ts → tasks-9YxYtByh.d.ts} +2 -2
  130. package/dist/transport-PfFnt-Ld.d.ts +525 -0
  131. package/dist/{types-DCD6BNZH.d.ts → types-BA4anrjI.d.ts} +2 -2
  132. package/dist/voice/index.d.ts +2 -2
  133. package/dist/websockets/index.d.ts +23 -6
  134. package/dist/websockets/index.js +1 -1
  135. package/dist/{websockets-D7IzWbZb.js → websockets-Ci-LZFHH.js} +45 -20
  136. package/dist/{websockets-D7IzWbZb.js.map → websockets-Ci-LZFHH.js.map} +1 -1
  137. package/dist/wire-types-CnMt6_HR.js.map +1 -1
  138. package/dist/wire-types-CrjKvDyi.d.ts +300 -0
  139. package/dist/workflow-types.d.ts +25 -25
  140. package/dist/workflows.d.ts +21 -21
  141. package/dist/workflows.js +1 -1
  142. package/dist/{ws-chat-transport-UNRIS2xl.d.ts → ws-chat-transport-BbT1A5Lc.d.ts} +51 -2
  143. package/dist/{ws-chat-transport-rWwta645.js → ws-chat-transport-cqMKq3IY.js} +143 -16
  144. package/dist/ws-chat-transport-cqMKq3IY.js.map +1 -0
  145. package/docs/agent-tools.md +29 -4
  146. package/docs/browse-the-web.md +87 -5
  147. package/docs/chat-agents.md +124 -16
  148. package/docs/context.md +27 -0
  149. package/docs/durable-execution.md +2 -0
  150. package/docs/harnesses/pi.md +173 -0
  151. package/docs/human-in-the-loop.md +4 -0
  152. package/docs/index.md +3 -0
  153. package/docs/lifecycle.md +20 -4
  154. package/docs/models-pi-ai.md +325 -0
  155. package/docs/models.md +504 -0
  156. package/docs/resumable-streaming.md +2 -0
  157. package/docs/routing.md +13 -13
  158. package/docs/sessions.md +36 -1
  159. package/docs/state.md +1 -0
  160. package/docs/sub-agents.md +9 -0
  161. package/package.json +40 -2
  162. package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
  163. package/dist/browser/ai.js.map +0 -1
  164. package/dist/client-C9DAjZIY.js.map +0 -1
  165. package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
  166. package/dist/connector-CptFKzRh.js.map +0 -1
  167. package/dist/message-builder-BymO4N_D.js.map +0 -1
  168. package/dist/src-BNU3ZiJM.js.map +0 -1
  169. package/dist/streams-CCPRV6dt.js.map +0 -1
  170. package/dist/ws-chat-transport-rWwta645.js.map +0 -1
@@ -0,0 +1,325 @@
1
+ # Models for pi-ai (Beta)
2
+
3
+ `agents/models/pi-ai` is the [pi-ai](https://github.com/earendil-works/pi/tree/main/packages/ai)
4
+ twin of [`agents/models/ai-sdk`](./models.md): the same `createAI` factory, the same options,
5
+ the same gateway, returning pi-ai `Model` objects instead of AI SDK ones. Use it with pi-ai's
6
+ `stream`/`complete`, with [pi-durable](https://github.com/earendil-works/pi/tree/main/packages/durable)'s
7
+ `Harness`, or with any framework built on a pi-ai `Models` registry.
8
+
9
+ ## The rule
10
+
11
+ **Workers AI is the catalog Cloudflare keeps up with. Every other vendor is pi-ai's.**
12
+
13
+ `agents` ships no vendor ids, wire formats, thinking rules or effort tables for any model
14
+ that is not `@cf/…`. A third-party model comes from pi-ai's own registry — the package that
15
+ tracks that vendor — and `agents` only routes it through AI Gateway. So there are two call
16
+ forms:
17
+
18
+ ```ts
19
+ import { createAI } from "agents/models/pi-ai";
20
+ import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
21
+
22
+ const ai = createAI({ binding: env.AI, id: "prod", cacheTtl: 60 });
23
+
24
+ ai("@cf/zai-org/glm-4.7-flash"); // Workers AI: our catalog, our compat layer
25
+ ai(getBuiltinModel("anthropic", "claude-opus-4-8")); // their model, our transport
26
+ ```
27
+
28
+ For convenience, pi-ai also ships a generated catalog of the models Cloudflare's gateway
29
+ serves, so a `<provider>/<id>` string resolves without importing anything:
30
+
31
+ ```ts
32
+ ai("anthropic/claude-opus-4.8"); // the same model, as AI Gateway's catalog names it
33
+ ai("openai/gpt-5.4");
34
+ ```
35
+
36
+ Any other string is a `TypeError` that names the way in. There is no prefix guessing here by
37
+ design: a model this package cannot name exactly is yours to construct.
38
+
39
+ 1. **Workers AI ids start with `@cf/`.** Everything else is a vendor's, and comes from a
40
+ vendor registry pi-ai maintains.
41
+ 2. **The gateway is an option, not a product.** Caching, logging, metadata, timeouts and
42
+ retries are options on `createAI`, on the model, or on the call; the `default` gateway is
43
+ created on first use.
44
+ 3. **No provider keys.** AI Gateway holds the credential — unified billing, or a key you
45
+ store on the gateway (BYOK) — so nothing vendor-shaped needs a secret in your Worker.
46
+ 4. **The binding is the only way in.** Workers AI goes through `env.AI.run`, vendor models
47
+ through `env.AI.gateway(id).run`; there is no HTTP transport and no API token to hold.
48
+ 5. Everything here is in beta and may change in a minor release.
49
+
50
+ ## Quick start
51
+
52
+ ```jsonc
53
+ // wrangler.jsonc
54
+ { "ai": { "binding": "AI" } }
55
+ ```
56
+
57
+ ```sh
58
+ npm install agents @earendil-works/pi-ai
59
+ ```
60
+
61
+ ```ts
62
+ import { createAI } from "agents/models/pi-ai";
63
+
64
+ export default {
65
+ async fetch(_request: Request, env: { AI: Ai }) {
66
+ const ai = createAI({ binding: env.AI });
67
+ const reply = await ai.complete(ai("@cf/zai-org/glm-4.7-flash"), {
68
+ messages: [{ role: "user", content: "Hello", timestamp: Date.now() }]
69
+ });
70
+ // `content` is an array of parts — text, thinking, tool calls.
71
+ const text = reply.content
72
+ .filter((part) => part.type === "text")
73
+ .map((part) => part.text)
74
+ .join("");
75
+ return Response.json({ text });
76
+ }
77
+ };
78
+ ```
79
+
80
+ ## Where a model comes from
81
+
82
+ ```ts
83
+ // 1. Workers AI — ours.
84
+ ai("@cf/zai-org/glm-4.7-flash");
85
+
86
+ // 2. pi-ai's Cloudflare AI Gateway catalog — a string, resolved by pi, not us.
87
+ ai("anthropic/claude-opus-4.8");
88
+
89
+ // 3. Any pi-ai model object, from any registry you imported.
90
+ import { anthropicProvider } from "@earendil-works/pi-ai/providers/anthropic";
91
+ const claude = anthropicProvider()
92
+ .getModels()
93
+ .find((m) => m.id === "claude-opus-4-8")!;
94
+ ai(claude);
95
+ ```
96
+
97
+ Forms 2 and 3 keep the model's own metadata verbatim — `api`, `compat`, `thinkingLevelMap`,
98
+ cost, context window, max tokens. That metadata is why Claude Opus gets
99
+ `thinking: { type: "adaptive" }` and GPT-5 gets `reasoning.effort`: the model's author said
100
+ so, and this package does not second-guess it. Form 3 never mutates the object you
101
+ passed; the options ride on a copy.
102
+
103
+ ## Running a model
104
+
105
+ The instance carries pi-ai's four entry points, so nothing global is registered:
106
+
107
+ ```ts
108
+ const model = ai("anthropic/claude-opus-4.8");
109
+ const context = {
110
+ messages: [{ role: "user", content: "Hi", timestamp: Date.now() }]
111
+ };
112
+
113
+ const message = await ai.complete(model, context); // AssistantMessage
114
+ for await (const event of ai.stream(model, context)) {
115
+ // AssistantMessageEvent: text_delta, thinking_delta, toolcall_*, done, error
116
+ }
117
+ await ai.completeSimple(model, context, { reasoning: "low" }); // SimpleStreamOptions
118
+ ai.streamSimple(model, context, { reasoning: "high", maxTokens: 2048 });
119
+ ```
120
+
121
+ ### With pi-durable
122
+
123
+ pi-durable's `Harness` takes a `Models` registry. Register the provider on one and name the
124
+ model by id:
125
+
126
+ ```ts
127
+ import { Harness } from "@earendil-works/pi-durable";
128
+ import { createModels } from "@earendil-works/pi-ai/models";
129
+
130
+ const models = createModels();
131
+ models.setProvider(ai.provider);
132
+ const harness = await Harness.open(storage, { models, registry }, context);
133
+ // a session's model: { provider: "cloudflare", modelId: "anthropic/claude-opus-4.8" }
134
+ ```
135
+
136
+ A session stores its model as `{ provider, modelId }`, not as a model object, so use the
137
+ `@cf/…` and `<provider>/<id>` forms there. Options given to `ai(model, options)` do not reach a
138
+ session; set them on `createAI` instead.
139
+
140
+ `ai.streamFn` is `streamSimple`, bound, for frameworks that take a stream function, such as
141
+ pi-agent-core's `Agent({ streamFn })`.
142
+
143
+ ### With a pi-ai `Models` registry
144
+
145
+ ```ts
146
+ import { createModels } from "@earendil-works/pi-ai/models";
147
+
148
+ const models = createModels();
149
+ models.setProvider(ai.provider); // provider id: "cloudflare"
150
+ models.getModel("cloudflare", "anthropic/claude-opus-4.8");
151
+ models.streamSimple(ai("@cf/zai-org/glm-4.7-flash"), context);
152
+ ```
153
+
154
+ The provider's static list is every Workers AI id pi-ai knows plus every `<provider>/<id>` in
155
+ its Cloudflare AI Gateway catalog, so `cloudflare/<id>` specifiers resolve. A model object
156
+ you pass to `ai(model)` streams whether or not it is on that list.
157
+
158
+ ## Options
159
+
160
+ Gateway options may be given flat or nested, on any of the three layers:
161
+
162
+ ```ts
163
+ const ai = createAI({ binding: env.AI, id: "prod", cacheTtl: 60 });
164
+
165
+ ai("anthropic/claude-opus-4.8", {
166
+ gateway: { id: "my-gateway" }, // or a string id
167
+ cacheTtl: 3600, // the same keys, flat
168
+ skipCache: false,
169
+ cacheKey: "greeting-v1",
170
+ collectLog: true,
171
+ metadata: { team: "growth" },
172
+ eventId: "turn-17",
173
+ requestTimeoutMs: 30_000,
174
+ retries: { maxAttempts: 3, backoff: "exponential", retryDelayMs: 500 },
175
+ fallback: ["@cf/zai-org/glm-4.7-flash"], // ids or models, in any mix
176
+ headers: { "x-request-source": "docs" },
177
+ sessionAffinity: "chat-42", // Workers AI prefix-cache routing
178
+ reasoningEffort: "low", // "low" | "medium" | "high" | null
179
+ chatTemplateKwargs: { enable_thinking: false } // Workers AI only
180
+ });
181
+ ```
182
+
183
+ `reasoningEffort` and `chatTemplateKwargs` are Workers AI settings. On a model object or a
184
+ `<slug>/<id>` model they are not sent; the drop is recorded on the message as a
185
+ `cloudflare-compat` diagnostic, and the pi `reasoning` level (mapped by the model's own
186
+ metadata) is the way to ask a vendor model to think. A string in `fallback` follows the same
187
+ rule as `ai(string)`: a `@cf/` id or a registry-known `<slug>/<id>`, never a raw vendor id.
188
+
189
+ The `@cf/` form also takes pi-ai metadata overrides — `name`, `contextWindow`, `maxTokens`,
190
+ `reasoning`, `input`, `cost`, `streamIdleTimeoutMs` — because the Workers AI catalog moves
191
+ faster than any generated registry. A model object brings its own; nothing here overrides it.
192
+
193
+ A pi-ai model is plain data, and these per-model options ride on it under symbol keys, which
194
+ `JSON.stringify` and `structuredClone` drop. Routing survives that — a model stored in `Agent`
195
+ state and read back still reaches its vendor under the vendor's own id — but the options do
196
+ not, so re-apply them with `ai(model, options)` after loading if the model carried any.
197
+
198
+ Per call, pi-ai's own options map onto the same layers: `headers` are merged last, `metadata`
199
+ (scalars) joins the gateway metadata, `sessionId` sets session affinity, and `reasoning` (a pi
200
+ thinking level) is remapped through the model's own `thinkingLevelMap` — as far as the wire
201
+ can carry it; see [thinking shapes](#thinking-shapes) for the one limit. Precedence is call,
202
+ then model, then `createAI` settings.
203
+
204
+ ## How a request travels
205
+
206
+ | Model | `api` | Transport |
207
+ | ------------------------ | -------------------- | ----------------------------------------- |
208
+ | `@cf/…` | `cloudflare-ai` | `env.AI.run` + Cloudflare's compat layer |
209
+ | Anthropic (any registry) | `anthropic-messages` | gateway universal → `v1/messages` |
210
+ | OpenAI (any registry) | `openai-responses` | gateway universal → `v1/responses` |
211
+ | Groq, DeepSeek, xAI, … | `openai-completions` | gateway universal → `v1/chat/completions` |
212
+
213
+ Everything that is not `@cf/` goes through **AI Gateway's universal request**: the vendor's
214
+ own body, built by pi-ai's own converter, posted to the gateway with the provider slug taken
215
+ from the model's `baseUrl` host. The gateway forwards it to the vendor with the credential it
216
+ holds, and streams the vendor's own SSE back untouched — pi-ai's stream processors parse it,
217
+ as they would if you had called the vendor directly, the thinking knobs aside (below). No
218
+ credential header leaves your Worker, and the model's `api`, `provider` and `id` are never
219
+ rewritten, so thinking signatures and encrypted reasoning items replay on the next turn.
220
+
221
+ The `api` markers in that table are the only ones this package routes, and the table is the
222
+ whole list: `cloudflare-ai`, `anthropic-messages`, `openai-responses`, `openai-completions`.
223
+ Check your model's `api` before you pass it — several pi registries declare something else and
224
+ are not routable today, among them `mistral` (`mistral-conversations`), `google`
225
+ (`google-generative-ai`), `google-vertex`, `amazon-bedrock`, `azure-openai-responses` and
226
+ `openai-codex`.
227
+
228
+ Workers AI stays on `env.AI.run`. Its departures from strict OpenAI chat completions (native
229
+ events, heartbeats, per-delta usage, per-family request quirks) are absorbed by the same
230
+ compat layer the [AI SDK provider](./models.md) uses, so the parser only ever sees strict
231
+ OpenAI chunks. Anything the layer had to drop is recorded on the message as a
232
+ `cloudflare-compat` diagnostic.
233
+
234
+ An `api` this package cannot route, or a `baseUrl` whose host AI Gateway does not serve, ends
235
+ the stream with an `error` event naming it.
236
+
237
+ ### Thinking shapes
238
+
239
+ On the `openai-completions` wire this package builds one thinking shape: OpenAI's
240
+ `reasoning_effort`, with the level remapped through the model's own `thinkingLevelMap`, and
241
+ only when the model reasons and its `compat` does not say `supportsReasoningEffort: false`.
242
+
243
+ A model whose `compat.thinkingFormat` is anything else — `deepseek`, `zai`, `openrouter`,
244
+ `together`, `ant-ling`, `qwen`, `qwen-chat-template`, `chat-template`, `string-thinking` — has
245
+ its reasoning level **dropped**, and the drop recorded on the message as a `cloudflare-compat`
246
+ diagnostic. Those shapes are the vendor's, and building them here would mean keeping vendor
247
+ wire formats in `agents`, which is the one thing this package does not do: reach such a model
248
+ through pi-ai's own provider if you need its thinking shape. The `anthropic-messages` and
249
+ `openai-responses` wires build the thinking shape their own model metadata declares.
250
+
251
+ ## Unified billing and BYOK
252
+
253
+ Most vendors work with no key at all: the gateway bills them to your Cloudflare account. A
254
+ vendor that is not on unified billing answers with its own error until you store a key on the
255
+ gateway — Google AI Studio, reached through a routable model, returns `403 unregistered
256
+ callers`. That error is the vendor's, surfaced as-is; the gateway's own failures (no such
257
+ gateway, an unpaid account) become a `CloudflareAIError`, so fallback and retry treat them the
258
+ way they treat Workers AI failures. A model whose `api` is not one of the four routed markers
259
+ never gets that far: the stream ends with an `error` event naming the `api`.
260
+
261
+ ## Observability
262
+
263
+ Every assistant message carries a `cloudflare` diagnostic with the gateway correlation for its
264
+ response:
265
+
266
+ ```ts
267
+ const message = await ai.complete(model, context);
268
+ const cloudflare = message.diagnostics?.find(
269
+ (d) => d.type === "cloudflare"
270
+ )?.details;
271
+ // { model, gateway, provider?, specifier?, logId?, eventId?, cacheStatus?,
272
+ // step?, traceId?, runId? }
273
+ ```
274
+
275
+ A failed dispatch ends the stream with an `error` event whose message carries a
276
+ `cloudflare-error` diagnostic (`status`, `code`, `logId`), and `onResponse` fires with the raw
277
+ status and headers for every request.
278
+
279
+ ## Fallback
280
+
281
+ ```ts
282
+ ai(claude, { fallback: ["@cf/zai-org/glm-4.7-flash"] });
283
+ ```
284
+
285
+ Legs may be Workers AI ids or models, in any mix. They are tried in order: a leg is abandoned
286
+ when its stream ends with an `error` before it produced any output, and once a leg has
287
+ produced output it is committed to. The message that answers carries a `cloudflare-fallback`
288
+ diagnostic listing the abandoned attempts, and `message.model` names the model that actually
289
+ answered.
290
+
291
+ A leg travels with the chain's gateway options — the chain's `id`, `cacheTtl` and the rest win
292
+ over a leg's own, and `metadata` merges — as they do in the [AI SDK provider](./models.md). A
293
+ leg built as `ai(model, options)` keeps everything else it was built with. A leg named by a
294
+ `@cf/` id has no options of its own, so it also inherits the chain's `headers`, session
295
+ affinity and Workers AI knobs; it never inherits the chain's model metadata (`name`, `cost`,
296
+ `contextWindow`, `maxTokens`, `reasoning`, `input`), which describes the model it was written
297
+ on — the leg's usage and cost are its own registry entry's.
298
+
299
+ ## Images
300
+
301
+ ```ts
302
+ const images = ai.images("@cf/black-forest-labs/flux-1-schnell", { steps: 8 });
303
+ const result = await ai.generateImages(images, {
304
+ input: [{ type: "text", text: "A red bicycle" }]
305
+ });
306
+ ```
307
+
308
+ Image generation is Workers AI only. The knobs a model takes are the model's:
309
+ `flux-1-schnell` takes a prompt and `steps` and nothing else, so `width`,
310
+ `height`, `seed`, `guidance` and `negativePrompt` are dropped for it and listed
311
+ in a `cloudflare-compat` diagnostic on the result rather than sent and ignored.
312
+ Every other model in the catalog takes the full set.
313
+
314
+ ## Beta
315
+
316
+ This subpath needs `@earendil-works/pi-ai@^1.0.0`, an optional peer of `agents`. `agents`
317
+ takes no vendor package as a dependency, optional or otherwise. The surface is in beta
318
+ and may change in a minor release. An unlisted `@cf/` id resolves with default metadata (a
319
+ 128k context window, 8192 output tokens, zero cost); pass overrides when you know better.
320
+
321
+ ## See also
322
+
323
+ - [Models for the AI SDK](./models.md) — the same factory for the Vercel AI SDK
324
+ - [pi-ai](https://github.com/earendil-works/pi/tree/main/packages/ai) and
325
+ [pi-durable](https://github.com/earendil-works/pi/tree/main/packages/durable)