agents 0.28.1 → 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 (112) hide show
  1. package/dist/{agent-routing-BMvWTFEp.d.ts → agent-routing-CDbpVjYq.d.ts} +7 -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-ohoewW-B.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-C2iTAwUd.js → ai-sdk-DRPh2rRa.js} +185 -125
  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-Bp-JwL3p.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/harness/container/index.js +1 -1
  39. package/dist/harness/container/index.js.map +1 -1
  40. package/dist/harness/opencode/index.js +1 -1
  41. package/dist/harness/pi/index.d.ts +13 -5
  42. package/dist/harness/pi/index.js +8 -214
  43. package/dist/harness/pi/index.js.map +1 -1
  44. package/dist/{images-C4hiIexM.js → images-xaDv2Fqe.js} +636 -168
  45. package/dist/images-xaDv2Fqe.js.map +1 -0
  46. package/dist/index-DCxk3OSJ.d.ts +378 -0
  47. package/dist/{index-kbSUIer3.d.ts → index-iv9FTXX9.d.ts} +80 -112
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +1 -1
  50. package/dist/{input-schema-xBofjBKC.js → input-schema-B0Etuo-o.js} +2 -2
  51. package/dist/{input-schema-xBofjBKC.js.map → input-schema-B0Etuo-o.js.map} +1 -1
  52. package/dist/lifecycle/index.js +1 -1
  53. package/dist/{lifecycle-DGorpAmA.js → lifecycle-ClT4DmF2.js} +11 -3
  54. package/dist/lifecycle-ClT4DmF2.js.map +1 -0
  55. package/dist/mcp/client/index.d.ts +1 -1
  56. package/dist/mcp/index.d.ts +1 -1
  57. package/dist/mcp/index.js +1 -1
  58. package/dist/models/ai-sdk/index.d.ts +29 -352
  59. package/dist/models/ai-sdk/index.js +1 -1
  60. package/dist/models/opencode/index.d.ts +25 -13
  61. package/dist/models/opencode/index.js +16 -11
  62. package/dist/models/opencode/index.js.map +1 -1
  63. package/dist/models/pi-ai/index.d.ts +13 -14
  64. package/dist/models/pi-ai/index.js +85 -32
  65. package/dist/models/pi-ai/index.js.map +1 -1
  66. package/dist/react.d.ts +2 -2
  67. package/dist/routing/index.d.ts +1 -1
  68. package/dist/serializable.d.ts +1 -1
  69. package/dist/{session-store-DoPY12xM.d.ts → session-store-_IK6dV-w.d.ts} +14 -2
  70. package/dist/{session-store-Do4xkDcf.js → session-store-yvwq1f9x.js} +55 -2
  71. package/dist/session-store-yvwq1f9x.js.map +1 -0
  72. package/dist/{settings-BjAiPd6Z.js → settings-Dk2il2Hf.js} +4 -1
  73. package/dist/settings-Dk2il2Hf.js.map +1 -0
  74. package/dist/skills/index.d.ts +22 -28
  75. package/dist/skills/index.js +183 -143
  76. package/dist/skills/index.js.map +1 -1
  77. package/dist/{src-CQxJFv4l.js → src-6lr7MtM_.js} +3 -3
  78. package/dist/src-6lr7MtM_.js.map +1 -0
  79. package/dist/sub-routing.d.ts +1 -1
  80. package/dist/{tool-VF0qiO7q.js → tool-DkT_DJBm.js} +2 -2
  81. package/dist/tool-DkT_DJBm.js.map +1 -0
  82. package/dist/transport-CPh7YGiw.d.ts +623 -0
  83. package/dist/voice/index.d.ts +1 -1
  84. package/dist/web/fetch/tools/pi.js +2 -2
  85. package/dist/web/fetch/tools/pi.js.map +1 -1
  86. package/dist/web/search/index.js +1 -1
  87. package/dist/web/search/tools/ai-sdk.js +2 -2
  88. package/dist/web/search/tools/pi.js +3 -3
  89. package/dist/web/search/tools/pi.js.map +1 -1
  90. package/dist/web/search/tools/tanstack-ai.js +2 -2
  91. package/dist/websockets/index.js +1 -1
  92. package/dist/{websockets-C3zy8YR0.js → websockets-CqxnntmE.js} +2 -2
  93. package/dist/{websockets-C3zy8YR0.js.map → websockets-CqxnntmE.js.map} +1 -1
  94. package/dist/workflow-types.d.ts +1 -1
  95. package/dist/workflows.d.ts +1 -1
  96. package/dist/workflows.js +1 -1
  97. package/docs/browse-the-web.md +1 -1
  98. package/docs/chat-agents.md +19 -18
  99. package/docs/harnesses/pi.md +47 -18
  100. package/docs/models-pi-ai.md +62 -20
  101. package/docs/models.md +197 -172
  102. package/package.json +7 -7
  103. package/dist/ai-sdk-C2iTAwUd.js.map +0 -1
  104. package/dist/browser-CEdNlsaD.js.map +0 -1
  105. package/dist/images-C4hiIexM.js.map +0 -1
  106. package/dist/lifecycle-DGorpAmA.js.map +0 -1
  107. package/dist/session-store-Do4xkDcf.js.map +0 -1
  108. package/dist/settings-BjAiPd6Z.js.map +0 -1
  109. package/dist/settings-DnD__xuE.d.ts +0 -349
  110. package/dist/src-CQxJFv4l.js.map +0 -1
  111. package/dist/tool-VF0qiO7q.js.map +0 -1
  112. package/dist/transport-DCyarnwm.d.ts +0 -200
@@ -9,6 +9,27 @@ function isDynamicRouteId(modelId) {
9
9
  return modelId.startsWith("dynamic/") && modelId.length > 8;
10
10
  }
11
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
+ /**
12
33
  * Whether a string names a language model that runs on `env.AI.run`: a
13
34
  * Workers AI id or an AI Gateway dynamic route.
14
35
  *
@@ -146,9 +167,155 @@ function normalizeThrownError(error, context) {
146
167
  });
147
168
  }
148
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
149
310
  //#region src/models/core/transport.ts
150
- /** The reported `url` for a universal call made through the binding. */
151
- 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";
152
319
  /**
153
320
  * Drops entries whose value is `undefined`. The AI SDK types call headers as
154
321
  * `Record<string, string | undefined>`, and an undefined value reaches the
@@ -242,44 +409,85 @@ async function errorFromResponse(response, context) {
242
409
  });
243
410
  }
244
411
  /**
245
- * The gateway's own options for the universal call. The id is not among them:
246
- * it selects the gateway (`env.AI.gateway(id)`), and the binding's own `id`
247
- * 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.
248
415
  */
249
- function universalGatewayOptions(gateway) {
250
- const options = { id: gateway.id };
251
- if (gateway.skipCache !== void 0) options.skipCache = gateway.skipCache;
252
- if (gateway.cacheTtl !== void 0) options.cacheTtl = gateway.cacheTtl;
253
- if (gateway.cacheKey !== void 0) options.cacheKey = gateway.cacheKey;
254
- if (gateway.metadata !== void 0) options.metadata = gateway.metadata;
255
- if (gateway.collectLog !== void 0) options.collectLog = gateway.collectLog;
256
- if (gateway.eventId !== void 0) options.eventId = gateway.eventId;
257
- if (gateway.requestTimeoutMs !== void 0) options.requestTimeoutMs = gateway.requestTimeoutMs;
258
- if (gateway.retries !== void 0) options.retries = gateway.retries;
259
- 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;
260
432
  }
261
433
  /**
262
- * The gateway options for the Workers AI run path, as the binding's own
263
- * `GatewayOptions` type has them. `zdr` is left out: the binding has no field
264
- * for it, and it applies to vendor models routed through the gateway.
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.
265
441
  */
266
- function runGatewayOptions(gateway) {
267
- const { zdr: _zdr, ...options } = gateway;
268
- return options;
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
+ };
269
451
  }
270
452
  /**
271
- * The universal call's own request headers: the caller's extra headers, plus
272
- * `cf-aig-zdr`. The gateway reads ZDR off the request itself, not off a
273
- * provider entry, and the binding's options have no field for it.
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`.
274
455
  */
275
- function universalRequestHeaders(request) {
276
- const headers = {
277
- ...request.extraHeaders,
278
- ...request.gateway.zdr === void 0 ? {} : { "cf-aig-zdr": String(request.gateway.zdr) }
279
- };
280
- return Object.keys(headers).length > 0 ? headers : void 0;
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
+ });
281
480
  }
282
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
+ /**
283
491
  * Whether a parsed body is the gateway's own error envelope rather than a
284
492
  * vendor payload — `{ success: false, error: [{ code, message }] }`, named
285
493
  * `AiGatewayError`. The gateway answers this for its own failures (no such
@@ -298,7 +506,7 @@ function isGatewayErrorEnvelope(json) {
298
506
  }
299
507
  /**
300
508
  * Builds a {@link CloudflareAIError} from the gateway's own error envelope, so
301
- * 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
302
510
  * one on the Workers AI run path.
303
511
  *
304
512
  * @experimental This surface is experimental and may change.
@@ -318,18 +526,6 @@ function errorFromGatewayEnvelope(body, context) {
318
526
  url: context.url
319
527
  });
320
528
  }
321
- /**
322
- * The model id a universal request is about, for error reporting. Every vendor
323
- * body carries `model`; the endpoint is the fallback.
324
- */
325
- function universalModel(request) {
326
- const query = request.query;
327
- if (query !== null && typeof query === "object" && !Array.isArray(query)) {
328
- const model = query.model;
329
- if (typeof model === "string") return model;
330
- }
331
- return `${request.provider}/${request.endpoint}`;
332
- }
333
529
  /** The keyless in-Worker backend: `env.AI.run(...)`. */
334
530
  function bindingTransport(binding) {
335
531
  const raw = binding;
@@ -340,15 +536,32 @@ function bindingTransport(binding) {
340
536
  },
341
537
  async run(request) {
342
538
  try {
343
- 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
+ }, {
344
546
  ...Object.keys(request.headers).length > 0 ? { extraHeaders: request.headers } : {},
345
- gateway: runGatewayOptions(request.gateway),
346
547
  returnRawResponse: true,
347
548
  signal: request.signal
348
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
+ }));
349
562
  const logId = binding.aiGatewayLogId;
350
- if (logId !== null && logId !== void 0) logIds.set(response, logId);
351
- return response;
563
+ if (logId !== null && logId !== void 0) logIds.set(resumable, logId);
564
+ return resumable;
352
565
  } catch (error) {
353
566
  throw normalizeThrownError(error, {
354
567
  model: request.model,
@@ -357,24 +570,22 @@ function bindingTransport(binding) {
357
570
  });
358
571
  }
359
572
  },
360
- async universal(request) {
573
+ async native(request) {
361
574
  try {
362
- const extraHeaders = universalRequestHeaders(request);
363
- return await binding.gateway(request.gateway.id).run({
364
- endpoint: request.endpoint,
365
- headers: request.headers,
366
- provider: request.provider,
367
- query: request.query
368
- }, {
369
- ...extraHeaders === void 0 ? {} : { extraHeaders },
370
- 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,
371
582
  signal: request.signal
372
583
  });
373
584
  } catch (error) {
374
585
  throw normalizeThrownError(error, {
375
- model: universalModel(request),
376
- requestBodyValues: request.query,
377
- url: UNIVERSAL_BINDING_URL
586
+ model: `${request.provider}/${request.endpoint}`,
587
+ requestBodyValues: void 0,
588
+ url: NATIVE_BINDING_URL
378
589
  });
379
590
  }
380
591
  },
@@ -382,6 +593,140 @@ function bindingTransport(binding) {
382
593
  };
383
594
  }
384
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
+ /**
385
730
  * Builds the transport for a set of provider settings. The model id, not the
386
731
  * caller, decides the wire format; the transport only decides how the request
387
732
  * physically leaves the Worker.
@@ -389,14 +734,15 @@ function bindingTransport(binding) {
389
734
  * @experimental This surface is experimental and may change.
390
735
  */
391
736
  function createTransport(settings) {
392
- if (settings.binding === void 0) throw new Error("createAI requires { binding }: the Workers AI binding.");
393
- 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 }.");
394
740
  }
395
741
  //#endregion
396
742
  //#region src/models/core/gateway-providers.ts
397
743
  /**
398
744
  * The AI Gateway provider table: which gateway provider slug a vendor's own
399
- * 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
400
746
  * given upstream URL.
401
747
  *
402
748
  * This is platform knowledge, not vendor knowledge — the gateway publishes the
@@ -691,7 +1037,7 @@ const STRICT_OPENAI = {
691
1037
  imageUrls: "any",
692
1038
  jsonSchema: "supported",
693
1039
  jsonSchemaStrict: false,
694
- maxTokensField: "max_tokens",
1040
+ maxTokensField: "max_completion_tokens",
695
1041
  modelEchoSuffix: void 0,
696
1042
  nullAssistantContent: "allowed",
697
1043
  reasoningEffortLevels: "basic",
@@ -706,6 +1052,7 @@ const STRICT_OPENAI = {
706
1052
  const WORKERS_AI = {
707
1053
  ...STRICT_OPENAI,
708
1054
  chatTemplateKwargs: "supported",
1055
+ maxTokensField: "max_tokens",
709
1056
  imageUrls: "data-only",
710
1057
  nullAssistantContent: "empty-string",
711
1058
  reasoningOff: "chat-template",
@@ -754,7 +1101,7 @@ function isWorkersAI(modelId) {
754
1101
  * The rewrite never throws: a feature the model cannot take is dropped and
755
1102
  * reported as a {@link CompatWarning}.
756
1103
  */
757
- function isRecord$2(value) {
1104
+ function isRecord$3(value) {
758
1105
  return value !== null && typeof value === "object" && !Array.isArray(value);
759
1106
  }
760
1107
  function clampEffort(effort) {
@@ -767,8 +1114,8 @@ function stripImageUrls(message, warnings) {
767
1114
  if (!Array.isArray(message.content)) return message;
768
1115
  let dropped = 0;
769
1116
  const content = message.content.filter((part) => {
770
- if (!isRecord$2(part) || part.type !== "image_url") return true;
771
- 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;
772
1119
  if ((typeof image?.url === "string" ? image.url : "").startsWith("data:")) return true;
773
1120
  dropped += 1;
774
1121
  return false;
@@ -810,9 +1157,9 @@ function toolCallIdsIn(messages) {
810
1157
  ids.push(value);
811
1158
  };
812
1159
  for (const message of messages) {
813
- if (!isRecord$2(message)) continue;
1160
+ if (!isRecord$3(message)) continue;
814
1161
  if (Array.isArray(message.tool_calls)) {
815
- 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);
816
1163
  }
817
1164
  add(message.tool_call_id);
818
1165
  }
@@ -842,7 +1189,7 @@ function rewriteToolCallIds(message, plan) {
842
1189
  if (Array.isArray(rewritten.tool_calls)) rewritten = {
843
1190
  ...rewritten,
844
1191
  tool_calls: rewritten.tool_calls.map((call) => {
845
- if (!isRecord$2(call) || typeof call.id !== "string") return call;
1192
+ if (!isRecord$3(call) || typeof call.id !== "string") return call;
846
1193
  const replacement = plan.get(call.id);
847
1194
  return replacement === void 0 ? call : {
848
1195
  ...call,
@@ -882,7 +1229,7 @@ function prepareChatCompletionsRequest(input, modelId) {
882
1229
  if (effort === null) {
883
1230
  delete body.reasoning_effort;
884
1231
  if (quirks.reasoningOff === "chat-template") body.chat_template_kwargs = {
885
- ...isRecord$2(body.chat_template_kwargs) ? body.chat_template_kwargs : {},
1232
+ ...isRecord$3(body.chat_template_kwargs) ? body.chat_template_kwargs : {},
886
1233
  enable_thinking: false
887
1234
  };
888
1235
  else warnings.push({
@@ -898,14 +1245,14 @@ function prepareChatCompletionsRequest(input, modelId) {
898
1245
  message: `${modelId} does not take chat_template_kwargs; they were dropped.`
899
1246
  });
900
1247
  }
901
- if (isRecord$2(body.response_format)) {
1248
+ if (isRecord$3(body.response_format)) {
902
1249
  if (quirks.jsonSchema === "unsupported") {
903
1250
  delete body.response_format;
904
1251
  warnings.push({
905
1252
  feature: "response-format",
906
1253
  message: `${modelId} does not support response_format; the request was sent without it.`
907
1254
  });
908
- } 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)) {
909
1256
  const { strict: _strict, ...envelope } = body.response_format.json_schema;
910
1257
  body.response_format = {
911
1258
  ...body.response_format,
@@ -918,8 +1265,8 @@ function prepareChatCompletionsRequest(input, modelId) {
918
1265
  }
919
1266
  if (body.tool_choice === "required" && quirks.toolChoiceRequired === "named-fallback") {
920
1267
  const tools = Array.isArray(body.tools) ? body.tools : [];
921
- const only = tools.length === 1 && isRecord$2(tools[0]) ? tools[0] : void 0;
922
- 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;
923
1270
  if (name !== void 0) body.tool_choice = {
924
1271
  function: { name },
925
1272
  type: "function"
@@ -941,7 +1288,7 @@ function prepareChatCompletionsRequest(input, modelId) {
941
1288
  const messages = [];
942
1289
  let previousRole;
943
1290
  for (const entry of body.messages) {
944
- if (!isRecord$2(entry)) continue;
1291
+ if (!isRecord$3(entry)) continue;
945
1292
  let message = entry;
946
1293
  if (toolCallIds.size > 0) message = rewriteToolCallIds(message, toolCallIds);
947
1294
  if (message.role === "user" && quirks.imageUrls === "data-only") message = stripImageUrls(message, warnings);
@@ -970,7 +1317,7 @@ function prepareChatCompletionsRequest(input, modelId) {
970
1317
  }
971
1318
  return {
972
1319
  body,
973
- 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,
974
1321
  warnings
975
1322
  };
976
1323
  }
@@ -981,13 +1328,13 @@ function prepareChatCompletionsRequest(input, modelId) {
981
1328
  * strict OpenAI `chat.completion` shape, so framework parsers never see
982
1329
  * Workers AI's native envelope or its field spellings.
983
1330
  */
984
- function isRecord$1(value) {
1331
+ function isRecord$2(value) {
985
1332
  return value !== null && typeof value === "object" && !Array.isArray(value);
986
1333
  }
987
1334
  /** Native `{ name, arguments: object }` → OpenAI `{ id, type, function }`. */
988
1335
  function normalizeToolCall(entry, index) {
989
- if (!isRecord$1(entry)) return void 0;
990
- 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;
991
1338
  const name = typeof fn?.name === "string" ? fn.name : typeof entry.name === "string" ? entry.name : void 0;
992
1339
  if (name === void 0) return void 0;
993
1340
  const rawArguments = fn !== void 0 && "arguments" in fn ? fn.arguments : entry.arguments;
@@ -1036,12 +1383,12 @@ function normalizeMessage(message) {
1036
1383
  * lifted into `choices[0]`.
1037
1384
  */
1038
1385
  function normalizeChatCompletion(body, modelId) {
1039
- if (!isRecord$1(body)) return { choices: [] };
1386
+ if (!isRecord$2(body)) return { choices: [] };
1040
1387
  if (Array.isArray(body.choices)) {
1041
1388
  const choices = body.choices.map((choice) => {
1042
- if (!isRecord$1(choice)) return choice;
1043
- const message = isRecord$1(choice.message) ? normalizeMessage(choice.message) : choice.message;
1044
- 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;
1045
1392
  return {
1046
1393
  ...choice,
1047
1394
  message,
@@ -1075,6 +1422,122 @@ function normalizeChatCompletion(body, modelId) {
1075
1422
  return body;
1076
1423
  }
1077
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
1078
1541
  //#region src/models/core/chat-completions/stream.ts
1079
1542
  /**
1080
1543
  * Normalizes a chat-completions SSE stream from the run path into strict
@@ -1153,6 +1616,7 @@ function normalizeChatCompletionsStream(modelId, options = {}) {
1153
1616
  let finalUsage;
1154
1617
  let finalUsageTemplate;
1155
1618
  const perDeltaUsage = isWorkersAI(modelId);
1619
+ const translateAnthropic = createAnthropicStreamTranslator();
1156
1620
  const envelope = (extra) => ({
1157
1621
  ...id !== void 0 ? { id } : {},
1158
1622
  object: "chat.completion.chunk",
@@ -1182,6 +1646,90 @@ function normalizeChatCompletionsStream(modelId, options = {}) {
1182
1646
  }
1183
1647
  if (sawDone) controller.enqueue("[DONE]");
1184
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
+ };
1185
1733
  return new TransformStream({
1186
1734
  flush(controller) {
1187
1735
  emitTail(controller);
@@ -1199,87 +1747,7 @@ function normalizeChatCompletionsStream(modelId, options = {}) {
1199
1747
  return;
1200
1748
  }
1201
1749
  if (!isRecord(parsed)) return;
1202
- const chunk = stripEchoSuffix(stripNoise(parsed), modelId);
1203
- if (typeof chunk.id === "string") id ??= chunk.id;
1204
- if (typeof chunk.model === "string") model ??= chunk.model;
1205
- if (typeof chunk.created === "number") created ??= chunk.created;
1206
- if (isRecord(chunk.error)) {
1207
- sawFinishReason = true;
1208
- controller.enqueue(JSON.stringify(chunk));
1209
- return;
1210
- }
1211
- if (Array.isArray(chunk.choices)) {
1212
- if (chunk.choices.length === 0) {
1213
- if (isRecord(chunk.usage)) {
1214
- finalUsage = chunk.usage;
1215
- finalUsageTemplate = {
1216
- ...chunk,
1217
- usage: void 0
1218
- };
1219
- delete finalUsageTemplate.usage;
1220
- }
1221
- return;
1222
- }
1223
- const choices = chunk.choices.map((choice) => {
1224
- if (!isRecord(choice)) return choice;
1225
- if (typeof choice.finish_reason === "string") sawFinishReason = true;
1226
- const delta = isRecord(choice.delta) ? normalizeDelta(choice.delta, relabelReasoning) : choice.delta;
1227
- if (isRecord(delta)) {
1228
- if (typeof delta.content === "string" && delta.content !== "") sawContent = true;
1229
- if (typeof delta.reasoning_content === "string") sawContent = true;
1230
- if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) sawToolCalls = true;
1231
- }
1232
- if (!perDeltaUsage) return {
1233
- ...choice,
1234
- delta
1235
- };
1236
- const { usage: _perDeltaUsage, ...rest } = choice;
1237
- return {
1238
- ...rest,
1239
- delta
1240
- };
1241
- });
1242
- if (perDeltaUsage) {
1243
- const { usage: _usage, ...rest } = chunk;
1244
- emit(controller, {
1245
- ...rest,
1246
- choices
1247
- });
1248
- } else emit(controller, {
1249
- ...chunk,
1250
- choices
1251
- });
1252
- return;
1253
- }
1254
- if ("response" in chunk || "tool_calls" in chunk) {
1255
- const text = chunk.response;
1256
- const hasText = typeof text === "string" && text !== "";
1257
- const calls = Array.isArray(chunk.tool_calls) ? chunk.tool_calls.map((call, index) => normalizeToolCall(call, index)).filter((call) => call !== void 0) : [];
1258
- if (hasText || calls.length > 0) {
1259
- if (hasText) sawContent = true;
1260
- if (calls.length > 0) sawToolCalls = true;
1261
- emit(controller, envelope({ choices: [{
1262
- delta: {
1263
- ...hasText ? { content: text } : {},
1264
- ...calls.length > 0 ? { tool_calls: calls } : {}
1265
- },
1266
- finish_reason: null,
1267
- index: 0
1268
- }] }));
1269
- }
1270
- if (isRecord(chunk.usage)) {
1271
- finalUsage = chunk.usage;
1272
- finalUsageTemplate = void 0;
1273
- }
1274
- if (typeof chunk.finish_reason === "string") {
1275
- sawFinishReason = true;
1276
- emit(controller, envelope({ choices: [{
1277
- delta: {},
1278
- finish_reason: chunk.finish_reason,
1279
- index: 0
1280
- }] }));
1281
- }
1282
- }
1750
+ for (const event of translateAnthropic(parsed) ?? [parsed]) handle(event, controller);
1283
1751
  }
1284
1752
  });
1285
1753
  }
@@ -1422,6 +1890,6 @@ function imageMediaTypeOf(base64) {
1422
1890
  return "image/png";
1423
1891
  }
1424
1892
  //#endregion
1425
- export { isDynamicRouteId as C, 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, isRunPathLanguageModelId as w, 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 };
1426
1894
 
1427
- //# sourceMappingURL=images-C4hiIexM.js.map
1895
+ //# sourceMappingURL=images-xaDv2Fqe.js.map