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,1631 @@
1
+ //#region src/models/core/errors.ts
2
+ /**
3
+ * Error thrown for every failed Cloudflare AI call, whether it came back as a
4
+ * non-2xx `Response` or as a thrown Workers AI binding error. Framework
5
+ * modules wrap it into their own error type at their boundary (the AI SDK
6
+ * module into an `APICallError`, so `ai`'s retry loop honours
7
+ * {@link CloudflareAIError.isRetryable}).
8
+ *
9
+ * @experimental This surface is experimental and may change.
10
+ */
11
+ var CloudflareAIError = class extends Error {
12
+ constructor(options) {
13
+ super(options.message);
14
+ this.name = "CloudflareAIError";
15
+ this.status = options.status;
16
+ this.code = options.code;
17
+ this.logId = options.logId;
18
+ this.model = options.model;
19
+ this.url = options.url;
20
+ this.requestBodyValues = options.requestBodyValues;
21
+ this.responseHeaders = options.responseHeaders;
22
+ this.responseBody = options.responseBody;
23
+ this.isRetryable = options.isRetryable ?? isRetryableStatus(options.status);
24
+ this.data = options.data;
25
+ this.cause = options.cause;
26
+ this.attempts = options.attempts;
27
+ }
28
+ };
29
+ /**
30
+ * Whether a status is worth retrying. Missing status means a transport-level
31
+ * failure (connection reset, DNS, abort-adjacent), which is retryable.
32
+ *
33
+ * @experimental This surface is experimental and may change.
34
+ */
35
+ function isRetryableStatus(status) {
36
+ if (status === void 0) return true;
37
+ return status === 408 || status === 409 || status === 429 || status >= 500;
38
+ }
39
+ /**
40
+ * Classifies an HTTP status into a {@link CloudflareAIErrorCode}. The
41
+ * `AiGatewayError` envelope name catches gateway-level statuses that are not
42
+ * otherwise meaningful (notably 402 "insufficient balance").
43
+ */
44
+ function classifyStatus(status, envelopeName) {
45
+ if (status === 401 || status === 403) return "auth";
46
+ if (status === 429) return "rate-limit";
47
+ if (status === 404) return "not-found";
48
+ if (status === 400 || status === 422) return "bad-request";
49
+ if (status !== void 0 && status >= 500) return "provider-error";
50
+ if (envelopeName === "AiGatewayError") return "gateway-error";
51
+ return "unknown";
52
+ }
53
+ /**
54
+ * Workers AI binding error codes that map onto an HTTP status.
55
+ *
56
+ * @see https://developers.cloudflare.com/workers-ai/platform/errors/
57
+ */
58
+ const BINDING_CODE_STATUS = {
59
+ 3003: 400,
60
+ 3006: 413,
61
+ 3007: 408,
62
+ 3008: 408,
63
+ 3023: 403,
64
+ 3036: 429,
65
+ 3039: 400,
66
+ 3040: 429,
67
+ 3041: 403,
68
+ 3042: 404,
69
+ 5004: 400,
70
+ 5005: 405,
71
+ 5007: 400,
72
+ 5016: 403,
73
+ 5018: 403,
74
+ 5019: 405
75
+ };
76
+ /** Abort-shaped errors must propagate untouched so callers can detect them. */
77
+ function isAbortError(error) {
78
+ const name = error instanceof Error || error instanceof DOMException ? error.name : void 0;
79
+ return name === "AbortError" || name === "ResponseAborted" || name === "TimeoutError";
80
+ }
81
+ function messageOf(error) {
82
+ if (typeof error === "string") return error;
83
+ if (error instanceof Error) return error.message;
84
+ if (error !== null && typeof error === "object" && "message" in error) {
85
+ const message = error.message;
86
+ if (typeof message === "string") return message;
87
+ }
88
+ return String(error);
89
+ }
90
+ /**
91
+ * Extracts a Workers AI internal error code from a thrown binding error. The
92
+ * binding reports codes inside the message text (`"3040: Capacity temporarily
93
+ * exceeded"`) rather than as a status, so only known codes are accepted — an
94
+ * unrelated leading number must not be misread as a code.
95
+ */
96
+ function bindingErrorCode(error) {
97
+ if (error !== null && typeof error === "object" && "code" in error) {
98
+ const code = error.code;
99
+ if (typeof code === "number" && code in BINDING_CODE_STATUS) return code;
100
+ }
101
+ const matches = messageOf(error).matchAll(/\b(\d{3,5})\s*:/g);
102
+ for (const match of matches) {
103
+ const code = Number(match[1]);
104
+ if (code in BINDING_CODE_STATUS) return code;
105
+ }
106
+ }
107
+ /**
108
+ * Normalizes a thrown Workers AI binding error into a {@link CloudflareAIError}.
109
+ * Abort errors and errors that are already {@link CloudflareAIError}s pass through.
110
+ */
111
+ function normalizeThrownError(error, context) {
112
+ if (isAbortError(error)) return error;
113
+ if (error instanceof CloudflareAIError) return error;
114
+ const code = bindingErrorCode(error);
115
+ const status = code === void 0 ? void 0 : BINDING_CODE_STATUS[code];
116
+ return new CloudflareAIError({
117
+ cause: error,
118
+ code: classifyStatus(status),
119
+ data: code === void 0 ? void 0 : { code },
120
+ isRetryable: status === void 0 ? false : isRetryableStatus(status),
121
+ message: messageOf(error),
122
+ model: context.model,
123
+ requestBodyValues: context.requestBodyValues,
124
+ status,
125
+ url: context.url
126
+ });
127
+ }
128
+ /** Key that per-call options live under in a framework's per-call options bag. */
129
+ const PROVIDER_OPTIONS_KEY = "cloudflare";
130
+ /** The id legs of a fallback list; model objects are their module's business. */
131
+ function idLegs(fallback) {
132
+ return (fallback ?? []).filter((leg) => typeof leg === "string");
133
+ }
134
+ /** Strips `undefined` values so a later layer never blanks an earlier one. */
135
+ function defined(value) {
136
+ const result = {};
137
+ for (const [key, entry] of Object.entries(value)) if (entry !== void 0) result[key] = entry;
138
+ return result;
139
+ }
140
+ function normalizeGateway(gateway) {
141
+ if (gateway === void 0) return {};
142
+ return typeof gateway === "string" ? { id: gateway } : defined(gateway);
143
+ }
144
+ /**
145
+ * Folds one layer's flat gateway sugar into its `gateway` object. The flat key
146
+ * wins over the nested one, and an explicit `undefined` on either never blanks
147
+ * a lower layer.
148
+ */
149
+ function layerGateway(options) {
150
+ if (options === void 0) return {};
151
+ return {
152
+ ...normalizeGateway(options.gateway),
153
+ ...defined({
154
+ cacheKey: options.cacheKey,
155
+ cacheTtl: options.cacheTtl,
156
+ collectLog: options.collectLog,
157
+ eventId: options.eventId,
158
+ id: options.id,
159
+ metadata: options.metadata,
160
+ requestTimeoutMs: options.requestTimeoutMs,
161
+ retries: options.retries,
162
+ skipCache: options.skipCache
163
+ })
164
+ };
165
+ }
166
+ /** The flat gateway sugar {@link layerGateway} folds into `gateway`. */
167
+ const FLAT_GATEWAY_KEYS = [
168
+ "cacheKey",
169
+ "cacheTtl",
170
+ "collectLog",
171
+ "eventId",
172
+ "gateway",
173
+ "id",
174
+ "metadata",
175
+ "requestTimeoutMs",
176
+ "retries",
177
+ "skipCache"
178
+ ];
179
+ /**
180
+ * One layer's gateway options as a single object, both spellings folded in.
181
+ * Exported so a module that collapses two option layers into one bag (a
182
+ * fallback leg inheriting a chain's gateway) can merge them field by field
183
+ * rather than replacing one object with the other.
184
+ *
185
+ * @experimental This surface is experimental and may change.
186
+ */
187
+ function gatewayLayerOf(options) {
188
+ return layerGateway(options);
189
+ }
190
+ /** An option bag without the gateway keys, whichever spelling they used. */
191
+ function withoutGatewayKeys(options) {
192
+ const rest = { ...options };
193
+ for (const key of FLAT_GATEWAY_KEYS) delete rest[key];
194
+ return defined(rest);
195
+ }
196
+ /**
197
+ * Collapses two option layers into one bag, `override` winning field by field.
198
+ *
199
+ * The gateway keys of both are folded into a single `gateway` object, so
200
+ * neither layer's flat sugar can out-rank the other's nested spelling when the
201
+ * bag is resolved later, and `metadata` merges rather than replaces — the same
202
+ * rules {@link resolveOptions} applies across its three layers.
203
+ *
204
+ * @experimental This surface is experimental and may change.
205
+ */
206
+ function mergeModelOptions(base, override) {
207
+ if (base === void 0) return override;
208
+ if (override === void 0) return base;
209
+ const layers = [layerGateway(base), layerGateway(override)];
210
+ const gateway = Object.assign({}, ...layers);
211
+ const metadata = Object.assign({}, ...layers.map((layer) => layer.metadata));
212
+ const retries = mergeRetries(layers);
213
+ const headers = {
214
+ ...base.headers,
215
+ ...override.headers
216
+ };
217
+ return {
218
+ ...withoutGatewayKeys(base),
219
+ ...withoutGatewayKeys(override),
220
+ ...Object.keys(headers).length > 0 ? { headers } : {},
221
+ gateway: {
222
+ ...gateway,
223
+ ...Object.keys(metadata).length > 0 ? { metadata } : {},
224
+ ...retries === void 0 ? {} : { retries }
225
+ }
226
+ };
227
+ }
228
+ /**
229
+ * Merges the retry policies field by field, so a provider-wide `backoff` still
230
+ * applies when a call only sets `maxAttempts`. `undefined` on any layer means
231
+ * "not set" and never blanks a lower one.
232
+ */
233
+ function mergeRetries(layers) {
234
+ const merged = layers.reduce((result, layer) => ({
235
+ ...result,
236
+ ...layer.retries === void 0 ? {} : defined(layer.retries)
237
+ }), {});
238
+ return Object.keys(merged).length > 0 ? merged : void 0;
239
+ }
240
+ /**
241
+ * Merges the option layers. Precedence is per-call
242
+ * (`providerOptions.cloudflare`) > per-model (`ai(id, options)`) >
243
+ * provider (`createAI({ ... })`).
244
+ *
245
+ * The provider layer takes the whole settings object as well as a bare
246
+ * `gateway` value, so `createAI({ binding, id: "prod", cacheTtl: 60 })` reads
247
+ * the same as `createAI({ binding, gateway: { id: "prod", cacheTtl: 60 } })`.
248
+ *
249
+ * @experimental This surface is experimental and may change.
250
+ */
251
+ function resolveOptions(provider, model, call) {
252
+ const layers = typeof provider === "string" ? [
253
+ normalizeGateway(provider),
254
+ layerGateway(model),
255
+ layerGateway(call)
256
+ ] : [
257
+ layerGateway(provider),
258
+ layerGateway(model),
259
+ layerGateway(call)
260
+ ];
261
+ const gateway = Object.assign({}, ...layers);
262
+ const metadata = Object.assign({}, ...layers.map((layer) => layer.metadata));
263
+ const retries = mergeRetries(layers);
264
+ return {
265
+ chatTemplateKwargs: call?.chatTemplateKwargs ?? model?.chatTemplateKwargs,
266
+ fallback: idLegs(call?.fallback ?? model?.fallback),
267
+ gateway: {
268
+ ...gateway,
269
+ id: gateway.id ?? "default",
270
+ ...Object.keys(metadata).length > 0 ? { metadata } : {},
271
+ ...retries === void 0 ? {} : { retries }
272
+ },
273
+ headers: {
274
+ ...model?.headers,
275
+ ...call?.headers
276
+ },
277
+ reasoningEffort: call?.reasoningEffort !== void 0 ? call.reasoningEffort : model?.reasoningEffort,
278
+ sessionAffinity: call?.sessionAffinity ?? model?.sessionAffinity
279
+ };
280
+ }
281
+ function asRecord(value) {
282
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
283
+ }
284
+ function asStringRecord(value) {
285
+ const record = asRecord(value);
286
+ if (record === void 0) return void 0;
287
+ const result = {};
288
+ for (const [key, entry] of Object.entries(record)) if (typeof entry === "string") result[key] = entry;
289
+ return result;
290
+ }
291
+ function asString(value) {
292
+ return typeof value === "string" ? value : void 0;
293
+ }
294
+ function asNumber(value) {
295
+ return typeof value === "number" ? value : void 0;
296
+ }
297
+ function asBoolean(value) {
298
+ return typeof value === "boolean" ? value : void 0;
299
+ }
300
+ function asReasoningEffort(value) {
301
+ if (value === null) return null;
302
+ if (value === "low" || value === "medium" || value === "high") return value;
303
+ }
304
+ /**
305
+ * Metadata values the gateway accepts are scalars only; a nested object or
306
+ * array would be rejected by the binding, so drop those rather than forward
307
+ * them.
308
+ */
309
+ function asMetadata(value) {
310
+ const record = asRecord(value);
311
+ if (record === void 0) return void 0;
312
+ const result = {};
313
+ for (const [key, entry] of Object.entries(record)) if (entry === null || typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean") result[key] = entry;
314
+ return result;
315
+ }
316
+ const BACKOFFS = [
317
+ "constant",
318
+ "linear",
319
+ "exponential"
320
+ ];
321
+ /** Reads the retry policy field by field; the binding validates it strictly. */
322
+ function asRetries(value) {
323
+ const record = asRecord(value);
324
+ if (record === void 0) return void 0;
325
+ const maxAttempts = asNumber(record.maxAttempts);
326
+ const backoff = asString(record.backoff);
327
+ const retries = defined({
328
+ backoff: BACKOFFS.find((entry) => entry === backoff),
329
+ maxAttempts: maxAttempts !== void 0 && Number.isInteger(maxAttempts) && maxAttempts >= 1 && maxAttempts <= 5 ? maxAttempts : void 0,
330
+ retryDelayMs: asNumber(record.retryDelayMs)
331
+ });
332
+ return Object.keys(retries).length > 0 ? retries : void 0;
333
+ }
334
+ /** Reads the shared gateway keys out of a loosely-typed record. */
335
+ function parseGatewayFields(raw) {
336
+ return defined({
337
+ cacheKey: asString(raw.cacheKey),
338
+ cacheTtl: asNumber(raw.cacheTtl),
339
+ collectLog: asBoolean(raw.collectLog),
340
+ eventId: asString(raw.eventId),
341
+ id: asString(raw.id),
342
+ metadata: asMetadata(raw.metadata),
343
+ requestTimeoutMs: asNumber(raw.requestTimeoutMs),
344
+ retries: asRetries(raw.retries),
345
+ skipCache: asBoolean(raw.skipCache)
346
+ });
347
+ }
348
+ /**
349
+ * Reads per-call options out of a loosely-typed options bag (the value a
350
+ * framework keeps under {@link PROVIDER_OPTIONS_KEY}).
351
+ *
352
+ * The bag is plain JSON, so every field is read defensively: anything of an
353
+ * unexpected type is dropped rather than thrown, because a malformed
354
+ * observability hint should never fail a generation.
355
+ *
356
+ * @experimental This surface is experimental and may change.
357
+ */
358
+ function parseModelOptions(value) {
359
+ const raw = asRecord(value);
360
+ if (raw === void 0) return void 0;
361
+ const nested = asRecord(raw.gateway);
362
+ const gateway = typeof raw.gateway === "string" ? raw.gateway : nested !== void 0 ? parseGatewayFields(nested) : void 0;
363
+ const fallback = Array.isArray(raw.fallback) ? raw.fallback.filter((entry) => typeof entry === "string") : void 0;
364
+ return defined({
365
+ ...parseGatewayFields(raw),
366
+ chatTemplateKwargs: asRecord(raw.chatTemplateKwargs),
367
+ fallback,
368
+ gateway,
369
+ headers: asStringRecord(raw.headers),
370
+ reasoningEffort: asReasoningEffort(raw.reasoningEffort),
371
+ sessionAffinity: asString(raw.sessionAffinity)
372
+ });
373
+ }
374
+ //#endregion
375
+ //#region src/models/core/transport.ts
376
+ /** The reported `url` for a universal call made through the binding. */
377
+ const UNIVERSAL_BINDING_URL = "ai-gateway-binding";
378
+ /**
379
+ * Drops entries whose value is `undefined`. The AI SDK types call headers as
380
+ * `Record<string, string | undefined>`, and an undefined value reaches the
381
+ * binding as the string "undefined" rather than as an absent header.
382
+ */
383
+ function definedHeaders(headers) {
384
+ const result = {};
385
+ for (const [key, value] of Object.entries(headers ?? {})) if (value !== void 0) result[key] = value;
386
+ return result;
387
+ }
388
+ /** Copies response headers into a plain object. */
389
+ function headersToObject(headers) {
390
+ const result = {};
391
+ headers.forEach((value, key) => {
392
+ result[key] = value;
393
+ });
394
+ return result;
395
+ }
396
+ /**
397
+ * Some run-path responses arrive bare and some wrapped in the Cloudflare API
398
+ * envelope. Unwrap only when the envelope keys are actually present, so a
399
+ * provider payload that happens to have a `result` field is left alone.
400
+ */
401
+ function unwrapEnvelope(value, context) {
402
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
403
+ const record = value;
404
+ if (record.success === false && firstEnvelopeError(record) !== void 0 || "success" in record && "result" in record) {
405
+ if (record.success === true) return record.result;
406
+ throw new CloudflareAIError({
407
+ code: classifyStatus(context?.status, typeof record.name === "string" ? record.name : void 0),
408
+ data: value,
409
+ message: errorMessageFrom(value, context?.status),
410
+ model: context?.model ?? "",
411
+ requestBodyValues: void 0,
412
+ status: context?.status,
413
+ url: context?.url ?? ""
414
+ });
415
+ }
416
+ return value;
417
+ }
418
+ function firstEnvelopeError(record) {
419
+ for (const key of ["error", "errors"]) {
420
+ const value = record[key];
421
+ if (Array.isArray(value) && value.length > 0) {
422
+ const first = value[0];
423
+ if (first !== null && typeof first === "object") return first;
424
+ }
425
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) return value;
426
+ }
427
+ }
428
+ /**
429
+ * Pulls a human message out of any of the error envelopes the platform emits:
430
+ * the gateway envelope (`error: [{ code, message }]`), the Workers AI
431
+ * validation envelope (`message`/`description` only), a bare
432
+ * `{ error: "..." }`, and the "Model ID missing" internal error, which carries
433
+ * only `description`.
434
+ */
435
+ function errorMessageFrom(body, status) {
436
+ const record = body !== null && typeof body === "object" && !Array.isArray(body) ? body : void 0;
437
+ if (record !== void 0) {
438
+ const envelope = firstEnvelopeError(record);
439
+ if (typeof envelope?.message === "string") return envelope.message;
440
+ if (typeof record.error === "string") return record.error;
441
+ if (typeof record.message === "string") return record.message;
442
+ if (typeof record.description === "string") return record.description;
443
+ }
444
+ if (typeof body === "string" && body !== "") return body;
445
+ return `HTTP ${status ?? "error"}`;
446
+ }
447
+ /** Builds a {@link CloudflareAIError} from a non-2xx run-path response. */
448
+ async function errorFromResponse(response, context) {
449
+ const text = await response.text().catch(() => "");
450
+ let body = text;
451
+ try {
452
+ body = text === "" ? void 0 : JSON.parse(text);
453
+ } catch {}
454
+ const record = body !== null && typeof body === "object" && !Array.isArray(body) ? body : void 0;
455
+ const envelopeName = typeof record?.name === "string" ? record.name : void 0;
456
+ const headers = headersToObject(response.headers);
457
+ return new CloudflareAIError({
458
+ code: classifyStatus(response.status, envelopeName),
459
+ data: body,
460
+ logId: headers["cf-aig-log-id"],
461
+ message: errorMessageFrom(body, response.status),
462
+ model: context.model,
463
+ requestBodyValues: context.requestBodyValues,
464
+ responseBody: text,
465
+ responseHeaders: headers,
466
+ status: response.status,
467
+ url: context.url
468
+ });
469
+ }
470
+ /**
471
+ * The gateway's own options for the universal call. The id is not among them:
472
+ * it selects the gateway (`env.AI.gateway(id)`), and the binding's own `id`
473
+ * field is deprecated.
474
+ */
475
+ function universalGatewayOptions(gateway) {
476
+ const options = { id: gateway.id };
477
+ if (gateway.skipCache !== void 0) options.skipCache = gateway.skipCache;
478
+ if (gateway.cacheTtl !== void 0) options.cacheTtl = gateway.cacheTtl;
479
+ if (gateway.cacheKey !== void 0) options.cacheKey = gateway.cacheKey;
480
+ if (gateway.metadata !== void 0) options.metadata = gateway.metadata;
481
+ if (gateway.collectLog !== void 0) options.collectLog = gateway.collectLog;
482
+ if (gateway.eventId !== void 0) options.eventId = gateway.eventId;
483
+ if (gateway.requestTimeoutMs !== void 0) options.requestTimeoutMs = gateway.requestTimeoutMs;
484
+ if (gateway.retries !== void 0) options.retries = gateway.retries;
485
+ return options;
486
+ }
487
+ /**
488
+ * Whether a parsed body is the gateway's own error envelope rather than a
489
+ * vendor payload — `{ success: false, error: [{ code, message }] }`, named
490
+ * `AiGatewayError`. The gateway answers this for its own failures (no such
491
+ * gateway, unified billing unpaid, provider not configured); everything else
492
+ * on this path is the vendor's own error, which the vendor's provider reads.
493
+ *
494
+ * @experimental This surface is experimental and may change.
495
+ */
496
+ function isGatewayErrorEnvelope(json) {
497
+ if (json === null || typeof json !== "object" || Array.isArray(json)) return false;
498
+ const record = json;
499
+ if (record.name === "AiGatewayError") return true;
500
+ if (record.success !== false) return false;
501
+ const errors = record.error ?? record.errors;
502
+ return Array.isArray(errors) && errors.some((entry) => entry !== null && typeof entry === "object" && typeof entry.message === "string");
503
+ }
504
+ /**
505
+ * Builds a {@link CloudflareAIError} from the gateway's own error envelope, so
506
+ * a gateway failure on the universal path classifies and retries exactly as
507
+ * one on the Workers AI run path.
508
+ *
509
+ * @experimental This surface is experimental and may change.
510
+ */
511
+ function errorFromGatewayEnvelope(body, context) {
512
+ const record = body !== null && typeof body === "object" && !Array.isArray(body) ? body : void 0;
513
+ return new CloudflareAIError({
514
+ code: classifyStatus(context.status, typeof record?.name === "string" ? record.name : "AiGatewayError"),
515
+ data: body,
516
+ logId: context.responseHeaders?.["cf-aig-log-id"],
517
+ message: errorMessageFrom(body, context.status),
518
+ model: context.model,
519
+ requestBodyValues: context.requestBodyValues,
520
+ responseBody: context.responseBody,
521
+ responseHeaders: context.responseHeaders,
522
+ status: context.status,
523
+ url: context.url
524
+ });
525
+ }
526
+ /**
527
+ * The model id a universal request is about, for error reporting. Every vendor
528
+ * body carries `model`; the endpoint is the fallback.
529
+ */
530
+ function universalModel(request) {
531
+ const query = request.query;
532
+ if (query !== null && typeof query === "object" && !Array.isArray(query)) {
533
+ const model = query.model;
534
+ if (typeof model === "string") return model;
535
+ }
536
+ return `${request.provider}/${request.endpoint}`;
537
+ }
538
+ /** The keyless in-Worker backend: `env.AI.run(...)`. */
539
+ function bindingTransport(binding) {
540
+ const raw = binding;
541
+ const logIds = /* @__PURE__ */ new WeakMap();
542
+ return {
543
+ logIdFallback(response) {
544
+ return logIds.get(response);
545
+ },
546
+ async run(request) {
547
+ try {
548
+ const response = await raw.run(request.model, request.input, {
549
+ ...Object.keys(request.headers).length > 0 ? { extraHeaders: request.headers } : {},
550
+ gateway: request.gateway,
551
+ returnRawResponse: true,
552
+ signal: request.signal
553
+ });
554
+ const logId = binding.aiGatewayLogId;
555
+ if (logId !== null && logId !== void 0) logIds.set(response, logId);
556
+ return response;
557
+ } catch (error) {
558
+ throw normalizeThrownError(error, {
559
+ model: request.model,
560
+ requestBodyValues: request.input,
561
+ url: "workers-ai-binding"
562
+ });
563
+ }
564
+ },
565
+ async universal(request) {
566
+ try {
567
+ return await binding.gateway(request.gateway.id).run({
568
+ endpoint: request.endpoint,
569
+ headers: request.headers,
570
+ provider: request.provider,
571
+ query: request.query
572
+ }, {
573
+ ...request.extraHeaders === void 0 ? {} : { extraHeaders: request.extraHeaders },
574
+ gateway: universalGatewayOptions(request.gateway),
575
+ signal: request.signal
576
+ });
577
+ } catch (error) {
578
+ throw normalizeThrownError(error, {
579
+ model: universalModel(request),
580
+ requestBodyValues: request.query,
581
+ url: UNIVERSAL_BINDING_URL
582
+ });
583
+ }
584
+ },
585
+ url: "workers-ai-binding"
586
+ };
587
+ }
588
+ /**
589
+ * Builds the transport for a set of provider settings. The model id, not the
590
+ * caller, decides the wire format; the transport only decides how the request
591
+ * physically leaves the Worker.
592
+ *
593
+ * @experimental This surface is experimental and may change.
594
+ */
595
+ function createTransport(settings) {
596
+ if (settings.binding === void 0) throw new Error("createAI requires { binding }: the Workers AI binding.");
597
+ return bindingTransport(settings.binding);
598
+ }
599
+ //#endregion
600
+ //#region src/models/core/gateway-providers.ts
601
+ /**
602
+ * The AI Gateway provider table: which gateway provider slug a vendor's own
603
+ * base URL belongs to, and what the universal request's `endpoint` is for a
604
+ * given upstream URL.
605
+ *
606
+ * This is platform knowledge, not vendor knowledge — the gateway publishes the
607
+ * list, and every entry here appears in `AIGatewayProviders` from
608
+ * `@cloudflare/workers-types`. Nothing about a vendor's wire format, model ids
609
+ * or capabilities lives here: the vendor's own provider package builds the
610
+ * request, and this table only says where it goes.
611
+ *
612
+ * Adapted from `@cloudflare/ai-gateway-provider`'s `providers.ts`.
613
+ *
614
+ * @experimental This surface is experimental and may change.
615
+ */
616
+ /**
617
+ * The gateway provider slugs this table maps onto. A subset of the ambient
618
+ * `AIGatewayProviders` union; {@link GatewayProviderNameCheck} keeps it one.
619
+ *
620
+ * @experimental This surface is experimental and may change.
621
+ */
622
+ const GATEWAY_PROVIDER_NAMES = [
623
+ "workers-ai",
624
+ "anthropic",
625
+ "openai",
626
+ "azure-openai",
627
+ "google-ai-studio",
628
+ "google-vertex-ai",
629
+ "grok",
630
+ "mistral",
631
+ "perplexity-ai",
632
+ "replicate",
633
+ "groq",
634
+ "deepseek",
635
+ "openrouter",
636
+ "cerebras",
637
+ "cohere",
638
+ "huggingface",
639
+ "aws-bedrock",
640
+ "elevenlabs",
641
+ "cartesia"
642
+ ];
643
+ /** The default credential header; providers add their own on top. */
644
+ const AUTHORIZATION = ["authorization"];
645
+ /** Path after the host, leading slash removed, query string kept. */
646
+ function pathOf(url) {
647
+ return `${url.pathname.replace(/^\/+/, "")}${url.search}`;
648
+ }
649
+ function buildProvider(spec) {
650
+ const map = spec.endpoint ?? ((url) => {
651
+ const path = pathOf(url);
652
+ return spec.pathPrefix === void 0 ? path : path.replace(spec.pathPrefix, "");
653
+ });
654
+ return {
655
+ authHeaders: spec.authHeaders ?? AUTHORIZATION,
656
+ endpoint(url) {
657
+ const parsed = parseUrl(url);
658
+ if (parsed === void 0 || !spec.host.test(parsed.host)) return;
659
+ return map(parsed);
660
+ },
661
+ host: spec.host,
662
+ name: spec.name
663
+ };
664
+ }
665
+ function parseUrl(url) {
666
+ try {
667
+ return new URL(url);
668
+ } catch {
669
+ return;
670
+ }
671
+ }
672
+ /**
673
+ * Azure names the deployment in the path; the gateway names it in the
674
+ * endpoint as `<resource>/<deployment>/<rest>`.
675
+ */
676
+ function azureEndpoint(url) {
677
+ const resource = /^([^.]+)\.openai\.azure\.com$/.exec(url.host)?.[1];
678
+ const rest = /^\/openai\/deployments\/([^/]+)\/(.+)$/.exec(url.pathname);
679
+ if (resource === void 0 || rest === null) return void 0;
680
+ return `${resource}/${rest[1]}/${rest[2]}${url.search}`;
681
+ }
682
+ /**
683
+ * Bedrock's region and service live in the host; the gateway wants them as the
684
+ * first two endpoint segments.
685
+ */
686
+ function bedrockEndpoint(url) {
687
+ const match = /^(bedrock[a-z0-9-]*)\.([a-z0-9-]+)\.amazonaws\.com$/.exec(url.host);
688
+ if (match === null) return void 0;
689
+ return `${match[1]}/${match[2]}/${pathOf(url)}`;
690
+ }
691
+ /**
692
+ * The provider table, in declaration order. Hosts do not overlap, so the order
693
+ * only decides which row a future overlapping host would win.
694
+ *
695
+ * @experimental This surface is experimental and may change.
696
+ */
697
+ const GATEWAY_PROVIDERS = [
698
+ {
699
+ host: /^api\.cloudflare\.com$/,
700
+ name: "workers-ai",
701
+ pathPrefix: /^client\/v4\/accounts\/[^/]+\/ai\//
702
+ },
703
+ {
704
+ authHeaders: ["authorization", "x-api-key"],
705
+ host: /^api\.anthropic\.com$/,
706
+ name: "anthropic"
707
+ },
708
+ {
709
+ host: /^api\.openai\.com$/,
710
+ name: "openai"
711
+ },
712
+ {
713
+ authHeaders: ["authorization", "api-key"],
714
+ endpoint: azureEndpoint,
715
+ host: /^[^.]+\.openai\.azure\.com$/,
716
+ name: "azure-openai"
717
+ },
718
+ {
719
+ authHeaders: ["authorization", "x-goog-api-key"],
720
+ host: /^generativelanguage\.googleapis\.com$/,
721
+ name: "google-ai-studio"
722
+ },
723
+ {
724
+ host: /^(?:[a-z0-9-]+-)?aiplatform\.googleapis\.com$/,
725
+ name: "google-vertex-ai"
726
+ },
727
+ {
728
+ host: /^api\.x\.ai$/,
729
+ name: "grok"
730
+ },
731
+ {
732
+ host: /^api\.mistral\.ai$/,
733
+ name: "mistral"
734
+ },
735
+ {
736
+ host: /^api\.perplexity\.ai$/,
737
+ name: "perplexity-ai"
738
+ },
739
+ {
740
+ host: /^api\.replicate\.com$/,
741
+ name: "replicate"
742
+ },
743
+ {
744
+ host: /^api\.groq\.com$/,
745
+ name: "groq",
746
+ pathPrefix: /^openai\/v1\//
747
+ },
748
+ {
749
+ host: /^api\.deepseek\.com$/,
750
+ name: "deepseek"
751
+ },
752
+ {
753
+ host: /^openrouter\.ai$/,
754
+ name: "openrouter",
755
+ pathPrefix: /^api\//
756
+ },
757
+ {
758
+ host: /^api\.cerebras\.ai$/,
759
+ name: "cerebras"
760
+ },
761
+ {
762
+ host: /^api\.cohere\.(?:ai|com)$/,
763
+ name: "cohere"
764
+ },
765
+ {
766
+ host: /^(?:api-inference|router)\.huggingface\.co$/,
767
+ name: "huggingface",
768
+ pathPrefix: /^models\//
769
+ },
770
+ {
771
+ endpoint: bedrockEndpoint,
772
+ host: /^bedrock.*\.amazonaws\.com$/,
773
+ name: "aws-bedrock"
774
+ },
775
+ {
776
+ host: /^api\.elevenlabs\.io$/,
777
+ name: "elevenlabs"
778
+ },
779
+ {
780
+ host: /^api\.cartesia\.ai$/,
781
+ name: "cartesia"
782
+ }
783
+ ].map(buildProvider);
784
+ /**
785
+ * The table row for a bare host (`api.anthropic.com`) or for anything a `URL`
786
+ * can be built from. pi-ai models carry a `baseUrl` rather than a full request
787
+ * URL, so both spellings resolve.
788
+ *
789
+ * @experimental This surface is experimental and may change.
790
+ */
791
+ function gatewayProviderForHost(hostOrUrl) {
792
+ const host = parseUrl(hostOrUrl)?.host ?? hostOf(hostOrUrl);
793
+ if (host === void 0) return void 0;
794
+ return GATEWAY_PROVIDERS.find((provider) => provider.host.test(host));
795
+ }
796
+ /** Reads a host out of a bare host, `host/path` or `//host` spelling. */
797
+ function hostOf(value) {
798
+ const host = value.replace(/^\/\//, "").split("/")[0];
799
+ return host === "" ? void 0 : host;
800
+ }
801
+ /**
802
+ * Maps a vendor request URL onto `{ provider, endpoint, authHeaders }` for the
803
+ * universal gateway request, or `undefined` when the host is not a gateway
804
+ * provider.
805
+ *
806
+ * @experimental This surface is experimental and may change.
807
+ */
808
+ function resolveGatewayProvider(url) {
809
+ const parsed = parseUrl(url);
810
+ if (parsed === void 0) return void 0;
811
+ for (const provider of GATEWAY_PROVIDERS) {
812
+ if (!provider.host.test(parsed.host)) continue;
813
+ const endpoint = provider.endpoint(url);
814
+ if (endpoint === void 0) continue;
815
+ return {
816
+ authHeaders: provider.authHeaders,
817
+ endpoint,
818
+ provider: provider.name
819
+ };
820
+ }
821
+ }
822
+ /**
823
+ * {@link resolveGatewayProvider}, throwing a `TypeError` that names the host
824
+ * and the provider list when the URL belongs to no gateway provider.
825
+ *
826
+ * @experimental This surface is experimental and may change.
827
+ */
828
+ function requireGatewayProvider(url) {
829
+ const resolved = resolveGatewayProvider(url);
830
+ if (resolved !== void 0) return resolved;
831
+ const host = parseUrl(url)?.host ?? url;
832
+ throw new TypeError(`AI Gateway has no provider for ${host}. Requests are routed by host; the supported providers are ${GATEWAY_PROVIDER_NAMES.join(", ")}.`);
833
+ }
834
+ /** The host a gateway-shaped base URL points at. */
835
+ const GATEWAY_HOST = "gateway.ai.cloudflare.com";
836
+ /**
837
+ * The provider slug of a gateway-shaped base URL —
838
+ * `https://gateway.ai.cloudflare.com/v1/{account}/{gateway}/{slug}`. Generated
839
+ * registries write models that way (pi-ai's Cloudflare AI Gateway catalog
840
+ * leaves the account and gateway as literal placeholders), so the slug is
841
+ * already in the path rather than implied by a vendor host. The account and
842
+ * gateway segments are ignored: the binding has already chosen both.
843
+ */
844
+ function gatewayShapedSlug(url) {
845
+ if (url.host !== GATEWAY_HOST) return void 0;
846
+ const segments = url.pathname.split("/").filter((part) => part !== "");
847
+ if (segments[0] !== "v1" || segments.length < 4) return void 0;
848
+ return segments[3];
849
+ }
850
+ /**
851
+ * Whether a base URL is written in the gateway's own shape
852
+ * (`https://gateway.ai.cloudflare.com/v1/{account}/{gateway}/{slug}`) rather
853
+ * than as a vendor's. A generated Cloudflare registry writes models that way,
854
+ * and the path after the slug is then the vendor's own, so a caller that
855
+ * derives an endpoint from a base URL has to tell the two apart.
856
+ *
857
+ * @experimental This surface is experimental and may change.
858
+ */
859
+ function isGatewayShapedUrl(url) {
860
+ return parseUrl(url)?.host === GATEWAY_HOST;
861
+ }
862
+ function isGatewayProviderName(slug) {
863
+ return GATEWAY_PROVIDER_NAMES.includes(slug);
864
+ }
865
+ /**
866
+ * The gateway provider slug a model's base URL belongs to. Two spellings
867
+ * resolve, because two kinds of registry write them:
868
+ *
869
+ * - a vendor's own base URL (`https://api.anthropic.com` → `anthropic`), which
870
+ * is what a provider package configured for the vendor carries;
871
+ * - a gateway-shaped base URL
872
+ * (`https://gateway.ai.cloudflare.com/v1/{account}/{gateway}/anthropic` →
873
+ * `anthropic`), which is what a generated Cloudflare registry carries.
874
+ *
875
+ * A base URL neither shape resolves is a wiring mistake rather than a request
876
+ * failure, so it throws a `TypeError` naming what was not recognised.
877
+ *
878
+ * @experimental This surface is experimental and may change.
879
+ */
880
+ function gatewaySlugForBaseUrl(baseUrl) {
881
+ const url = parseUrl(baseUrl);
882
+ const shaped = url === void 0 ? void 0 : gatewayShapedSlug(url);
883
+ if (shaped !== void 0) {
884
+ if (isGatewayProviderName(shaped)) return shaped;
885
+ throw new TypeError(`AI Gateway has no provider "${shaped}" (from ${baseUrl}). The supported providers are ${GATEWAY_PROVIDER_NAMES.join(", ")}.`);
886
+ }
887
+ const provider = gatewayProviderForHost(baseUrl);
888
+ if (provider !== void 0) return provider.name;
889
+ throw new TypeError(`AI Gateway has no provider for ${url?.host ?? baseUrl}. Requests are routed by the model's base URL; the supported providers are ${GATEWAY_PROVIDER_NAMES.join(", ")}.`);
890
+ }
891
+ //#endregion
892
+ //#region src/models/core/chat-completions/quirks.ts
893
+ const STRICT_OPENAI = {
894
+ chatTemplateKwargs: "unsupported",
895
+ imageUrls: "any",
896
+ jsonSchema: "supported",
897
+ jsonSchemaStrict: false,
898
+ maxTokensField: "max_tokens",
899
+ modelEchoSuffix: void 0,
900
+ nullAssistantContent: "allowed",
901
+ reasoningEffortLevels: "basic",
902
+ reasoningOff: "unsupported",
903
+ reasoningOffStreamLabel: "content",
904
+ reasoningReplay: "drop",
905
+ requiresAssistantAfterToolResult: false,
906
+ toolCallIds: "any",
907
+ toolChoiceRequired: "supported"
908
+ };
909
+ /** Workers AI's vLLM front end: what every `@cf/` model has in common. */
910
+ const WORKERS_AI = {
911
+ ...STRICT_OPENAI,
912
+ chatTemplateKwargs: "supported",
913
+ imageUrls: "data-only",
914
+ nullAssistantContent: "empty-string",
915
+ reasoningOff: "chat-template",
916
+ reasoningReplay: "reasoning_content"
917
+ };
918
+ /**
919
+ * Longest-prefix overrides. Keep each one evidence-backed (the capture that
920
+ * proves it is named in the comment) and delete it when the platform fixes
921
+ * the deviation.
922
+ */
923
+ const OVERRIDES = [
924
+ ["@cf/zai-org/glm-4.7-flash", { toolChoiceRequired: "named-fallback" }],
925
+ ["@cf/moonshotai/", { reasoningOff: "unsupported" }],
926
+ ["@cf/openai/", { reasoningOff: "unsupported" }],
927
+ ["@cf/mistralai/", {
928
+ chatTemplateKwargs: "unsupported",
929
+ jsonSchema: "unsupported",
930
+ reasoningOff: "unsupported",
931
+ requiresAssistantAfterToolResult: true,
932
+ toolCallIds: "alnum-9"
933
+ }],
934
+ ["@cf/google/", { modelEchoSuffix: "-external" }],
935
+ ["@cf/nvidia/nemotron-", { reasoningOffStreamLabel: "reasoning" }]
936
+ ];
937
+ /** The quirks for a catalog id: base profile plus the longest matching override. */
938
+ function quirksFor(modelId) {
939
+ const base = modelId.startsWith("@cf/") ? WORKERS_AI : STRICT_OPENAI;
940
+ let best;
941
+ for (const entry of OVERRIDES) if (modelId.startsWith(entry[0]) && (best === void 0 || entry[0].length > best[0].length)) best = entry;
942
+ return best === void 0 ? base : {
943
+ ...base,
944
+ ...best[1]
945
+ };
946
+ }
947
+ /** Whether an id is a Workers AI model rather than a third-party catalog model. */
948
+ function isWorkersAI(modelId) {
949
+ return modelId.startsWith("@cf/");
950
+ }
951
+ //#endregion
952
+ //#region src/models/core/chat-completions/request.ts
953
+ /**
954
+ * Rewrites a strict OpenAI chat-completions request body into what a given
955
+ * catalog model accepts. Frameworks build the strict body from their own
956
+ * prompt types; everything Cloudflare- or vendor-specific happens here, once.
957
+ *
958
+ * The rewrite never throws: a feature the model cannot take is dropped and
959
+ * reported as a {@link CompatWarning}.
960
+ */
961
+ function isRecord$2(value) {
962
+ return value !== null && typeof value === "object" && !Array.isArray(value);
963
+ }
964
+ function clampEffort(effort) {
965
+ if (effort === "minimal") return "low";
966
+ if (effort === "xhigh") return "high";
967
+ return effort;
968
+ }
969
+ /** Drops URL image parts (keeps inline data URLs) from a user message. */
970
+ function stripImageUrls(message, warnings) {
971
+ if (!Array.isArray(message.content)) return message;
972
+ let dropped = 0;
973
+ const content = message.content.filter((part) => {
974
+ if (!isRecord$2(part) || part.type !== "image_url") return true;
975
+ const image = isRecord$2(part.image_url) ? part.image_url : void 0;
976
+ if ((typeof image?.url === "string" ? image.url : "").startsWith("data:")) return true;
977
+ dropped += 1;
978
+ return false;
979
+ });
980
+ if (dropped > 0) warnings.push({
981
+ feature: "image-url",
982
+ message: `Workers AI models need image bytes; ${dropped} image URL${dropped === 1 ? " was" : "s were"} dropped.`
983
+ });
984
+ return {
985
+ ...message,
986
+ content
987
+ };
988
+ }
989
+ /** Nine `[A-Za-z0-9]` characters: what Mistral accepts as a tool-call id. */
990
+ const CONFORMANT_TOOL_CALL_ID = /^[0-9A-Za-z]{9}$/;
991
+ const TOOL_CALL_ID_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
992
+ /**
993
+ * A deterministic nine-character id for an original id: the same conversation
994
+ * replayed twice produces the same ids, so a provider cache or a log stays
995
+ * comparable. `salt` is bumped only to break a collision.
996
+ */
997
+ function hashedToolCallId(original, salt) {
998
+ let state = (2166136261 ^ salt) >>> 0;
999
+ for (let index = 0; index < original.length; index += 1) state = Math.imul(state ^ original.charCodeAt(index), 16777619) >>> 0;
1000
+ let id = "";
1001
+ for (let index = 0; index < 9; index += 1) {
1002
+ state = Math.imul(state ^ state >>> 15, 16777619) >>> 0;
1003
+ id += TOOL_CALL_ID_ALPHABET[state % 62];
1004
+ }
1005
+ return id;
1006
+ }
1007
+ /** Every tool-call id a message set mentions, in the order it mentions them. */
1008
+ function toolCallIdsIn(messages) {
1009
+ const ids = [];
1010
+ const seen = /* @__PURE__ */ new Set();
1011
+ const add = (value) => {
1012
+ if (typeof value !== "string" || seen.has(value)) return;
1013
+ seen.add(value);
1014
+ ids.push(value);
1015
+ };
1016
+ for (const message of messages) {
1017
+ if (!isRecord$2(message)) continue;
1018
+ if (Array.isArray(message.tool_calls)) {
1019
+ for (const call of message.tool_calls) if (isRecord$2(call)) add(call.id);
1020
+ }
1021
+ add(message.tool_call_id);
1022
+ }
1023
+ return ids;
1024
+ }
1025
+ /**
1026
+ * Plans the rewrite: which ids have to change, and to what. Ids that already
1027
+ * conform are kept exactly as they are — including as reserved values, so a
1028
+ * rewritten id can never collide with one.
1029
+ */
1030
+ function planToolCallIds(messages) {
1031
+ const plan = /* @__PURE__ */ new Map();
1032
+ const ids = toolCallIdsIn(messages);
1033
+ const taken = new Set(ids.filter((id) => CONFORMANT_TOOL_CALL_ID.test(id)));
1034
+ for (const id of ids) {
1035
+ if (taken.has(id)) continue;
1036
+ let replacement = hashedToolCallId(id, 0);
1037
+ for (let salt = 1; taken.has(replacement) && salt <= 64; salt += 1) replacement = hashedToolCallId(id, salt);
1038
+ taken.add(replacement);
1039
+ plan.set(id, replacement);
1040
+ }
1041
+ return plan;
1042
+ }
1043
+ /** Applies the plan to an assistant `tool_calls` turn or a `tool` result. */
1044
+ function rewriteToolCallIds(message, plan) {
1045
+ let rewritten = message;
1046
+ if (Array.isArray(rewritten.tool_calls)) rewritten = {
1047
+ ...rewritten,
1048
+ tool_calls: rewritten.tool_calls.map((call) => {
1049
+ if (!isRecord$2(call) || typeof call.id !== "string") return call;
1050
+ const replacement = plan.get(call.id);
1051
+ return replacement === void 0 ? call : {
1052
+ ...call,
1053
+ id: replacement
1054
+ };
1055
+ })
1056
+ };
1057
+ if (typeof rewritten.tool_call_id === "string") {
1058
+ const replacement = plan.get(rewritten.tool_call_id);
1059
+ if (replacement !== void 0) rewritten = {
1060
+ ...rewritten,
1061
+ tool_call_id: replacement
1062
+ };
1063
+ }
1064
+ return rewritten;
1065
+ }
1066
+ /**
1067
+ * Applies the model's quirks to a strict OpenAI chat-completions body.
1068
+ *
1069
+ * Conventions the strict body follows, which this function translates:
1070
+ * - `max_tokens` is the cap; it becomes `max_completion_tokens` where needed.
1071
+ * - `reasoning_effort: null` means "turn reasoning off".
1072
+ * - Replayed assistant reasoning sits in `reasoning_content`.
1073
+ * - `response_format.json_schema` is the OpenAI `{ name, schema }` envelope.
1074
+ * - `chat_template_kwargs` may be present; it is only meaningful on Workers AI.
1075
+ */
1076
+ function prepareChatCompletionsRequest(input, modelId) {
1077
+ const quirks = quirksFor(modelId);
1078
+ const warnings = [];
1079
+ const body = { ...input };
1080
+ if (quirks.maxTokensField === "max_completion_tokens" && "max_tokens" in body) {
1081
+ body.max_completion_tokens = body.max_tokens;
1082
+ delete body.max_tokens;
1083
+ }
1084
+ if ("reasoning_effort" in body) {
1085
+ const effort = body.reasoning_effort;
1086
+ if (effort === null) {
1087
+ delete body.reasoning_effort;
1088
+ if (quirks.reasoningOff === "chat-template") body.chat_template_kwargs = {
1089
+ ...isRecord$2(body.chat_template_kwargs) ? body.chat_template_kwargs : {},
1090
+ enable_thinking: false
1091
+ };
1092
+ else warnings.push({
1093
+ feature: "reasoning-off",
1094
+ message: `Reasoning cannot be turned off on ${modelId}; the request was sent with the model's default effort.`
1095
+ });
1096
+ } else if (typeof effort === "string" && quirks.reasoningEffortLevels === "basic") body.reasoning_effort = clampEffort(effort);
1097
+ }
1098
+ if ("chat_template_kwargs" in body && quirks.chatTemplateKwargs === "unsupported") {
1099
+ delete body.chat_template_kwargs;
1100
+ warnings.push({
1101
+ feature: "chat-template-kwargs",
1102
+ message: `${modelId} does not take chat_template_kwargs; they were dropped.`
1103
+ });
1104
+ }
1105
+ if (isRecord$2(body.response_format)) {
1106
+ if (quirks.jsonSchema === "unsupported") {
1107
+ delete body.response_format;
1108
+ warnings.push({
1109
+ feature: "response-format",
1110
+ message: `${modelId} does not support response_format; the request was sent without it.`
1111
+ });
1112
+ } else if (body.response_format.type === "json_schema" && isRecord$2(body.response_format.json_schema)) {
1113
+ const { strict: _strict, ...envelope } = body.response_format.json_schema;
1114
+ body.response_format = {
1115
+ ...body.response_format,
1116
+ json_schema: quirks.jsonSchemaStrict ? {
1117
+ ...envelope,
1118
+ strict: true
1119
+ } : envelope
1120
+ };
1121
+ }
1122
+ }
1123
+ if (body.tool_choice === "required" && quirks.toolChoiceRequired === "named-fallback") {
1124
+ const tools = Array.isArray(body.tools) ? body.tools : [];
1125
+ const only = tools.length === 1 && isRecord$2(tools[0]) ? tools[0] : void 0;
1126
+ const name = only !== void 0 && isRecord$2(only.function) && typeof only.function.name === "string" ? only.function.name : void 0;
1127
+ if (name !== void 0) body.tool_choice = {
1128
+ function: { name },
1129
+ type: "function"
1130
+ };
1131
+ else {
1132
+ delete body.tool_choice;
1133
+ warnings.push({
1134
+ feature: "tool-choice-required",
1135
+ message: `${modelId} rejects tool_choice "required"; the model was left free to choose.`
1136
+ });
1137
+ }
1138
+ }
1139
+ if (Array.isArray(body.messages)) {
1140
+ const toolCallIds = quirks.toolCallIds === "alnum-9" ? planToolCallIds(body.messages) : /* @__PURE__ */ new Map();
1141
+ if (toolCallIds.size > 0) warnings.push({
1142
+ feature: "tool-call-id",
1143
+ message: `${modelId} needs 9-character alphanumeric tool-call ids; ${toolCallIds.size} id${toolCallIds.size === 1 ? " was" : "s were"} rewritten.`
1144
+ });
1145
+ const messages = [];
1146
+ let previousRole;
1147
+ for (const entry of body.messages) {
1148
+ if (!isRecord$2(entry)) continue;
1149
+ let message = entry;
1150
+ if (toolCallIds.size > 0) message = rewriteToolCallIds(message, toolCallIds);
1151
+ if (message.role === "user" && quirks.imageUrls === "data-only") message = stripImageUrls(message, warnings);
1152
+ if (message.role === "assistant" && message.content === null && quirks.nullAssistantContent === "empty-string") message = {
1153
+ ...message,
1154
+ content: ""
1155
+ };
1156
+ if (message.role === "assistant" && "reasoning_content" in message) {
1157
+ if (quirks.reasoningReplay === "drop") {
1158
+ const { reasoning_content: _reasoning, ...rest } = message;
1159
+ message = rest;
1160
+ warnings.push({
1161
+ feature: "assistant-reasoning",
1162
+ message: `${modelId} has no field for replayed reasoning; the assistant reasoning was dropped.`
1163
+ });
1164
+ }
1165
+ }
1166
+ if (quirks.requiresAssistantAfterToolResult && previousRole === "tool" && message.role === "user") messages.push({
1167
+ content: "",
1168
+ role: "assistant"
1169
+ });
1170
+ messages.push(message);
1171
+ previousRole = message.role;
1172
+ }
1173
+ body.messages = messages;
1174
+ }
1175
+ return {
1176
+ body,
1177
+ reasoningOff: isRecord$2(body.chat_template_kwargs) && body.chat_template_kwargs.enable_thinking === false,
1178
+ warnings
1179
+ };
1180
+ }
1181
+ //#endregion
1182
+ //#region src/models/core/chat-completions/response.ts
1183
+ /**
1184
+ * Normalizes a chat-completions response body from the run path into the
1185
+ * strict OpenAI `chat.completion` shape, so framework parsers never see
1186
+ * Workers AI's native envelope or its field spellings.
1187
+ */
1188
+ function isRecord$1(value) {
1189
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1190
+ }
1191
+ /** Native `{ name, arguments: object }` → OpenAI `{ id, type, function }`. */
1192
+ function normalizeToolCall(entry, index) {
1193
+ if (!isRecord$1(entry)) return void 0;
1194
+ const fn = isRecord$1(entry.function) ? entry.function : void 0;
1195
+ const name = typeof fn?.name === "string" ? fn.name : typeof entry.name === "string" ? entry.name : void 0;
1196
+ if (name === void 0) return void 0;
1197
+ const rawArguments = fn !== void 0 && "arguments" in fn ? fn.arguments : entry.arguments;
1198
+ const args = typeof rawArguments === "string" ? rawArguments : rawArguments === void 0 || rawArguments === null ? "{}" : JSON.stringify(rawArguments);
1199
+ return {
1200
+ ...typeof entry.index === "number" ? { index: entry.index } : { index },
1201
+ ...typeof entry.id === "string" ? { id: entry.id } : {},
1202
+ function: {
1203
+ arguments: args,
1204
+ name
1205
+ },
1206
+ type: "function",
1207
+ ...entry.extra_content !== void 0 ? { extra_content: entry.extra_content } : {}
1208
+ };
1209
+ }
1210
+ /** Text as the model produced it; structured-output models may return an object. */
1211
+ function contentText(value) {
1212
+ if (typeof value === "string") return value;
1213
+ if (value === null || value === void 0) return null;
1214
+ return JSON.stringify(value);
1215
+ }
1216
+ function stripEchoSuffix$1(model, modelId) {
1217
+ const suffix = quirksFor(modelId).modelEchoSuffix;
1218
+ if (typeof model !== "string" || suffix === void 0) return model;
1219
+ return model.endsWith(suffix) ? model.slice(0, -suffix.length) : model;
1220
+ }
1221
+ /** Puts reasoning under `reasoning_content` whichever spelling the model used. */
1222
+ function normalizeMessage(message) {
1223
+ const out = { ...message };
1224
+ const reasoning = typeof out.reasoning_content === "string" && out.reasoning_content !== "" ? out.reasoning_content : typeof out.reasoning === "string" && out.reasoning !== "" ? out.reasoning : void 0;
1225
+ delete out.reasoning;
1226
+ if (reasoning !== void 0) out.reasoning_content = reasoning;
1227
+ else delete out.reasoning_content;
1228
+ if ("content" in out) out.content = contentText(out.content);
1229
+ if (Array.isArray(out.tool_calls)) {
1230
+ const calls = out.tool_calls.map((call, index) => normalizeToolCall(call, index)).filter((call) => call !== void 0);
1231
+ if (calls.length > 0) out.tool_calls = calls;
1232
+ else delete out.tool_calls;
1233
+ }
1234
+ return out;
1235
+ }
1236
+ /**
1237
+ * Returns the body in the strict `chat.completion` shape. Already-OpenAI
1238
+ * bodies come back with their reasoning spelling unified and the echoed model
1239
+ * id cleaned; native Workers AI `{ response, tool_calls, usage }` bodies are
1240
+ * lifted into `choices[0]`.
1241
+ */
1242
+ function normalizeChatCompletion(body, modelId) {
1243
+ if (!isRecord$1(body)) return { choices: [] };
1244
+ if (Array.isArray(body.choices)) {
1245
+ const choices = body.choices.map((choice) => {
1246
+ if (!isRecord$1(choice)) return choice;
1247
+ const message = isRecord$1(choice.message) ? normalizeMessage(choice.message) : choice.message;
1248
+ const hasToolCalls = isRecord$1(message) && Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
1249
+ return {
1250
+ ...choice,
1251
+ message,
1252
+ finish_reason: choice.finish_reason ?? (hasToolCalls ? "tool_calls" : "stop")
1253
+ };
1254
+ });
1255
+ return {
1256
+ ...body,
1257
+ choices,
1258
+ model: stripEchoSuffix$1(body.model, modelId)
1259
+ };
1260
+ }
1261
+ if ("response" in body || "tool_calls" in body) {
1262
+ const toolCalls = Array.isArray(body.tool_calls) ? body.tool_calls.map((call, index) => normalizeToolCall(call, index)).filter((call) => call !== void 0) : [];
1263
+ const { response, tool_calls: _toolCalls, usage, ...rest } = body;
1264
+ return {
1265
+ ...rest,
1266
+ choices: [{
1267
+ finish_reason: typeof body.finish_reason === "string" ? body.finish_reason : toolCalls.length > 0 ? "tool_calls" : "stop",
1268
+ index: 0,
1269
+ message: {
1270
+ content: contentText(response),
1271
+ role: "assistant",
1272
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
1273
+ }
1274
+ }],
1275
+ model: stripEchoSuffix$1(body.model, modelId),
1276
+ ...usage !== void 0 ? { usage } : {}
1277
+ };
1278
+ }
1279
+ return body;
1280
+ }
1281
+ //#endregion
1282
+ //#region src/models/core/chat-completions/stream.ts
1283
+ /**
1284
+ * Normalizes a chat-completions SSE stream from the run path into strict
1285
+ * OpenAI `chat.completion.chunk` events.
1286
+ *
1287
+ * Every Workers AI model streams the same three-part hybrid (live captures,
1288
+ * all families): OpenAI-shaped delta chunks that each carry a per-delta
1289
+ * `usage`, then a `choices: []` heartbeat, then a native
1290
+ * `{ response: "", usage }` tail with the cumulative usage, then `[DONE]`.
1291
+ * Some models also emit native `{ response: "<text>" }` deltas or top-level
1292
+ * `tool_calls`. After this transform a consumer sees only:
1293
+ *
1294
+ * - delta chunks without per-delta usage and with reasoning under
1295
+ * `reasoning_content`;
1296
+ * - at most one final `choices: []` chunk carrying the cumulative usage
1297
+ * (the OpenAI `stream_options.include_usage` shape);
1298
+ * - a `finish_reason` on some chunk before the end, synthesized when the
1299
+ * model finished cleanly without sending one;
1300
+ * - `[DONE]`.
1301
+ *
1302
+ * Input and output are SSE `data` payloads (strings), so it slots between the
1303
+ * SSE decoder and either framework's OpenAI chunk parser.
1304
+ */
1305
+ function isRecord(value) {
1306
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1307
+ }
1308
+ const NOISE_FIELDS = [
1309
+ "p",
1310
+ "token_ids",
1311
+ "prompt_token_ids",
1312
+ "kv_transfer_params"
1313
+ ];
1314
+ /** Fields the run path adds to every chunk that mean nothing to a consumer. */
1315
+ function stripNoise(chunk) {
1316
+ const out = { ...chunk };
1317
+ for (const key of NOISE_FIELDS) delete out[key];
1318
+ return out;
1319
+ }
1320
+ function stripEchoSuffix(chunk, modelId) {
1321
+ const suffix = quirksFor(modelId).modelEchoSuffix;
1322
+ if (suffix === void 0 || typeof chunk.model !== "string") return chunk;
1323
+ return chunk.model.endsWith(suffix) ? {
1324
+ ...chunk,
1325
+ model: chunk.model.slice(0, -suffix.length)
1326
+ } : chunk;
1327
+ }
1328
+ function normalizeDelta(delta, relabelReasoning) {
1329
+ const out = { ...delta };
1330
+ const reasoning = typeof out.reasoning_content === "string" && out.reasoning_content !== "" ? out.reasoning_content : typeof out.reasoning === "string" && out.reasoning !== "" ? out.reasoning : void 0;
1331
+ delete out.reasoning;
1332
+ delete out.reasoning_content;
1333
+ if (reasoning !== void 0) if (relabelReasoning) out.content = (typeof out.content === "string" ? out.content : "") + reasoning;
1334
+ else out.reasoning_content = reasoning;
1335
+ if (Array.isArray(out.tool_calls)) out.tool_calls = out.tool_calls.map((call, index) => {
1336
+ if (!isRecord(call)) return call;
1337
+ const fn = isRecord(call.function) ? call.function : void 0;
1338
+ const args = fn !== void 0 ? fn.arguments : call.arguments;
1339
+ return fn === void 0 && typeof call.name === "string" || isRecord(args) ? normalizeToolCall(call, index) ?? call : call;
1340
+ });
1341
+ return out;
1342
+ }
1343
+ /**
1344
+ * The transform. Backpressure is preserved: nothing is buffered except the
1345
+ * single pending usage chunk, which must wait for the tail.
1346
+ */
1347
+ function normalizeChatCompletionsStream(modelId, options = {}) {
1348
+ const quirks = quirksFor(modelId);
1349
+ const relabelReasoning = options.reasoningOff === true && quirks.reasoningOffStreamLabel === "reasoning";
1350
+ let id;
1351
+ let model;
1352
+ let created;
1353
+ let sawFinishReason = false;
1354
+ let sawContent = false;
1355
+ let sawToolCalls = false;
1356
+ let sawDone = false;
1357
+ let finalUsage;
1358
+ let finalUsageTemplate;
1359
+ const perDeltaUsage = isWorkersAI(modelId);
1360
+ const envelope = (extra) => ({
1361
+ ...id !== void 0 ? { id } : {},
1362
+ object: "chat.completion.chunk",
1363
+ ...created !== void 0 ? { created } : {},
1364
+ ...model !== void 0 ? { model } : {},
1365
+ ...extra
1366
+ });
1367
+ const emit = (controller, chunk) => {
1368
+ controller.enqueue(JSON.stringify(chunk));
1369
+ };
1370
+ const emitTail = (controller) => {
1371
+ if (!sawFinishReason && sawDone && (sawContent || sawToolCalls)) {
1372
+ emit(controller, envelope({ choices: [{
1373
+ delta: {},
1374
+ finish_reason: sawToolCalls ? "tool_calls" : "stop",
1375
+ index: 0
1376
+ }] }));
1377
+ sawFinishReason = true;
1378
+ }
1379
+ if (finalUsage !== void 0) {
1380
+ emit(controller, {
1381
+ ...finalUsageTemplate ?? envelope({}),
1382
+ choices: [],
1383
+ usage: finalUsage
1384
+ });
1385
+ finalUsage = void 0;
1386
+ }
1387
+ if (sawDone) controller.enqueue("[DONE]");
1388
+ };
1389
+ return new TransformStream({
1390
+ flush(controller) {
1391
+ emitTail(controller);
1392
+ },
1393
+ transform(data, controller) {
1394
+ if (data === "") return;
1395
+ if (data === "[DONE]") {
1396
+ sawDone = true;
1397
+ return;
1398
+ }
1399
+ let parsed;
1400
+ try {
1401
+ parsed = JSON.parse(data);
1402
+ } catch {
1403
+ return;
1404
+ }
1405
+ if (!isRecord(parsed)) return;
1406
+ const chunk = stripEchoSuffix(stripNoise(parsed), modelId);
1407
+ if (typeof chunk.id === "string") id ??= chunk.id;
1408
+ if (typeof chunk.model === "string") model ??= chunk.model;
1409
+ if (typeof chunk.created === "number") created ??= chunk.created;
1410
+ if (isRecord(chunk.error)) {
1411
+ sawFinishReason = true;
1412
+ controller.enqueue(JSON.stringify(chunk));
1413
+ return;
1414
+ }
1415
+ if (Array.isArray(chunk.choices)) {
1416
+ if (chunk.choices.length === 0) {
1417
+ if (isRecord(chunk.usage)) {
1418
+ finalUsage = chunk.usage;
1419
+ finalUsageTemplate = {
1420
+ ...chunk,
1421
+ usage: void 0
1422
+ };
1423
+ delete finalUsageTemplate.usage;
1424
+ }
1425
+ return;
1426
+ }
1427
+ const choices = chunk.choices.map((choice) => {
1428
+ if (!isRecord(choice)) return choice;
1429
+ if (typeof choice.finish_reason === "string") sawFinishReason = true;
1430
+ const delta = isRecord(choice.delta) ? normalizeDelta(choice.delta, relabelReasoning) : choice.delta;
1431
+ if (isRecord(delta)) {
1432
+ if (typeof delta.content === "string" && delta.content !== "") sawContent = true;
1433
+ if (typeof delta.reasoning_content === "string") sawContent = true;
1434
+ if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) sawToolCalls = true;
1435
+ }
1436
+ if (!perDeltaUsage) return {
1437
+ ...choice,
1438
+ delta
1439
+ };
1440
+ const { usage: _perDeltaUsage, ...rest } = choice;
1441
+ return {
1442
+ ...rest,
1443
+ delta
1444
+ };
1445
+ });
1446
+ if (perDeltaUsage) {
1447
+ const { usage: _usage, ...rest } = chunk;
1448
+ emit(controller, {
1449
+ ...rest,
1450
+ choices
1451
+ });
1452
+ } else emit(controller, {
1453
+ ...chunk,
1454
+ choices
1455
+ });
1456
+ return;
1457
+ }
1458
+ if ("response" in chunk || "tool_calls" in chunk) {
1459
+ const text = chunk.response;
1460
+ const hasText = typeof text === "string" && text !== "";
1461
+ const calls = Array.isArray(chunk.tool_calls) ? chunk.tool_calls.map((call, index) => normalizeToolCall(call, index)).filter((call) => call !== void 0) : [];
1462
+ if (hasText || calls.length > 0) {
1463
+ if (hasText) sawContent = true;
1464
+ if (calls.length > 0) sawToolCalls = true;
1465
+ emit(controller, envelope({ choices: [{
1466
+ delta: {
1467
+ ...hasText ? { content: text } : {},
1468
+ ...calls.length > 0 ? { tool_calls: calls } : {}
1469
+ },
1470
+ finish_reason: null,
1471
+ index: 0
1472
+ }] }));
1473
+ }
1474
+ if (isRecord(chunk.usage)) {
1475
+ finalUsage = chunk.usage;
1476
+ finalUsageTemplate = void 0;
1477
+ }
1478
+ if (typeof chunk.finish_reason === "string") {
1479
+ sawFinishReason = true;
1480
+ emit(controller, envelope({ choices: [{
1481
+ delta: {},
1482
+ finish_reason: chunk.finish_reason,
1483
+ index: 0
1484
+ }] }));
1485
+ }
1486
+ }
1487
+ }
1488
+ });
1489
+ }
1490
+ //#endregion
1491
+ //#region src/models/core/sse.ts
1492
+ /**
1493
+ * Decodes a `text/event-stream` byte stream into the `data` payload of each
1494
+ * event, as a `TransformStream` so the whole pipeline keeps backpressure and
1495
+ * nothing is ever buffered beyond the current event.
1496
+ *
1497
+ * Handles all three line terminators the SSE spec allows (LF, CRLF and a bare
1498
+ * CR), multi-line `data:` fields (joined with `\n`, per the spec), comment
1499
+ * lines (`:` prefixed), and non-`data` fields (`event:`, `id:`, `retry:`),
1500
+ * which are ignored. A trailing event that the server never terminated with a
1501
+ * blank line is still flushed.
1502
+ *
1503
+ * @experimental This surface is experimental and may change.
1504
+ */
1505
+ function sseDataStream() {
1506
+ const decoder = new TextDecoder();
1507
+ let buffer = "";
1508
+ let data = [];
1509
+ const flushEvent = (controller) => {
1510
+ if (data.length === 0) return;
1511
+ controller.enqueue(data.join("\n"));
1512
+ data = [];
1513
+ };
1514
+ const consumeLine = (line, controller) => {
1515
+ if (line === "") {
1516
+ flushEvent(controller);
1517
+ return;
1518
+ }
1519
+ if (line.startsWith(":")) return;
1520
+ const colon = line.indexOf(":");
1521
+ if ((colon === -1 ? line : line.slice(0, colon)) !== "data") return;
1522
+ let value = colon === -1 ? "" : line.slice(colon + 1);
1523
+ if (value.startsWith(" ")) value = value.slice(1);
1524
+ data.push(value);
1525
+ };
1526
+ /**
1527
+ * Consumes whole lines out of the buffer. A buffer ending in a lone `\r` is
1528
+ * held back while more bytes may arrive, because the next byte decides
1529
+ * whether that `\r` was a CRLF pair or a bare-CR terminator.
1530
+ */
1531
+ const drain = (controller, final) => {
1532
+ for (;;) {
1533
+ const match = /\r\n|[\r\n]/.exec(buffer);
1534
+ if (match === null) break;
1535
+ const end = match.index + match[0].length;
1536
+ if (!final && match[0] === "\r" && end === buffer.length) break;
1537
+ consumeLine(buffer.slice(0, match.index), controller);
1538
+ buffer = buffer.slice(end);
1539
+ }
1540
+ };
1541
+ return new TransformStream({
1542
+ flush(controller) {
1543
+ buffer += decoder.decode();
1544
+ drain(controller, true);
1545
+ if (buffer !== "") {
1546
+ consumeLine(buffer, controller);
1547
+ buffer = "";
1548
+ }
1549
+ flushEvent(controller);
1550
+ },
1551
+ transform(chunk, controller) {
1552
+ buffer += decoder.decode(chunk, { stream: true });
1553
+ drain(controller, false);
1554
+ }
1555
+ });
1556
+ }
1557
+ //#endregion
1558
+ //#region src/models/core/images.ts
1559
+ /**
1560
+ * `flux-1-schnell` takes `{ prompt, steps }` and nothing else — no size, no
1561
+ * seed, no guidance. Sending those anyway would be silently ignored, so they
1562
+ * come back as warnings instead.
1563
+ *
1564
+ * @see https://developers.cloudflare.com/workers-ai/models/flux-1-schnell/
1565
+ */
1566
+ const PROMPT_AND_STEPS_ONLY = /^@cf\/black-forest-labs\/flux-1/;
1567
+ /**
1568
+ * Builds the run-path body for a text-to-image model: `prompt`, `width`,
1569
+ * `height`, `num_steps`, `guidance`, `strength`, `negative_prompt` and `seed`,
1570
+ * or the flux-1 two-key subset with a warning for everything dropped.
1571
+ *
1572
+ * @experimental This surface is experimental and may change.
1573
+ */
1574
+ function buildImageInput(modelId, prompt, settings) {
1575
+ const warnings = [];
1576
+ const input = { prompt };
1577
+ if (PROMPT_AND_STEPS_ONLY.test(modelId)) {
1578
+ if (settings.steps !== void 0) input.steps = settings.steps;
1579
+ for (const [feature, value] of [
1580
+ ["size", settings.width ?? settings.height],
1581
+ ["seed", settings.seed],
1582
+ ["guidance", settings.guidance],
1583
+ ["negativePrompt", settings.negativePrompt],
1584
+ ["strength", settings.strength],
1585
+ ["files", settings.sourceImage === true ? true : void 0],
1586
+ ["mask", settings.mask === true ? true : void 0]
1587
+ ]) if (value !== void 0) warnings.push({
1588
+ feature,
1589
+ message: `${modelId} takes only a prompt and steps.`
1590
+ });
1591
+ return {
1592
+ input,
1593
+ minimal: true,
1594
+ warnings
1595
+ };
1596
+ }
1597
+ if (settings.width !== void 0) input.width = settings.width;
1598
+ if (settings.height !== void 0) input.height = settings.height;
1599
+ if (settings.seed !== void 0) input.seed = settings.seed;
1600
+ if (settings.steps !== void 0) input.num_steps = settings.steps;
1601
+ if (settings.guidance !== void 0) input.guidance = settings.guidance;
1602
+ if (settings.strength !== void 0) input.strength = settings.strength;
1603
+ if (settings.negativePrompt !== void 0) input.negative_prompt = settings.negativePrompt;
1604
+ return {
1605
+ input,
1606
+ minimal: false,
1607
+ warnings
1608
+ };
1609
+ }
1610
+ const JPEG_PREFIX = "/9j/";
1611
+ const PNG_PREFIX = "iVBORw0KGgo";
1612
+ const WEBP_PREFIX = "UklGR";
1613
+ const GIF_PREFIX = "R0lGOD";
1614
+ /**
1615
+ * Sniffs an image media type from the head of its base64. The bytes decide,
1616
+ * never the model name: `flux-1-schnell` answers JPEG where every other model
1617
+ * in this catalog answers PNG.
1618
+ *
1619
+ * @experimental This surface is experimental and may change.
1620
+ */
1621
+ function imageMediaTypeOf(base64) {
1622
+ if (base64.startsWith(JPEG_PREFIX)) return "image/jpeg";
1623
+ if (base64.startsWith(PNG_PREFIX)) return "image/png";
1624
+ if (base64.startsWith(WEBP_PREFIX)) return "image/webp";
1625
+ if (base64.startsWith(GIF_PREFIX)) return "image/gif";
1626
+ return "image/png";
1627
+ }
1628
+ //#endregion
1629
+ export { resolveOptions as C, isRetryableStatus as D, isAbortError as E, parseModelOptions as S, classifyStatus as T, isGatewayErrorEnvelope as _, normalizeChatCompletion as a, gatewayLayerOf as b, gatewaySlugForBaseUrl as c, resolveGatewayProvider as d, createTransport as f, headersToObject as g, errorFromResponse as h, normalizeChatCompletionsStream as i, isGatewayShapedUrl as l, errorFromGatewayEnvelope as m, imageMediaTypeOf as n, prepareChatCompletionsRequest as o, definedHeaders as p, sseDataStream as r, GATEWAY_PROVIDER_NAMES as s, buildImageInput as t, requireGatewayProvider as u, unwrapEnvelope as v, CloudflareAIError as w, mergeModelOptions as x, PROVIDER_OPTIONS_KEY as y };
1630
+
1631
+ //# sourceMappingURL=images-iiTDSyRu.js.map