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