agents 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/{agent-routing-QDcchcQP.d.ts → agent-routing-CDbpVjYq.d.ts} +18 -8
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-DHL2XI7u.d.ts → agent-tools-C8DH8Yii.d.ts} +2 -2
  5. package/dist/agent-tools.d.ts +1 -1
  6. package/dist/{ai-ahruGw7e.d.ts → ai-iXw0uOGj.d.ts} +3 -3
  7. package/dist/{ai-sdk-Bdsf6mSA.js → ai-sdk-DRPh2rRa.js} +218 -132
  8. package/dist/ai-sdk-DRPh2rRa.js.map +1 -0
  9. package/dist/browser/ai.d.ts +1 -1
  10. package/dist/browser/ai.js +2 -2
  11. package/dist/browser/index.d.ts +8 -8
  12. package/dist/browser/index.js +3 -3
  13. package/dist/browser/tools/ai-sdk.d.ts +2 -2
  14. package/dist/browser/tools/ai-sdk.js +1 -1
  15. package/dist/browser/tools/pi.d.ts +1 -1
  16. package/dist/browser/tools/pi.js +3 -3
  17. package/dist/browser/tools/pi.js.map +1 -1
  18. package/dist/browser/tools/tanstack-ai.d.ts +2 -2
  19. package/dist/browser/tools/tanstack-ai.js +1 -1
  20. package/dist/{browser-CEdNlsaD.js → browser-DccPw_wW.js} +130 -79
  21. package/dist/browser-DccPw_wW.js.map +1 -0
  22. package/dist/{browser-D30VlpcA.d.ts → browser-DlXhSAdf.d.ts} +41 -37
  23. package/dist/{browser-tool-4iomavs_.js → browser-tool-B1mhifin.js} +3 -3
  24. package/dist/{browser-tool-4iomavs_.js.map → browser-tool-B1mhifin.js.map} +1 -1
  25. package/dist/{browser-tool-vtrPOV8e.d.ts → browser-tool-Cxjk5sgG.d.ts} +2 -2
  26. package/dist/chat/index.d.ts +143 -71
  27. package/dist/chat/index.js +269 -110
  28. package/dist/chat/index.js.map +1 -1
  29. package/dist/chat-sdk/index.d.ts +1 -1
  30. package/dist/chat-sdk/index.js +1 -1
  31. package/dist/cli.js +33 -33
  32. package/dist/{client-BGIQKJhX.d.ts → client-C27CtRZr.d.ts} +2 -2
  33. package/dist/client.d.ts +1 -1
  34. package/dist/{connector-DEXzVoaY.d.ts → connector-20DJ6p4k.d.ts} +2 -2
  35. package/dist/{connector-CtZylXdk.js → connector-DAXBOIIi.js} +2 -2
  36. package/dist/{connector-CtZylXdk.js.map → connector-DAXBOIIi.js.map} +1 -1
  37. package/dist/experimental/channels/web/index.js +1 -1
  38. package/dist/{handler-stateless-VvrWSAVA.js → handler-stateless-Bly_nSH3.js} +12 -3
  39. package/dist/handler-stateless-Bly_nSH3.js.map +1 -0
  40. package/dist/harness/container/index.js +1 -1
  41. package/dist/harness/container/index.js.map +1 -1
  42. package/dist/harness/opencode/index.d.ts +182 -6
  43. package/dist/harness/opencode/index.js +354 -23
  44. package/dist/harness/opencode/index.js.map +1 -1
  45. package/dist/harness/pi/index.d.ts +13 -5
  46. package/dist/harness/pi/index.js +8 -214
  47. package/dist/harness/pi/index.js.map +1 -1
  48. package/dist/{images-Ce9SCu3X.js → images-xaDv2Fqe.js} +662 -152
  49. package/dist/images-xaDv2Fqe.js.map +1 -0
  50. package/dist/index-DCxk3OSJ.d.ts +378 -0
  51. package/dist/{index-kbSUIer3.d.ts → index-iv9FTXX9.d.ts} +80 -112
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.js +1 -1
  54. package/dist/{input-schema-D2QAtHZH.js → input-schema-B0Etuo-o.js} +2 -2
  55. package/dist/{input-schema-D2QAtHZH.js.map → input-schema-B0Etuo-o.js.map} +1 -1
  56. package/dist/lifecycle/index.js +1 -1
  57. package/dist/{lifecycle-DGorpAmA.js → lifecycle-ClT4DmF2.js} +11 -3
  58. package/dist/lifecycle-ClT4DmF2.js.map +1 -0
  59. package/dist/mcp/client/index.d.ts +1 -1
  60. package/dist/mcp/index.d.ts +1 -1
  61. package/dist/mcp/index.js +17 -3
  62. package/dist/mcp/index.js.map +1 -1
  63. package/dist/mcp/server/index.js +1 -1
  64. package/dist/models/ai-sdk/index.d.ts +31 -326
  65. package/dist/models/ai-sdk/index.js +1 -1
  66. package/dist/models/opencode/index.d.ts +25 -13
  67. package/dist/models/opencode/index.js +16 -11
  68. package/dist/models/opencode/index.js.map +1 -1
  69. package/dist/models/pi-ai/index.d.ts +17 -13
  70. package/dist/models/pi-ai/index.js +89 -36
  71. package/dist/models/pi-ai/index.js.map +1 -1
  72. package/dist/react.d.ts +2 -2
  73. package/dist/routing/index.d.ts +1 -1
  74. package/dist/serializable.d.ts +1 -1
  75. package/dist/{session-store-DoPY12xM.d.ts → session-store-_IK6dV-w.d.ts} +14 -2
  76. package/dist/{session-store-Do4xkDcf.js → session-store-yvwq1f9x.js} +55 -2
  77. package/dist/session-store-yvwq1f9x.js.map +1 -0
  78. package/dist/{settings-C45hDliX.js → settings-Dk2il2Hf.js} +10 -4
  79. package/dist/settings-Dk2il2Hf.js.map +1 -0
  80. package/dist/skills/index.d.ts +22 -28
  81. package/dist/skills/index.js +183 -143
  82. package/dist/skills/index.js.map +1 -1
  83. package/dist/{src-CQxJFv4l.js → src-6lr7MtM_.js} +3 -3
  84. package/dist/src-6lr7MtM_.js.map +1 -0
  85. package/dist/sub-routing.d.ts +1 -1
  86. package/dist/{tool-cQRkwSZZ.js → tool-DkT_DJBm.js} +2 -2
  87. package/dist/tool-DkT_DJBm.js.map +1 -0
  88. package/dist/transport-CPh7YGiw.d.ts +623 -0
  89. package/dist/voice/index.d.ts +1 -1
  90. package/dist/web/fetch/tools/pi.js +2 -2
  91. package/dist/web/fetch/tools/pi.js.map +1 -1
  92. package/dist/web/search/index.js +1 -1
  93. package/dist/web/search/tools/ai-sdk.js +2 -2
  94. package/dist/web/search/tools/pi.js +3 -3
  95. package/dist/web/search/tools/pi.js.map +1 -1
  96. package/dist/web/search/tools/tanstack-ai.js +2 -2
  97. package/dist/websockets/index.js +1 -1
  98. package/dist/{websockets-C3zy8YR0.js → websockets-CqxnntmE.js} +2 -2
  99. package/dist/{websockets-C3zy8YR0.js.map → websockets-CqxnntmE.js.map} +1 -1
  100. package/dist/workflow-types.d.ts +1 -1
  101. package/dist/workflows.d.ts +1 -1
  102. package/dist/workflows.js +1 -1
  103. package/docs/browse-the-web.md +1 -1
  104. package/docs/chat-agents.md +19 -18
  105. package/docs/harnesses/pi.md +47 -18
  106. package/docs/mcp-client.md +1 -1
  107. package/docs/mcp-servers.md +1 -1
  108. package/docs/models-pi-ai.md +70 -17
  109. package/docs/models.md +219 -152
  110. package/package.json +14 -14
  111. package/dist/ai-sdk-Bdsf6mSA.js.map +0 -1
  112. package/dist/browser-CEdNlsaD.js.map +0 -1
  113. package/dist/handler-stateless-VvrWSAVA.js.map +0 -1
  114. package/dist/images-Ce9SCu3X.js.map +0 -1
  115. package/dist/lifecycle-DGorpAmA.js.map +0 -1
  116. package/dist/session-store-Do4xkDcf.js.map +0 -1
  117. package/dist/settings-C45hDliX.js.map +0 -1
  118. package/dist/settings-NgGwF7Qt.d.ts +0 -331
  119. package/dist/src-CQxJFv4l.js.map +0 -1
  120. package/dist/tool-cQRkwSZZ.js.map +0 -1
  121. package/dist/transport-k2mFrtL8.d.ts +0 -200
@@ -1,3 +1,44 @@
1
+ //#region src/models/core/catalog.ts
2
+ /**
3
+ * Whether an id names an AI Gateway dynamic route: `dynamic/` and a route
4
+ * name after it.
5
+ *
6
+ * @experimental This surface is experimental and may change.
7
+ */
8
+ function isDynamicRouteId(modelId) {
9
+ return modelId.startsWith("dynamic/") && modelId.length > 8;
10
+ }
11
+ /**
12
+ * Whether an id names a Workers AI model, served by Workers AI itself rather
13
+ * than by AI Gateway's `/run`.
14
+ *
15
+ * @experimental This surface is experimental and may change.
16
+ */
17
+ function isWorkersAIId(modelId) {
18
+ return modelId.startsWith("@cf/") || modelId.startsWith("@hf/");
19
+ }
20
+ /**
21
+ * Whether an id names an AI Gateway catalog model: an author and a model,
22
+ * neither empty, and none of the other id namespaces (`@cf/`, `@hf/`,
23
+ * `dynamic/`).
24
+ *
25
+ * @experimental This surface is experimental and may change.
26
+ */
27
+ function isCatalogModelId(modelId) {
28
+ if (modelId.startsWith("@") || modelId.startsWith("dynamic/")) return false;
29
+ const slash = modelId.indexOf("/");
30
+ return slash > 0 && slash < modelId.length - 1;
31
+ }
32
+ /**
33
+ * Whether a string names a language model that runs on `env.AI.run`: a
34
+ * Workers AI id or an AI Gateway dynamic route.
35
+ *
36
+ * @experimental This surface is experimental and may change.
37
+ */
38
+ function isRunPathLanguageModelId(modelId) {
39
+ return modelId.startsWith("@cf/") || isDynamicRouteId(modelId);
40
+ }
41
+ //#endregion
1
42
  //#region src/models/core/errors.ts
2
43
  /**
3
44
  * Error thrown for every failed Cloudflare AI call, whether it came back as a
@@ -126,9 +167,155 @@ function normalizeThrownError(error, context) {
126
167
  });
127
168
  }
128
169
  //#endregion
170
+ //#region src/models/core/resume.ts
171
+ /**
172
+ * Resuming a streamed `/run` answer that dropped mid-way.
173
+ *
174
+ * AI Gateway's Run Durable Object keeps every SSE event of a streamed run and
175
+ * names the run in `cf-aig-run-id`. The binding can replay the stream from any
176
+ * event index:
177
+ *
178
+ * ```ts
179
+ * env.AI.fetch(`https://workers-binding.ai/ai-gateway/gateways/${id}/run/${runId}/resume?from=${n}`)
180
+ * ```
181
+ *
182
+ * The run keeps going on the gateway after the client is gone, so a replay
183
+ * from the right index continues the same answer rather than starting a new
184
+ * one. This wrapper makes that transparent: it reads the original body, and
185
+ * when a read fails it reconnects to the resume route and keeps feeding the
186
+ * same consumer, which never sees the break.
187
+ *
188
+ * The one subtlety is alignment. `from` counts complete SSE events, so the
189
+ * wrapper only ever passes complete events on and holds back a trailing
190
+ * partial one. After a drop the partial is discarded and the replay starts at
191
+ * the count of events already delivered: no byte is duplicated or lost.
192
+ *
193
+ * @experimental This surface is experimental and may change.
194
+ */
195
+ const EMPTY = /* @__PURE__ */ new Uint8Array(0);
196
+ function concat(a, b) {
197
+ const out = new Uint8Array(a.length + b.length);
198
+ out.set(a, 0);
199
+ out.set(b, a.length);
200
+ return out;
201
+ }
202
+ /** Index just past the last `\n\n` in `buffer`, or -1 when none is complete. */
203
+ function lastEventBoundary(buffer) {
204
+ for (let i = buffer.length - 2; i >= 0; i--) if (buffer[i] === 10 && buffer[i + 1] === 10) return i + 2;
205
+ return -1;
206
+ }
207
+ /** Complete SSE events in `buffer`: `\n\n` terminators, counted once each. */
208
+ function countEvents(buffer) {
209
+ let count = 0;
210
+ for (let i = 0; i + 1 < buffer.length; i++) if (buffer[i] === 10 && buffer[i + 1] === 10) {
211
+ count++;
212
+ i++;
213
+ }
214
+ return count;
215
+ }
216
+ /**
217
+ * Wraps a streamed `/run` body so a dropped connection resumes from the last
218
+ * complete event. Errors once the replay buffer has expired (404), when a
219
+ * replay fails, or after `maxReconnects` drops.
220
+ *
221
+ * @experimental This surface is experimental and may change.
222
+ */
223
+ function resumableBody(options) {
224
+ const maxReconnects = options.maxReconnects ?? 5;
225
+ let delivered = 0;
226
+ let pending = EMPTY;
227
+ let reconnects = 0;
228
+ let cancelled = false;
229
+ let reader;
230
+ const stopped = () => cancelled || options.signal?.aborted === true;
231
+ const failure = (message, extra = {}) => new CloudflareAIError({
232
+ code: "provider-error",
233
+ isRetryable: false,
234
+ message,
235
+ model: options.model,
236
+ requestBodyValues: void 0,
237
+ url: options.url,
238
+ ...extra
239
+ });
240
+ /** The replay from the events already delivered, or undefined after an error. */
241
+ async function replay(controller) {
242
+ let response;
243
+ try {
244
+ response = await options.resume(delivered, options.signal);
245
+ } catch (error) {
246
+ controller.error(failure(`The stream dropped at event ${delivered} and the resume request failed.`, { cause: error }));
247
+ return;
248
+ }
249
+ if (response.status === 404) {
250
+ controller.error(failure(`The stream dropped at event ${delivered}, and AI Gateway no longer holds the run to resume it.`, { status: 404 }));
251
+ return;
252
+ }
253
+ if (!response.ok || response.body === null) {
254
+ controller.error(failure(`The stream dropped at event ${delivered} and resuming it failed (${response.status}).`, { status: response.status }));
255
+ return;
256
+ }
257
+ return response.body;
258
+ }
259
+ return new ReadableStream({
260
+ async start(controller) {
261
+ let current = options.initial;
262
+ for (;;) {
263
+ reader = current.getReader();
264
+ try {
265
+ for (;;) {
266
+ const { done, value } = await reader.read();
267
+ if (done) {
268
+ if (pending.length > 0) controller.enqueue(pending);
269
+ pending = EMPTY;
270
+ controller.close();
271
+ return;
272
+ }
273
+ if (value === void 0 || value.length === 0) continue;
274
+ pending = concat(pending, value);
275
+ const boundary = lastEventBoundary(pending);
276
+ if (boundary > 0) {
277
+ const complete = pending.slice(0, boundary);
278
+ controller.enqueue(complete);
279
+ delivered += countEvents(complete);
280
+ pending = pending.slice(boundary);
281
+ }
282
+ }
283
+ } catch (error) {
284
+ try {
285
+ reader.releaseLock();
286
+ } catch {}
287
+ if (stopped()) {
288
+ if (!cancelled) controller.error(error);
289
+ return;
290
+ }
291
+ if (reconnects >= maxReconnects) {
292
+ controller.error(failure(`The stream dropped ${reconnects + 1} times; giving up at event ${delivered}.`, { cause: error }));
293
+ return;
294
+ }
295
+ pending = EMPTY;
296
+ reconnects++;
297
+ const body = await replay(controller);
298
+ if (body === void 0) return;
299
+ current = body;
300
+ }
301
+ }
302
+ },
303
+ cancel(reason) {
304
+ cancelled = true;
305
+ reader?.cancel(reason).catch(() => {});
306
+ }
307
+ });
308
+ }
309
+ //#endregion
129
310
  //#region src/models/core/transport.ts
130
- /** The reported `url` for a universal call made through the binding. */
131
- const UNIVERSAL_BINDING_URL = "ai-gateway-binding";
311
+ /** The reported `url` for a provider-native call made through the binding. */
312
+ const NATIVE_BINDING_URL = "ai-gateway-binding-native";
313
+ /** The origin the binding's own `fetch` answers on. */
314
+ const BINDING_ORIGIN = "https://workers-binding.ai";
315
+ /** The REST API's run path, for a model id reached with an account and token. */
316
+ const REST_API_ORIGIN = "https://api.cloudflare.com/client/v4/accounts";
317
+ /** The provider-native path, for a model object reached with a token. */
318
+ const GATEWAY_ORIGIN = "https://gateway.ai.cloudflare.com/v1";
132
319
  /**
133
320
  * Drops entries whose value is `undefined`. The AI SDK types call headers as
134
321
  * `Record<string, string | undefined>`, and an undefined value reaches the
@@ -222,23 +409,85 @@ async function errorFromResponse(response, context) {
222
409
  });
223
410
  }
224
411
  /**
225
- * The gateway's own options for the universal call. The id is not among them:
226
- * it selects the gateway (`env.AI.gateway(id)`), and the binding's own `id`
227
- * field is deprecated.
412
+ * The gateway options as the `cf-aig-*` request headers AI Gateway reads.
413
+ * The provider-native path has no options object, so every option travels
414
+ * this way; the names are the gateway's own.
228
415
  */
229
- function universalGatewayOptions(gateway) {
230
- const options = { id: gateway.id };
231
- if (gateway.skipCache !== void 0) options.skipCache = gateway.skipCache;
232
- if (gateway.cacheTtl !== void 0) options.cacheTtl = gateway.cacheTtl;
233
- if (gateway.cacheKey !== void 0) options.cacheKey = gateway.cacheKey;
234
- if (gateway.metadata !== void 0) options.metadata = gateway.metadata;
235
- if (gateway.collectLog !== void 0) options.collectLog = gateway.collectLog;
236
- if (gateway.eventId !== void 0) options.eventId = gateway.eventId;
237
- if (gateway.requestTimeoutMs !== void 0) options.requestTimeoutMs = gateway.requestTimeoutMs;
238
- if (gateway.retries !== void 0) options.retries = gateway.retries;
239
- return options;
416
+ function gatewayHeaders(gateway) {
417
+ const headers = {};
418
+ if (gateway.cacheTtl !== void 0) headers["cf-aig-cache-ttl"] = String(gateway.cacheTtl);
419
+ if (gateway.skipCache !== void 0) headers["cf-aig-skip-cache"] = String(gateway.skipCache);
420
+ if (gateway.cacheKey !== void 0) headers["cf-aig-cache-key"] = gateway.cacheKey;
421
+ if (gateway.metadata !== void 0) headers["cf-aig-metadata"] = JSON.stringify(gateway.metadata);
422
+ if (gateway.collectLog !== void 0) headers["cf-aig-collect-log"] = String(gateway.collectLog);
423
+ if (gateway.eventId !== void 0) headers["cf-aig-event-id"] = gateway.eventId;
424
+ if (gateway.requestTimeoutMs !== void 0) headers["cf-aig-request-timeout"] = String(gateway.requestTimeoutMs);
425
+ const retries = gateway.retries;
426
+ if (retries?.maxAttempts !== void 0) headers["cf-aig-max-attempts"] = String(retries.maxAttempts);
427
+ if (retries?.retryDelayMs !== void 0) headers["cf-aig-retry-delay"] = String(retries.retryDelayMs);
428
+ if (retries?.backoff !== void 0) headers["cf-aig-backoff"] = retries.backoff;
429
+ if (gateway.byokAlias !== void 0) headers["cf-aig-byok-alias"] = gateway.byokAlias;
430
+ if (gateway.zdr !== void 0) headers["cf-aig-zdr"] = String(gateway.zdr);
431
+ return headers;
240
432
  }
241
433
  /**
434
+ * The run path's gateway options, and the headers for the ones the binding's
435
+ * `GatewayOptions` type does not define. Both transports use it: the binding
436
+ * takes `gateway` as an options object, HTTP as `cf-aig-*` headers.
437
+ *
438
+ * `byokAlias` reaches `/run` as `cf-aig-byok-alias`, which `/run` reads for a
439
+ * catalog model's stored key. `zdr` is left off: `/run` does not read it, so it
440
+ * applies to provider-native requests only.
441
+ */
442
+ function runOptions(request) {
443
+ const { byokAlias, zdr: _zdr, ...gateway } = request.gateway;
444
+ return {
445
+ gateway,
446
+ headers: {
447
+ ...request.headers,
448
+ ...byokAlias !== void 0 && isCatalogModelId(request.model) ? { "cf-aig-byok-alias": byokAlias } : {}
449
+ }
450
+ };
451
+ }
452
+ /**
453
+ * The binding's resume route for the run a response names. Only called for a
454
+ * response {@link withResume} has checked carries `cf-aig-run-id`.
455
+ */
456
+ function resumeUrl(gatewayId, response, from) {
457
+ const runId = response.headers.get("cf-aig-run-id") ?? "";
458
+ return `${BINDING_ORIGIN}/ai-gateway/gateways/${encodeURIComponent(gatewayId)}/run/${encodeURIComponent(runId)}/resume?from=${from}`;
459
+ }
460
+ /**
461
+ * A streamed `/run` answer that names its run (`cf-aig-run-id`) resumes from
462
+ * the last complete event if the connection drops; see `resume.ts`. Anything
463
+ * else — a JSON answer, an error, a Workers AI stream with no run id — is
464
+ * returned as it is.
465
+ */
466
+ function withResume(response, request, resume) {
467
+ const contentType = response.headers.get("content-type") ?? "";
468
+ if (!response.ok || response.body === null || !contentType.includes("text/event-stream") || response.headers.get("cf-aig-run-id") === null) return response;
469
+ return new Response(resumableBody({
470
+ initial: response.body,
471
+ model: request.model,
472
+ resume,
473
+ signal: request.signal,
474
+ url: "workers-ai-binding"
475
+ }), {
476
+ headers: response.headers,
477
+ status: response.status,
478
+ statusText: response.statusText
479
+ });
480
+ }
481
+ /**
482
+ * What `/run` calls each request format in the binding's `openaiEndpoint`
483
+ * option, which `aig-entry` turns into `/run`'s `requestFormat`.
484
+ */
485
+ const OPENAI_ENDPOINTS = {
486
+ "chat-completions": "/v1/chat/completions",
487
+ messages: "/v1/messages",
488
+ responses: "/v1/responses"
489
+ };
490
+ /**
242
491
  * Whether a parsed body is the gateway's own error envelope rather than a
243
492
  * vendor payload — `{ success: false, error: [{ code, message }] }`, named
244
493
  * `AiGatewayError`. The gateway answers this for its own failures (no such
@@ -257,7 +506,7 @@ function isGatewayErrorEnvelope(json) {
257
506
  }
258
507
  /**
259
508
  * Builds a {@link CloudflareAIError} from the gateway's own error envelope, so
260
- * a gateway failure on the universal path classifies and retries exactly as
509
+ * a gateway failure on the provider-native path classifies and retries exactly as
261
510
  * one on the Workers AI run path.
262
511
  *
263
512
  * @experimental This surface is experimental and may change.
@@ -277,18 +526,6 @@ function errorFromGatewayEnvelope(body, context) {
277
526
  url: context.url
278
527
  });
279
528
  }
280
- /**
281
- * The model id a universal request is about, for error reporting. Every vendor
282
- * body carries `model`; the endpoint is the fallback.
283
- */
284
- function universalModel(request) {
285
- const query = request.query;
286
- if (query !== null && typeof query === "object" && !Array.isArray(query)) {
287
- const model = query.model;
288
- if (typeof model === "string") return model;
289
- }
290
- return `${request.provider}/${request.endpoint}`;
291
- }
292
529
  /** The keyless in-Worker backend: `env.AI.run(...)`. */
293
530
  function bindingTransport(binding) {
294
531
  const raw = binding;
@@ -299,15 +536,32 @@ function bindingTransport(binding) {
299
536
  },
300
537
  async run(request) {
301
538
  try {
302
- const response = await raw.run(request.model, request.input, {
539
+ if (request.audio !== void 0) return await raw.run(request.model, {
540
+ ...request.input,
541
+ audio: {
542
+ body: new Response(new Uint8Array(request.audio.bytes)).body,
543
+ contentType: request.audio.contentType
544
+ }
545
+ }, {
303
546
  ...Object.keys(request.headers).length > 0 ? { extraHeaders: request.headers } : {},
304
- gateway: request.gateway,
305
547
  returnRawResponse: true,
306
548
  signal: request.signal
307
549
  });
550
+ const run = runOptions(request);
551
+ const response = await raw.run(request.model, request.input, {
552
+ ...Object.keys(run.headers).length > 0 ? { extraHeaders: run.headers } : {},
553
+ gateway: run.gateway,
554
+ ...isWorkersAIId(request.model) ? {} : { openaiEndpoint: OPENAI_ENDPOINTS[request.format ?? "chat-completions"] },
555
+ returnRawResponse: true,
556
+ signal: request.signal
557
+ });
558
+ const resumable = withResume(response, request, (from, signal) => binding.fetch(resumeUrl(run.gateway.id, response, from), {
559
+ method: "GET",
560
+ signal
561
+ }));
308
562
  const logId = binding.aiGatewayLogId;
309
- if (logId !== null && logId !== void 0) logIds.set(response, logId);
310
- return response;
563
+ if (logId !== null && logId !== void 0) logIds.set(resumable, logId);
564
+ return resumable;
311
565
  } catch (error) {
312
566
  throw normalizeThrownError(error, {
313
567
  model: request.model,
@@ -316,23 +570,22 @@ function bindingTransport(binding) {
316
570
  });
317
571
  }
318
572
  },
319
- async universal(request) {
573
+ async native(request) {
320
574
  try {
321
- return await binding.gateway(request.gateway.id).run({
322
- endpoint: request.endpoint,
323
- headers: request.headers,
324
- provider: request.provider,
325
- query: request.query
326
- }, {
327
- ...request.extraHeaders === void 0 ? {} : { extraHeaders: request.extraHeaders },
328
- gateway: universalGatewayOptions(request.gateway),
575
+ return await binding.fetch(`${BINDING_ORIGIN}/ai-gateway/gateways/${encodeURIComponent(request.gateway.id)}/${request.provider}/${request.endpoint}`, {
576
+ body: request.body ?? null,
577
+ headers: {
578
+ ...request.headers,
579
+ ...gatewayHeaders(request.gateway)
580
+ },
581
+ method: request.method,
329
582
  signal: request.signal
330
583
  });
331
584
  } catch (error) {
332
585
  throw normalizeThrownError(error, {
333
- model: universalModel(request),
334
- requestBodyValues: request.query,
335
- url: UNIVERSAL_BINDING_URL
586
+ model: `${request.provider}/${request.endpoint}`,
587
+ requestBodyValues: void 0,
588
+ url: NATIVE_BINDING_URL
336
589
  });
337
590
  }
338
591
  },
@@ -340,6 +593,140 @@ function bindingTransport(binding) {
340
593
  };
341
594
  }
342
595
  /**
596
+ * Request headers with the credential set last. `Headers` matches names
597
+ * case-insensitively, so a caller's `Authorization` is replaced rather than
598
+ * sent alongside the token.
599
+ */
600
+ function withCredential(headers, name, token) {
601
+ const result = new Headers(headers);
602
+ result.set(name, `Bearer ${token}`);
603
+ return result;
604
+ }
605
+ /**
606
+ * A Workers AI answer as the binding returns it. The REST API wraps a JSON
607
+ * answer in the `{ success, result }` envelope where the binding returns the
608
+ * bare result, so a successful envelope is unwrapped; anything else is left
609
+ * for the caller, as it is on the binding.
610
+ */
611
+ async function bareWorkersAIAnswer(response) {
612
+ const contentType = response.headers.get("content-type") ?? "";
613
+ if (!response.ok || !contentType.includes("application/json")) return response;
614
+ const text = await response.text();
615
+ let body;
616
+ try {
617
+ body = JSON.parse(text);
618
+ } catch {
619
+ body = void 0;
620
+ }
621
+ const record = body !== null && typeof body === "object" && !Array.isArray(body) ? body : void 0;
622
+ const unwrapped = record?.success === true && "result" in record ? record.result : void 0;
623
+ const headers = new Headers(response.headers);
624
+ headers.delete("content-length");
625
+ return new Response(unwrapped === void 0 ? text : JSON.stringify(unwrapped), {
626
+ headers,
627
+ status: response.status,
628
+ statusText: response.statusText
629
+ });
630
+ }
631
+ /**
632
+ * The HTTP backend, for an account id and API token. A model id goes to the
633
+ * REST API, which serves it with the same `/run` call the binding makes: a
634
+ * Workers AI id on `/ai/run/{model}` with its own input, any other id on the
635
+ * endpoint for its request format with the model in the body. A model object
636
+ * goes to the provider-native path on `gateway.ai.cloudflare.com`, exactly as
637
+ * the binding's `fetch` forwards it.
638
+ *
639
+ * A dropped stream is not resumed: the binding has a resume route, but the
640
+ * public one is not open to every gateway yet.
641
+ */
642
+ function httpTransport({ accountId, apiToken }) {
643
+ const account = encodeURIComponent(accountId);
644
+ const runBase = `${REST_API_ORIGIN}/${account}/ai`;
645
+ /**
646
+ * A raw audio body goes to Workers AI's own run route, with the model's
647
+ * other fields as query parameters. TODO: send it through the gateway once
648
+ * AI Gateway accepts the body; today the REST API answers `500` when
649
+ * `cf-aig-gateway-id` is set.
650
+ */
651
+ async function runRawAudio(request, audio) {
652
+ const query = new URLSearchParams();
653
+ for (const [key, value] of Object.entries(request.input)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") query.set(key, String(value));
654
+ const search = query.size > 0 ? `?${query}` : "";
655
+ const url = `${runBase}/run/${request.model}${search}`;
656
+ try {
657
+ return await bareWorkersAIAnswer(await fetch(url, {
658
+ body: new Uint8Array(audio.bytes),
659
+ headers: withCredential({
660
+ ...request.headers,
661
+ "content-type": audio.contentType
662
+ }, "authorization", apiToken),
663
+ method: "POST",
664
+ signal: request.signal
665
+ }));
666
+ } catch (error) {
667
+ throw normalizeThrownError(error, {
668
+ model: request.model,
669
+ requestBodyValues: request.input,
670
+ url
671
+ });
672
+ }
673
+ }
674
+ return {
675
+ logIdFallback() {},
676
+ async run(request) {
677
+ if (request.audio !== void 0) return runRawAudio(request, request.audio);
678
+ const workersAI = isWorkersAIId(request.model);
679
+ const run = runOptions(request);
680
+ const url = workersAI ? `${runBase}/run/${request.model}` : `${runBase}${OPENAI_ENDPOINTS[request.format ?? "chat-completions"]}`;
681
+ try {
682
+ const response = await fetch(url, {
683
+ body: JSON.stringify(workersAI ? request.input : {
684
+ ...request.input,
685
+ model: request.model
686
+ }),
687
+ headers: withCredential({
688
+ "content-type": "application/json",
689
+ ...gatewayHeaders(run.gateway),
690
+ "cf-aig-gateway-id": run.gateway.id,
691
+ ...run.headers
692
+ }, "authorization", apiToken),
693
+ method: "POST",
694
+ signal: request.signal
695
+ });
696
+ return workersAI ? await bareWorkersAIAnswer(response) : response;
697
+ } catch (error) {
698
+ throw normalizeThrownError(error, {
699
+ model: request.model,
700
+ requestBodyValues: request.input,
701
+ url
702
+ });
703
+ }
704
+ },
705
+ async native(request) {
706
+ const url = `${GATEWAY_ORIGIN}/${account}/${encodeURIComponent(request.gateway.id)}/${request.provider}/${request.endpoint}`;
707
+ try {
708
+ return await fetch(url, {
709
+ body: request.body ?? null,
710
+ ...request.body instanceof ReadableStream ? { duplex: "half" } : {},
711
+ headers: withCredential({
712
+ ...request.headers,
713
+ ...gatewayHeaders(request.gateway)
714
+ }, "cf-aig-authorization", apiToken),
715
+ method: request.method,
716
+ signal: request.signal
717
+ });
718
+ } catch (error) {
719
+ throw normalizeThrownError(error, {
720
+ model: `${request.provider}/${request.endpoint}`,
721
+ requestBodyValues: void 0,
722
+ url
723
+ });
724
+ }
725
+ },
726
+ url: runBase
727
+ };
728
+ }
729
+ /**
343
730
  * Builds the transport for a set of provider settings. The model id, not the
344
731
  * caller, decides the wire format; the transport only decides how the request
345
732
  * physically leaves the Worker.
@@ -347,14 +734,15 @@ function bindingTransport(binding) {
347
734
  * @experimental This surface is experimental and may change.
348
735
  */
349
736
  function createTransport(settings) {
350
- if (settings.binding === void 0) throw new Error("createAI requires { binding }: the Workers AI binding.");
351
- return bindingTransport(settings.binding);
737
+ if (settings.binding !== void 0) return bindingTransport(settings.binding);
738
+ if (settings.accountId && settings.apiToken) return httpTransport(settings);
739
+ throw new Error("createAI requires { binding } (the AI binding) or { accountId, apiToken }.");
352
740
  }
353
741
  //#endregion
354
742
  //#region src/models/core/gateway-providers.ts
355
743
  /**
356
744
  * The AI Gateway provider table: which gateway provider slug a vendor's own
357
- * base URL belongs to, and what the universal request's `endpoint` is for a
745
+ * base URL belongs to, and what the provider-native `endpoint` is for a
358
746
  * given upstream URL.
359
747
  *
360
748
  * This is platform knowledge, not vendor knowledge — the gateway publishes the
@@ -649,7 +1037,7 @@ const STRICT_OPENAI = {
649
1037
  imageUrls: "any",
650
1038
  jsonSchema: "supported",
651
1039
  jsonSchemaStrict: false,
652
- maxTokensField: "max_tokens",
1040
+ maxTokensField: "max_completion_tokens",
653
1041
  modelEchoSuffix: void 0,
654
1042
  nullAssistantContent: "allowed",
655
1043
  reasoningEffortLevels: "basic",
@@ -664,6 +1052,7 @@ const STRICT_OPENAI = {
664
1052
  const WORKERS_AI = {
665
1053
  ...STRICT_OPENAI,
666
1054
  chatTemplateKwargs: "supported",
1055
+ maxTokensField: "max_tokens",
667
1056
  imageUrls: "data-only",
668
1057
  nullAssistantContent: "empty-string",
669
1058
  reasoningOff: "chat-template",
@@ -712,7 +1101,7 @@ function isWorkersAI(modelId) {
712
1101
  * The rewrite never throws: a feature the model cannot take is dropped and
713
1102
  * reported as a {@link CompatWarning}.
714
1103
  */
715
- function isRecord$2(value) {
1104
+ function isRecord$3(value) {
716
1105
  return value !== null && typeof value === "object" && !Array.isArray(value);
717
1106
  }
718
1107
  function clampEffort(effort) {
@@ -725,8 +1114,8 @@ function stripImageUrls(message, warnings) {
725
1114
  if (!Array.isArray(message.content)) return message;
726
1115
  let dropped = 0;
727
1116
  const content = message.content.filter((part) => {
728
- if (!isRecord$2(part) || part.type !== "image_url") return true;
729
- const image = isRecord$2(part.image_url) ? part.image_url : void 0;
1117
+ if (!isRecord$3(part) || part.type !== "image_url") return true;
1118
+ const image = isRecord$3(part.image_url) ? part.image_url : void 0;
730
1119
  if ((typeof image?.url === "string" ? image.url : "").startsWith("data:")) return true;
731
1120
  dropped += 1;
732
1121
  return false;
@@ -768,9 +1157,9 @@ function toolCallIdsIn(messages) {
768
1157
  ids.push(value);
769
1158
  };
770
1159
  for (const message of messages) {
771
- if (!isRecord$2(message)) continue;
1160
+ if (!isRecord$3(message)) continue;
772
1161
  if (Array.isArray(message.tool_calls)) {
773
- for (const call of message.tool_calls) if (isRecord$2(call)) add(call.id);
1162
+ for (const call of message.tool_calls) if (isRecord$3(call)) add(call.id);
774
1163
  }
775
1164
  add(message.tool_call_id);
776
1165
  }
@@ -800,7 +1189,7 @@ function rewriteToolCallIds(message, plan) {
800
1189
  if (Array.isArray(rewritten.tool_calls)) rewritten = {
801
1190
  ...rewritten,
802
1191
  tool_calls: rewritten.tool_calls.map((call) => {
803
- if (!isRecord$2(call) || typeof call.id !== "string") return call;
1192
+ if (!isRecord$3(call) || typeof call.id !== "string") return call;
804
1193
  const replacement = plan.get(call.id);
805
1194
  return replacement === void 0 ? call : {
806
1195
  ...call,
@@ -840,7 +1229,7 @@ function prepareChatCompletionsRequest(input, modelId) {
840
1229
  if (effort === null) {
841
1230
  delete body.reasoning_effort;
842
1231
  if (quirks.reasoningOff === "chat-template") body.chat_template_kwargs = {
843
- ...isRecord$2(body.chat_template_kwargs) ? body.chat_template_kwargs : {},
1232
+ ...isRecord$3(body.chat_template_kwargs) ? body.chat_template_kwargs : {},
844
1233
  enable_thinking: false
845
1234
  };
846
1235
  else warnings.push({
@@ -856,14 +1245,14 @@ function prepareChatCompletionsRequest(input, modelId) {
856
1245
  message: `${modelId} does not take chat_template_kwargs; they were dropped.`
857
1246
  });
858
1247
  }
859
- if (isRecord$2(body.response_format)) {
1248
+ if (isRecord$3(body.response_format)) {
860
1249
  if (quirks.jsonSchema === "unsupported") {
861
1250
  delete body.response_format;
862
1251
  warnings.push({
863
1252
  feature: "response-format",
864
1253
  message: `${modelId} does not support response_format; the request was sent without it.`
865
1254
  });
866
- } else if (body.response_format.type === "json_schema" && isRecord$2(body.response_format.json_schema)) {
1255
+ } else if (body.response_format.type === "json_schema" && isRecord$3(body.response_format.json_schema)) {
867
1256
  const { strict: _strict, ...envelope } = body.response_format.json_schema;
868
1257
  body.response_format = {
869
1258
  ...body.response_format,
@@ -876,8 +1265,8 @@ function prepareChatCompletionsRequest(input, modelId) {
876
1265
  }
877
1266
  if (body.tool_choice === "required" && quirks.toolChoiceRequired === "named-fallback") {
878
1267
  const tools = Array.isArray(body.tools) ? body.tools : [];
879
- const only = tools.length === 1 && isRecord$2(tools[0]) ? tools[0] : void 0;
880
- const name = only !== void 0 && isRecord$2(only.function) && typeof only.function.name === "string" ? only.function.name : void 0;
1268
+ const only = tools.length === 1 && isRecord$3(tools[0]) ? tools[0] : void 0;
1269
+ const name = only !== void 0 && isRecord$3(only.function) && typeof only.function.name === "string" ? only.function.name : void 0;
881
1270
  if (name !== void 0) body.tool_choice = {
882
1271
  function: { name },
883
1272
  type: "function"
@@ -899,7 +1288,7 @@ function prepareChatCompletionsRequest(input, modelId) {
899
1288
  const messages = [];
900
1289
  let previousRole;
901
1290
  for (const entry of body.messages) {
902
- if (!isRecord$2(entry)) continue;
1291
+ if (!isRecord$3(entry)) continue;
903
1292
  let message = entry;
904
1293
  if (toolCallIds.size > 0) message = rewriteToolCallIds(message, toolCallIds);
905
1294
  if (message.role === "user" && quirks.imageUrls === "data-only") message = stripImageUrls(message, warnings);
@@ -928,7 +1317,7 @@ function prepareChatCompletionsRequest(input, modelId) {
928
1317
  }
929
1318
  return {
930
1319
  body,
931
- reasoningOff: isRecord$2(body.chat_template_kwargs) && body.chat_template_kwargs.enable_thinking === false,
1320
+ reasoningOff: isRecord$3(body.chat_template_kwargs) && body.chat_template_kwargs.enable_thinking === false,
932
1321
  warnings
933
1322
  };
934
1323
  }
@@ -939,13 +1328,13 @@ function prepareChatCompletionsRequest(input, modelId) {
939
1328
  * strict OpenAI `chat.completion` shape, so framework parsers never see
940
1329
  * Workers AI's native envelope or its field spellings.
941
1330
  */
942
- function isRecord$1(value) {
1331
+ function isRecord$2(value) {
943
1332
  return value !== null && typeof value === "object" && !Array.isArray(value);
944
1333
  }
945
1334
  /** Native `{ name, arguments: object }` → OpenAI `{ id, type, function }`. */
946
1335
  function normalizeToolCall(entry, index) {
947
- if (!isRecord$1(entry)) return void 0;
948
- const fn = isRecord$1(entry.function) ? entry.function : void 0;
1336
+ if (!isRecord$2(entry)) return void 0;
1337
+ const fn = isRecord$2(entry.function) ? entry.function : void 0;
949
1338
  const name = typeof fn?.name === "string" ? fn.name : typeof entry.name === "string" ? entry.name : void 0;
950
1339
  if (name === void 0) return void 0;
951
1340
  const rawArguments = fn !== void 0 && "arguments" in fn ? fn.arguments : entry.arguments;
@@ -994,12 +1383,12 @@ function normalizeMessage(message) {
994
1383
  * lifted into `choices[0]`.
995
1384
  */
996
1385
  function normalizeChatCompletion(body, modelId) {
997
- if (!isRecord$1(body)) return { choices: [] };
1386
+ if (!isRecord$2(body)) return { choices: [] };
998
1387
  if (Array.isArray(body.choices)) {
999
1388
  const choices = body.choices.map((choice) => {
1000
- if (!isRecord$1(choice)) return choice;
1001
- const message = isRecord$1(choice.message) ? normalizeMessage(choice.message) : choice.message;
1002
- const hasToolCalls = isRecord$1(message) && Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
1389
+ if (!isRecord$2(choice)) return choice;
1390
+ const message = isRecord$2(choice.message) ? normalizeMessage(choice.message) : choice.message;
1391
+ const hasToolCalls = isRecord$2(message) && Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
1003
1392
  return {
1004
1393
  ...choice,
1005
1394
  message,
@@ -1033,6 +1422,122 @@ function normalizeChatCompletion(body, modelId) {
1033
1422
  return body;
1034
1423
  }
1035
1424
  //#endregion
1425
+ //#region src/models/core/chat-completions/anthropic-stream.ts
1426
+ function isRecord$1(value) {
1427
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1428
+ }
1429
+ /** Anthropic's stop reasons, as chat-completions finish reasons. */
1430
+ const FINISH_REASONS = {
1431
+ end_turn: "stop",
1432
+ max_tokens: "length",
1433
+ pause_turn: "stop",
1434
+ refusal: "content_filter",
1435
+ stop_sequence: "stop",
1436
+ tool_use: "tool_calls"
1437
+ };
1438
+ /** Whether a parsed SSE payload is an Anthropic Messages event. */
1439
+ function isAnthropicEvent(event) {
1440
+ return typeof event.type === "string" && !("choices" in event);
1441
+ }
1442
+ /**
1443
+ * A stateful translator for one stream. Returns the chat-completions chunks
1444
+ * an Anthropic event becomes — possibly none — or `undefined` for an event
1445
+ * that is not Anthropic, which the caller handles as it is.
1446
+ */
1447
+ function createAnthropicStreamTranslator() {
1448
+ let id;
1449
+ let model;
1450
+ let inputTokens = 0;
1451
+ let outputTokens = 0;
1452
+ let cachedTokens = 0;
1453
+ const toolIndexes = /* @__PURE__ */ new Map();
1454
+ const chunk = (delta, finishReason = null) => ({
1455
+ choices: [{
1456
+ delta,
1457
+ finish_reason: finishReason,
1458
+ index: 0
1459
+ }],
1460
+ ...id === void 0 ? {} : { id },
1461
+ ...model === void 0 ? {} : { model },
1462
+ object: "chat.completion.chunk"
1463
+ });
1464
+ return (event) => {
1465
+ if (!isAnthropicEvent(event)) return void 0;
1466
+ switch (event.type) {
1467
+ case "message_start": {
1468
+ const message = isRecord$1(event.message) ? event.message : {};
1469
+ if (typeof message.id === "string") id = message.id;
1470
+ if (typeof message.model === "string") model = message.model;
1471
+ const usage = isRecord$1(message.usage) ? message.usage : {};
1472
+ const cacheRead = Number(usage.cache_read_input_tokens ?? 0) || 0;
1473
+ const cacheWrite = Number(usage.cache_creation_input_tokens ?? 0) || 0;
1474
+ inputTokens = (Number(usage.input_tokens ?? 0) || 0) + cacheRead + cacheWrite;
1475
+ cachedTokens = cacheRead;
1476
+ outputTokens = Number(usage.output_tokens ?? 0) || 0;
1477
+ return [chunk({ role: "assistant" })];
1478
+ }
1479
+ case "content_block_start": {
1480
+ const block = isRecord$1(event.content_block) ? event.content_block : {};
1481
+ const index = Number(event.index ?? 0);
1482
+ if (block.type !== "tool_use") return [];
1483
+ const toolIndex = toolIndexes.size;
1484
+ toolIndexes.set(index, toolIndex);
1485
+ return [chunk({ tool_calls: [{
1486
+ function: {
1487
+ arguments: "",
1488
+ name: typeof block.name === "string" ? block.name : ""
1489
+ },
1490
+ id: typeof block.id === "string" ? block.id : void 0,
1491
+ index: toolIndex,
1492
+ type: "function"
1493
+ }] })];
1494
+ }
1495
+ case "content_block_delta": {
1496
+ const delta = isRecord$1(event.delta) ? event.delta : {};
1497
+ if (delta.type === "text_delta" && typeof delta.text === "string") return [chunk({ content: delta.text })];
1498
+ if (delta.type === "thinking_delta" && typeof delta.thinking === "string") return [chunk({ reasoning_content: delta.thinking })];
1499
+ if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
1500
+ const toolIndex = toolIndexes.get(Number(event.index ?? 0));
1501
+ if (toolIndex === void 0) return [];
1502
+ return [chunk({ tool_calls: [{
1503
+ function: { arguments: delta.partial_json },
1504
+ index: toolIndex
1505
+ }] })];
1506
+ }
1507
+ return [];
1508
+ }
1509
+ case "message_delta": {
1510
+ const usage = isRecord$1(event.usage) ? event.usage : {};
1511
+ if (typeof usage.output_tokens === "number") outputTokens = usage.output_tokens;
1512
+ const delta = isRecord$1(event.delta) ? event.delta : {};
1513
+ const stop = typeof delta.stop_reason === "string" ? delta.stop_reason : void 0;
1514
+ if (stop === void 0) return [];
1515
+ return [chunk({}, FINISH_REASONS[stop] ?? "stop")];
1516
+ }
1517
+ case "message_stop": return [{
1518
+ choices: [],
1519
+ ...id === void 0 ? {} : { id },
1520
+ ...model === void 0 ? {} : { model },
1521
+ object: "chat.completion.chunk",
1522
+ usage: {
1523
+ completion_tokens: outputTokens,
1524
+ prompt_tokens: inputTokens,
1525
+ ...cachedTokens > 0 ? { prompt_tokens_details: { cached_tokens: cachedTokens } } : {},
1526
+ total_tokens: inputTokens + outputTokens
1527
+ }
1528
+ }];
1529
+ case "error": {
1530
+ const error = isRecord$1(event.error) ? event.error : {};
1531
+ return [{ error: {
1532
+ message: typeof error.message === "string" ? error.message : "The model's stream ended with an error.",
1533
+ type: typeof error.type === "string" ? error.type : "error"
1534
+ } }];
1535
+ }
1536
+ default: return [];
1537
+ }
1538
+ };
1539
+ }
1540
+ //#endregion
1036
1541
  //#region src/models/core/chat-completions/stream.ts
1037
1542
  /**
1038
1543
  * Normalizes a chat-completions SSE stream from the run path into strict
@@ -1111,6 +1616,7 @@ function normalizeChatCompletionsStream(modelId, options = {}) {
1111
1616
  let finalUsage;
1112
1617
  let finalUsageTemplate;
1113
1618
  const perDeltaUsage = isWorkersAI(modelId);
1619
+ const translateAnthropic = createAnthropicStreamTranslator();
1114
1620
  const envelope = (extra) => ({
1115
1621
  ...id !== void 0 ? { id } : {},
1116
1622
  object: "chat.completion.chunk",
@@ -1140,6 +1646,90 @@ function normalizeChatCompletionsStream(modelId, options = {}) {
1140
1646
  }
1141
1647
  if (sawDone) controller.enqueue("[DONE]");
1142
1648
  };
1649
+ /** Normalizes one chat-completions chunk (or native Workers AI event). */
1650
+ const handle = (parsed, controller) => {
1651
+ const chunk = stripEchoSuffix(stripNoise(parsed), modelId);
1652
+ if (typeof chunk.id === "string") id ??= chunk.id;
1653
+ if (typeof chunk.model === "string") model ??= chunk.model;
1654
+ if (typeof chunk.created === "number") created ??= chunk.created;
1655
+ if (isRecord(chunk.error)) {
1656
+ sawFinishReason = true;
1657
+ controller.enqueue(JSON.stringify(chunk));
1658
+ return;
1659
+ }
1660
+ if (Array.isArray(chunk.choices)) {
1661
+ if (chunk.choices.length === 0) {
1662
+ if (isRecord(chunk.usage)) {
1663
+ finalUsage = chunk.usage;
1664
+ finalUsageTemplate = {
1665
+ ...chunk,
1666
+ usage: void 0
1667
+ };
1668
+ delete finalUsageTemplate.usage;
1669
+ }
1670
+ return;
1671
+ }
1672
+ const choices = chunk.choices.map((choice) => {
1673
+ if (!isRecord(choice)) return choice;
1674
+ if (typeof choice.finish_reason === "string") sawFinishReason = true;
1675
+ const delta = isRecord(choice.delta) ? normalizeDelta(choice.delta, relabelReasoning) : choice.delta;
1676
+ if (isRecord(delta)) {
1677
+ if (typeof delta.content === "string" && delta.content !== "") sawContent = true;
1678
+ if (typeof delta.reasoning_content === "string") sawContent = true;
1679
+ if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) sawToolCalls = true;
1680
+ }
1681
+ if (!perDeltaUsage) return {
1682
+ ...choice,
1683
+ delta
1684
+ };
1685
+ const { usage: _perDeltaUsage, ...rest } = choice;
1686
+ return {
1687
+ ...rest,
1688
+ delta
1689
+ };
1690
+ });
1691
+ if (perDeltaUsage) {
1692
+ const { usage: _usage, ...rest } = chunk;
1693
+ emit(controller, {
1694
+ ...rest,
1695
+ choices
1696
+ });
1697
+ } else emit(controller, {
1698
+ ...chunk,
1699
+ choices
1700
+ });
1701
+ return;
1702
+ }
1703
+ if ("response" in chunk || "tool_calls" in chunk) {
1704
+ const text = chunk.response;
1705
+ const hasText = typeof text === "string" && text !== "";
1706
+ const calls = Array.isArray(chunk.tool_calls) ? chunk.tool_calls.map((call, index) => normalizeToolCall(call, index)).filter((call) => call !== void 0) : [];
1707
+ if (hasText || calls.length > 0) {
1708
+ if (hasText) sawContent = true;
1709
+ if (calls.length > 0) sawToolCalls = true;
1710
+ emit(controller, envelope({ choices: [{
1711
+ delta: {
1712
+ ...hasText ? { content: text } : {},
1713
+ ...calls.length > 0 ? { tool_calls: calls } : {}
1714
+ },
1715
+ finish_reason: null,
1716
+ index: 0
1717
+ }] }));
1718
+ }
1719
+ if (isRecord(chunk.usage)) {
1720
+ finalUsage = chunk.usage;
1721
+ finalUsageTemplate = void 0;
1722
+ }
1723
+ if (typeof chunk.finish_reason === "string") {
1724
+ sawFinishReason = true;
1725
+ emit(controller, envelope({ choices: [{
1726
+ delta: {},
1727
+ finish_reason: chunk.finish_reason,
1728
+ index: 0
1729
+ }] }));
1730
+ }
1731
+ }
1732
+ };
1143
1733
  return new TransformStream({
1144
1734
  flush(controller) {
1145
1735
  emitTail(controller);
@@ -1157,87 +1747,7 @@ function normalizeChatCompletionsStream(modelId, options = {}) {
1157
1747
  return;
1158
1748
  }
1159
1749
  if (!isRecord(parsed)) return;
1160
- const chunk = stripEchoSuffix(stripNoise(parsed), modelId);
1161
- if (typeof chunk.id === "string") id ??= chunk.id;
1162
- if (typeof chunk.model === "string") model ??= chunk.model;
1163
- if (typeof chunk.created === "number") created ??= chunk.created;
1164
- if (isRecord(chunk.error)) {
1165
- sawFinishReason = true;
1166
- controller.enqueue(JSON.stringify(chunk));
1167
- return;
1168
- }
1169
- if (Array.isArray(chunk.choices)) {
1170
- if (chunk.choices.length === 0) {
1171
- if (isRecord(chunk.usage)) {
1172
- finalUsage = chunk.usage;
1173
- finalUsageTemplate = {
1174
- ...chunk,
1175
- usage: void 0
1176
- };
1177
- delete finalUsageTemplate.usage;
1178
- }
1179
- return;
1180
- }
1181
- const choices = chunk.choices.map((choice) => {
1182
- if (!isRecord(choice)) return choice;
1183
- if (typeof choice.finish_reason === "string") sawFinishReason = true;
1184
- const delta = isRecord(choice.delta) ? normalizeDelta(choice.delta, relabelReasoning) : choice.delta;
1185
- if (isRecord(delta)) {
1186
- if (typeof delta.content === "string" && delta.content !== "") sawContent = true;
1187
- if (typeof delta.reasoning_content === "string") sawContent = true;
1188
- if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) sawToolCalls = true;
1189
- }
1190
- if (!perDeltaUsage) return {
1191
- ...choice,
1192
- delta
1193
- };
1194
- const { usage: _perDeltaUsage, ...rest } = choice;
1195
- return {
1196
- ...rest,
1197
- delta
1198
- };
1199
- });
1200
- if (perDeltaUsage) {
1201
- const { usage: _usage, ...rest } = chunk;
1202
- emit(controller, {
1203
- ...rest,
1204
- choices
1205
- });
1206
- } else emit(controller, {
1207
- ...chunk,
1208
- choices
1209
- });
1210
- return;
1211
- }
1212
- if ("response" in chunk || "tool_calls" in chunk) {
1213
- const text = chunk.response;
1214
- const hasText = typeof text === "string" && text !== "";
1215
- const calls = Array.isArray(chunk.tool_calls) ? chunk.tool_calls.map((call, index) => normalizeToolCall(call, index)).filter((call) => call !== void 0) : [];
1216
- if (hasText || calls.length > 0) {
1217
- if (hasText) sawContent = true;
1218
- if (calls.length > 0) sawToolCalls = true;
1219
- emit(controller, envelope({ choices: [{
1220
- delta: {
1221
- ...hasText ? { content: text } : {},
1222
- ...calls.length > 0 ? { tool_calls: calls } : {}
1223
- },
1224
- finish_reason: null,
1225
- index: 0
1226
- }] }));
1227
- }
1228
- if (isRecord(chunk.usage)) {
1229
- finalUsage = chunk.usage;
1230
- finalUsageTemplate = void 0;
1231
- }
1232
- if (typeof chunk.finish_reason === "string") {
1233
- sawFinishReason = true;
1234
- emit(controller, envelope({ choices: [{
1235
- delta: {},
1236
- finish_reason: chunk.finish_reason,
1237
- index: 0
1238
- }] }));
1239
- }
1240
- }
1750
+ for (const event of translateAnthropic(parsed) ?? [parsed]) handle(event, controller);
1241
1751
  }
1242
1752
  });
1243
1753
  }
@@ -1380,6 +1890,6 @@ function imageMediaTypeOf(base64) {
1380
1890
  return "image/png";
1381
1891
  }
1382
1892
  //#endregion
1383
- export { isRetryableStatus as S, isGatewayErrorEnvelope as _, normalizeChatCompletion as a, classifyStatus 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, isAbortError as x, CloudflareAIError as y };
1893
+ export { isCatalogModelId as C, isRetryableStatus as S, isRunPathLanguageModelId as T, isGatewayErrorEnvelope as _, normalizeChatCompletion as a, classifyStatus 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, isDynamicRouteId as w, isAbortError as x, CloudflareAIError as y };
1384
1894
 
1385
- //# sourceMappingURL=images-Ce9SCu3X.js.map
1895
+ //# sourceMappingURL=images-xaDv2Fqe.js.map