agents 0.26.0 → 0.27.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/README.md +8 -9
- package/dist/{agent-routing-0DR0Tk2v.d.ts → agent-routing-Bhdbvf7s.d.ts} +13 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-YrATZJu2.d.ts → agent-tools-bJZwBMfq.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/ai-sdk-OoGkFy3s.js +2444 -0
- package/dist/ai-sdk-OoGkFy3s.js.map +1 -0
- package/dist/apply-chunks-1FheRb9W.js +99 -0
- package/dist/apply-chunks-1FheRb9W.js.map +1 -0
- package/dist/broadcast-state-CEyI4uAK.js +69 -0
- package/dist/broadcast-state-CEyI4uAK.js.map +1 -0
- package/dist/browser/ai-sdk.d.ts +1 -1
- package/dist/browser/ai-sdk.js +2 -2
- package/dist/browser/ai-sdk.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/tanstack-ai.d.ts +1 -1
- package/dist/browser/tanstack-ai.js +1 -1
- package/dist/{browser-DE3d-RLk.d.ts → browser-tmkcAAI3.d.ts} +2 -2
- package/dist/{browser-tool-DuNdlwZ9.d.ts → browser-tool-Bn-nzJQI.d.ts} +3 -3
- package/dist/{browser-tool-CQ10e6Kj.js → browser-tool-CBqs6hqa.js} +58 -18
- package/dist/browser-tool-CBqs6hqa.js.map +1 -0
- package/dist/capability-B4WbF81e.js.map +1 -1
- package/dist/{capability-runner-DBAnGr6L.d.ts → capability-runner-O5VbQzqg.d.ts} +9 -1
- package/dist/channel-Bak_6zDP.d.ts +333 -0
- package/dist/chat/index.d.ts +11 -9
- package/dist/chat/index.js +5 -194
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +2 -4
- package/dist/chat/react.js +3 -1
- package/dist/chat/react.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 +144 -0
- package/dist/{client-fyQrLGkD.js → client-1tEQ1Oqz.js} +27 -2
- package/dist/{client-fyQrLGkD.js.map → client-1tEQ1Oqz.js.map} +1 -1
- package/dist/{client-je3koD9Z.d.ts → client-DS3Dz9l0.d.ts} +2 -2
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +1 -1
- package/dist/conversations-DH0ut_8n.d.ts +192 -0
- package/dist/{current-agent-vAk_5JMt.d.ts → current-agent-C07C5o5Y.d.ts} +3 -3
- package/dist/daemon-core-Buubj-y1.js +481 -0
- package/dist/daemon-core-Buubj-y1.js.map +1 -0
- package/dist/daemon-core-CbohVPAu.d.ts +132 -0
- package/dist/{do-oauth-client-provider-Tmf1vgKz.d.ts → do-oauth-client-provider-DfsUcpc3.d.ts} +7 -1
- package/dist/{durable-object-lifecycle-HhgOMWq9.d.ts → durable-object-lifecycle-DRqp475_.d.ts} +2 -2
- package/dist/{channels/email.d.ts → experimental/channels/email/index.d.ts} +25 -12
- package/dist/{channels/email.js → experimental/channels/email/index.js} +13 -35
- package/dist/experimental/channels/email/index.js.map +1 -0
- package/dist/experimental/channels/index.d.ts +270 -0
- package/dist/experimental/channels/index.js +1186 -0
- package/dist/experimental/channels/index.js.map +1 -0
- package/dist/{channels/slack.d.ts → experimental/channels/slack/index.d.ts} +19 -9
- package/dist/experimental/channels/slack/index.js +267 -0
- package/dist/experimental/channels/slack/index.js.map +1 -0
- package/dist/{channels/telegram.d.ts → experimental/channels/telegram/index.d.ts} +19 -9
- package/dist/experimental/channels/telegram/index.js +198 -0
- package/dist/experimental/channels/telegram/index.js.map +1 -0
- package/dist/experimental/channels/web/ai-sdk.d.ts +55 -0
- package/dist/experimental/channels/web/ai-sdk.js +266 -0
- package/dist/experimental/channels/web/ai-sdk.js.map +1 -0
- package/dist/experimental/channels/web/client.d.ts +97 -0
- package/dist/experimental/channels/web/client.js +349 -0
- package/dist/experimental/channels/web/client.js.map +1 -0
- package/dist/experimental/channels/web/index.d.ts +189 -0
- package/dist/experimental/channels/web/index.js +332 -0
- package/dist/experimental/channels/web/index.js.map +1 -0
- package/dist/gateway-BDC07gYO.d.ts +100 -0
- package/dist/handle-zAzh0UW2.d.ts +460 -0
- package/dist/harness/ai-sdk/index.d.ts +175 -0
- package/dist/harness/ai-sdk/index.js +605 -0
- package/dist/harness/ai-sdk/index.js.map +1 -0
- package/dist/harness/container/daemon/index.d.ts +76 -0
- package/dist/harness/container/daemon/index.js +3 -0
- package/dist/harness/container/index.d.ts +740 -0
- package/dist/harness/container/index.js +2152 -0
- package/dist/harness/container/index.js.map +1 -0
- package/dist/harness/container/runtime/index.d.ts +144 -0
- package/dist/harness/container/runtime/index.js +1082 -0
- package/dist/harness/container/runtime/index.js.map +1 -0
- package/dist/harness/opencode/index.d.ts +369 -0
- package/dist/harness/opencode/index.js +1072 -0
- package/dist/harness/opencode/index.js.map +1 -0
- package/dist/harness/pi/index.d.ts +2 -1
- package/dist/harness/store/index.d.ts +24 -0
- package/dist/harness/store/index.js +2 -0
- package/dist/harness/think/index.d.ts +821 -0
- package/dist/harness/think/index.js +2529 -0
- package/dist/harness/think/index.js.map +1 -0
- package/dist/harness-B6B9S3rb.d.ts +203 -0
- package/dist/{images-iiTDSyRu.js → images-Ce9SCu3X.js} +2 -248
- package/dist/images-Ce9SCu3X.js.map +1 -0
- package/dist/{index-BreL0YrQ.d.ts → index-D0A5tbWu.d.ts} +2 -2
- package/dist/index-Ib_4dwOo.d.ts +200 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/ingress-EFbrS547.js +9 -0
- package/dist/ingress-EFbrS547.js.map +1 -0
- package/dist/input-schema-DNrpTu49.js +23 -0
- package/dist/input-schema-DNrpTu49.js.map +1 -0
- package/dist/internal-kmw70xwR.js +46 -0
- package/dist/internal-kmw70xwR.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +3 -3
- package/dist/mcp/client/do-oauth-client-provider.d.ts +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js +9 -0
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/client/x402.d.ts +35 -4
- package/dist/mcp/client/x402.js +24 -3
- package/dist/mcp/client/x402.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +2 -2
- package/dist/models/ai-sdk/index.d.ts +16 -14
- package/dist/models/ai-sdk/index.js +1 -2442
- package/dist/models/opencode/index.d.ts +65 -0
- package/dist/models/opencode/index.js +285 -0
- package/dist/models/opencode/index.js.map +1 -0
- package/dist/models/pi-ai/index.d.ts +9 -7
- package/dist/models/pi-ai/index.js +2 -1
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/options-pwTNq9z6.d.ts +134 -0
- package/dist/protocol-BEgrJYC5.d.ts +331 -0
- package/dist/protocol-CukuI73M.d.ts +386 -0
- package/dist/protocol-cOklVBBD.js +227 -0
- package/dist/protocol-cOklVBBD.js.map +1 -0
- package/dist/{protocol-kHsr-f0X.js → protocol-eXeJxhgu.js} +2 -67
- package/dist/protocol-eXeJxhgu.js.map +1 -0
- package/dist/protocol-y2sSY584.js +148 -0
- package/dist/protocol-y2sSY584.js.map +1 -0
- package/dist/queue/index.d.ts +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +2 -2
- package/dist/{scheduler-RdACNXDP.d.ts → scheduler-CrFCnv_J.d.ts} +2 -2
- package/dist/schedules/index.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +11 -434
- package/dist/sessions/index.js +1 -2271
- package/dist/sessions-B9pNuxdT.js +2272 -0
- package/dist/sessions-B9pNuxdT.js.map +1 -0
- package/dist/settings-C45hDliX.js +253 -0
- package/dist/settings-C45hDliX.js.map +1 -0
- package/dist/{transport-PfFnt-Ld.d.ts → settings-NgGwF7Qt.d.ts} +14 -208
- package/dist/{src-BZeu-SCD.js → src-DgCbZtIR.js} +4 -4
- package/dist/{src-BZeu-SCD.js.map → src-DgCbZtIR.js.map} +1 -1
- package/dist/stall-watchdog-BpRF2uwG.js +195 -0
- package/dist/stall-watchdog-BpRF2uwG.js.map +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/store-DLeqKuj3.d.ts +310 -0
- package/dist/store-vgcPYo8K.js +432 -0
- package/dist/store-vgcPYo8K.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-DW-UCOUc.js → streams-CITClVer.js} +16 -3
- package/dist/streams-CITClVer.js.map +1 -0
- package/dist/{streams-Cmkj9QfW.d.ts → streams-fbG61qM8.d.ts} +2 -2
- package/dist/sub-routing.d.ts +1 -1
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-68DESxf0.js → tasks-CkTZNaV1.js} +2 -1
- package/dist/tasks-CkTZNaV1.js.map +1 -0
- package/dist/{tasks-9YxYtByh.d.ts → tasks-PtlB4Uyn.d.ts} +2 -2
- package/dist/tool-3PA8xjB_.js +421 -0
- package/dist/tool-3PA8xjB_.js.map +1 -0
- package/dist/tool-BPJEbwaX.d.ts +278 -0
- package/dist/transport-k2mFrtL8.d.ts +200 -0
- package/dist/turns-DCV3ycDl.js +317 -0
- package/dist/turns-DCV3ycDl.js.map +1 -0
- package/dist/voice/client.js +1 -1
- package/dist/voice/index.d.ts +2 -2
- package/dist/voice/index.js +1 -1
- package/dist/websearch/index.d.ts +58 -0
- package/dist/websearch/index.js +2 -0
- package/dist/websearch/tools/ai-sdk.d.ts +77 -0
- package/dist/websearch/tools/ai-sdk.js +46 -0
- package/dist/websearch/tools/ai-sdk.js.map +1 -0
- package/dist/websearch/tools/pi.d.ts +82 -0
- package/dist/websearch/tools/pi.js +89 -0
- package/dist/websearch/tools/pi.js.map +1 -0
- package/dist/websearch/tools/tanstack-ai.d.ts +71 -0
- package/dist/websearch/tools/tanstack-ai.js +46 -0
- package/dist/websearch/tools/tanstack-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +12 -296
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-Ci-LZFHH.js → websockets-CmENGb-Z.js} +18 -4
- package/dist/websockets-CmENGb-Z.js.map +1 -0
- package/dist/{wire-types-CrjKvDyi.d.ts → wire-types-Cv0iwjk5.d.ts} +3 -51
- 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 +3 -0
- package/docs/harnesses/think.md +215 -0
- package/docs/index.md +2 -1
- package/docs/lifecycle.md +40 -0
- package/docs/search-the-web.md +133 -0
- package/package.json +117 -38
- package/dist/browser-tool-CQ10e6Kj.js.map +0 -1
- package/dist/channel-Bnm4S7T2.d.ts +0 -491
- package/dist/channels/ai-sdk.d.ts +0 -46
- package/dist/channels/ai-sdk.js +0 -120
- package/dist/channels/ai-sdk.js.map +0 -1
- package/dist/channels/email.js.map +0 -1
- package/dist/channels/index.d.ts +0 -233
- package/dist/channels/index.js +0 -608
- package/dist/channels/index.js.map +0 -1
- package/dist/channels/slack.js +0 -614
- package/dist/channels/slack.js.map +0 -1
- package/dist/channels/tanstack-ai.d.ts +0 -39
- package/dist/channels/tanstack-ai.js +0 -17
- package/dist/channels/tanstack-ai.js.map +0 -1
- package/dist/channels/telegram.js +0 -427
- package/dist/channels/telegram.js.map +0 -1
- package/dist/channels/voice.d.ts +0 -45
- package/dist/channels/voice.js +0 -122
- package/dist/channels/voice.js.map +0 -1
- package/dist/images-iiTDSyRu.js.map +0 -1
- package/dist/index-BB0kqhIz.d.ts +0 -101
- package/dist/ingress-BfetZbMO.js +0 -83
- package/dist/ingress-BfetZbMO.js.map +0 -1
- package/dist/internal-CYlgHl1l.js +0 -59
- package/dist/internal-CYlgHl1l.js.map +0 -1
- package/dist/models/ai-sdk/index.js.map +0 -1
- package/dist/protocol-kHsr-f0X.js.map +0 -1
- package/dist/sessions/index.js.map +0 -1
- package/dist/streams-DW-UCOUc.js.map +0 -1
- package/dist/surface-bZZJqBka.js +0 -17
- package/dist/surface-bZZJqBka.js.map +0 -1
- package/dist/tasks-68DESxf0.js.map +0 -1
- package/dist/tool-schema-CBjGPrsQ.js +0 -31
- package/dist/tool-schema-CBjGPrsQ.js.map +0 -1
- package/dist/websockets-Ci-LZFHH.js.map +0 -1
- package/docs/channels.md +0 -323
|
@@ -0,0 +1,2444 @@
|
|
|
1
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
|
|
2
|
+
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
3
|
+
import { a as parseModelOptions, i as mergeModelOptions, n as PROVIDER_OPTIONS_KEY, o as resolveOptions, r as gatewayLayerOf } from "./settings-C45hDliX.js";
|
|
4
|
+
import { S as isRetryableStatus, _ as isGatewayErrorEnvelope, a as normalizeChatCompletion, b as classifyStatus, f as createTransport$1, g as headersToObject, h as errorFromResponse$1, i as normalizeChatCompletionsStream, m as errorFromGatewayEnvelope$1, n as imageMediaTypeOf, o as prepareChatCompletionsRequest, p as definedHeaders, r as sseDataStream, t as buildImageInput, u as requireGatewayProvider, v as unwrapEnvelope$1, x as isAbortError, y as CloudflareAIError$1 } from "./images-Ce9SCu3X.js";
|
|
5
|
+
import { APICallError, TooManyEmbeddingValuesForCallError } from "@ai-sdk/provider";
|
|
6
|
+
//#region src/models/ai-sdk/errors.ts
|
|
7
|
+
/** The provider packages worth naming when someone passes a vendor id. */
|
|
8
|
+
const VENDOR_PACKAGES = {
|
|
9
|
+
anthropic: {
|
|
10
|
+
call: "anthropic(\"claude-opus-4-8\")",
|
|
11
|
+
package: "@ai-sdk/anthropic"
|
|
12
|
+
},
|
|
13
|
+
google: {
|
|
14
|
+
call: "google(\"gemini-3-flash\")",
|
|
15
|
+
package: "@ai-sdk/google"
|
|
16
|
+
},
|
|
17
|
+
openai: {
|
|
18
|
+
call: "openai(\"gpt-5-mini\")",
|
|
19
|
+
package: "@ai-sdk/openai"
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The one error a bad model id produces. Workers AI ids are the only strings
|
|
24
|
+
* this provider knows; a third-party model is a model object built by that
|
|
25
|
+
* vendor's own provider package.
|
|
26
|
+
*/
|
|
27
|
+
function notAWorkersAIId(modelId) {
|
|
28
|
+
const vendor = VENDOR_PACKAGES[modelId.split("/")[0] ?? ""];
|
|
29
|
+
const advice = vendor === void 0 ? "install its AI SDK provider package and pass the model object, e.g. ai(anthropic(\"claude-opus-4-8\"))" : `install ${vendor.package} and pass the model object: ai(${vendor.call})`;
|
|
30
|
+
return /* @__PURE__ */ new TypeError(`"${modelId}" is not a Workers AI model id. Workers AI ids start with "@cf/", and they are the only ids this provider takes: a third-party model goes through its own provider and is only routed by AI Gateway — ${advice}.`);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The gate every string that names a model passes through, wherever it enters
|
|
34
|
+
* the module: `ai("…")`, a per-model fallback leg, or a leg read out of the
|
|
35
|
+
* per-call `providerOptions.cloudflare` bag. One rule, one message, thrown at
|
|
36
|
+
* the line that supplied the id rather than at the request it would have made.
|
|
37
|
+
*/
|
|
38
|
+
function requireWorkersAIId(modelId) {
|
|
39
|
+
if (!modelId.startsWith("@cf/")) throw notAWorkersAIId(modelId);
|
|
40
|
+
return modelId;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Error thrown for every failed Cloudflare AI call, whether it came back as a
|
|
44
|
+
* non-2xx `Response` or as a thrown Workers AI binding error.
|
|
45
|
+
*
|
|
46
|
+
* Extends the AI SDK's `APICallError` so `ai`'s built-in retry loop honours
|
|
47
|
+
* {@link CloudflareAIError.isRetryable} without any extra wiring.
|
|
48
|
+
*
|
|
49
|
+
* @experimental This surface is experimental and may change.
|
|
50
|
+
*/
|
|
51
|
+
var CloudflareAIError = class CloudflareAIError extends APICallError {
|
|
52
|
+
constructor(options) {
|
|
53
|
+
super({
|
|
54
|
+
cause: options.cause,
|
|
55
|
+
data: options.data,
|
|
56
|
+
isRetryable: options.isRetryable ?? isRetryableStatus(options.status),
|
|
57
|
+
message: options.message,
|
|
58
|
+
requestBodyValues: options.requestBodyValues,
|
|
59
|
+
responseBody: options.responseBody,
|
|
60
|
+
responseHeaders: options.responseHeaders,
|
|
61
|
+
statusCode: options.status,
|
|
62
|
+
url: options.url
|
|
63
|
+
});
|
|
64
|
+
this.name = "CloudflareAIError";
|
|
65
|
+
this.status = options.status;
|
|
66
|
+
this.code = options.code;
|
|
67
|
+
this.logId = options.logId;
|
|
68
|
+
this.model = options.model;
|
|
69
|
+
this.attempts = options.attempts;
|
|
70
|
+
}
|
|
71
|
+
/** Lifts the framework-neutral core error into the AI SDK error type. */
|
|
72
|
+
static fromCore(error) {
|
|
73
|
+
return new CloudflareAIError({
|
|
74
|
+
attempts: error.attempts,
|
|
75
|
+
cause: error.cause,
|
|
76
|
+
code: error.code,
|
|
77
|
+
data: error.data,
|
|
78
|
+
isRetryable: error.isRetryable,
|
|
79
|
+
logId: error.logId,
|
|
80
|
+
message: error.message,
|
|
81
|
+
model: error.model,
|
|
82
|
+
requestBodyValues: error.requestBodyValues,
|
|
83
|
+
responseBody: error.responseBody,
|
|
84
|
+
responseHeaders: error.responseHeaders,
|
|
85
|
+
status: error.status,
|
|
86
|
+
url: error.url
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Converts whatever the core transport threw into what AI SDK callers expect:
|
|
92
|
+
* core errors become {@link CloudflareAIError}s; everything else passes
|
|
93
|
+
* through untouched.
|
|
94
|
+
*/
|
|
95
|
+
function toAISDKError(error) {
|
|
96
|
+
return error instanceof CloudflareAIError$1 ? CloudflareAIError.fromCore(error) : error;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/models/ai-sdk/settings.ts
|
|
100
|
+
/**
|
|
101
|
+
* The fallback legs for one call, in the caller's order. Precedence matches
|
|
102
|
+
* the rest of the option merge: a per-call list replaces the per-model one
|
|
103
|
+
* rather than extending it.
|
|
104
|
+
*/
|
|
105
|
+
function fallbackLegs(model, call) {
|
|
106
|
+
return call?.fallback ?? model?.fallback ?? [];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Reads per-call options out of AI SDK `providerOptions.cloudflare`.
|
|
110
|
+
*
|
|
111
|
+
* This is the one place the untyped per-call bag enters the module, so it is
|
|
112
|
+
* where the "`@cf/` ids only" rule is enforced for the fallback legs a caller
|
|
113
|
+
* wrote there: `parseModelOptions` is framework-neutral and drops rather than
|
|
114
|
+
* throws, and no type ever sees that JSON. A vendor id in a leg would
|
|
115
|
+
* otherwise reach `env.AI.run` — the path a vendor model must never take.
|
|
116
|
+
*
|
|
117
|
+
* @experimental This surface is experimental and may change.
|
|
118
|
+
*/
|
|
119
|
+
function parseCallOptions(providerOptions) {
|
|
120
|
+
const options = parseModelOptions(providerOptions?.[PROVIDER_OPTIONS_KEY]);
|
|
121
|
+
for (const leg of options?.fallback ?? []) requireWorkersAIId(leg);
|
|
122
|
+
return options;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The options each model this provider built was built with.
|
|
126
|
+
*
|
|
127
|
+
* A `WeakMap` rather than a field: `createAI` needs to read a leg's own bag
|
|
128
|
+
* back when it layers a fallback chain's gateway options over it, and that is
|
|
129
|
+
* this module's business rather than part of any model's public shape.
|
|
130
|
+
*/
|
|
131
|
+
const MODEL_OPTIONS = /* @__PURE__ */ new WeakMap();
|
|
132
|
+
/** Records the options a model of this provider was built with. */
|
|
133
|
+
function rememberOptions(model, options) {
|
|
134
|
+
if (options !== void 0) MODEL_OPTIONS.set(model, options);
|
|
135
|
+
}
|
|
136
|
+
/** The options a model of this provider was built with, if it is one. */
|
|
137
|
+
function optionsOf(model) {
|
|
138
|
+
return model !== null && typeof model === "object" ? MODEL_OPTIONS.get(model) : void 0;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The per-call options a fallback leg is asked with: everything the caller
|
|
142
|
+
* sent, minus the fallback list itself.
|
|
143
|
+
*
|
|
144
|
+
* Every leg here is a model object, so a leg that read the same
|
|
145
|
+
* `providerOptions.cloudflare.fallback` would build the whole chain again —
|
|
146
|
+
* once per leg, each of those re-expanding in turn. The rest of the per-call
|
|
147
|
+
* bag stays, so a leg still honours a per-call `cacheTtl` or gateway id.
|
|
148
|
+
*/
|
|
149
|
+
function withoutCallFallback(options) {
|
|
150
|
+
const providerOptions = options.providerOptions;
|
|
151
|
+
const ours = providerOptions?.[PROVIDER_OPTIONS_KEY];
|
|
152
|
+
if (ours === void 0 || !("fallback" in ours)) return options;
|
|
153
|
+
const bag = { ...ours };
|
|
154
|
+
delete bag.fallback;
|
|
155
|
+
return {
|
|
156
|
+
...options,
|
|
157
|
+
providerOptions: {
|
|
158
|
+
...providerOptions,
|
|
159
|
+
[PROVIDER_OPTIONS_KEY]: bag
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/models/ai-sdk/transport.ts
|
|
165
|
+
/**
|
|
166
|
+
* The typed AI SDK error for the gateway's own error envelope — a missing
|
|
167
|
+
* gateway, an unpaid account — on the universal path.
|
|
168
|
+
*/
|
|
169
|
+
function errorFromGatewayEnvelope(body, context) {
|
|
170
|
+
return CloudflareAIError.fromCore(errorFromGatewayEnvelope$1(body, context));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Unwraps the Cloudflare `{ success, result }` envelope, throwing an AI SDK
|
|
174
|
+
* {@link CloudflareAIError} for a failed envelope.
|
|
175
|
+
*/
|
|
176
|
+
function unwrapEnvelope(value, context) {
|
|
177
|
+
try {
|
|
178
|
+
return unwrapEnvelope$1(value, context);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
throw toAISDKError(error);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** The typed AI SDK error for a non-2xx response. */
|
|
184
|
+
async function errorFromResponse(response, context) {
|
|
185
|
+
return CloudflareAIError.fromCore(await errorFromResponse$1(response, context));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Builds the transport for a set of provider settings, with every failure
|
|
189
|
+
* lifted into the AI SDK error type.
|
|
190
|
+
*
|
|
191
|
+
* @experimental This surface is experimental and may change.
|
|
192
|
+
*/
|
|
193
|
+
function createTransport(settings) {
|
|
194
|
+
const inner = createTransport$1(settings);
|
|
195
|
+
return {
|
|
196
|
+
logIdFallback: (response) => inner.logIdFallback(response),
|
|
197
|
+
async run(request) {
|
|
198
|
+
try {
|
|
199
|
+
return await inner.run(request);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw toAISDKError(error);
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
async universal(request) {
|
|
205
|
+
try {
|
|
206
|
+
return await inner.universal(request);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
throw toAISDKError(error);
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
url: inner.url
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/models/ai-sdk/models/fallback.ts
|
|
216
|
+
/**
|
|
217
|
+
* Client-side fallback across models: the primary is tried first, then each
|
|
218
|
+
* fallback in order, when a call fails before producing output. Shared by the
|
|
219
|
+
* language and embedding models.
|
|
220
|
+
*/
|
|
221
|
+
/**
|
|
222
|
+
* Runs each leg in order until one answers. Legs may be anything — a model id
|
|
223
|
+
* this provider owns, or a whole other `LanguageModelV4` — because a leg is
|
|
224
|
+
* only ever a name and a thunk here.
|
|
225
|
+
*/
|
|
226
|
+
async function withFallbackLegs(legs, url) {
|
|
227
|
+
const attempts = [];
|
|
228
|
+
let lastError;
|
|
229
|
+
for (const leg of legs) try {
|
|
230
|
+
return await leg.run();
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (isAbortError(error)) throw error;
|
|
233
|
+
attempts.push({
|
|
234
|
+
error,
|
|
235
|
+
model: leg.model
|
|
236
|
+
});
|
|
237
|
+
lastError = error;
|
|
238
|
+
}
|
|
239
|
+
if (attempts.length <= 1) throw lastError;
|
|
240
|
+
throw fallbackError(lastError, attempts, url);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The parts a stream emits before it has said anything: protocol preamble
|
|
244
|
+
* that every leg would repeat. Anything else is output the caller has seen,
|
|
245
|
+
* after which the leg is committed and a later failure is its own.
|
|
246
|
+
*/
|
|
247
|
+
const PREAMBLE_PARTS = /* @__PURE__ */ new Set([
|
|
248
|
+
"stream-start",
|
|
249
|
+
"response-metadata",
|
|
250
|
+
"text-start",
|
|
251
|
+
"reasoning-start",
|
|
252
|
+
"tool-input-start",
|
|
253
|
+
"raw"
|
|
254
|
+
]);
|
|
255
|
+
/**
|
|
256
|
+
* {@link withFallbackLegs} for streams. A 2xx that then fails before any
|
|
257
|
+
* output — an `error` part, an error finish, or a broken body — is still a
|
|
258
|
+
* failed leg, so the next one is tried; the preamble the failed leg sent is
|
|
259
|
+
* dropped and the answering leg's own is delivered instead. Once a leg has
|
|
260
|
+
* produced output it is the answer, failures and all.
|
|
261
|
+
*/
|
|
262
|
+
async function withStreamFallbackLegs(legs, url) {
|
|
263
|
+
if (legs.length <= 1) return withFallbackLegs(legs, url);
|
|
264
|
+
const attempts = [];
|
|
265
|
+
let lastError;
|
|
266
|
+
for (const leg of legs) {
|
|
267
|
+
let result;
|
|
268
|
+
try {
|
|
269
|
+
result = await leg.run();
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (isAbortError(error)) throw error;
|
|
272
|
+
attempts.push({
|
|
273
|
+
error,
|
|
274
|
+
model: leg.model
|
|
275
|
+
});
|
|
276
|
+
lastError = error;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const reader = result.stream.getReader();
|
|
280
|
+
const preamble = [];
|
|
281
|
+
let failure;
|
|
282
|
+
let committed = false;
|
|
283
|
+
try {
|
|
284
|
+
for (;;) {
|
|
285
|
+
const { done, value } = await reader.read();
|
|
286
|
+
if (done) {
|
|
287
|
+
committed = true;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
if (value.type === "error") {
|
|
291
|
+
failure = value.error;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
if (value.type === "finish" && value.finishReason.unified === "error") {
|
|
295
|
+
failure = streamFailure(leg.model, url, value.finishReason.raw);
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
preamble.push(value);
|
|
299
|
+
if (!PREAMBLE_PARTS.has(value.type)) {
|
|
300
|
+
committed = true;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
if (isAbortError(error)) throw error;
|
|
306
|
+
failure = error;
|
|
307
|
+
}
|
|
308
|
+
if (!committed) {
|
|
309
|
+
await reader.cancel().catch(() => void 0);
|
|
310
|
+
attempts.push({
|
|
311
|
+
error: failure,
|
|
312
|
+
model: leg.model
|
|
313
|
+
});
|
|
314
|
+
lastError = failure;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
...result,
|
|
319
|
+
stream: resumed(preamble, reader)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (attempts.length <= 1) throw lastError;
|
|
323
|
+
throw fallbackError(lastError, attempts, url);
|
|
324
|
+
}
|
|
325
|
+
/** The parts already read, then the rest of the stream, as one stream. */
|
|
326
|
+
function resumed(head, reader) {
|
|
327
|
+
return new ReadableStream({
|
|
328
|
+
cancel(reason) {
|
|
329
|
+
return reader.cancel(reason);
|
|
330
|
+
},
|
|
331
|
+
async pull(controller) {
|
|
332
|
+
const { done, value } = await reader.read();
|
|
333
|
+
if (done) controller.close();
|
|
334
|
+
else controller.enqueue(value);
|
|
335
|
+
},
|
|
336
|
+
start(controller) {
|
|
337
|
+
for (const part of head) controller.enqueue(part);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
/** A stream that ended in an error finish before it produced anything. */
|
|
342
|
+
function streamFailure(model, url, raw) {
|
|
343
|
+
return new CloudflareAIError({
|
|
344
|
+
code: "provider-error",
|
|
345
|
+
isRetryable: false,
|
|
346
|
+
message: `The stream from ${model} ended in an error before producing any output${raw === void 0 ? "" : ` (${raw})`}.`,
|
|
347
|
+
model,
|
|
348
|
+
requestBodyValues: void 0,
|
|
349
|
+
url
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async function withFallback(primary, fallback, url, run) {
|
|
353
|
+
return withFallbackLegs([primary, ...fallback].map((modelId) => ({
|
|
354
|
+
model: modelId,
|
|
355
|
+
run: () => run(modelId)
|
|
356
|
+
})), url);
|
|
357
|
+
}
|
|
358
|
+
/** Rebuilds the last error with the whole attempt chain attached. */
|
|
359
|
+
function fallbackError(lastError, attempts, url) {
|
|
360
|
+
const models = attempts.map((attempt) => attempt.model).join(", ");
|
|
361
|
+
if (lastError instanceof CloudflareAIError) return new CloudflareAIError({
|
|
362
|
+
attempts,
|
|
363
|
+
cause: lastError,
|
|
364
|
+
code: lastError.code,
|
|
365
|
+
data: lastError.data,
|
|
366
|
+
isRetryable: lastError.isRetryable,
|
|
367
|
+
logId: lastError.logId,
|
|
368
|
+
message: `All models failed (${models}). Last error: ${lastError.message}`,
|
|
369
|
+
model: lastError.model,
|
|
370
|
+
requestBodyValues: lastError.requestBodyValues,
|
|
371
|
+
responseBody: lastError.responseBody,
|
|
372
|
+
responseHeaders: lastError.responseHeaders,
|
|
373
|
+
status: lastError.status,
|
|
374
|
+
url: lastError.url
|
|
375
|
+
});
|
|
376
|
+
if (APICallError.isInstance(lastError)) return new CloudflareAIError({
|
|
377
|
+
attempts,
|
|
378
|
+
cause: lastError,
|
|
379
|
+
code: classifyStatus(lastError.statusCode),
|
|
380
|
+
isRetryable: lastError.isRetryable,
|
|
381
|
+
message: `All models failed (${models}). Last error: ${lastError.message}`,
|
|
382
|
+
model: attempts.at(-1)?.model ?? "",
|
|
383
|
+
requestBodyValues: lastError.requestBodyValues,
|
|
384
|
+
responseBody: lastError.responseBody,
|
|
385
|
+
responseHeaders: lastError.responseHeaders,
|
|
386
|
+
status: lastError.statusCode,
|
|
387
|
+
url: lastError.url
|
|
388
|
+
});
|
|
389
|
+
return new CloudflareAIError({
|
|
390
|
+
attempts,
|
|
391
|
+
cause: lastError,
|
|
392
|
+
code: "unknown",
|
|
393
|
+
isRetryable: false,
|
|
394
|
+
message: `All models failed (${models}). Last error: ${String(lastError)}`,
|
|
395
|
+
model: attempts.at(-1)?.model ?? "",
|
|
396
|
+
requestBodyValues: void 0,
|
|
397
|
+
url
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/models/ai-sdk/wires/shared.ts
|
|
402
|
+
/** Usage with every field unknown — the shape callers can always fall back to. */
|
|
403
|
+
function emptyUsage() {
|
|
404
|
+
return {
|
|
405
|
+
inputTokens: {
|
|
406
|
+
cacheRead: void 0,
|
|
407
|
+
cacheWrite: void 0,
|
|
408
|
+
noCache: void 0,
|
|
409
|
+
total: void 0
|
|
410
|
+
},
|
|
411
|
+
outputTokens: {
|
|
412
|
+
reasoning: void 0,
|
|
413
|
+
text: void 0,
|
|
414
|
+
total: void 0
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/** A fresh id for a stream part or a tool call the provider did not name. */
|
|
419
|
+
function newId() {
|
|
420
|
+
return crypto.randomUUID();
|
|
421
|
+
}
|
|
422
|
+
const BASE64_CHUNK = 32768;
|
|
423
|
+
/** Base64-encodes bytes without blowing the argument limit on large files. */
|
|
424
|
+
function toBase64(data) {
|
|
425
|
+
if (typeof data === "string") return data;
|
|
426
|
+
let binary = "";
|
|
427
|
+
for (let offset = 0; offset < data.length; offset += BASE64_CHUNK) binary += String.fromCharCode(...data.subarray(offset, offset + BASE64_CHUNK));
|
|
428
|
+
return btoa(binary);
|
|
429
|
+
}
|
|
430
|
+
/** Reads a record field without widening everything to `any`. */
|
|
431
|
+
function record(value) {
|
|
432
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
433
|
+
}
|
|
434
|
+
/** Reads an array field, or `undefined` when it is not one. */
|
|
435
|
+
function array(value) {
|
|
436
|
+
return Array.isArray(value) ? value : void 0;
|
|
437
|
+
}
|
|
438
|
+
/** Reads a string field, or `undefined` when it is not one. */
|
|
439
|
+
function text(value) {
|
|
440
|
+
return typeof value === "string" ? value : void 0;
|
|
441
|
+
}
|
|
442
|
+
/** Reads a number field, or `undefined` when it is not one. */
|
|
443
|
+
function count(value) {
|
|
444
|
+
return typeof value === "number" ? value : void 0;
|
|
445
|
+
}
|
|
446
|
+
/** The finish reason used when a provider ends a stream without saying why. */
|
|
447
|
+
function defaultFinishReason() {
|
|
448
|
+
return {
|
|
449
|
+
raw: void 0,
|
|
450
|
+
unified: "stop"
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/models/ai-sdk/models/embedding.ts
|
|
455
|
+
/**
|
|
456
|
+
* Workers AI batches up to this many strings per embedding call.
|
|
457
|
+
*
|
|
458
|
+
* @see https://developers.cloudflare.com/workers-ai/platform/limits/
|
|
459
|
+
*/
|
|
460
|
+
const MAX_EMBEDDINGS_PER_CALL = 3e3;
|
|
461
|
+
var _config$3 = /* @__PURE__ */ new WeakMap();
|
|
462
|
+
var _options$3 = /* @__PURE__ */ new WeakMap();
|
|
463
|
+
/**
|
|
464
|
+
* An `EmbeddingModelV4` over the Workers AI text-embedding catalog. The run
|
|
465
|
+
* path takes `{ text: string[] }` and answers `{ data, shape, usage }`,
|
|
466
|
+
* sometimes inside the Cloudflare envelope.
|
|
467
|
+
*
|
|
468
|
+
* @experimental This surface is experimental and may change.
|
|
469
|
+
*/
|
|
470
|
+
var CloudflareEmbeddingModel = class {
|
|
471
|
+
constructor(modelId, options, config) {
|
|
472
|
+
this.specificationVersion = "v4";
|
|
473
|
+
this.provider = "cloudflare";
|
|
474
|
+
this.maxEmbeddingsPerCall = MAX_EMBEDDINGS_PER_CALL;
|
|
475
|
+
this.supportsParallelCalls = true;
|
|
476
|
+
_classPrivateFieldInitSpec(this, _config$3, void 0);
|
|
477
|
+
_classPrivateFieldInitSpec(this, _options$3, void 0);
|
|
478
|
+
options?.fallback?.forEach((leg) => {
|
|
479
|
+
if (typeof leg === "string") requireWorkersAIId(leg);
|
|
480
|
+
});
|
|
481
|
+
this.modelId = modelId;
|
|
482
|
+
_classPrivateFieldSet2(_options$3, this, options);
|
|
483
|
+
_classPrivateFieldSet2(_config$3, this, config);
|
|
484
|
+
}
|
|
485
|
+
async doEmbed(options) {
|
|
486
|
+
if (options.values.length > MAX_EMBEDDINGS_PER_CALL) throw new TooManyEmbeddingValuesForCallError({
|
|
487
|
+
maxEmbeddingsPerCall: MAX_EMBEDDINGS_PER_CALL,
|
|
488
|
+
modelId: this.modelId,
|
|
489
|
+
provider: this.provider,
|
|
490
|
+
values: options.values
|
|
491
|
+
});
|
|
492
|
+
const resolved = resolveOptions(_classPrivateFieldGet2(_config$3, this).providerGateway, _classPrivateFieldGet2(_options$3, this), parseCallOptions(options.providerOptions));
|
|
493
|
+
const input = { text: options.values };
|
|
494
|
+
const transport = _classPrivateFieldGet2(_config$3, this).transport;
|
|
495
|
+
return withFallback(this.modelId, resolved.fallback, transport.url, async (modelId) => {
|
|
496
|
+
const response = await transport.run({
|
|
497
|
+
gateway: resolved.gateway,
|
|
498
|
+
headers: {
|
|
499
|
+
...definedHeaders(options.headers),
|
|
500
|
+
...resolved.headers,
|
|
501
|
+
...resolved.sessionAffinity === void 0 ? {} : { "x-session-affinity": resolved.sessionAffinity }
|
|
502
|
+
},
|
|
503
|
+
input,
|
|
504
|
+
model: modelId,
|
|
505
|
+
signal: options.abortSignal
|
|
506
|
+
});
|
|
507
|
+
if (!response.ok) throw await errorFromResponse(response, {
|
|
508
|
+
model: modelId,
|
|
509
|
+
requestBodyValues: input,
|
|
510
|
+
url: transport.url
|
|
511
|
+
});
|
|
512
|
+
const body = record(unwrapEnvelope(await response.json(), {
|
|
513
|
+
model: modelId,
|
|
514
|
+
status: response.status,
|
|
515
|
+
url: transport.url
|
|
516
|
+
})) ?? {};
|
|
517
|
+
const embeddings = (array(body.data) ?? []).map((row) => (array(row) ?? []).filter((value) => typeof value === "number"));
|
|
518
|
+
const tokens = count(record(body.usage)?.prompt_tokens);
|
|
519
|
+
return {
|
|
520
|
+
embeddings,
|
|
521
|
+
response: { headers: headersToObject(response.headers) },
|
|
522
|
+
...tokens === void 0 ? {} : { usage: { tokens } },
|
|
523
|
+
warnings: []
|
|
524
|
+
};
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region src/models/ai-sdk/wires/chat-completions.ts
|
|
530
|
+
function fullMediaType(mediaType) {
|
|
531
|
+
return mediaType.includes("/") ? mediaType : `${mediaType}/jpeg`;
|
|
532
|
+
}
|
|
533
|
+
function unsupported$1(feature, details) {
|
|
534
|
+
return {
|
|
535
|
+
details,
|
|
536
|
+
feature,
|
|
537
|
+
type: "unsupported"
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/** A compat-layer warning in the AI SDK's warning shape. */
|
|
541
|
+
function fromCompat(warning) {
|
|
542
|
+
return unsupported$1(warning.feature, warning.message);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Gemini 3 signs each assistant turn and each function call with an opaque
|
|
546
|
+
* `extra_content.google.thought_signature`, and rejects a function call
|
|
547
|
+
* replayed in the same turn without it. Nothing here reads the payload: it is
|
|
548
|
+
* carried out as provider metadata and put back verbatim on the next turn.
|
|
549
|
+
*
|
|
550
|
+
* `extraContent` is the signature of the assistant message; `toolExtraContent`
|
|
551
|
+
* is the one that belongs to an individual `tool_calls[i]` entry.
|
|
552
|
+
*/
|
|
553
|
+
function extraContentMetadata(fields) {
|
|
554
|
+
const metadata = {};
|
|
555
|
+
const message = record(fields.extraContent);
|
|
556
|
+
const tool = record(fields.toolExtraContent);
|
|
557
|
+
if (message !== void 0) metadata.extraContent = message;
|
|
558
|
+
if (tool !== void 0) metadata.toolExtraContent = tool;
|
|
559
|
+
if (Object.keys(metadata).length === 0) return {};
|
|
560
|
+
return { providerMetadata: { [PROVIDER_OPTIONS_KEY]: metadata } };
|
|
561
|
+
}
|
|
562
|
+
/** Reads back what {@link extraContentMetadata} stored on a replayed part. */
|
|
563
|
+
function storedExtraContent(options, key) {
|
|
564
|
+
return record(options?.[PROVIDER_OPTIONS_KEY]?.[key]);
|
|
565
|
+
}
|
|
566
|
+
function toolResultText(output, warnings) {
|
|
567
|
+
switch (output.type) {
|
|
568
|
+
case "text":
|
|
569
|
+
case "error-text": return output.value;
|
|
570
|
+
case "json":
|
|
571
|
+
case "error-json": return JSON.stringify(output.value);
|
|
572
|
+
case "execution-denied": return output.reason ?? "The user denied this tool call.";
|
|
573
|
+
case "content": {
|
|
574
|
+
const dropped = output.value.filter((part) => part.type !== "text");
|
|
575
|
+
if (dropped.length > 0) warnings.push(unsupported$1("tool-result-part", `Tool result parts of type ${[...new Set(dropped.map((part) => part.type))].join(", ")} cannot be sent on the chat-completions wire and were dropped.`));
|
|
576
|
+
return output.value.map((part) => part.type === "text" ? part.text : "").filter((part) => part !== "").join("\n");
|
|
577
|
+
}
|
|
578
|
+
default: return String(output);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function userContent(message, warnings) {
|
|
582
|
+
const parts = [];
|
|
583
|
+
let onlyText = true;
|
|
584
|
+
for (const part of message.content) {
|
|
585
|
+
if (part.type === "text") {
|
|
586
|
+
parts.push({
|
|
587
|
+
text: part.text,
|
|
588
|
+
type: "text"
|
|
589
|
+
});
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (!(part.mediaType === "image" || part.mediaType.startsWith("image/"))) {
|
|
593
|
+
warnings.push(unsupported$1("file-part", `Files of type ${part.mediaType} are not supported on this wire and were dropped.`));
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (part.data.type === "data") {
|
|
597
|
+
onlyText = false;
|
|
598
|
+
parts.push({
|
|
599
|
+
image_url: { url: `data:${fullMediaType(part.mediaType)};base64,${toBase64(part.data.data)}` },
|
|
600
|
+
type: "image_url"
|
|
601
|
+
});
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (part.data.type === "url") {
|
|
605
|
+
onlyText = false;
|
|
606
|
+
parts.push({
|
|
607
|
+
image_url: { url: part.data.url.toString() },
|
|
608
|
+
type: "image_url"
|
|
609
|
+
});
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
warnings.push(unsupported$1("file-part", "Only inline data and URL images are supported; the file was dropped."));
|
|
613
|
+
}
|
|
614
|
+
if (onlyText) return parts.map((part) => part.type === "text" ? part.text : "").join("");
|
|
615
|
+
return parts;
|
|
616
|
+
}
|
|
617
|
+
function convertPrompt(prompt, warnings) {
|
|
618
|
+
const messages = [];
|
|
619
|
+
for (const message of prompt) switch (message.role) {
|
|
620
|
+
case "system":
|
|
621
|
+
messages.push({
|
|
622
|
+
content: message.content,
|
|
623
|
+
role: "system"
|
|
624
|
+
});
|
|
625
|
+
break;
|
|
626
|
+
case "user":
|
|
627
|
+
messages.push({
|
|
628
|
+
content: userContent(message, warnings),
|
|
629
|
+
role: "user"
|
|
630
|
+
});
|
|
631
|
+
break;
|
|
632
|
+
case "assistant": {
|
|
633
|
+
let content = "";
|
|
634
|
+
let reasoning = "";
|
|
635
|
+
let extraContent;
|
|
636
|
+
const toolCalls = [];
|
|
637
|
+
for (const part of message.content) {
|
|
638
|
+
extraContent ??= storedExtraContent(part.providerOptions, "extraContent");
|
|
639
|
+
if (part.type === "text") content += part.text;
|
|
640
|
+
else if (part.type === "reasoning") reasoning += part.text;
|
|
641
|
+
else if (part.type === "tool-call") {
|
|
642
|
+
const toolExtra = storedExtraContent(part.providerOptions, "toolExtraContent");
|
|
643
|
+
toolCalls.push({
|
|
644
|
+
function: {
|
|
645
|
+
arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input ?? {}),
|
|
646
|
+
name: part.toolName
|
|
647
|
+
},
|
|
648
|
+
id: part.toolCallId,
|
|
649
|
+
type: "function",
|
|
650
|
+
...toolExtra === void 0 ? {} : { extra_content: toolExtra }
|
|
651
|
+
});
|
|
652
|
+
} else warnings.push(unsupported$1("assistant-part", `Assistant "${part.type}" parts cannot be replayed on the chat-completions wire and were dropped.`));
|
|
653
|
+
}
|
|
654
|
+
messages.push({
|
|
655
|
+
content,
|
|
656
|
+
...reasoning !== "" ? { reasoning_content: reasoning } : {},
|
|
657
|
+
...extraContent === void 0 ? {} : { extra_content: extraContent },
|
|
658
|
+
...toolCalls.length > 0 ? { tool_calls: toolCalls } : {},
|
|
659
|
+
role: "assistant"
|
|
660
|
+
});
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
case "tool":
|
|
664
|
+
for (const part of message.content) {
|
|
665
|
+
if (part.type !== "tool-result") {
|
|
666
|
+
warnings.push(unsupported$1("tool-part", `Tool "${part.type}" parts are not carried on the chat-completions wire and were dropped.`));
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
messages.push({
|
|
670
|
+
content: toolResultText(part.output, warnings),
|
|
671
|
+
name: part.toolName,
|
|
672
|
+
role: "tool",
|
|
673
|
+
tool_call_id: part.toolCallId
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
break;
|
|
677
|
+
default: throw new Error(`Unsupported message role: ${String(message)}`);
|
|
678
|
+
}
|
|
679
|
+
return messages;
|
|
680
|
+
}
|
|
681
|
+
/** Folds the AI SDK's schema description into the schema itself. */
|
|
682
|
+
function foldSchema(schema, description) {
|
|
683
|
+
if (description === void 0 || schema.description !== void 0) return schema;
|
|
684
|
+
return {
|
|
685
|
+
...schema,
|
|
686
|
+
description
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function responseFormat(options) {
|
|
690
|
+
const format = options.responseFormat;
|
|
691
|
+
if (format === void 0 || format.type !== "json") return void 0;
|
|
692
|
+
if (format.schema === void 0) return { type: "json_object" };
|
|
693
|
+
return {
|
|
694
|
+
json_schema: {
|
|
695
|
+
name: format.name ?? "response",
|
|
696
|
+
schema: foldSchema(format.schema, format.description),
|
|
697
|
+
strict: true
|
|
698
|
+
},
|
|
699
|
+
type: "json_schema"
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function toolFields(options, warnings) {
|
|
703
|
+
const tools = [];
|
|
704
|
+
for (const tool of options.tools ?? []) {
|
|
705
|
+
if (tool.type !== "function") {
|
|
706
|
+
warnings.push(unsupported$1("provider-tool", `Provider tool "${tool.name}" is not available through the Cloudflare run path.`));
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
tools.push({
|
|
710
|
+
function: {
|
|
711
|
+
description: tool.description,
|
|
712
|
+
name: tool.name,
|
|
713
|
+
parameters: tool.inputSchema
|
|
714
|
+
},
|
|
715
|
+
type: "function"
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
if (tools.length === 0) return {};
|
|
719
|
+
const choice = options.toolChoice;
|
|
720
|
+
let toolChoice;
|
|
721
|
+
switch (choice?.type) {
|
|
722
|
+
case void 0: break;
|
|
723
|
+
case "auto":
|
|
724
|
+
toolChoice = "auto";
|
|
725
|
+
break;
|
|
726
|
+
case "none":
|
|
727
|
+
toolChoice = "none";
|
|
728
|
+
break;
|
|
729
|
+
case "required":
|
|
730
|
+
toolChoice = "required";
|
|
731
|
+
break;
|
|
732
|
+
case "tool":
|
|
733
|
+
toolChoice = {
|
|
734
|
+
function: { name: choice.toolName },
|
|
735
|
+
type: "function"
|
|
736
|
+
};
|
|
737
|
+
break;
|
|
738
|
+
default: throw new Error(`Unsupported tool choice: ${String(choice)}`);
|
|
739
|
+
}
|
|
740
|
+
return {
|
|
741
|
+
tools,
|
|
742
|
+
...toolChoice === void 0 ? {} : { tool_choice: toolChoice }
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* The strict-body spelling of the reasoning request: an effort level, or
|
|
747
|
+
* `null` for "off". The compat layer turns `null` into whatever the model
|
|
748
|
+
* understands, or warns when nothing does.
|
|
749
|
+
*/
|
|
750
|
+
function reasoningEffortField(options, context) {
|
|
751
|
+
if (context.reasoningEffort !== void 0) return { reasoning_effort: context.reasoningEffort };
|
|
752
|
+
const reasoning = options.reasoning;
|
|
753
|
+
if (reasoning === void 0 || reasoning === "provider-default") return {};
|
|
754
|
+
if (reasoning === "none") return { reasoning_effort: null };
|
|
755
|
+
return { reasoning_effort: reasoning };
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Builds a strict OpenAI chat-completions body from AI SDK call options and
|
|
759
|
+
* runs it through the compat layer for the target model.
|
|
760
|
+
*
|
|
761
|
+
* @experimental This surface is experimental and may change.
|
|
762
|
+
*/
|
|
763
|
+
function buildOpenAIRequest(options, context) {
|
|
764
|
+
const warnings = [];
|
|
765
|
+
if (options.topK !== void 0) warnings.push(unsupported$1("topK", "topK is not part of the chat-completions API."));
|
|
766
|
+
const format = responseFormat(options);
|
|
767
|
+
const prepared = prepareChatCompletionsRequest({
|
|
768
|
+
messages: convertPrompt(options.prompt, warnings),
|
|
769
|
+
...options.maxOutputTokens === void 0 ? {} : { max_tokens: options.maxOutputTokens },
|
|
770
|
+
...options.temperature === void 0 ? {} : { temperature: options.temperature },
|
|
771
|
+
...options.topP === void 0 ? {} : { top_p: options.topP },
|
|
772
|
+
...options.stopSequences === void 0 || options.stopSequences.length === 0 ? {} : { stop: options.stopSequences },
|
|
773
|
+
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
774
|
+
...options.frequencyPenalty === void 0 ? {} : { frequency_penalty: options.frequencyPenalty },
|
|
775
|
+
...options.presencePenalty === void 0 ? {} : { presence_penalty: options.presencePenalty },
|
|
776
|
+
...format === void 0 ? {} : { response_format: format },
|
|
777
|
+
...toolFields(options, warnings),
|
|
778
|
+
...reasoningEffortField(options, context),
|
|
779
|
+
...context.chatTemplateKwargs === void 0 ? {} : { chat_template_kwargs: context.chatTemplateKwargs },
|
|
780
|
+
...context.stream ? {
|
|
781
|
+
stream: true,
|
|
782
|
+
stream_options: { include_usage: true }
|
|
783
|
+
} : {}
|
|
784
|
+
}, context.modelId);
|
|
785
|
+
return {
|
|
786
|
+
body: prepared.body,
|
|
787
|
+
reasoningOff: prepared.reasoningOff,
|
|
788
|
+
warnings: [...warnings, ...prepared.warnings.map(fromCompat)]
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function mapFinishReason(raw) {
|
|
792
|
+
const value = text(raw);
|
|
793
|
+
switch (value) {
|
|
794
|
+
case "stop": return {
|
|
795
|
+
raw: value,
|
|
796
|
+
unified: "stop"
|
|
797
|
+
};
|
|
798
|
+
case "length": return {
|
|
799
|
+
raw: value,
|
|
800
|
+
unified: "length"
|
|
801
|
+
};
|
|
802
|
+
case "tool_calls":
|
|
803
|
+
case "function_call": return {
|
|
804
|
+
raw: value,
|
|
805
|
+
unified: "tool-calls"
|
|
806
|
+
};
|
|
807
|
+
case "content_filter": return {
|
|
808
|
+
raw: value,
|
|
809
|
+
unified: "content-filter"
|
|
810
|
+
};
|
|
811
|
+
case "error": return {
|
|
812
|
+
raw: value,
|
|
813
|
+
unified: "error"
|
|
814
|
+
};
|
|
815
|
+
default: return value === void 0 ? defaultFinishReason() : {
|
|
816
|
+
raw: value,
|
|
817
|
+
unified: "other"
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function mapUsage(raw) {
|
|
822
|
+
const usage = record(raw);
|
|
823
|
+
if (usage === void 0) return emptyUsage();
|
|
824
|
+
const promptTokens = count(usage.prompt_tokens);
|
|
825
|
+
const cacheRead = count(record(usage.prompt_tokens_details)?.cached_tokens);
|
|
826
|
+
const reasoningTokens = count(record(usage.completion_tokens_details)?.reasoning_tokens);
|
|
827
|
+
const completionTokens = count(usage.completion_tokens);
|
|
828
|
+
const totalTokens = count(usage.total_tokens);
|
|
829
|
+
const outputTotal = totalTokens !== void 0 && promptTokens !== void 0 ? Math.max(completionTokens ?? 0, totalTokens - promptTokens) : completionTokens;
|
|
830
|
+
return {
|
|
831
|
+
inputTokens: {
|
|
832
|
+
cacheRead,
|
|
833
|
+
cacheWrite: void 0,
|
|
834
|
+
noCache: promptTokens === void 0 ? void 0 : Math.max(0, promptTokens - (cacheRead ?? 0)),
|
|
835
|
+
total: promptTokens
|
|
836
|
+
},
|
|
837
|
+
outputTokens: {
|
|
838
|
+
reasoning: reasoningTokens,
|
|
839
|
+
text: void 0,
|
|
840
|
+
total: outputTotal
|
|
841
|
+
},
|
|
842
|
+
raw: usage
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
function toolCallsFrom(raw, messageExtra) {
|
|
846
|
+
const calls = array(raw) ?? [];
|
|
847
|
+
const content = [];
|
|
848
|
+
for (const entry of calls) {
|
|
849
|
+
const call = record(entry);
|
|
850
|
+
if (call === void 0) continue;
|
|
851
|
+
const fn = record(call.function);
|
|
852
|
+
const name = text(fn?.name);
|
|
853
|
+
if (name === void 0) continue;
|
|
854
|
+
content.push({
|
|
855
|
+
input: text(fn?.arguments) ?? "{}",
|
|
856
|
+
toolCallId: text(call.id) ?? newId(),
|
|
857
|
+
toolName: name,
|
|
858
|
+
type: "tool-call",
|
|
859
|
+
...extraContentMetadata({
|
|
860
|
+
extraContent: content.length === 0 ? messageExtra : void 0,
|
|
861
|
+
toolExtraContent: call.extra_content
|
|
862
|
+
})
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
return content;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Parses a non-streaming response. The body is normalized to the strict
|
|
869
|
+
* `chat.completion` shape first, so only `choices[0].message` is read.
|
|
870
|
+
*
|
|
871
|
+
* @experimental This surface is experimental and may change.
|
|
872
|
+
*/
|
|
873
|
+
function parseOpenAIGeneration(json, modelId) {
|
|
874
|
+
const body = normalizeChatCompletion(json, modelId);
|
|
875
|
+
const choice = record(array(body.choices)?.[0]);
|
|
876
|
+
const message = record(choice?.message);
|
|
877
|
+
const content = [];
|
|
878
|
+
const reasoning = text(message?.reasoning_content);
|
|
879
|
+
if (reasoning !== void 0 && reasoning !== "") content.push({
|
|
880
|
+
text: reasoning,
|
|
881
|
+
type: "reasoning"
|
|
882
|
+
});
|
|
883
|
+
const answer = text(message?.content);
|
|
884
|
+
const messageExtra = message?.extra_content;
|
|
885
|
+
if (answer !== void 0 && answer !== "") content.push({
|
|
886
|
+
text: answer,
|
|
887
|
+
type: "text",
|
|
888
|
+
...extraContentMetadata({ extraContent: messageExtra })
|
|
889
|
+
});
|
|
890
|
+
content.push(...toolCallsFrom(message?.tool_calls, answer === void 0 || answer === "" ? messageExtra : void 0));
|
|
891
|
+
const created = count(body.created);
|
|
892
|
+
return {
|
|
893
|
+
content,
|
|
894
|
+
finishReason: mapFinishReason(choice?.finish_reason),
|
|
895
|
+
responseId: text(body.id),
|
|
896
|
+
responseModelId: text(body.model),
|
|
897
|
+
timestamp: created === void 0 ? void 0 : /* @__PURE__ */ new Date(created * 1e3),
|
|
898
|
+
usage: mapUsage(body.usage)
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Streams strict OpenAI `chat.completion.chunk` events into AI SDK stream
|
|
903
|
+
* parts. Tool calls arrive either as incremental `arguments` fragments closed
|
|
904
|
+
* by a null-finalization chunk, or as one delta with the complete arguments
|
|
905
|
+
* string; both are handled. Usage is read from whichever chunk carries it.
|
|
906
|
+
*
|
|
907
|
+
* @experimental This surface is experimental and may change.
|
|
908
|
+
*/
|
|
909
|
+
function openAIStreamParser() {
|
|
910
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
911
|
+
let lastToolIndex;
|
|
912
|
+
let textId;
|
|
913
|
+
let reasoningId;
|
|
914
|
+
let usage = emptyUsage();
|
|
915
|
+
let finishReason;
|
|
916
|
+
let sentMetadata = false;
|
|
917
|
+
let receivedDone = false;
|
|
918
|
+
let receivedEvent = false;
|
|
919
|
+
/** Message-level `extra_content` (Gemini's thought signature). */
|
|
920
|
+
let messageExtra;
|
|
921
|
+
let messageExtraClaimed = false;
|
|
922
|
+
let textEverOpened = false;
|
|
923
|
+
/**
|
|
924
|
+
* The message signature belongs on whichever part replays the message: the
|
|
925
|
+
* text block when there is one, otherwise the first tool call.
|
|
926
|
+
*/
|
|
927
|
+
const claimMessageExtra = () => {
|
|
928
|
+
if (messageExtraClaimed) return void 0;
|
|
929
|
+
messageExtraClaimed = true;
|
|
930
|
+
return messageExtra;
|
|
931
|
+
};
|
|
932
|
+
const closeReasoning = (controller) => {
|
|
933
|
+
if (reasoningId === void 0) return;
|
|
934
|
+
controller.enqueue({
|
|
935
|
+
id: reasoningId,
|
|
936
|
+
type: "reasoning-end"
|
|
937
|
+
});
|
|
938
|
+
reasoningId = void 0;
|
|
939
|
+
};
|
|
940
|
+
const closeText = (controller) => {
|
|
941
|
+
if (textId === void 0) return;
|
|
942
|
+
controller.enqueue({
|
|
943
|
+
id: textId,
|
|
944
|
+
type: "text-end",
|
|
945
|
+
...extraContentMetadata({ extraContent: claimMessageExtra() })
|
|
946
|
+
});
|
|
947
|
+
textId = void 0;
|
|
948
|
+
};
|
|
949
|
+
const closeToolCall = (index, controller) => {
|
|
950
|
+
const state = toolCalls.get(index);
|
|
951
|
+
if (state === void 0 || state.closed) return;
|
|
952
|
+
state.closed = true;
|
|
953
|
+
controller.enqueue({
|
|
954
|
+
id: state.id,
|
|
955
|
+
type: "tool-input-end"
|
|
956
|
+
});
|
|
957
|
+
controller.enqueue({
|
|
958
|
+
input: state.input,
|
|
959
|
+
toolCallId: state.id,
|
|
960
|
+
toolName: state.name,
|
|
961
|
+
type: "tool-call",
|
|
962
|
+
...extraContentMetadata({
|
|
963
|
+
extraContent: textEverOpened ? void 0 : claimMessageExtra(),
|
|
964
|
+
toolExtraContent: state.extraContent
|
|
965
|
+
})
|
|
966
|
+
});
|
|
967
|
+
};
|
|
968
|
+
const emitToolDeltas = (raw, controller) => {
|
|
969
|
+
const deltas = array(raw);
|
|
970
|
+
if (deltas === void 0) return;
|
|
971
|
+
for (const entry of deltas) {
|
|
972
|
+
const call = record(entry);
|
|
973
|
+
if (call === void 0) continue;
|
|
974
|
+
const fn = record(call.function);
|
|
975
|
+
const name = text(fn?.name);
|
|
976
|
+
const input = text(fn?.arguments);
|
|
977
|
+
const id = text(call.id);
|
|
978
|
+
const rawIndex = count(call.index);
|
|
979
|
+
if (rawIndex === void 0 && id === void 0 && (name === void 0 || name === "") && (input === void 0 || input === "")) {
|
|
980
|
+
if (lastToolIndex !== void 0) closeToolCall(lastToolIndex, controller);
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
closeReasoning(controller);
|
|
984
|
+
closeText(controller);
|
|
985
|
+
const index = rawIndex ?? lastToolIndex ?? 0;
|
|
986
|
+
let state = toolCalls.get(index);
|
|
987
|
+
if (state?.closed === true) continue;
|
|
988
|
+
if (state === void 0) {
|
|
989
|
+
if (lastToolIndex !== void 0 && lastToolIndex !== index) closeToolCall(lastToolIndex, controller);
|
|
990
|
+
state = {
|
|
991
|
+
closed: false,
|
|
992
|
+
extraContent: call.extra_content,
|
|
993
|
+
id: id ?? newId(),
|
|
994
|
+
input: "",
|
|
995
|
+
name: name ?? ""
|
|
996
|
+
};
|
|
997
|
+
toolCalls.set(index, state);
|
|
998
|
+
lastToolIndex = index;
|
|
999
|
+
controller.enqueue({
|
|
1000
|
+
id: state.id,
|
|
1001
|
+
toolName: state.name,
|
|
1002
|
+
type: "tool-input-start"
|
|
1003
|
+
});
|
|
1004
|
+
} else {
|
|
1005
|
+
if (name !== void 0 && state.name === "") state.name = name;
|
|
1006
|
+
state.extraContent ??= call.extra_content;
|
|
1007
|
+
}
|
|
1008
|
+
if (input !== void 0 && input !== "") {
|
|
1009
|
+
state.input += input;
|
|
1010
|
+
controller.enqueue({
|
|
1011
|
+
delta: input,
|
|
1012
|
+
id: state.id,
|
|
1013
|
+
type: "tool-input-delta"
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
return new TransformStream({
|
|
1019
|
+
flush(controller) {
|
|
1020
|
+
for (const index of toolCalls.keys()) closeToolCall(index, controller);
|
|
1021
|
+
closeReasoning(controller);
|
|
1022
|
+
closeText(controller);
|
|
1023
|
+
if (finishReason === void 0) finishReason = receivedEvent && !receivedDone ? {
|
|
1024
|
+
raw: "stream-truncated",
|
|
1025
|
+
unified: "error"
|
|
1026
|
+
} : defaultFinishReason();
|
|
1027
|
+
controller.enqueue({
|
|
1028
|
+
finishReason,
|
|
1029
|
+
type: "finish",
|
|
1030
|
+
usage
|
|
1031
|
+
});
|
|
1032
|
+
},
|
|
1033
|
+
transform(data, controller) {
|
|
1034
|
+
if (data === "") return;
|
|
1035
|
+
if (data === "[DONE]") {
|
|
1036
|
+
receivedDone = true;
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
let parsed;
|
|
1040
|
+
try {
|
|
1041
|
+
parsed = JSON.parse(data);
|
|
1042
|
+
} catch {
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const chunk = record(parsed);
|
|
1046
|
+
if (chunk === void 0) return;
|
|
1047
|
+
receivedEvent = true;
|
|
1048
|
+
const failure = record(chunk.error);
|
|
1049
|
+
if (failure !== void 0) {
|
|
1050
|
+
closeReasoning(controller);
|
|
1051
|
+
closeText(controller);
|
|
1052
|
+
controller.enqueue({
|
|
1053
|
+
error: failure,
|
|
1054
|
+
type: "error"
|
|
1055
|
+
});
|
|
1056
|
+
finishReason = {
|
|
1057
|
+
raw: text(failure.type) ?? "error",
|
|
1058
|
+
unified: "error"
|
|
1059
|
+
};
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
if (!sentMetadata) {
|
|
1063
|
+
const created = count(chunk.created);
|
|
1064
|
+
const id = text(chunk.id);
|
|
1065
|
+
const modelId = text(chunk.model);
|
|
1066
|
+
if (id !== void 0 || modelId !== void 0) {
|
|
1067
|
+
sentMetadata = true;
|
|
1068
|
+
controller.enqueue({
|
|
1069
|
+
id,
|
|
1070
|
+
modelId,
|
|
1071
|
+
timestamp: created === void 0 ? void 0 : /* @__PURE__ */ new Date(created * 1e3),
|
|
1072
|
+
type: "response-metadata"
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (record(chunk.usage) !== void 0) usage = mapUsage(chunk.usage);
|
|
1077
|
+
const choice = record(array(chunk.choices)?.[0]);
|
|
1078
|
+
const delta = record(choice?.delta);
|
|
1079
|
+
if (delta !== void 0) {
|
|
1080
|
+
messageExtra ??= record(delta.extra_content);
|
|
1081
|
+
const reasoning = text(delta.reasoning_content);
|
|
1082
|
+
if (reasoning !== void 0 && reasoning !== "") {
|
|
1083
|
+
if (reasoningId === void 0) {
|
|
1084
|
+
reasoningId = newId();
|
|
1085
|
+
controller.enqueue({
|
|
1086
|
+
id: reasoningId,
|
|
1087
|
+
type: "reasoning-start"
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
controller.enqueue({
|
|
1091
|
+
delta: reasoning,
|
|
1092
|
+
id: reasoningId,
|
|
1093
|
+
type: "reasoning-delta"
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
const content = text(delta.content);
|
|
1097
|
+
if (content !== void 0 && content !== "") {
|
|
1098
|
+
closeReasoning(controller);
|
|
1099
|
+
if (textId === void 0) {
|
|
1100
|
+
textId = newId();
|
|
1101
|
+
textEverOpened = true;
|
|
1102
|
+
controller.enqueue({
|
|
1103
|
+
id: textId,
|
|
1104
|
+
type: "text-start"
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
controller.enqueue({
|
|
1108
|
+
delta: content,
|
|
1109
|
+
id: textId,
|
|
1110
|
+
type: "text-delta"
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
emitToolDeltas(delta.tool_calls, controller);
|
|
1114
|
+
}
|
|
1115
|
+
if (text(choice?.finish_reason) !== void 0) finishReason = mapFinishReason(choice?.finish_reason);
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region src/models/ai-sdk/models/language.ts
|
|
1121
|
+
function callHeaders(options, resolved) {
|
|
1122
|
+
const headers = definedHeaders(options.headers);
|
|
1123
|
+
Object.assign(headers, resolved.headers);
|
|
1124
|
+
if (resolved.sessionAffinity !== void 0) headers["x-session-affinity"] = resolved.sessionAffinity;
|
|
1125
|
+
return headers;
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Builds the `providerMetadata.cloudflare` block every model in this module
|
|
1129
|
+
* attaches to its result. Shared with the non-language models in
|
|
1130
|
+
* `models/modality.ts`.
|
|
1131
|
+
*/
|
|
1132
|
+
function cloudflareMetadata(model, resolved, response, transport, provider) {
|
|
1133
|
+
const headers = response.headers;
|
|
1134
|
+
const rawStep = headers.get("cf-aig-step");
|
|
1135
|
+
const step = rawStep === null ? NaN : Number(rawStep);
|
|
1136
|
+
const metadata = {
|
|
1137
|
+
gateway: resolved.gateway.id,
|
|
1138
|
+
model,
|
|
1139
|
+
...provider === void 0 ? {} : { provider },
|
|
1140
|
+
...Number.isNaN(step) ? {} : { step }
|
|
1141
|
+
};
|
|
1142
|
+
for (const [field, header] of [
|
|
1143
|
+
["cacheStatus", "cf-aig-cache-status"],
|
|
1144
|
+
["eventId", "cf-aig-event-id"],
|
|
1145
|
+
["requestId", "cf-aig-request-id"],
|
|
1146
|
+
["runId", "cf-aig-run-id"],
|
|
1147
|
+
["traceId", "cf-aig-trace-id"]
|
|
1148
|
+
]) {
|
|
1149
|
+
const value = headers.get(header);
|
|
1150
|
+
if (value !== null) metadata[field] = value;
|
|
1151
|
+
}
|
|
1152
|
+
const logId = headers.get("cf-aig-log-id") ?? transport.logIdFallback(response);
|
|
1153
|
+
if (logId !== void 0 && logId !== null) metadata.logId = logId;
|
|
1154
|
+
return { [PROVIDER_OPTIONS_KEY]: metadata };
|
|
1155
|
+
}
|
|
1156
|
+
function buildRequest(modelId, options, resolved, stream) {
|
|
1157
|
+
return buildOpenAIRequest(options, {
|
|
1158
|
+
chatTemplateKwargs: resolved.chatTemplateKwargs,
|
|
1159
|
+
modelId,
|
|
1160
|
+
reasoningEffort: resolved.reasoningEffort,
|
|
1161
|
+
stream
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Prepends the single `stream-start` part and stamps `providerMetadata` onto
|
|
1166
|
+
* the `finish` part. A `TransformStream` rather than a manual enqueue so the
|
|
1167
|
+
* pipeline keeps its backpressure and error propagation.
|
|
1168
|
+
*/
|
|
1169
|
+
function decorateStream(warnings, providerMetadata) {
|
|
1170
|
+
let started = false;
|
|
1171
|
+
const start = (controller) => {
|
|
1172
|
+
if (started) return;
|
|
1173
|
+
started = true;
|
|
1174
|
+
controller.enqueue({
|
|
1175
|
+
type: "stream-start",
|
|
1176
|
+
warnings
|
|
1177
|
+
});
|
|
1178
|
+
};
|
|
1179
|
+
return new TransformStream({
|
|
1180
|
+
flush(controller) {
|
|
1181
|
+
start(controller);
|
|
1182
|
+
},
|
|
1183
|
+
transform(part, controller) {
|
|
1184
|
+
start(controller);
|
|
1185
|
+
controller.enqueue(part.type === "finish" ? {
|
|
1186
|
+
...part,
|
|
1187
|
+
providerMetadata
|
|
1188
|
+
} : part);
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Replays an already-complete generation as a stream. Workers AI sometimes
|
|
1194
|
+
* answers a `stream: true` request with a plain JSON body; degrading
|
|
1195
|
+
* gracefully beats failing the call.
|
|
1196
|
+
*/
|
|
1197
|
+
function oneShotStream(generation) {
|
|
1198
|
+
return new ReadableStream({ start(controller) {
|
|
1199
|
+
if (generation.responseId !== void 0 || generation.responseModelId !== void 0) controller.enqueue({
|
|
1200
|
+
id: generation.responseId,
|
|
1201
|
+
modelId: generation.responseModelId,
|
|
1202
|
+
timestamp: generation.timestamp,
|
|
1203
|
+
type: "response-metadata"
|
|
1204
|
+
});
|
|
1205
|
+
for (const part of generation.content) emitContent(part, controller);
|
|
1206
|
+
controller.enqueue({
|
|
1207
|
+
finishReason: generation.finishReason,
|
|
1208
|
+
type: "finish",
|
|
1209
|
+
usage: generation.usage
|
|
1210
|
+
});
|
|
1211
|
+
controller.close();
|
|
1212
|
+
} });
|
|
1213
|
+
}
|
|
1214
|
+
function emitContent(part, controller) {
|
|
1215
|
+
switch (part.type) {
|
|
1216
|
+
case "text": {
|
|
1217
|
+
const id = newId();
|
|
1218
|
+
controller.enqueue({
|
|
1219
|
+
id,
|
|
1220
|
+
type: "text-start"
|
|
1221
|
+
});
|
|
1222
|
+
controller.enqueue({
|
|
1223
|
+
delta: part.text,
|
|
1224
|
+
id,
|
|
1225
|
+
type: "text-delta"
|
|
1226
|
+
});
|
|
1227
|
+
controller.enqueue({
|
|
1228
|
+
id,
|
|
1229
|
+
type: "text-end",
|
|
1230
|
+
...part.providerMetadata === void 0 ? {} : { providerMetadata: part.providerMetadata }
|
|
1231
|
+
});
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
case "reasoning": {
|
|
1235
|
+
const id = newId();
|
|
1236
|
+
controller.enqueue({
|
|
1237
|
+
id,
|
|
1238
|
+
type: "reasoning-start"
|
|
1239
|
+
});
|
|
1240
|
+
controller.enqueue({
|
|
1241
|
+
delta: part.text,
|
|
1242
|
+
id,
|
|
1243
|
+
type: "reasoning-delta"
|
|
1244
|
+
});
|
|
1245
|
+
controller.enqueue({
|
|
1246
|
+
id,
|
|
1247
|
+
type: "reasoning-end",
|
|
1248
|
+
...part.providerMetadata === void 0 ? {} : { providerMetadata: part.providerMetadata }
|
|
1249
|
+
});
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
case "tool-call":
|
|
1253
|
+
controller.enqueue({
|
|
1254
|
+
id: part.toolCallId,
|
|
1255
|
+
toolName: part.toolName,
|
|
1256
|
+
type: "tool-input-start"
|
|
1257
|
+
});
|
|
1258
|
+
controller.enqueue({
|
|
1259
|
+
delta: part.input,
|
|
1260
|
+
id: part.toolCallId,
|
|
1261
|
+
type: "tool-input-delta"
|
|
1262
|
+
});
|
|
1263
|
+
controller.enqueue({
|
|
1264
|
+
id: part.toolCallId,
|
|
1265
|
+
type: "tool-input-end"
|
|
1266
|
+
});
|
|
1267
|
+
controller.enqueue(part);
|
|
1268
|
+
break;
|
|
1269
|
+
default: controller.enqueue(part);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
var _config$2 = /* @__PURE__ */ new WeakMap();
|
|
1273
|
+
var _options$2 = /* @__PURE__ */ new WeakMap();
|
|
1274
|
+
var _CloudflareLanguageModel_brand = /* @__PURE__ */ new WeakSet();
|
|
1275
|
+
/**
|
|
1276
|
+
* A `LanguageModelV4` backed by the Cloudflare run path. The model id decides
|
|
1277
|
+
* the wire format; nothing about transport or wire format reaches user land.
|
|
1278
|
+
*
|
|
1279
|
+
* @experimental This surface is experimental and may change.
|
|
1280
|
+
*/
|
|
1281
|
+
var CloudflareLanguageModel = class {
|
|
1282
|
+
constructor(modelId, options, config) {
|
|
1283
|
+
_classPrivateMethodInitSpec(this, _CloudflareLanguageModel_brand);
|
|
1284
|
+
this.specificationVersion = "v4";
|
|
1285
|
+
this.provider = "cloudflare";
|
|
1286
|
+
_classPrivateFieldInitSpec(this, _config$2, void 0);
|
|
1287
|
+
_classPrivateFieldInitSpec(this, _options$2, void 0);
|
|
1288
|
+
this.modelId = modelId;
|
|
1289
|
+
_classPrivateFieldSet2(_options$2, this, options);
|
|
1290
|
+
_classPrivateFieldSet2(_config$2, this, config);
|
|
1291
|
+
rememberOptions(this, options);
|
|
1292
|
+
this.supportedUrls = {};
|
|
1293
|
+
}
|
|
1294
|
+
async doGenerate(options) {
|
|
1295
|
+
const call = parseCallOptions(options.providerOptions);
|
|
1296
|
+
const resolved = _assertClassBrand(_CloudflareLanguageModel_brand, this, _resolve$1).call(this, call);
|
|
1297
|
+
const legOptions = withoutCallFallback(options);
|
|
1298
|
+
return _assertClassBrand(_CloudflareLanguageModel_brand, this, _withFallback$1).call(this, call, (model) => model.doGenerate(legOptions), async (modelId) => {
|
|
1299
|
+
const request = buildRequest(modelId, options, resolved, false);
|
|
1300
|
+
const response = await _assertClassBrand(_CloudflareLanguageModel_brand, this, _send).call(this, modelId, request, options, resolved);
|
|
1301
|
+
const generation = parseOpenAIGeneration(await _assertClassBrand(_CloudflareLanguageModel_brand, this, _readBody).call(this, modelId, request, response), modelId);
|
|
1302
|
+
return {
|
|
1303
|
+
content: generation.content,
|
|
1304
|
+
finishReason: generation.finishReason,
|
|
1305
|
+
providerMetadata: cloudflareMetadata(modelId, resolved, response, _classPrivateFieldGet2(_config$2, this).transport),
|
|
1306
|
+
request: { body: request.body },
|
|
1307
|
+
response: {
|
|
1308
|
+
headers: headersToObject(response.headers),
|
|
1309
|
+
id: generation.responseId,
|
|
1310
|
+
modelId: generation.responseModelId ?? modelId,
|
|
1311
|
+
timestamp: generation.timestamp
|
|
1312
|
+
},
|
|
1313
|
+
usage: generation.usage,
|
|
1314
|
+
warnings: request.warnings
|
|
1315
|
+
};
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
async doStream(options) {
|
|
1319
|
+
const call = parseCallOptions(options.providerOptions);
|
|
1320
|
+
const resolved = _assertClassBrand(_CloudflareLanguageModel_brand, this, _resolve$1).call(this, call);
|
|
1321
|
+
const legOptions = withoutCallFallback(options);
|
|
1322
|
+
return _assertClassBrand(_CloudflareLanguageModel_brand, this, _withFallback$1).call(this, call, (model) => model.doStream(legOptions), async (modelId) => {
|
|
1323
|
+
const request = buildRequest(modelId, options, resolved, true);
|
|
1324
|
+
const response = await _assertClassBrand(_CloudflareLanguageModel_brand, this, _send).call(this, modelId, request, options, resolved);
|
|
1325
|
+
const providerMetadata = cloudflareMetadata(modelId, resolved, response, _classPrivateFieldGet2(_config$2, this).transport);
|
|
1326
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1327
|
+
const decorate = decorateStream(request.warnings, providerMetadata);
|
|
1328
|
+
let parts;
|
|
1329
|
+
if (!contentType.includes("event-stream") || response.body === null) parts = oneShotStream(parseOpenAIGeneration(await _assertClassBrand(_CloudflareLanguageModel_brand, this, _readBody).call(this, modelId, request, response), modelId));
|
|
1330
|
+
else parts = response.body.pipeThrough(sseDataStream()).pipeThrough(normalizeChatCompletionsStream(modelId, { reasoningOff: request.reasoningOff })).pipeThrough(openAIStreamParser());
|
|
1331
|
+
return {
|
|
1332
|
+
request: { body: request.body },
|
|
1333
|
+
response: { headers: headersToObject(response.headers) },
|
|
1334
|
+
stream: parts.pipeThrough(decorate)
|
|
1335
|
+
};
|
|
1336
|
+
}, "stream");
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
/**
|
|
1340
|
+
* Reads a 2xx body as JSON and unwraps the Cloudflare envelope. A 2xx that
|
|
1341
|
+
* is not JSON at all is a provider failure, not a caller bug, so it comes
|
|
1342
|
+
* back as a {@link CloudflareAIError} rather than a bare `SyntaxError`.
|
|
1343
|
+
*/
|
|
1344
|
+
async function _readBody(modelId, request, response) {
|
|
1345
|
+
let json;
|
|
1346
|
+
try {
|
|
1347
|
+
json = await response.json();
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
throw new CloudflareAIError({
|
|
1350
|
+
cause: error,
|
|
1351
|
+
code: "provider-error",
|
|
1352
|
+
isRetryable: false,
|
|
1353
|
+
message: `The model answered ${response.status} with a body that is not JSON.`,
|
|
1354
|
+
model: modelId,
|
|
1355
|
+
requestBodyValues: request.body,
|
|
1356
|
+
status: response.status,
|
|
1357
|
+
url: _classPrivateFieldGet2(_config$2, this).transport.url
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
return unwrapEnvelope(json, {
|
|
1361
|
+
model: modelId,
|
|
1362
|
+
status: response.status,
|
|
1363
|
+
url: _classPrivateFieldGet2(_config$2, this).transport.url
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
function _resolve$1(call) {
|
|
1367
|
+
return resolveOptions(_classPrivateFieldGet2(_config$2, this).providerGateway, _classPrivateFieldGet2(_options$2, this), call);
|
|
1368
|
+
}
|
|
1369
|
+
async function _send(modelId, request, options, resolved) {
|
|
1370
|
+
const response = await _classPrivateFieldGet2(_config$2, this).transport.run({
|
|
1371
|
+
gateway: resolved.gateway,
|
|
1372
|
+
headers: callHeaders(options, resolved),
|
|
1373
|
+
input: request.body,
|
|
1374
|
+
model: modelId,
|
|
1375
|
+
signal: options.abortSignal
|
|
1376
|
+
});
|
|
1377
|
+
if (!response.ok) throw await errorFromResponse(response, {
|
|
1378
|
+
model: modelId,
|
|
1379
|
+
requestBodyValues: request.body,
|
|
1380
|
+
url: _classPrivateFieldGet2(_config$2, this).transport.url
|
|
1381
|
+
});
|
|
1382
|
+
return response;
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Runs this model, then each fallback leg in order. A leg named by id runs
|
|
1386
|
+
* through this same pipeline; a leg that is a model object — a vendor model
|
|
1387
|
+
* `ai()` already wrapped, or another provider's — is asked directly, so a
|
|
1388
|
+
* chain can mix Workers AI with anything the AI SDK can reach.
|
|
1389
|
+
*/
|
|
1390
|
+
function _withFallback$1(call, runLeg, runSelf, mode = "generate") {
|
|
1391
|
+
const legs = fallbackLegs(_classPrivateFieldGet2(_options$2, this), call);
|
|
1392
|
+
const chain = (attempts) => mode === "stream" ? withStreamFallbackLegs(attempts, _classPrivateFieldGet2(_config$2, this).transport.url) : withFallbackLegs(attempts, _classPrivateFieldGet2(_config$2, this).transport.url);
|
|
1393
|
+
return chain([{
|
|
1394
|
+
model: this.modelId,
|
|
1395
|
+
run: () => runSelf(this.modelId)
|
|
1396
|
+
}, ...legs.map((leg) => typeof leg === "string" ? {
|
|
1397
|
+
model: requireWorkersAIId(leg),
|
|
1398
|
+
run: () => runSelf(leg)
|
|
1399
|
+
} : {
|
|
1400
|
+
model: leg.modelId,
|
|
1401
|
+
run: () => runLeg(leg)
|
|
1402
|
+
})]);
|
|
1403
|
+
}
|
|
1404
|
+
//#endregion
|
|
1405
|
+
//#region src/models/ai-sdk/models/gateway.ts
|
|
1406
|
+
/**
|
|
1407
|
+
* Throws unless a model's provider keeps its settings where every `@ai-sdk/*`
|
|
1408
|
+
* provider keeps them. Checked when the model is wrapped rather than when it
|
|
1409
|
+
* is called, so the mistake is reported at the line that made it.
|
|
1410
|
+
*/
|
|
1411
|
+
function requireConfig(model) {
|
|
1412
|
+
const config = model.config;
|
|
1413
|
+
if (config === null || typeof config !== "object") throw new TypeError(`The model "${String(model.modelId ?? "unknown")}" from provider "${String(model.provider ?? "unknown")}" has no \`config\` setting, so AI Gateway cannot take over its requests. Every \`@ai-sdk/*\` provider has one; a middleware-wrapped or hand-written model does not. Wrap the provider's own model first and apply middleware afterwards: wrapLanguageModel({ model: ai(anthropic("claude-opus-4-8")), middleware }).`);
|
|
1414
|
+
return config;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* A per-call copy of the upstream model whose `config.fetch` is ours. The
|
|
1418
|
+
* user's model object is never touched: it may be shared across requests, and
|
|
1419
|
+
* two concurrent calls must not see each other's transport.
|
|
1420
|
+
*/
|
|
1421
|
+
function cloneWithFetch(model, fetchImpl) {
|
|
1422
|
+
const config = requireConfig(model);
|
|
1423
|
+
const clone = Object.create(Object.getPrototypeOf(model));
|
|
1424
|
+
Object.defineProperties(clone, Object.getOwnPropertyDescriptors(model));
|
|
1425
|
+
Object.defineProperty(clone, "config", {
|
|
1426
|
+
configurable: true,
|
|
1427
|
+
enumerable: true,
|
|
1428
|
+
value: {
|
|
1429
|
+
...config,
|
|
1430
|
+
fetch: fetchImpl
|
|
1431
|
+
},
|
|
1432
|
+
writable: true
|
|
1433
|
+
});
|
|
1434
|
+
return clone;
|
|
1435
|
+
}
|
|
1436
|
+
/** The request URL, whichever of `fetch`'s two call shapes was used. */
|
|
1437
|
+
function requestUrl(input) {
|
|
1438
|
+
if (typeof input === "string") return input;
|
|
1439
|
+
if (input instanceof URL) return input.href;
|
|
1440
|
+
return input.url;
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* The body of either `fetch` call form: the `RequestInit`'s when one was
|
|
1444
|
+
* given, else the `Request`'s own, read from a clone so the caller's object
|
|
1445
|
+
* is neither consumed nor mutated.
|
|
1446
|
+
*/
|
|
1447
|
+
async function requestBody(input, init) {
|
|
1448
|
+
if (init?.body !== void 0) return init.body;
|
|
1449
|
+
if (input instanceof Request && input.body !== null) return await input.clone().text();
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* The vendor's body as the universal request's `query`. Only JSON travels:
|
|
1453
|
+
* the gateway's universal request has one JSON field for the whole upstream
|
|
1454
|
+
* body, so a multipart upload or a streamed body cannot be expressed.
|
|
1455
|
+
*/
|
|
1456
|
+
function universalQuery(body) {
|
|
1457
|
+
if (typeof body !== "string") {
|
|
1458
|
+
const kind = body === null || body === void 0 ? "an empty" : body instanceof FormData ? "a FormData" : `a ${body.constructor?.name ?? typeof body}`;
|
|
1459
|
+
throw new TypeError(`AI Gateway's universal request carries a JSON body only, and this model sent ${kind} body. File uploads and other multipart requests cannot be routed through the gateway; call the vendor directly for those.`);
|
|
1460
|
+
}
|
|
1461
|
+
try {
|
|
1462
|
+
return JSON.parse(body);
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
throw new TypeError(`AI Gateway's universal request carries a JSON body only, and this model sent a body that is not JSON: ${String(error)}`);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* The vendor's own request headers, minus the credential the gateway supplies
|
|
1469
|
+
* itself and the length of a body we are about to re-serialize.
|
|
1470
|
+
*/
|
|
1471
|
+
function forwardedHeaders(init, input, route) {
|
|
1472
|
+
const source = init?.headers ?? (input instanceof Request ? input.headers : void 0);
|
|
1473
|
+
const dropped = /* @__PURE__ */ new Set([...route.authHeaders, "content-length"]);
|
|
1474
|
+
const headers = {};
|
|
1475
|
+
new Headers(source).forEach((value, key) => {
|
|
1476
|
+
if (!dropped.has(key.toLowerCase())) headers[key] = value;
|
|
1477
|
+
});
|
|
1478
|
+
return headers;
|
|
1479
|
+
}
|
|
1480
|
+
/** A response whose body can be read again, for the vendor's error handler. */
|
|
1481
|
+
function replayed(response, text) {
|
|
1482
|
+
const bodyless = response.status === 204 || response.status === 205 || response.status === 304;
|
|
1483
|
+
return new Response(bodyless ? null : text, {
|
|
1484
|
+
headers: response.headers,
|
|
1485
|
+
status: response.status,
|
|
1486
|
+
statusText: response.statusText
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* The `fetch` a routed model is given. It never reaches the vendor: it maps
|
|
1491
|
+
* the vendor's URL onto a gateway provider slug and endpoint, hands the body
|
|
1492
|
+
* over untouched, and returns the gateway's answer as the vendor's own.
|
|
1493
|
+
*/
|
|
1494
|
+
function routedFetch(context) {
|
|
1495
|
+
const routed = async (input, init) => {
|
|
1496
|
+
const route = requireGatewayProvider(requestUrl(input));
|
|
1497
|
+
const query = universalQuery(await requestBody(input, init));
|
|
1498
|
+
const { resolved } = context;
|
|
1499
|
+
const extraHeaders = {
|
|
1500
|
+
...resolved.headers,
|
|
1501
|
+
...resolved.sessionAffinity === void 0 ? {} : { "x-session-affinity": resolved.sessionAffinity }
|
|
1502
|
+
};
|
|
1503
|
+
const response = await context.config.transport.universal({
|
|
1504
|
+
endpoint: route.endpoint,
|
|
1505
|
+
...Object.keys(extraHeaders).length > 0 ? { extraHeaders } : {},
|
|
1506
|
+
gateway: resolved.gateway,
|
|
1507
|
+
headers: forwardedHeaders(init, input, route),
|
|
1508
|
+
provider: route.provider,
|
|
1509
|
+
query,
|
|
1510
|
+
signal: init?.signal ?? (input instanceof Request ? input.signal : void 0) ?? context.signal
|
|
1511
|
+
});
|
|
1512
|
+
context.capture.provider = route.provider;
|
|
1513
|
+
context.capture.response = response;
|
|
1514
|
+
if (response.ok) return response;
|
|
1515
|
+
const text = await response.text();
|
|
1516
|
+
let body;
|
|
1517
|
+
try {
|
|
1518
|
+
body = JSON.parse(text);
|
|
1519
|
+
} catch {
|
|
1520
|
+
return replayed(response, text);
|
|
1521
|
+
}
|
|
1522
|
+
if (!isGatewayErrorEnvelope(body)) return replayed(response, text);
|
|
1523
|
+
throw errorFromGatewayEnvelope(body, {
|
|
1524
|
+
model: modelOf(query) ?? route.provider,
|
|
1525
|
+
requestBodyValues: query,
|
|
1526
|
+
responseBody: text,
|
|
1527
|
+
responseHeaders: headersToObject(response.headers),
|
|
1528
|
+
status: response.status,
|
|
1529
|
+
url: context.config.transport.url
|
|
1530
|
+
});
|
|
1531
|
+
};
|
|
1532
|
+
return routed;
|
|
1533
|
+
}
|
|
1534
|
+
/** The model id out of a vendor body, for error reporting. */
|
|
1535
|
+
function modelOf(query) {
|
|
1536
|
+
if (query === null || typeof query !== "object") return void 0;
|
|
1537
|
+
const model = query.model;
|
|
1538
|
+
return typeof model === "string" ? model : void 0;
|
|
1539
|
+
}
|
|
1540
|
+
/** The call options a routed model sees: ours are read out and removed. */
|
|
1541
|
+
function withoutOurOptions(options) {
|
|
1542
|
+
const providerOptions = options.providerOptions;
|
|
1543
|
+
if (providerOptions === void 0 || !("cloudflare" in providerOptions)) return options;
|
|
1544
|
+
const rest = { ...providerOptions };
|
|
1545
|
+
delete rest[PROVIDER_OPTIONS_KEY];
|
|
1546
|
+
return {
|
|
1547
|
+
...options,
|
|
1548
|
+
providerOptions: rest
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
/** Adds our metadata block without dropping the vendor's own keys. */
|
|
1552
|
+
function mergeMetadata(existing, ours) {
|
|
1553
|
+
return {
|
|
1554
|
+
...existing,
|
|
1555
|
+
...ours
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* The same metadata, in the shape an image result's provider metadata takes.
|
|
1560
|
+
*
|
|
1561
|
+
* `ImageModelV4ProviderMetadata` is not the shared shape: every provider block
|
|
1562
|
+
* must carry an `images` array, one entry per generated image, and the AI SDK
|
|
1563
|
+
* spreads that array when it merges the calls a multi-image request fanned out
|
|
1564
|
+
* into. A block without one makes `generateImage` throw, and only the array's
|
|
1565
|
+
* entries survive that merge, so ours goes inside it.
|
|
1566
|
+
*/
|
|
1567
|
+
function metadataFor(result, ours) {
|
|
1568
|
+
const images = result.images;
|
|
1569
|
+
if (!Array.isArray(images)) return ours;
|
|
1570
|
+
const block = ours[PROVIDER_OPTIONS_KEY];
|
|
1571
|
+
return {
|
|
1572
|
+
...ours,
|
|
1573
|
+
[PROVIDER_OPTIONS_KEY]: {
|
|
1574
|
+
...block,
|
|
1575
|
+
images: images.map(() => ({ ...block }))
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* The Workers AI knobs a routed vendor model cannot honour. They are not
|
|
1581
|
+
* dropped silently: the vendor decides its own reasoning, so a caller who set
|
|
1582
|
+
* `reasoningEffort` here is asking for something that never leaves.
|
|
1583
|
+
*/
|
|
1584
|
+
function ignoredWarnings(resolved) {
|
|
1585
|
+
const warnings = [];
|
|
1586
|
+
for (const [option, value] of [["reasoningEffort", resolved.reasoningEffort], ["chatTemplateKwargs", resolved.chatTemplateKwargs]]) {
|
|
1587
|
+
if (value === void 0) continue;
|
|
1588
|
+
warnings.push({
|
|
1589
|
+
details: `\`${option}\` is a Workers AI setting and does not apply to a model routed through AI Gateway; use the vendor's own reasoning options instead.`,
|
|
1590
|
+
feature: option,
|
|
1591
|
+
type: "unsupported"
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
return warnings;
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* The Workers AI knobs a routed non-language model was asked with, added to
|
|
1598
|
+
* the result's own `warnings` when it has one.
|
|
1599
|
+
*
|
|
1600
|
+
* Only the per-call `providerOptions.cloudflare` bag can carry them here —
|
|
1601
|
+
* {@link RoutedModelOptions} leaves them off the per-model type — and only
|
|
1602
|
+
* some v4 result shapes have somewhere to put a warning: image, speech,
|
|
1603
|
+
* transcription and reranking results do, an embedding result does not.
|
|
1604
|
+
*/
|
|
1605
|
+
function routedWarnings(result, resolved) {
|
|
1606
|
+
const existing = result.warnings;
|
|
1607
|
+
if (!Array.isArray(existing)) return {};
|
|
1608
|
+
const warnings = ignoredWarnings(resolved);
|
|
1609
|
+
if (warnings.length === 0) return {};
|
|
1610
|
+
return { warnings: [...existing, ...warnings] };
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Stamps the finish part with `providerMetadata.cloudflare`, and adds our own
|
|
1614
|
+
* warnings to the `stream-start` the vendor emitted.
|
|
1615
|
+
*/
|
|
1616
|
+
function stampStream(metadata, warnings) {
|
|
1617
|
+
return new TransformStream({ transform(part, controller) {
|
|
1618
|
+
if (part.type === "finish") {
|
|
1619
|
+
controller.enqueue({
|
|
1620
|
+
...part,
|
|
1621
|
+
providerMetadata: mergeMetadata(part.providerMetadata, metadata())
|
|
1622
|
+
});
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
if (part.type === "stream-start" && warnings.length > 0) {
|
|
1626
|
+
controller.enqueue({
|
|
1627
|
+
...part,
|
|
1628
|
+
warnings: [...part.warnings, ...warnings]
|
|
1629
|
+
});
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
controller.enqueue(part);
|
|
1633
|
+
} });
|
|
1634
|
+
}
|
|
1635
|
+
var _model = /* @__PURE__ */ new WeakMap();
|
|
1636
|
+
var _options$1 = /* @__PURE__ */ new WeakMap();
|
|
1637
|
+
var _config$1 = /* @__PURE__ */ new WeakMap();
|
|
1638
|
+
var _GatewayLanguageModel_brand = /* @__PURE__ */ new WeakSet();
|
|
1639
|
+
/**
|
|
1640
|
+
* A `LanguageModelV4` that is the user's own vendor model, routed through AI
|
|
1641
|
+
* Gateway. Generation, parsing, tool handling and error shapes stay the
|
|
1642
|
+
* vendor's; only the transport is Cloudflare's.
|
|
1643
|
+
*
|
|
1644
|
+
* @experimental This surface is experimental and may change.
|
|
1645
|
+
*/
|
|
1646
|
+
var GatewayLanguageModel = class {
|
|
1647
|
+
constructor(model, options, config) {
|
|
1648
|
+
_classPrivateMethodInitSpec(this, _GatewayLanguageModel_brand);
|
|
1649
|
+
this.specificationVersion = "v4";
|
|
1650
|
+
_classPrivateFieldInitSpec(this, _model, void 0);
|
|
1651
|
+
_classPrivateFieldInitSpec(this, _options$1, void 0);
|
|
1652
|
+
_classPrivateFieldInitSpec(this, _config$1, void 0);
|
|
1653
|
+
requireConfig(model);
|
|
1654
|
+
_classPrivateFieldSet2(_model, this, model);
|
|
1655
|
+
_classPrivateFieldSet2(_options$1, this, options);
|
|
1656
|
+
_classPrivateFieldSet2(_config$1, this, config);
|
|
1657
|
+
rememberOptions(this, options);
|
|
1658
|
+
}
|
|
1659
|
+
/** The vendor's own provider name — `anthropic.messages`, `openai.responses`. */
|
|
1660
|
+
get provider() {
|
|
1661
|
+
return _classPrivateFieldGet2(_model, this).provider;
|
|
1662
|
+
}
|
|
1663
|
+
/** The vendor's own id spelling, which the gateway requires verbatim. */
|
|
1664
|
+
get modelId() {
|
|
1665
|
+
return _classPrivateFieldGet2(_model, this).modelId;
|
|
1666
|
+
}
|
|
1667
|
+
get supportedUrls() {
|
|
1668
|
+
return _classPrivateFieldGet2(_model, this).supportedUrls;
|
|
1669
|
+
}
|
|
1670
|
+
/** The model this wraps, for callers that need the vendor object back. */
|
|
1671
|
+
get model() {
|
|
1672
|
+
return _classPrivateFieldGet2(_model, this);
|
|
1673
|
+
}
|
|
1674
|
+
async doGenerate(options) {
|
|
1675
|
+
const call = parseCallOptions(options.providerOptions);
|
|
1676
|
+
const resolved = _assertClassBrand(_GatewayLanguageModel_brand, this, _resolve).call(this, call);
|
|
1677
|
+
const upstream = withoutOurOptions(options);
|
|
1678
|
+
const legOptions = withoutCallFallback(options);
|
|
1679
|
+
return _assertClassBrand(_GatewayLanguageModel_brand, this, _withFallback).call(this, call, (leg) => leg.doGenerate(legOptions), async () => {
|
|
1680
|
+
const capture = {};
|
|
1681
|
+
const result = await _assertClassBrand(_GatewayLanguageModel_brand, this, _route).call(this, resolved, capture, options.abortSignal).doGenerate(upstream);
|
|
1682
|
+
const warnings = ignoredWarnings(resolved);
|
|
1683
|
+
return {
|
|
1684
|
+
...result,
|
|
1685
|
+
providerMetadata: mergeMetadata(result.providerMetadata, _assertClassBrand(_GatewayLanguageModel_brand, this, _metadata).call(this, resolved, capture)),
|
|
1686
|
+
response: _assertClassBrand(_GatewayLanguageModel_brand, this, _response).call(this, result.response, capture),
|
|
1687
|
+
warnings: warnings.length > 0 ? [...result.warnings, ...warnings] : result.warnings
|
|
1688
|
+
};
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
async doStream(options) {
|
|
1692
|
+
const call = parseCallOptions(options.providerOptions);
|
|
1693
|
+
const resolved = _assertClassBrand(_GatewayLanguageModel_brand, this, _resolve).call(this, call);
|
|
1694
|
+
const upstream = withoutOurOptions(options);
|
|
1695
|
+
const legOptions = withoutCallFallback(options);
|
|
1696
|
+
return _assertClassBrand(_GatewayLanguageModel_brand, this, _withFallback).call(this, call, (leg) => leg.doStream(legOptions), async () => {
|
|
1697
|
+
const capture = {};
|
|
1698
|
+
const result = await _assertClassBrand(_GatewayLanguageModel_brand, this, _route).call(this, resolved, capture, options.abortSignal).doStream(upstream);
|
|
1699
|
+
return {
|
|
1700
|
+
...result,
|
|
1701
|
+
response: _assertClassBrand(_GatewayLanguageModel_brand, this, _response).call(this, result.response, capture),
|
|
1702
|
+
stream: result.stream.pipeThrough(stampStream(() => _assertClassBrand(_GatewayLanguageModel_brand, this, _metadata).call(this, resolved, capture), ignoredWarnings(resolved)))
|
|
1703
|
+
};
|
|
1704
|
+
}, "stream");
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
function _route(resolved, capture, signal) {
|
|
1708
|
+
return cloneWithFetch(_classPrivateFieldGet2(_model, this), routedFetch({
|
|
1709
|
+
capture,
|
|
1710
|
+
config: _classPrivateFieldGet2(_config$1, this),
|
|
1711
|
+
resolved,
|
|
1712
|
+
signal
|
|
1713
|
+
}));
|
|
1714
|
+
}
|
|
1715
|
+
function _resolve(call) {
|
|
1716
|
+
return resolveOptions(_classPrivateFieldGet2(_config$1, this).providerGateway, _classPrivateFieldGet2(_options$1, this), call);
|
|
1717
|
+
}
|
|
1718
|
+
function _metadata(resolved, capture) {
|
|
1719
|
+
const response = capture.response ?? new Response(null);
|
|
1720
|
+
return cloudflareMetadata(this.modelId, resolved, response, _classPrivateFieldGet2(_config$1, this).transport, capture.provider);
|
|
1721
|
+
}
|
|
1722
|
+
function _response(response, capture) {
|
|
1723
|
+
if (capture.response === void 0) return response;
|
|
1724
|
+
const headers = headersToObject(capture.response.headers);
|
|
1725
|
+
return {
|
|
1726
|
+
...response,
|
|
1727
|
+
headers: {
|
|
1728
|
+
...response?.headers,
|
|
1729
|
+
...headers
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* The vendor model first, then each fallback leg. A leg named by id is a
|
|
1735
|
+
* Workers AI model; a leg that is a model object is asked as it stands, so
|
|
1736
|
+
* `ai()` has already wrapped it if it needed wrapping.
|
|
1737
|
+
*/
|
|
1738
|
+
function _withFallback(call, runLeg, run, mode = "generate") {
|
|
1739
|
+
const legs = fallbackLegs(_classPrivateFieldGet2(_options$1, this), call);
|
|
1740
|
+
if (legs.length === 0) return run();
|
|
1741
|
+
const chain = (attempts) => mode === "stream" ? withStreamFallbackLegs(attempts, _classPrivateFieldGet2(_config$1, this).transport.url) : withFallbackLegs(attempts, _classPrivateFieldGet2(_config$1, this).transport.url);
|
|
1742
|
+
return chain([{
|
|
1743
|
+
model: this.modelId,
|
|
1744
|
+
run
|
|
1745
|
+
}, ...legs.map((leg) => {
|
|
1746
|
+
const model = typeof leg === "string" ? _assertClassBrand(_GatewayLanguageModel_brand, this, _workersAI).call(this, leg) : leg;
|
|
1747
|
+
return {
|
|
1748
|
+
model: model.modelId,
|
|
1749
|
+
run: () => runLeg(model)
|
|
1750
|
+
};
|
|
1751
|
+
})]);
|
|
1752
|
+
}
|
|
1753
|
+
/** A Workers AI leg, carrying this model's gateway options but no chain. */
|
|
1754
|
+
function _workersAI(modelId) {
|
|
1755
|
+
return new CloudflareLanguageModel(requireWorkersAIId(modelId), {
|
|
1756
|
+
..._classPrivateFieldGet2(_options$1, this),
|
|
1757
|
+
fallback: void 0
|
|
1758
|
+
}, _classPrivateFieldGet2(_config$1, this));
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Routes any other AI SDK v4 model the vendor built — embedding, image,
|
|
1762
|
+
* speech, transcription — through the same universal gateway request. Only
|
|
1763
|
+
* `fetch` is swapped; every `do…` method stays the vendor's, and its result
|
|
1764
|
+
* gains `providerMetadata.cloudflare` when the result is an object that can
|
|
1765
|
+
* carry one.
|
|
1766
|
+
*
|
|
1767
|
+
* @experimental This surface is experimental and may change.
|
|
1768
|
+
*/
|
|
1769
|
+
function routed(model, options, config) {
|
|
1770
|
+
requireConfig(model);
|
|
1771
|
+
return new Proxy(model, { get(target, property, receiver) {
|
|
1772
|
+
const value = Reflect.get(target, property, receiver);
|
|
1773
|
+
if (typeof value !== "function" || typeof property !== "string" || !property.startsWith("do")) return value;
|
|
1774
|
+
return async (call) => {
|
|
1775
|
+
const resolved = resolveOptions(config.providerGateway, options, parseCallOptions(call?.providerOptions));
|
|
1776
|
+
const upstream = call?.providerOptions === void 0 ? call : withoutOurOptions(call);
|
|
1777
|
+
const capture = {};
|
|
1778
|
+
const clone = cloneWithFetch(target, routedFetch({
|
|
1779
|
+
capture,
|
|
1780
|
+
config,
|
|
1781
|
+
resolved,
|
|
1782
|
+
signal: call?.abortSignal
|
|
1783
|
+
}));
|
|
1784
|
+
const result = await Reflect.get(clone, property).call(clone, upstream);
|
|
1785
|
+
if (result === null || typeof result !== "object") return result;
|
|
1786
|
+
const existing = result.providerMetadata;
|
|
1787
|
+
return {
|
|
1788
|
+
...result,
|
|
1789
|
+
...routedWarnings(result, resolved),
|
|
1790
|
+
providerMetadata: mergeMetadata(existing, metadataFor(result, cloudflareMetadata(String(target.modelId ?? ""), resolved, capture.response ?? new Response(null), config.transport, capture.provider)))
|
|
1791
|
+
};
|
|
1792
|
+
};
|
|
1793
|
+
} });
|
|
1794
|
+
}
|
|
1795
|
+
//#endregion
|
|
1796
|
+
//#region src/models/ai-sdk/models/modality.ts
|
|
1797
|
+
/** The `unsupported` warning shape, which every modality needs. */
|
|
1798
|
+
function unsupported(feature, details) {
|
|
1799
|
+
return {
|
|
1800
|
+
feature,
|
|
1801
|
+
type: "unsupported",
|
|
1802
|
+
...details === void 0 ? {} : { details }
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
/** Decodes base64 into bytes; passes `Uint8Array` input straight through. */
|
|
1806
|
+
function toBytes(data) {
|
|
1807
|
+
if (typeof data !== "string") return data;
|
|
1808
|
+
const binary = atob(data);
|
|
1809
|
+
const bytes = new Uint8Array(binary.length);
|
|
1810
|
+
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
1811
|
+
return bytes;
|
|
1812
|
+
}
|
|
1813
|
+
var _config = /* @__PURE__ */ new WeakMap();
|
|
1814
|
+
var _options = /* @__PURE__ */ new WeakMap();
|
|
1815
|
+
var _CloudflareModalityModel_brand = /* @__PURE__ */ new WeakSet();
|
|
1816
|
+
/**
|
|
1817
|
+
* A model over the Cloudflare run path that is not a language model. Holds
|
|
1818
|
+
* the one call path all four modalities share; each subclass only builds a
|
|
1819
|
+
* body and reads an answer.
|
|
1820
|
+
*
|
|
1821
|
+
* @experimental This surface is experimental and may change.
|
|
1822
|
+
*/
|
|
1823
|
+
var CloudflareModalityModel = class {
|
|
1824
|
+
constructor(modelId, options, config) {
|
|
1825
|
+
_classPrivateMethodInitSpec(this, _CloudflareModalityModel_brand);
|
|
1826
|
+
this.specificationVersion = "v4";
|
|
1827
|
+
this.provider = "cloudflare";
|
|
1828
|
+
_classPrivateFieldInitSpec(this, _config, void 0);
|
|
1829
|
+
_classPrivateFieldInitSpec(this, _options, void 0);
|
|
1830
|
+
options?.fallback?.forEach((leg) => {
|
|
1831
|
+
if (typeof leg === "string") requireWorkersAIId(leg);
|
|
1832
|
+
});
|
|
1833
|
+
this.modelId = modelId;
|
|
1834
|
+
_classPrivateFieldSet2(_options, this, options);
|
|
1835
|
+
_classPrivateFieldSet2(_config, this, config);
|
|
1836
|
+
}
|
|
1837
|
+
/** The endpoint this model calls, for error reporting. */
|
|
1838
|
+
get endpoint() {
|
|
1839
|
+
return _classPrivateFieldGet2(_config, this).transport.url;
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Sends one call, trying each fallback leg in turn, and decodes the answer
|
|
1843
|
+
* as JSON or as raw bytes depending on what the model sent back.
|
|
1844
|
+
*/
|
|
1845
|
+
async send(call) {
|
|
1846
|
+
const resolved = resolveOptions(_classPrivateFieldGet2(_config, this).providerGateway, _classPrivateFieldGet2(_options, this), parseCallOptions(call.providerOptions));
|
|
1847
|
+
const transport = _classPrivateFieldGet2(_config, this).transport;
|
|
1848
|
+
return withFallback(this.modelId, resolved.fallback, transport.url, async (modelId) => {
|
|
1849
|
+
const request = call.build(modelId);
|
|
1850
|
+
const response = await transport.run({
|
|
1851
|
+
gateway: resolved.gateway,
|
|
1852
|
+
headers: {
|
|
1853
|
+
...definedHeaders(call.headers),
|
|
1854
|
+
...resolved.headers,
|
|
1855
|
+
...resolved.sessionAffinity === void 0 ? {} : { "x-session-affinity": resolved.sessionAffinity }
|
|
1856
|
+
},
|
|
1857
|
+
input: request.input,
|
|
1858
|
+
model: modelId,
|
|
1859
|
+
signal: call.abortSignal
|
|
1860
|
+
});
|
|
1861
|
+
if (!response.ok) throw await errorFromResponse(response, {
|
|
1862
|
+
model: modelId,
|
|
1863
|
+
requestBodyValues: request.input,
|
|
1864
|
+
url: transport.url
|
|
1865
|
+
});
|
|
1866
|
+
const mediaType = (response.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
1867
|
+
const body = await _assertClassBrand(_CloudflareModalityModel_brand, this, _decode).call(this, response, modelId, request.input, mediaType);
|
|
1868
|
+
const answer = {
|
|
1869
|
+
headers: headersToObject(response.headers),
|
|
1870
|
+
input: request.input,
|
|
1871
|
+
mediaType,
|
|
1872
|
+
modelId,
|
|
1873
|
+
providerMetadata: cloudflareMetadata(modelId, resolved, response, transport),
|
|
1874
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1875
|
+
url: transport.url,
|
|
1876
|
+
warnings: request.warnings,
|
|
1877
|
+
...body
|
|
1878
|
+
};
|
|
1879
|
+
call.check?.(answer);
|
|
1880
|
+
return answer;
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
/**
|
|
1885
|
+
* Reads a 2xx body. A JSON content type is parsed and unwrapped; anything
|
|
1886
|
+
* else is the model's own bytes (PNG, JPEG, MP3). A JSON content type with a
|
|
1887
|
+
* body that will not parse is a provider failure, not a caller bug, so it
|
|
1888
|
+
* comes back as a {@link CloudflareAIError} rather than a bare `SyntaxError`.
|
|
1889
|
+
*/
|
|
1890
|
+
async function _decode(response, modelId, input, mediaType) {
|
|
1891
|
+
if (!mediaType.includes("json")) return {
|
|
1892
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
1893
|
+
json: void 0
|
|
1894
|
+
};
|
|
1895
|
+
let json;
|
|
1896
|
+
try {
|
|
1897
|
+
json = await response.json();
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
throw new CloudflareAIError({
|
|
1900
|
+
cause: error,
|
|
1901
|
+
code: "provider-error",
|
|
1902
|
+
isRetryable: false,
|
|
1903
|
+
message: `The model answered ${response.status} with a body that is not JSON.`,
|
|
1904
|
+
model: modelId,
|
|
1905
|
+
requestBodyValues: input,
|
|
1906
|
+
status: response.status,
|
|
1907
|
+
url: _classPrivateFieldGet2(_config, this).transport.url
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
bytes: void 0,
|
|
1912
|
+
json: unwrapEnvelope(json, {
|
|
1913
|
+
model: modelId,
|
|
1914
|
+
status: response.status,
|
|
1915
|
+
url: _classPrivateFieldGet2(_config, this).transport.url
|
|
1916
|
+
})
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
//#endregion
|
|
1920
|
+
//#region src/models/ai-sdk/models/image.ts
|
|
1921
|
+
function imageOptions(providerOptions) {
|
|
1922
|
+
const raw = record(providerOptions?.["cloudflare"]) ?? {};
|
|
1923
|
+
const negativePrompt = text(raw.negativePrompt);
|
|
1924
|
+
return {
|
|
1925
|
+
...count(raw.steps) === void 0 ? {} : { steps: count(raw.steps) },
|
|
1926
|
+
...count(raw.guidance) === void 0 ? {} : { guidance: count(raw.guidance) },
|
|
1927
|
+
...count(raw.strength) === void 0 ? {} : { strength: count(raw.strength) },
|
|
1928
|
+
...negativePrompt === void 0 ? {} : { negativePrompt }
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
/** `1024x768` → `{ width: 1024, height: 768 }`. */
|
|
1932
|
+
function parseSize(size) {
|
|
1933
|
+
const [width, height] = size.split("x").map(Number);
|
|
1934
|
+
return Number.isFinite(width) && Number.isFinite(height) ? {
|
|
1935
|
+
height,
|
|
1936
|
+
width
|
|
1937
|
+
} : void 0;
|
|
1938
|
+
}
|
|
1939
|
+
/** The base64 bytes of a file the caller passed for img2img or masking. */
|
|
1940
|
+
function fileData(file) {
|
|
1941
|
+
if (file.type === "file") return toBase64(file.data);
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Builds the run-path body. The knobs and the flux-1 subset rule live in
|
|
1945
|
+
* `core/images.ts`, shared with the pi-ai module so the two cannot drift; the
|
|
1946
|
+
* AI SDK call options and the `files`/`mask` byte handling are mapped here.
|
|
1947
|
+
*/
|
|
1948
|
+
function buildImageRequest(modelId, options) {
|
|
1949
|
+
const warnings = [];
|
|
1950
|
+
const extra = imageOptions(options.providerOptions);
|
|
1951
|
+
if (options.prompt === void 0) warnings.push({
|
|
1952
|
+
message: "Cloudflare image models require a prompt; sent an empty one.",
|
|
1953
|
+
type: "other"
|
|
1954
|
+
});
|
|
1955
|
+
if (options.aspectRatio !== void 0) warnings.push(unsupported("aspectRatio", "Use `size` instead: Workers AI takes pixels."));
|
|
1956
|
+
let size;
|
|
1957
|
+
if (options.size !== void 0) {
|
|
1958
|
+
size = parseSize(options.size);
|
|
1959
|
+
if (size === void 0) warnings.push(unsupported("size", `Could not read "${options.size}".`));
|
|
1960
|
+
}
|
|
1961
|
+
const built = buildImageInput(modelId, options.prompt ?? "", {
|
|
1962
|
+
...size === void 0 ? {} : {
|
|
1963
|
+
height: size.height,
|
|
1964
|
+
width: size.width
|
|
1965
|
+
},
|
|
1966
|
+
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
1967
|
+
...extra.steps === void 0 ? {} : { steps: extra.steps },
|
|
1968
|
+
...extra.guidance === void 0 ? {} : { guidance: extra.guidance },
|
|
1969
|
+
...extra.strength === void 0 ? {} : { strength: extra.strength },
|
|
1970
|
+
...extra.negativePrompt === void 0 ? {} : { negativePrompt: extra.negativePrompt },
|
|
1971
|
+
...options.mask === void 0 ? {} : { mask: true },
|
|
1972
|
+
...options.files?.[0] === void 0 ? {} : { sourceImage: true }
|
|
1973
|
+
});
|
|
1974
|
+
const input = built.input;
|
|
1975
|
+
for (const warning of built.warnings) warnings.push(unsupported(warning.feature, warning.message));
|
|
1976
|
+
if (built.minimal) return {
|
|
1977
|
+
input,
|
|
1978
|
+
warnings
|
|
1979
|
+
};
|
|
1980
|
+
const source = options.files?.[0];
|
|
1981
|
+
if (source !== void 0) {
|
|
1982
|
+
const data = fileData(source);
|
|
1983
|
+
if (data === void 0) warnings.push(unsupported("files", "Image URLs are not supported; send the bytes."));
|
|
1984
|
+
else input.image_b64 = data;
|
|
1985
|
+
if ((options.files?.length ?? 0) > 1) warnings.push(unsupported("files", "Only the first image is used for img2img."));
|
|
1986
|
+
}
|
|
1987
|
+
if (options.mask !== void 0) {
|
|
1988
|
+
const data = fileData(options.mask);
|
|
1989
|
+
if (data === void 0) warnings.push(unsupported("mask", "Mask URLs are not supported; send the bytes."));
|
|
1990
|
+
else input.mask = Array.from(toBytes(data));
|
|
1991
|
+
}
|
|
1992
|
+
return {
|
|
1993
|
+
input,
|
|
1994
|
+
warnings
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
function readImage(answer) {
|
|
1998
|
+
if (answer.bytes !== void 0) return {
|
|
1999
|
+
base64: void 0,
|
|
2000
|
+
bytes: answer.bytes,
|
|
2001
|
+
mediaType: answer.mediaType === "" ? "image/png" : answer.mediaType
|
|
2002
|
+
};
|
|
2003
|
+
const base64 = text((record(answer.json) ?? {}).image);
|
|
2004
|
+
if (base64 === void 0) throw new CloudflareAIError({
|
|
2005
|
+
code: "provider-error",
|
|
2006
|
+
data: answer.json,
|
|
2007
|
+
isRetryable: false,
|
|
2008
|
+
message: "The image model answered JSON without an `image` field.",
|
|
2009
|
+
model: answer.modelId,
|
|
2010
|
+
requestBodyValues: answer.input,
|
|
2011
|
+
url: answer.url
|
|
2012
|
+
});
|
|
2013
|
+
return {
|
|
2014
|
+
base64,
|
|
2015
|
+
bytes: void 0,
|
|
2016
|
+
mediaType: imageMediaTypeOf(base64)
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
function imageUsage(json) {
|
|
2020
|
+
const usage = record(record(json)?.usage);
|
|
2021
|
+
if (usage === void 0) return void 0;
|
|
2022
|
+
return {
|
|
2023
|
+
inputTokens: count(usage.prompt_tokens),
|
|
2024
|
+
outputTokens: count(usage.completion_tokens),
|
|
2025
|
+
totalTokens: count(usage.total_tokens)
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* An `ImageModelV4` over the Cloudflare text-to-image catalog.
|
|
2030
|
+
*
|
|
2031
|
+
* The run path generates one image per call, so `maxImagesPerCall` is 1 and
|
|
2032
|
+
* `generateImage` fans a larger `n` out itself; a direct `doGenerate` with
|
|
2033
|
+
* `n > 1` is fanned out here instead.
|
|
2034
|
+
*
|
|
2035
|
+
* @experimental This surface is experimental and may change.
|
|
2036
|
+
*/
|
|
2037
|
+
var CloudflareImageModel = class extends CloudflareModalityModel {
|
|
2038
|
+
constructor(modelId, options, config) {
|
|
2039
|
+
super(modelId, options, config);
|
|
2040
|
+
this.maxImagesPerCall = 1;
|
|
2041
|
+
}
|
|
2042
|
+
async doGenerate(options) {
|
|
2043
|
+
const wanted = Math.max(1, options.n);
|
|
2044
|
+
const answers = await Promise.all(Array.from({ length: wanted }, () => this.send({
|
|
2045
|
+
abortSignal: options.abortSignal,
|
|
2046
|
+
build: (modelId) => buildImageRequest(modelId, options),
|
|
2047
|
+
check: (answer) => {
|
|
2048
|
+
readImage(answer);
|
|
2049
|
+
},
|
|
2050
|
+
headers: options.headers,
|
|
2051
|
+
providerOptions: options.providerOptions
|
|
2052
|
+
})));
|
|
2053
|
+
const first = answers[0];
|
|
2054
|
+
const generated = answers.map(readImage);
|
|
2055
|
+
const images = generated.some((image) => image.base64 !== void 0) ? generated.map((image) => image.base64 ?? toBase64(image.bytes)) : generated.map((image) => image.bytes);
|
|
2056
|
+
const usage = imageUsage(first.json);
|
|
2057
|
+
return {
|
|
2058
|
+
images,
|
|
2059
|
+
providerMetadata: { [PROVIDER_OPTIONS_KEY]: {
|
|
2060
|
+
...first.providerMetadata[PROVIDER_OPTIONS_KEY],
|
|
2061
|
+
images: generated.map((image, index) => ({
|
|
2062
|
+
...answers[index]?.providerMetadata[PROVIDER_OPTIONS_KEY],
|
|
2063
|
+
mediaType: image.mediaType
|
|
2064
|
+
}))
|
|
2065
|
+
} },
|
|
2066
|
+
response: {
|
|
2067
|
+
headers: first.headers,
|
|
2068
|
+
modelId: first.modelId,
|
|
2069
|
+
timestamp: first.timestamp
|
|
2070
|
+
},
|
|
2071
|
+
...usage === void 0 ? {} : { usage },
|
|
2072
|
+
warnings: first.warnings
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
};
|
|
2076
|
+
//#endregion
|
|
2077
|
+
//#region src/models/ai-sdk/models/reranking.ts
|
|
2078
|
+
/** Builds the run-path body for one reranking leg. */
|
|
2079
|
+
function buildRerankingRequest(options) {
|
|
2080
|
+
const warnings = [];
|
|
2081
|
+
const documents = options.documents.type === "text" ? options.documents.values : options.documents.values.map((value) => JSON.stringify(value));
|
|
2082
|
+
if (options.documents.type === "object") warnings.push({
|
|
2083
|
+
details: "Object documents are sent as JSON text.",
|
|
2084
|
+
feature: "documents",
|
|
2085
|
+
type: "compatibility"
|
|
2086
|
+
});
|
|
2087
|
+
const input = {
|
|
2088
|
+
contexts: documents.map((value) => ({ text: value })),
|
|
2089
|
+
query: options.query
|
|
2090
|
+
};
|
|
2091
|
+
if (options.topN !== void 0) input.top_k = options.topN;
|
|
2092
|
+
return {
|
|
2093
|
+
input,
|
|
2094
|
+
warnings
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* A `RerankingModelV4` over the Workers AI reranker catalog.
|
|
2099
|
+
*
|
|
2100
|
+
* @experimental This surface is experimental and may change.
|
|
2101
|
+
*/
|
|
2102
|
+
var CloudflareRerankingModel = class extends CloudflareModalityModel {
|
|
2103
|
+
constructor(modelId, options, config) {
|
|
2104
|
+
super(modelId, options, config);
|
|
2105
|
+
}
|
|
2106
|
+
async doRerank(options) {
|
|
2107
|
+
const answer = await this.send({
|
|
2108
|
+
abortSignal: options.abortSignal,
|
|
2109
|
+
build: () => buildRerankingRequest(options),
|
|
2110
|
+
headers: options.headers,
|
|
2111
|
+
providerOptions: options.providerOptions
|
|
2112
|
+
});
|
|
2113
|
+
const ranking = (array((record(answer.json) ?? {}).response) ?? []).flatMap((entry) => {
|
|
2114
|
+
const row = record(entry);
|
|
2115
|
+
const index = count(row?.id);
|
|
2116
|
+
const relevanceScore = count(row?.score);
|
|
2117
|
+
if (index === void 0 || relevanceScore === void 0) return [];
|
|
2118
|
+
return [{
|
|
2119
|
+
index,
|
|
2120
|
+
relevanceScore
|
|
2121
|
+
}];
|
|
2122
|
+
});
|
|
2123
|
+
ranking.sort((left, right) => right.relevanceScore - left.relevanceScore);
|
|
2124
|
+
return {
|
|
2125
|
+
providerMetadata: answer.providerMetadata,
|
|
2126
|
+
ranking,
|
|
2127
|
+
response: {
|
|
2128
|
+
body: answer.json,
|
|
2129
|
+
headers: answer.headers,
|
|
2130
|
+
modelId: answer.modelId,
|
|
2131
|
+
timestamp: answer.timestamp
|
|
2132
|
+
},
|
|
2133
|
+
warnings: answer.warnings
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
//#endregion
|
|
2138
|
+
//#region src/models/ai-sdk/models/speech.ts
|
|
2139
|
+
/**
|
|
2140
|
+
* MeloTTS is the odd one out: the prompt key is `prompt`, the language key is
|
|
2141
|
+
* `lang`, and there is no voice.
|
|
2142
|
+
*
|
|
2143
|
+
* @see https://developers.cloudflare.com/workers-ai/models/melotts/
|
|
2144
|
+
*/
|
|
2145
|
+
const MELOTTS = "@cf/myshell-ai/melotts";
|
|
2146
|
+
/**
|
|
2147
|
+
* Output encodings Aura accepts. `outputFormat` maps onto this; anything else
|
|
2148
|
+
* is warned about rather than sent, because the run path validates the enum.
|
|
2149
|
+
*
|
|
2150
|
+
* @see https://developers.cloudflare.com/workers-ai/models/aura-1/
|
|
2151
|
+
*/
|
|
2152
|
+
const AURA_ENCODINGS = /* @__PURE__ */ new Set([
|
|
2153
|
+
"linear16",
|
|
2154
|
+
"flac",
|
|
2155
|
+
"mulaw",
|
|
2156
|
+
"alaw",
|
|
2157
|
+
"mp3",
|
|
2158
|
+
"opus",
|
|
2159
|
+
"aac"
|
|
2160
|
+
]);
|
|
2161
|
+
/** Builds the run-path body for one speech leg. */
|
|
2162
|
+
function buildSpeechRequest(modelId, options) {
|
|
2163
|
+
const warnings = [];
|
|
2164
|
+
if (options.speed !== void 0) warnings.push(unsupported("speed", "Workers AI speech models do not take a speed."));
|
|
2165
|
+
if (options.instructions !== void 0) warnings.push(unsupported("instructions", "Workers AI speech models take text, not instructions."));
|
|
2166
|
+
if (modelId === MELOTTS) {
|
|
2167
|
+
const input = { prompt: options.text };
|
|
2168
|
+
if (options.language !== void 0) input.lang = options.language;
|
|
2169
|
+
if (options.voice !== void 0) warnings.push(unsupported("voice", `${MELOTTS} has a single voice.`));
|
|
2170
|
+
if (options.outputFormat !== void 0) warnings.push(unsupported("outputFormat", `${MELOTTS} picks its own audio format.`));
|
|
2171
|
+
return {
|
|
2172
|
+
input,
|
|
2173
|
+
warnings
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
const input = { text: options.text };
|
|
2177
|
+
if (options.voice !== void 0) input.speaker = options.voice;
|
|
2178
|
+
if (options.outputFormat !== void 0) if (AURA_ENCODINGS.has(options.outputFormat)) input.encoding = options.outputFormat;
|
|
2179
|
+
else warnings.push(unsupported("outputFormat", `Expected one of ${[...AURA_ENCODINGS].join(", ")}.`));
|
|
2180
|
+
if (options.language !== void 0) warnings.push(unsupported("language", "Aura picks its language from the model id, e.g. @cf/deepgram/aura-2-es."));
|
|
2181
|
+
return {
|
|
2182
|
+
input,
|
|
2183
|
+
warnings
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
/**
|
|
2187
|
+
* The audio in one answer. Bytes stay bytes and base64 stays base64: the AI
|
|
2188
|
+
* SDK converts either way on demand, and converting here would only lose
|
|
2189
|
+
* fidelity.
|
|
2190
|
+
*/
|
|
2191
|
+
function readAudio(answer) {
|
|
2192
|
+
if (answer.bytes !== void 0) return answer.bytes;
|
|
2193
|
+
const encoded = text((record(answer.json) ?? {}).audio);
|
|
2194
|
+
if (encoded === void 0) throw new CloudflareAIError({
|
|
2195
|
+
code: "provider-error",
|
|
2196
|
+
data: answer.json,
|
|
2197
|
+
isRetryable: false,
|
|
2198
|
+
message: "The speech model answered JSON without an `audio` field.",
|
|
2199
|
+
model: answer.modelId,
|
|
2200
|
+
requestBodyValues: answer.input,
|
|
2201
|
+
url: answer.url
|
|
2202
|
+
});
|
|
2203
|
+
return encoded;
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* A `SpeechModelV4` over the Workers AI text-to-speech catalog.
|
|
2207
|
+
*
|
|
2208
|
+
* @experimental This surface is experimental and may change.
|
|
2209
|
+
*/
|
|
2210
|
+
var CloudflareSpeechModel = class extends CloudflareModalityModel {
|
|
2211
|
+
constructor(modelId, options, config) {
|
|
2212
|
+
super(modelId, options, config);
|
|
2213
|
+
}
|
|
2214
|
+
async doGenerate(options) {
|
|
2215
|
+
const answer = await this.send({
|
|
2216
|
+
abortSignal: options.abortSignal,
|
|
2217
|
+
build: (modelId) => buildSpeechRequest(modelId, options),
|
|
2218
|
+
check: (answer) => {
|
|
2219
|
+
readAudio(answer);
|
|
2220
|
+
},
|
|
2221
|
+
headers: options.headers,
|
|
2222
|
+
providerOptions: options.providerOptions
|
|
2223
|
+
});
|
|
2224
|
+
return {
|
|
2225
|
+
audio: readAudio(answer),
|
|
2226
|
+
providerMetadata: answer.providerMetadata,
|
|
2227
|
+
request: { body: answer.input },
|
|
2228
|
+
response: {
|
|
2229
|
+
headers: answer.headers,
|
|
2230
|
+
modelId: answer.modelId,
|
|
2231
|
+
timestamp: answer.timestamp
|
|
2232
|
+
},
|
|
2233
|
+
warnings: answer.warnings
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
//#endregion
|
|
2238
|
+
//#region src/models/ai-sdk/models/transcription.ts
|
|
2239
|
+
/**
|
|
2240
|
+
* Whisper ids that take the audio as an unsigned-byte array rather than as
|
|
2241
|
+
* base64.
|
|
2242
|
+
*
|
|
2243
|
+
* @see https://developers.cloudflare.com/workers-ai/models/whisper/
|
|
2244
|
+
*/
|
|
2245
|
+
const BYTE_ARRAY_AUDIO = /* @__PURE__ */ new Set(["@cf/openai/whisper", "@cf/openai/whisper-tiny-en"]);
|
|
2246
|
+
/**
|
|
2247
|
+
* Deepgram's recognition models declare `audio: { body: object, contentType }`.
|
|
2248
|
+
* Every documented encoding of that shape answered `400 required properties at
|
|
2249
|
+
* '/audio' are 'body,contentType'` on the live run path, because `body` has to
|
|
2250
|
+
* be the audio object itself and JSON cannot carry it. Refuse early rather
|
|
2251
|
+
* than send a body known to fail.
|
|
2252
|
+
*/
|
|
2253
|
+
const UNSUPPORTED = /* @__PURE__ */ new Set(["@cf/deepgram/nova-3", "@cf/deepgram/flux"]);
|
|
2254
|
+
function transcriptionOptions(providerOptions) {
|
|
2255
|
+
const raw = record(providerOptions?.["cloudflare"]) ?? {};
|
|
2256
|
+
const vadFilter = raw.vadFilter;
|
|
2257
|
+
return {
|
|
2258
|
+
...text(raw.language) === void 0 ? {} : { language: text(raw.language) },
|
|
2259
|
+
...text(raw.task) === void 0 ? {} : { task: text(raw.task) },
|
|
2260
|
+
...text(raw.initialPrompt) === void 0 ? {} : { initialPrompt: text(raw.initialPrompt) },
|
|
2261
|
+
...text(raw.prefix) === void 0 ? {} : { prefix: text(raw.prefix) },
|
|
2262
|
+
...typeof vadFilter === "boolean" ? { vadFilter } : {}
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
/** Builds the run-path body for one whisper leg. */
|
|
2266
|
+
function buildTranscriptionRequest(modelId, options) {
|
|
2267
|
+
const warnings = [];
|
|
2268
|
+
const extra = transcriptionOptions(options.providerOptions);
|
|
2269
|
+
const input = BYTE_ARRAY_AUDIO.has(modelId) ? { audio: Array.from(toBytes(options.audio)) } : { audio: toBase64(options.audio) };
|
|
2270
|
+
if (extra.language !== void 0) input.language = extra.language;
|
|
2271
|
+
if (extra.task !== void 0) input.task = extra.task;
|
|
2272
|
+
if (extra.initialPrompt !== void 0) input.initial_prompt = extra.initialPrompt;
|
|
2273
|
+
if (extra.prefix !== void 0) input.prefix = extra.prefix;
|
|
2274
|
+
if (extra.vadFilter !== void 0) input.vad_filter = extra.vadFilter;
|
|
2275
|
+
return {
|
|
2276
|
+
input,
|
|
2277
|
+
warnings
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Reads segments out of a whisper answer. `segments` is what the large models
|
|
2282
|
+
* send; the smaller ones send only `words`, whose timings are the best segment
|
|
2283
|
+
* boundaries available.
|
|
2284
|
+
*/
|
|
2285
|
+
function readSegments(body) {
|
|
2286
|
+
const segments = array(body.segments);
|
|
2287
|
+
if (segments !== void 0) return segments.flatMap((entry) => {
|
|
2288
|
+
const segment = record(entry);
|
|
2289
|
+
const value = text(segment?.text);
|
|
2290
|
+
if (segment === void 0 || value === void 0) return [];
|
|
2291
|
+
return [{
|
|
2292
|
+
endSecond: count(segment.end) ?? 0,
|
|
2293
|
+
startSecond: count(segment.start) ?? 0,
|
|
2294
|
+
text: value
|
|
2295
|
+
}];
|
|
2296
|
+
});
|
|
2297
|
+
return (array(body.words) ?? []).flatMap((entry) => {
|
|
2298
|
+
const word = record(entry);
|
|
2299
|
+
const value = text(word?.word);
|
|
2300
|
+
if (word === void 0 || value === void 0) return [];
|
|
2301
|
+
return [{
|
|
2302
|
+
endSecond: count(word.end) ?? 0,
|
|
2303
|
+
startSecond: count(word.start) ?? 0,
|
|
2304
|
+
text: value
|
|
2305
|
+
}];
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* A `TranscriptionModelV4` over the Workers AI speech-recognition catalog.
|
|
2310
|
+
*
|
|
2311
|
+
* @experimental This surface is experimental and may change.
|
|
2312
|
+
*/
|
|
2313
|
+
var CloudflareTranscriptionModel = class extends CloudflareModalityModel {
|
|
2314
|
+
constructor(modelId, options, config) {
|
|
2315
|
+
super(modelId, options, config);
|
|
2316
|
+
}
|
|
2317
|
+
async doGenerate(options) {
|
|
2318
|
+
const answer = await this.send({
|
|
2319
|
+
abortSignal: options.abortSignal,
|
|
2320
|
+
build: (modelId) => {
|
|
2321
|
+
if (UNSUPPORTED.has(modelId)) throw new CloudflareAIError({
|
|
2322
|
+
code: "bad-request",
|
|
2323
|
+
isRetryable: false,
|
|
2324
|
+
message: `${modelId} takes the audio as an object body, which the JSON run path cannot carry. Use @cf/openai/whisper-large-v3-turbo.`,
|
|
2325
|
+
model: modelId,
|
|
2326
|
+
requestBodyValues: void 0,
|
|
2327
|
+
url: this.endpoint
|
|
2328
|
+
});
|
|
2329
|
+
return buildTranscriptionRequest(modelId, options);
|
|
2330
|
+
},
|
|
2331
|
+
headers: options.headers,
|
|
2332
|
+
providerOptions: options.providerOptions
|
|
2333
|
+
});
|
|
2334
|
+
const body = record(answer.json) ?? {};
|
|
2335
|
+
const info = record(body.transcription_info);
|
|
2336
|
+
const language = text(info?.language);
|
|
2337
|
+
return {
|
|
2338
|
+
durationInSeconds: count(info?.duration),
|
|
2339
|
+
language,
|
|
2340
|
+
providerMetadata: answer.providerMetadata,
|
|
2341
|
+
request: { body: JSON.stringify(answer.input) },
|
|
2342
|
+
response: {
|
|
2343
|
+
body: answer.json,
|
|
2344
|
+
headers: answer.headers,
|
|
2345
|
+
modelId: answer.modelId,
|
|
2346
|
+
timestamp: answer.timestamp
|
|
2347
|
+
},
|
|
2348
|
+
segments: readSegments(body),
|
|
2349
|
+
text: text(body.text) ?? "",
|
|
2350
|
+
warnings: answer.warnings
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
//#endregion
|
|
2355
|
+
//#region src/models/ai-sdk/index.ts
|
|
2356
|
+
/** Whether a value is a model object rather than a model id. */
|
|
2357
|
+
function isModel(value) {
|
|
2358
|
+
return typeof value === "object" && value !== null;
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Creates a provider over Workers AI, and over every vendor AI Gateway can
|
|
2362
|
+
* reach.
|
|
2363
|
+
*
|
|
2364
|
+
* The Workers AI binding is the only way in — Workers AI models go through
|
|
2365
|
+
* `env.AI.run`, vendor models through `env.AI.gateway(id).run`, and there is
|
|
2366
|
+
* no HTTP transport and no API token. Gateway options are flat (`id`,
|
|
2367
|
+
* `cacheTtl`, …) or nested under `gateway`; per-model options and per-call
|
|
2368
|
+
* `providerOptions.cloudflare` override them in that order.
|
|
2369
|
+
*
|
|
2370
|
+
* ```ts
|
|
2371
|
+
* const ai = createAI({ binding: env.AI, id: "prod", cacheTtl: 60 });
|
|
2372
|
+
* const anthropic = createAnthropic({ apiKey: "cloudflare" });
|
|
2373
|
+
*
|
|
2374
|
+
* ai("@cf/zai-org/glm-4.7-flash");
|
|
2375
|
+
* ai(anthropic("claude-opus-4-8"), { fallback: [ai("@cf/zai-org/glm-4.7-flash")] });
|
|
2376
|
+
* ai.embedding("@cf/baai/bge-base-en-v1.5");
|
|
2377
|
+
* ```
|
|
2378
|
+
*
|
|
2379
|
+
* @experimental This surface is experimental and may change.
|
|
2380
|
+
*/
|
|
2381
|
+
function createAI(settings) {
|
|
2382
|
+
const config = {
|
|
2383
|
+
providerGateway: settings,
|
|
2384
|
+
transport: createTransport(settings)
|
|
2385
|
+
};
|
|
2386
|
+
/**
|
|
2387
|
+
* Fallback legs are normalized once, here: a model object becomes a routed
|
|
2388
|
+
* model, so every leg a model runs is already this provider's.
|
|
2389
|
+
*
|
|
2390
|
+
* A leg inherits the chain's *gateway* options and nothing else. The rest of
|
|
2391
|
+
* the bag — `reasoningEffort`, `chatTemplateKwargs`, `headers`,
|
|
2392
|
+
* `sessionAffinity` — was written for the model it was written on, and a leg
|
|
2393
|
+
* that was configured with its own keeps every one of them.
|
|
2394
|
+
*/
|
|
2395
|
+
const normalize = (options) => {
|
|
2396
|
+
if (options?.fallback === void 0) return options;
|
|
2397
|
+
const inherited = gatewayLayerOf(options);
|
|
2398
|
+
return {
|
|
2399
|
+
...options,
|
|
2400
|
+
fallback: options.fallback.map((leg) => typeof leg === "string" ? requireWorkersAIId(leg) : languageModel(leg, inherited))
|
|
2401
|
+
};
|
|
2402
|
+
};
|
|
2403
|
+
function languageModel(model, options) {
|
|
2404
|
+
const resolved = normalize(options);
|
|
2405
|
+
if (isModel(model)) {
|
|
2406
|
+
if (model instanceof CloudflareLanguageModel) {
|
|
2407
|
+
const merged = normalize(mergeModelOptions(optionsOf(model), resolved));
|
|
2408
|
+
return new CloudflareLanguageModel(model.modelId, merged, config);
|
|
2409
|
+
}
|
|
2410
|
+
if (model instanceof GatewayLanguageModel) {
|
|
2411
|
+
const merged = normalize(mergeModelOptions(optionsOf(model), resolved));
|
|
2412
|
+
return new GatewayLanguageModel(model.model, merged, config);
|
|
2413
|
+
}
|
|
2414
|
+
return new GatewayLanguageModel(model, resolved, config);
|
|
2415
|
+
}
|
|
2416
|
+
return new CloudflareLanguageModel(requireWorkersAIId(model), resolved, config);
|
|
2417
|
+
}
|
|
2418
|
+
const embedding = (modelId, options) => new CloudflareEmbeddingModel(modelId, options, config);
|
|
2419
|
+
const image = (modelId, options) => new CloudflareImageModel(modelId, options, config);
|
|
2420
|
+
const transcription = (modelId, options) => new CloudflareTranscriptionModel(modelId, options, config);
|
|
2421
|
+
const speech = (modelId, options) => new CloudflareSpeechModel(modelId, options, config);
|
|
2422
|
+
const reranking = (modelId, options) => new CloudflareRerankingModel(modelId, options, config);
|
|
2423
|
+
const provider = (model, options) => languageModel(model, options);
|
|
2424
|
+
return Object.assign(provider, {
|
|
2425
|
+
embedding,
|
|
2426
|
+
embeddingModel: embedding,
|
|
2427
|
+
image,
|
|
2428
|
+
imageModel: image,
|
|
2429
|
+
languageModel,
|
|
2430
|
+
reranking,
|
|
2431
|
+
rerankingModel: reranking,
|
|
2432
|
+
routed: (model, options) => routed(model, options, config),
|
|
2433
|
+
specificationVersion: "v4",
|
|
2434
|
+
speech,
|
|
2435
|
+
speechModel: speech,
|
|
2436
|
+
textEmbeddingModel: embedding,
|
|
2437
|
+
transcription,
|
|
2438
|
+
transcriptionModel: transcription
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
//#endregion
|
|
2442
|
+
export { GatewayLanguageModel as n, CloudflareAIError as r, createAI as t };
|
|
2443
|
+
|
|
2444
|
+
//# sourceMappingURL=ai-sdk-OoGkFy3s.js.map
|