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.
- package/dist/{agent-routing-BMvWTFEp.d.ts → agent-routing-CDbpVjYq.d.ts} +7 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-ohoewW-B.d.ts → agent-tools-C8DH8Yii.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/{ai-ahruGw7e.d.ts → ai-iXw0uOGj.d.ts} +3 -3
- package/dist/{ai-sdk-C2iTAwUd.js → ai-sdk-DRPh2rRa.js} +185 -125
- package/dist/ai-sdk-DRPh2rRa.js.map +1 -0
- package/dist/browser/ai.d.ts +1 -1
- package/dist/browser/ai.js +2 -2
- package/dist/browser/index.d.ts +8 -8
- package/dist/browser/index.js +3 -3
- package/dist/browser/tools/ai-sdk.d.ts +2 -2
- package/dist/browser/tools/ai-sdk.js +1 -1
- package/dist/browser/tools/pi.d.ts +1 -1
- package/dist/browser/tools/pi.js +3 -3
- package/dist/browser/tools/pi.js.map +1 -1
- package/dist/browser/tools/tanstack-ai.d.ts +2 -2
- package/dist/browser/tools/tanstack-ai.js +1 -1
- package/dist/{browser-CEdNlsaD.js → browser-DccPw_wW.js} +130 -79
- package/dist/browser-DccPw_wW.js.map +1 -0
- package/dist/{browser-D30VlpcA.d.ts → browser-DlXhSAdf.d.ts} +41 -37
- package/dist/{browser-tool-4iomavs_.js → browser-tool-B1mhifin.js} +3 -3
- package/dist/{browser-tool-4iomavs_.js.map → browser-tool-B1mhifin.js.map} +1 -1
- package/dist/{browser-tool-vtrPOV8e.d.ts → browser-tool-Cxjk5sgG.d.ts} +2 -2
- package/dist/chat/index.d.ts +143 -71
- package/dist/chat/index.js +269 -110
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +33 -33
- package/dist/{client-Bp-JwL3p.d.ts → client-C27CtRZr.d.ts} +2 -2
- package/dist/client.d.ts +1 -1
- package/dist/{connector-DEXzVoaY.d.ts → connector-20DJ6p4k.d.ts} +2 -2
- package/dist/{connector-CtZylXdk.js → connector-DAXBOIIi.js} +2 -2
- package/dist/{connector-CtZylXdk.js.map → connector-DAXBOIIi.js.map} +1 -1
- package/dist/experimental/channels/web/index.js +1 -1
- package/dist/harness/container/index.js +1 -1
- package/dist/harness/container/index.js.map +1 -1
- package/dist/harness/opencode/index.js +1 -1
- package/dist/harness/pi/index.d.ts +13 -5
- package/dist/harness/pi/index.js +8 -214
- package/dist/harness/pi/index.js.map +1 -1
- package/dist/{images-C4hiIexM.js → images-xaDv2Fqe.js} +636 -168
- package/dist/images-xaDv2Fqe.js.map +1 -0
- package/dist/index-DCxk3OSJ.d.ts +378 -0
- package/dist/{index-kbSUIer3.d.ts → index-iv9FTXX9.d.ts} +80 -112
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{input-schema-xBofjBKC.js → input-schema-B0Etuo-o.js} +2 -2
- package/dist/{input-schema-xBofjBKC.js.map → input-schema-B0Etuo-o.js.map} +1 -1
- package/dist/lifecycle/index.js +1 -1
- package/dist/{lifecycle-DGorpAmA.js → lifecycle-ClT4DmF2.js} +11 -3
- package/dist/lifecycle-ClT4DmF2.js.map +1 -0
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/models/ai-sdk/index.d.ts +29 -352
- package/dist/models/ai-sdk/index.js +1 -1
- package/dist/models/opencode/index.d.ts +25 -13
- package/dist/models/opencode/index.js +16 -11
- package/dist/models/opencode/index.js.map +1 -1
- package/dist/models/pi-ai/index.d.ts +13 -14
- package/dist/models/pi-ai/index.js +85 -32
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/{session-store-DoPY12xM.d.ts → session-store-_IK6dV-w.d.ts} +14 -2
- package/dist/{session-store-Do4xkDcf.js → session-store-yvwq1f9x.js} +55 -2
- package/dist/session-store-yvwq1f9x.js.map +1 -0
- package/dist/{settings-BjAiPd6Z.js → settings-Dk2il2Hf.js} +4 -1
- package/dist/settings-Dk2il2Hf.js.map +1 -0
- package/dist/skills/index.d.ts +22 -28
- package/dist/skills/index.js +183 -143
- package/dist/skills/index.js.map +1 -1
- package/dist/{src-CQxJFv4l.js → src-6lr7MtM_.js} +3 -3
- package/dist/src-6lr7MtM_.js.map +1 -0
- package/dist/sub-routing.d.ts +1 -1
- package/dist/{tool-VF0qiO7q.js → tool-DkT_DJBm.js} +2 -2
- package/dist/tool-DkT_DJBm.js.map +1 -0
- package/dist/transport-CPh7YGiw.d.ts +623 -0
- package/dist/voice/index.d.ts +1 -1
- package/dist/web/fetch/tools/pi.js +2 -2
- package/dist/web/fetch/tools/pi.js.map +1 -1
- package/dist/web/search/index.js +1 -1
- package/dist/web/search/tools/ai-sdk.js +2 -2
- package/dist/web/search/tools/pi.js +3 -3
- package/dist/web/search/tools/pi.js.map +1 -1
- package/dist/web/search/tools/tanstack-ai.js +2 -2
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-C3zy8YR0.js → websockets-CqxnntmE.js} +2 -2
- package/dist/{websockets-C3zy8YR0.js.map → websockets-CqxnntmE.js.map} +1 -1
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +1 -1
- package/docs/browse-the-web.md +1 -1
- package/docs/chat-agents.md +19 -18
- package/docs/harnesses/pi.md +47 -18
- package/docs/models-pi-ai.md +62 -20
- package/docs/models.md +197 -172
- package/package.json +7 -7
- package/dist/ai-sdk-C2iTAwUd.js.map +0 -1
- package/dist/browser-CEdNlsaD.js.map +0 -1
- package/dist/images-C4hiIexM.js.map +0 -1
- package/dist/lifecycle-DGorpAmA.js.map +0 -1
- package/dist/session-store-Do4xkDcf.js.map +0 -1
- package/dist/settings-BjAiPd6Z.js.map +0 -1
- package/dist/settings-DnD__xuE.d.ts +0 -349
- package/dist/src-CQxJFv4l.js.map +0 -1
- package/dist/tool-VF0qiO7q.js.map +0 -1
- 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
|
|
151
|
-
const
|
|
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
|
|
246
|
-
*
|
|
247
|
-
*
|
|
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
|
|
250
|
-
const
|
|
251
|
-
if (gateway.
|
|
252
|
-
if (gateway.
|
|
253
|
-
if (gateway.cacheKey !== void 0)
|
|
254
|
-
if (gateway.metadata !== void 0)
|
|
255
|
-
if (gateway.collectLog !== void 0)
|
|
256
|
-
if (gateway.eventId !== void 0)
|
|
257
|
-
if (gateway.requestTimeoutMs !== void 0)
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
263
|
-
* `GatewayOptions` type
|
|
264
|
-
*
|
|
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
|
|
267
|
-
const { zdr: _zdr, ...
|
|
268
|
-
return
|
|
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
|
|
272
|
-
* `cf-aig-
|
|
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
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
351
|
-
return
|
|
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
|
|
573
|
+
async native(request) {
|
|
361
574
|
try {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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:
|
|
376
|
-
requestBodyValues:
|
|
377
|
-
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
|
|
393
|
-
|
|
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
|
|
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: "
|
|
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$
|
|
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$
|
|
771
|
-
const image = isRecord$
|
|
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$
|
|
1160
|
+
if (!isRecord$3(message)) continue;
|
|
814
1161
|
if (Array.isArray(message.tool_calls)) {
|
|
815
|
-
for (const call of message.tool_calls) if (isRecord$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
922
|
-
const name = only !== void 0 && isRecord$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
990
|
-
const fn = isRecord$
|
|
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$
|
|
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$
|
|
1043
|
-
const message = isRecord$
|
|
1044
|
-
const hasToolCalls = isRecord$
|
|
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
|
|
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 {
|
|
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-
|
|
1895
|
+
//# sourceMappingURL=images-xaDv2Fqe.js.map
|