workers-ai-provider 3.3.0 → 4.0.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 -6
- package/dist/anthropic.d.mts +1 -1
- package/dist/{gateway-delegate-BAALxuBO.d.mts → gateway-delegate-CAX7trdL.d.mts} +21 -10
- package/dist/{gateway-provider-BjPLZLob.mjs → gateway-provider-BIZ2bimW.mjs} +4 -4
- package/dist/gateway-provider-BIZ2bimW.mjs.map +1 -0
- package/dist/gateway-provider.mjs +1 -1
- package/dist/google.d.mts +1 -1
- package/dist/google.mjs +2 -2
- package/dist/google.mjs.map +1 -1
- package/dist/index.d.mts +33 -28
- package/dist/index.mjs +106 -44
- package/dist/index.mjs.map +1 -1
- package/dist/openai.d.mts +4 -3
- package/dist/openai.mjs +3 -2
- package/dist/openai.mjs.map +1 -1
- package/package.json +11 -11
- package/src/aisearch-chat-language-model.ts +10 -10
- package/src/client-fallback.ts +11 -11
- package/src/convert-to-workersai-chat-messages.ts +69 -30
- package/src/gateway-delegate.ts +40 -15
- package/src/google.ts +2 -4
- package/src/index.ts +45 -8
- package/src/map-workersai-finish-reason.ts +2 -2
- package/src/map-workersai-usage.ts +4 -4
- package/src/openai.ts +3 -2
- package/src/streaming.ts +18 -18
- package/src/utils.ts +8 -8
- package/src/workersai-chat-language-model.ts +64 -14
- package/src/workersai-embedding-model.ts +6 -6
- package/src/workersai-image-model.ts +6 -6
- package/src/workersai-reranking-model.ts +9 -9
- package/src/workersai-speech-model.ts +7 -7
- package/src/workersai-transcription-model.ts +11 -11
- package/dist/gateway-provider-BjPLZLob.mjs.map +0 -1
package/dist/openai.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ProviderPlugin } from "./gateway-delegate-
|
|
1
|
+
import { a as ProviderPlugin } from "./gateway-delegate-CAX7trdL.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/openai.d.ts
|
|
4
4
|
/**
|
|
@@ -11,8 +11,9 @@ import { a as ProviderPlugin } from "./gateway-delegate-BAALxuBO.mjs";
|
|
|
11
11
|
*
|
|
12
12
|
* Requires `@ai-sdk/openai` (an optional peer dependency — install it yourself).
|
|
13
13
|
*
|
|
14
|
-
* Uses `.chat()` (Chat Completions) deliberately:
|
|
15
|
-
* defaults to the Responses API, which the AI Gateway run catalog
|
|
14
|
+
* Uses `.chat()` (Chat Completions) deliberately: the bare `openai()` call
|
|
15
|
+
* (AI SDK v6+) defaults to the Responses API, which the AI Gateway run catalog
|
|
16
|
+
* does not serve.
|
|
16
17
|
*/
|
|
17
18
|
declare const openai: ProviderPlugin;
|
|
18
19
|
//#endregion
|
package/dist/openai.mjs
CHANGED
|
@@ -10,8 +10,9 @@ import { createOpenAI } from "@ai-sdk/openai";
|
|
|
10
10
|
*
|
|
11
11
|
* Requires `@ai-sdk/openai` (an optional peer dependency — install it yourself).
|
|
12
12
|
*
|
|
13
|
-
* Uses `.chat()` (Chat Completions) deliberately:
|
|
14
|
-
* defaults to the Responses API, which the AI Gateway run catalog
|
|
13
|
+
* Uses `.chat()` (Chat Completions) deliberately: the bare `openai()` call
|
|
14
|
+
* (AI SDK v6+) defaults to the Responses API, which the AI Gateway run catalog
|
|
15
|
+
* does not serve.
|
|
15
16
|
*/
|
|
16
17
|
const openai = {
|
|
17
18
|
wireFormat: "openai",
|
package/dist/openai.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.mjs","names":[],"sources":["../src/openai.ts"],"sourcesContent":["import { createOpenAI } from \"@ai-sdk/openai\";\nimport type { ProviderPlugin } from \"./gateway-delegate\";\n\n/**\n * OpenAI-wire provider plugin for the gateway delegate. Pass to\n * `createGatewayDelegate({ providers: [openai] })` to handle every\n * OpenAI-compatible provider in one go — `openai/…`, plus the OpenAI-compatible\n * long tail (`deepseek/…`, `xai/…`, `groq/…`, `mistral/…`, `perplexity/…`,\n * `openrouter/…`, `cohere/…`, …). The registry routes each slug to its gateway\n * provider id; this plugin only supplies the response parser.\n *\n * Requires `@ai-sdk/openai` (an optional peer dependency — install it yourself).\n *\n * Uses `.chat()` (Chat Completions) deliberately:
|
|
1
|
+
{"version":3,"file":"openai.mjs","names":[],"sources":["../src/openai.ts"],"sourcesContent":["import { createOpenAI } from \"@ai-sdk/openai\";\nimport type { ProviderPlugin } from \"./gateway-delegate\";\n\n/**\n * OpenAI-wire provider plugin for the gateway delegate. Pass to\n * `createGatewayDelegate({ providers: [openai] })` to handle every\n * OpenAI-compatible provider in one go — `openai/…`, plus the OpenAI-compatible\n * long tail (`deepseek/…`, `xai/…`, `groq/…`, `mistral/…`, `perplexity/…`,\n * `openrouter/…`, `cohere/…`, …). The registry routes each slug to its gateway\n * provider id; this plugin only supplies the response parser.\n *\n * Requires `@ai-sdk/openai` (an optional peer dependency — install it yourself).\n *\n * Uses `.chat()` (Chat Completions) deliberately: the bare `openai()` call\n * (AI SDK v6+) defaults to the Responses API, which the AI Gateway run catalog\n * does not serve.\n */\nexport const openai: ProviderPlugin = {\n\twireFormat: \"openai\",\n\tcreate: ({ modelId, fetch, baseURL }) =>\n\t\t// apiKey is a placeholder — the gateway handles auth (unified billing / BYOK)\n\t\t// and the delegate strips the Authorization header on the gateway path.\n\t\t// baseURL (set by the registry for non-OpenAI openai-wire providers) makes\n\t\t// the generated URL host-strip to the right gateway-native endpoint.\n\t\tcreateOpenAI({ apiKey: \"unused\", fetch, ...(baseURL ? { baseURL } : {}) }).chat(modelId),\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAa,SAAyB;CACrC,YAAY;CACZ,SAAS,EAAE,SAAS,OAAO,cAK1B,aAAa;EAAE,QAAQ;EAAU;EAAO,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;CAAG,CAAC,CAAC,CAAC,KAAK,OAAO;AACzF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workers-ai-provider",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Workers AI Provider for the vercel AI SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -53,21 +53,21 @@
|
|
|
53
53
|
"./package.json": "./package.json"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@ai-sdk/anthropic": "^
|
|
57
|
-
"@ai-sdk/google": "^
|
|
58
|
-
"@ai-sdk/openai": "^
|
|
59
|
-
"@ai-sdk/provider": "^
|
|
56
|
+
"@ai-sdk/anthropic": "^4.0.5",
|
|
57
|
+
"@ai-sdk/google": "^4.0.6",
|
|
58
|
+
"@ai-sdk/openai": "^4.0.5",
|
|
59
|
+
"@ai-sdk/provider": "^4.0.1",
|
|
60
60
|
"@cloudflare/workers-types": "^4.20260628.1",
|
|
61
|
-
"ai": "^
|
|
61
|
+
"ai": "^7.0.11",
|
|
62
62
|
"zod": "^4.4.3",
|
|
63
63
|
"@cloudflare/gateway-core": "0.0.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@ai-sdk/anthropic": "^
|
|
67
|
-
"@ai-sdk/google": "^
|
|
68
|
-
"@ai-sdk/openai": "^
|
|
69
|
-
"@ai-sdk/provider": "^
|
|
70
|
-
"ai": "^
|
|
66
|
+
"@ai-sdk/anthropic": "^4.0.0",
|
|
67
|
+
"@ai-sdk/google": "^4.0.0",
|
|
68
|
+
"@ai-sdk/openai": "^4.0.0",
|
|
69
|
+
"@ai-sdk/provider": "^4.0.0",
|
|
70
|
+
"ai": "^7.0.0"
|
|
71
71
|
},
|
|
72
72
|
"peerDependenciesMeta": {
|
|
73
73
|
"@ai-sdk/anthropic": {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LanguageModelV4, SharedV4Warning } from "@ai-sdk/provider";
|
|
2
2
|
|
|
3
3
|
import type { AISearchChatSettings } from "./aisearch-chat-settings";
|
|
4
4
|
import { convertToWorkersAIChatMessages } from "./convert-to-workersai-chat-messages";
|
|
@@ -13,8 +13,8 @@ type AISearchChatConfig = {
|
|
|
13
13
|
gateway?: GatewayOptions;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export class AISearchChatLanguageModel implements
|
|
17
|
-
readonly specificationVersion = "
|
|
16
|
+
export class AISearchChatLanguageModel implements LanguageModelV4 {
|
|
17
|
+
readonly specificationVersion = "v4";
|
|
18
18
|
readonly defaultObjectGenerationMode = "json";
|
|
19
19
|
|
|
20
20
|
readonly supportedUrls: Record<string, RegExp[]> | PromiseLike<Record<string, RegExp[]>> = {};
|
|
@@ -43,8 +43,8 @@ export class AISearchChatLanguageModel implements LanguageModelV3 {
|
|
|
43
43
|
frequencyPenalty,
|
|
44
44
|
presencePenalty,
|
|
45
45
|
responseFormat,
|
|
46
|
-
}: Parameters<
|
|
47
|
-
const warnings:
|
|
46
|
+
}: Parameters<LanguageModelV4["doGenerate"]>[0]): SharedV4Warning[] {
|
|
47
|
+
const warnings: SharedV4Warning[] = [];
|
|
48
48
|
|
|
49
49
|
if (tools != null && tools.length > 0) {
|
|
50
50
|
console.warn(
|
|
@@ -72,14 +72,14 @@ export class AISearchChatLanguageModel implements LanguageModelV3 {
|
|
|
72
72
|
* Build the search query from messages.
|
|
73
73
|
* Flattens the conversation into a single string for aiSearch.
|
|
74
74
|
*/
|
|
75
|
-
private buildQuery(prompt: Parameters<
|
|
75
|
+
private buildQuery(prompt: Parameters<LanguageModelV4["doGenerate"]>[0]["prompt"]): string {
|
|
76
76
|
const { messages } = convertToWorkersAIChatMessages(prompt);
|
|
77
77
|
return messages.map(({ content, role }) => `${role}: ${content}`).join("\n\n");
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
async doGenerate(
|
|
81
|
-
options: Parameters<
|
|
82
|
-
): Promise<Awaited<ReturnType<
|
|
81
|
+
options: Parameters<LanguageModelV4["doGenerate"]>[0],
|
|
82
|
+
): Promise<Awaited<ReturnType<LanguageModelV4["doGenerate"]>>> {
|
|
83
83
|
const warnings = this.getWarnings(options);
|
|
84
84
|
const query = this.buildQuery(options.prompt);
|
|
85
85
|
|
|
@@ -109,8 +109,8 @@ export class AISearchChatLanguageModel implements LanguageModelV3 {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
async doStream(
|
|
112
|
-
options: Parameters<
|
|
113
|
-
): Promise<Awaited<ReturnType<
|
|
112
|
+
options: Parameters<LanguageModelV4["doStream"]>[0],
|
|
113
|
+
): Promise<Awaited<ReturnType<LanguageModelV4["doStream"]>>> {
|
|
114
114
|
const warnings = this.getWarnings(options);
|
|
115
115
|
const query = this.buildQuery(options.prompt);
|
|
116
116
|
|
package/src/client-fallback.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
LanguageModelV4,
|
|
3
|
+
LanguageModelV4CallOptions,
|
|
4
|
+
LanguageModelV4GenerateResult,
|
|
5
|
+
LanguageModelV4StreamResult,
|
|
6
6
|
} from "@ai-sdk/provider";
|
|
7
7
|
import { type FallbackAttempt, WorkersAIFallbackError, WorkersAIGatewayError } from "./errors";
|
|
8
8
|
import type { Transport } from "./gateway-delegate";
|
|
@@ -12,7 +12,7 @@ export interface FallbackLeg {
|
|
|
12
12
|
/** The model slug this leg dispatches. */
|
|
13
13
|
slug: string;
|
|
14
14
|
/** The built AI SDK model. */
|
|
15
|
-
model:
|
|
15
|
+
model: LanguageModelV4;
|
|
16
16
|
/** Transport the leg uses. */
|
|
17
17
|
transport: Transport;
|
|
18
18
|
}
|
|
@@ -26,13 +26,13 @@ export interface FallbackLeg {
|
|
|
26
26
|
* produced a stream). Errors that surface *mid-stream* — after content has
|
|
27
27
|
* already been emitted — are not recoverable here and propagate as-is.
|
|
28
28
|
*/
|
|
29
|
-
export function createClientFallbackModel(legs: FallbackLeg[]):
|
|
29
|
+
export function createClientFallbackModel(legs: FallbackLeg[]): LanguageModelV4 {
|
|
30
30
|
if (legs.length === 0) {
|
|
31
31
|
throw new Error("createClientFallbackModel requires at least one model leg.");
|
|
32
32
|
}
|
|
33
33
|
const primary = legs[0].model;
|
|
34
34
|
|
|
35
|
-
async function attempt<T>(run: (model:
|
|
35
|
+
async function attempt<T>(run: (model: LanguageModelV4) => PromiseLike<T>): Promise<T> {
|
|
36
36
|
const attempts: FallbackAttempt[] = [];
|
|
37
37
|
for (const leg of legs) {
|
|
38
38
|
try {
|
|
@@ -54,16 +54,16 @@ export function createClientFallbackModel(legs: FallbackLeg[]): LanguageModelV3
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
return {
|
|
57
|
-
specificationVersion: "
|
|
57
|
+
specificationVersion: "v4",
|
|
58
58
|
provider: primary.provider,
|
|
59
59
|
modelId: primary.modelId,
|
|
60
60
|
supportedUrls: primary.supportedUrls,
|
|
61
61
|
doGenerate(
|
|
62
|
-
options:
|
|
63
|
-
): PromiseLike<
|
|
62
|
+
options: LanguageModelV4CallOptions,
|
|
63
|
+
): PromiseLike<LanguageModelV4GenerateResult> {
|
|
64
64
|
return attempt((m) => m.doGenerate(options));
|
|
65
65
|
},
|
|
66
|
-
doStream(options:
|
|
66
|
+
doStream(options: LanguageModelV4CallOptions): PromiseLike<LanguageModelV4StreamResult> {
|
|
67
67
|
return attempt((m) => m.doStream(options));
|
|
68
68
|
},
|
|
69
69
|
};
|
|
@@ -1,45 +1,72 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LanguageModelV4Prompt, SharedV4FileData } from "@ai-sdk/provider";
|
|
2
2
|
import { UnsupportedFunctionalityError } from "@ai-sdk/provider";
|
|
3
3
|
import { toWorkersAIToolCallId } from "./utils";
|
|
4
4
|
import type { WorkersAIContentPart, WorkersAIChatPrompt } from "./workersai-chat-prompt";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Normalise
|
|
7
|
+
* Normalise a tagged `SharedV4FileData` value to a Uint8Array.
|
|
8
8
|
*
|
|
9
9
|
* Handles:
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
10
|
+
* - { type: 'data' } → Uint8Array returned as-is; string decoded from
|
|
11
|
+
* base64 (with or without data-URL prefix)
|
|
12
|
+
* - { type: 'url' } → not supported (Workers AI needs raw bytes, not a reference)
|
|
13
|
+
* - { type: 'reference' } → not supported (Workers AI has no file store to resolve it)
|
|
14
|
+
* - { type: 'text' } → not supported for image parts
|
|
13
15
|
*/
|
|
14
|
-
function toUint8Array(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
function toUint8Array(fileData: SharedV4FileData): Uint8Array | null {
|
|
17
|
+
switch (fileData.type) {
|
|
18
|
+
case "data": {
|
|
19
|
+
const data = fileData.data;
|
|
20
|
+
if (data instanceof Uint8Array) {
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
let base64 = data;
|
|
24
|
+
if (base64.startsWith("data:")) {
|
|
25
|
+
const commaIndex = base64.indexOf(",");
|
|
26
|
+
if (commaIndex >= 0) {
|
|
27
|
+
base64 = base64.slice(commaIndex + 1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const binaryString = atob(base64);
|
|
31
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
32
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
33
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
25
34
|
}
|
|
35
|
+
return bytes;
|
|
26
36
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
|
|
38
|
+
case "url": {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"URL image sources are not supported by Workers AI. " +
|
|
41
|
+
"Provide image data as a Uint8Array or base64 string instead.",
|
|
42
|
+
);
|
|
31
43
|
}
|
|
32
|
-
return bytes;
|
|
33
|
-
}
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
case "reference": {
|
|
46
|
+
throw new UnsupportedFunctionalityError({
|
|
47
|
+
functionality: "file-part-provider-reference",
|
|
48
|
+
message:
|
|
49
|
+
"Provider file references are not supported by Workers AI. " +
|
|
50
|
+
"Provide image data as a Uint8Array or base64 string instead.",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
case "text": {
|
|
55
|
+
throw new UnsupportedFunctionalityError({
|
|
56
|
+
functionality: "file-part-inline-text",
|
|
57
|
+
message:
|
|
58
|
+
"Inline text file parts are not supported by Workers AI chat. " +
|
|
59
|
+
"Pass text as a regular text part instead.",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
default: {
|
|
64
|
+
const exhaustiveCheck = fileData satisfies never;
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Unsupported file data type: ${(exhaustiveCheck as { type: string }).type}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
43
70
|
}
|
|
44
71
|
|
|
45
72
|
function assertImageMediaType(mediaType: string | undefined): string {
|
|
@@ -76,7 +103,7 @@ function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|
|
76
103
|
return btoa(binary);
|
|
77
104
|
}
|
|
78
105
|
|
|
79
|
-
export function convertToWorkersAIChatMessages(prompt:
|
|
106
|
+
export function convertToWorkersAIChatMessages(prompt: LanguageModelV4Prompt): {
|
|
80
107
|
messages: WorkersAIChatPrompt;
|
|
81
108
|
} {
|
|
82
109
|
const messages: WorkersAIChatPrompt = [];
|
|
@@ -164,6 +191,18 @@ export function convertToWorkersAIChatMessages(prompt: LanguageModelV3Prompt): {
|
|
|
164
191
|
break;
|
|
165
192
|
}
|
|
166
193
|
|
|
194
|
+
case "reasoning-file": {
|
|
195
|
+
// Files inside reasoning traces (new in spec v4) cannot be
|
|
196
|
+
// replayed to Workers AI - no action needed
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case "custom": {
|
|
201
|
+
// Provider-specific custom parts (new in spec v4) from other
|
|
202
|
+
// providers are not replayable to Workers AI - no action needed
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
|
|
167
206
|
case "tool-call": {
|
|
168
207
|
toolCalls.push({
|
|
169
208
|
function: {
|
package/src/gateway-delegate.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LanguageModelV4, LanguageModelV4CallOptions } from "@ai-sdk/provider";
|
|
2
2
|
import {
|
|
3
3
|
asText,
|
|
4
4
|
buildGatewayEntry,
|
|
@@ -154,7 +154,7 @@ export interface ProviderPlugin {
|
|
|
154
154
|
modelId: string;
|
|
155
155
|
fetch: typeof globalThis.fetch;
|
|
156
156
|
baseURL?: string;
|
|
157
|
-
}):
|
|
157
|
+
}): LanguageModelV4;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// ---------------------------------------------------------------------------
|
|
@@ -271,13 +271,18 @@ export function selectTransport(
|
|
|
271
271
|
// Run-path-only providers (on the run catalog, but not native gateway
|
|
272
272
|
// providers) have no gateway path at all — reject anything that would need it
|
|
273
273
|
// here, with a clear message, rather than letting it fail upstream.
|
|
274
|
-
if (
|
|
275
|
-
|
|
274
|
+
if (
|
|
275
|
+
runCatalog &&
|
|
276
|
+
!gatewayAvailable &&
|
|
277
|
+
(opts.transport === "gateway" || opts.byok || gatewayOnly)
|
|
278
|
+
) {
|
|
279
|
+
const what =
|
|
280
|
+
opts.transport === "gateway" ? 'transport:"gateway"' : opts.byok ? "byok" : feature;
|
|
276
281
|
throw new GatewayDelegateError(
|
|
277
282
|
"config",
|
|
278
283
|
`${what} is unavailable: this provider is on the unified run catalog but is not a ` +
|
|
279
284
|
"native gateway provider, so it has no gateway path (no caching, server-side " +
|
|
280
|
-
'fallback, or transport:"gateway"). Use the default run path, or fallback.mode:"client".',
|
|
285
|
+
'fallback, BYOK, or transport:"gateway"). Use the default run path, or fallback.mode:"client".',
|
|
281
286
|
);
|
|
282
287
|
}
|
|
283
288
|
|
|
@@ -301,6 +306,26 @@ export function selectTransport(
|
|
|
301
306
|
return { transport: "gateway", resumeEnabled: false, warnings };
|
|
302
307
|
}
|
|
303
308
|
|
|
309
|
+
// BYOK forwards the caller's own provider key, which only the gateway path
|
|
310
|
+
// supports — treat it like an explicit gateway transport (no resume-disabled
|
|
311
|
+
// warning; the caller opted into a gateway-only mode).
|
|
312
|
+
if (opts.byok) {
|
|
313
|
+
if (opts.transport === "run") {
|
|
314
|
+
throw new GatewayDelegateError(
|
|
315
|
+
"config",
|
|
316
|
+
'transport:"run" cannot forward a BYOK key — BYOK is a gateway-path feature. ' +
|
|
317
|
+
'Drop transport:"run" (or set transport:"gateway").',
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
if (resumeExplicitlyTrue) {
|
|
321
|
+
throw new GatewayDelegateError(
|
|
322
|
+
"config",
|
|
323
|
+
"byok cannot provide resume — cf-aig-run-id is only on the unified-billing run path.",
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
return { transport: "gateway", resumeEnabled: false, warnings };
|
|
327
|
+
}
|
|
328
|
+
|
|
304
329
|
if (opts.transport === "run" && gatewayOnly) {
|
|
305
330
|
throw new GatewayDelegateError(
|
|
306
331
|
"config",
|
|
@@ -376,7 +401,7 @@ export interface GatewayDelegateConfig {
|
|
|
376
401
|
}
|
|
377
402
|
|
|
378
403
|
export interface GatewayDelegate {
|
|
379
|
-
(slug: string, options?: DelegateCallOptions):
|
|
404
|
+
(slug: string, options?: DelegateCallOptions): LanguageModelV4;
|
|
380
405
|
}
|
|
381
406
|
|
|
382
407
|
/**
|
|
@@ -406,7 +431,7 @@ export function createGatewayDelegate(config: GatewayDelegateConfig): GatewayDel
|
|
|
406
431
|
const buildOne = (
|
|
407
432
|
slug: string,
|
|
408
433
|
options: DelegateCallOptions,
|
|
409
|
-
): { model:
|
|
434
|
+
): { model: LanguageModelV4; transport: Transport } => {
|
|
410
435
|
const parsed = parseSlug(slug);
|
|
411
436
|
const info = resolveProvider(slug, parsed);
|
|
412
437
|
|
|
@@ -791,7 +816,7 @@ function makeServerFallbackModel(params: {
|
|
|
791
816
|
opts: DelegateCallOptions;
|
|
792
817
|
selection: Selection;
|
|
793
818
|
callOptions: DelegateCallOptions;
|
|
794
|
-
}):
|
|
819
|
+
}): LanguageModelV4 {
|
|
795
820
|
const { binding, gatewayId, gatewayOptions, legs, opts, selection, callOptions } = params;
|
|
796
821
|
const first = legs[0]!;
|
|
797
822
|
|
|
@@ -809,7 +834,7 @@ function makeServerFallbackModel(params: {
|
|
|
809
834
|
|
|
810
835
|
const dispatch = async (
|
|
811
836
|
method: "doGenerate" | "doStream",
|
|
812
|
-
options:
|
|
837
|
+
options: LanguageModelV4CallOptions,
|
|
813
838
|
): Promise<unknown> => {
|
|
814
839
|
// 1) Capture each leg's native request without hitting the network.
|
|
815
840
|
const entries: GatewayEntry[] = [];
|
|
@@ -829,7 +854,7 @@ function makeServerFallbackModel(params: {
|
|
|
829
854
|
...(leg.info.baseURL ? { baseURL: leg.info.baseURL } : {}),
|
|
830
855
|
});
|
|
831
856
|
try {
|
|
832
|
-
await (model[method] as (o:
|
|
857
|
+
await (model[method] as (o: LanguageModelV4CallOptions) => Promise<unknown>)(
|
|
833
858
|
options,
|
|
834
859
|
);
|
|
835
860
|
} catch (e) {
|
|
@@ -880,21 +905,21 @@ function makeServerFallbackModel(params: {
|
|
|
880
905
|
fetch: (async () => resp) as typeof globalThis.fetch,
|
|
881
906
|
...(winner.info.baseURL ? { baseURL: winner.info.baseURL } : {}),
|
|
882
907
|
});
|
|
883
|
-
return (winnerModel[method] as (o:
|
|
908
|
+
return (winnerModel[method] as (o: LanguageModelV4CallOptions) => Promise<unknown>)(
|
|
884
909
|
options,
|
|
885
910
|
);
|
|
886
911
|
};
|
|
887
912
|
|
|
888
913
|
return {
|
|
889
|
-
specificationVersion: "
|
|
914
|
+
specificationVersion: "v4",
|
|
890
915
|
provider: refModel.provider,
|
|
891
916
|
modelId: refModel.modelId,
|
|
892
917
|
supportedUrls: refModel.supportedUrls,
|
|
893
918
|
doGenerate(options) {
|
|
894
|
-
return dispatch("doGenerate", options) as ReturnType<
|
|
919
|
+
return dispatch("doGenerate", options) as ReturnType<LanguageModelV4["doGenerate"]>;
|
|
895
920
|
},
|
|
896
921
|
doStream(options) {
|
|
897
|
-
return dispatch("doStream", options) as ReturnType<
|
|
922
|
+
return dispatch("doStream", options) as ReturnType<LanguageModelV4["doStream"]>;
|
|
898
923
|
},
|
|
899
|
-
} as
|
|
924
|
+
} as LanguageModelV4;
|
|
900
925
|
}
|
package/src/google.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createGoogle } from "@ai-sdk/google";
|
|
2
2
|
import type { ProviderPlugin } from "./gateway-delegate";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -13,7 +13,5 @@ export const google: ProviderPlugin = {
|
|
|
13
13
|
create: ({ modelId, fetch, baseURL }) =>
|
|
14
14
|
// apiKey is a placeholder — the gateway handles auth (unified billing / BYOK)
|
|
15
15
|
// and the delegate strips the x-goog-api-key header on the gateway path.
|
|
16
|
-
|
|
17
|
-
modelId,
|
|
18
|
-
),
|
|
16
|
+
createGoogle({ apiKey: "unused", fetch, ...(baseURL ? { baseURL } : {}) })(modelId),
|
|
19
17
|
};
|
package/src/index.ts
CHANGED
|
@@ -309,9 +309,21 @@ export function createWorkersAI(options: WorkersAISettings): WorkersAI {
|
|
|
309
309
|
gateway: GatewayOptions | string | undefined,
|
|
310
310
|
): GatewayOptions | undefined => (typeof gateway === "string" ? { id: gateway } : gateway);
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
// The bare unified-billing run path (`env.AI.run(model, …)`) shared by two
|
|
313
|
+
// callers that both hit it WITHOUT the gateway delegate:
|
|
314
|
+
// - `dynamic/<route>` AI Gateway dynamic routes, and
|
|
315
|
+
// - `"<vendor>/<model>"` catalog slugs when no `providers` are configured
|
|
316
|
+
// (the #596 passthrough).
|
|
317
|
+
// Neither has a transport choice, resume engine, or BYOK forwarding — those
|
|
318
|
+
// only exist once `providers` are set and the delegate takes over — so this
|
|
319
|
+
// path defaults the gateway (third-party run models need one), folds
|
|
320
|
+
// cache/metadata into the gateway options, and rejects delegate-only options
|
|
321
|
+
// loudly instead of letting `WorkersAIChatLanguageModel` spread them as junk
|
|
322
|
+
// into `binding.run`'s options arg.
|
|
323
|
+
const createRunPathModel = (
|
|
313
324
|
modelId: TextGenerationModels,
|
|
314
325
|
settings: WorkersAIChatSettings & DelegateCallOptions = {},
|
|
326
|
+
kind: "dynamic" | "catalog",
|
|
315
327
|
) => {
|
|
316
328
|
if (
|
|
317
329
|
settings.fallback ||
|
|
@@ -321,11 +333,16 @@ export function createWorkersAI(options: WorkersAISettings): WorkersAI {
|
|
|
321
333
|
settings.onResumeExpired ||
|
|
322
334
|
settings.byok
|
|
323
335
|
) {
|
|
336
|
+
const subject =
|
|
337
|
+
kind === "dynamic"
|
|
338
|
+
? `"${modelId}" is an AI Gateway dynamic route`
|
|
339
|
+
: `"${modelId}" routes through the bare unified-billing run path because no \`providers\` are configured`;
|
|
324
340
|
throw new Error(
|
|
325
|
-
|
|
326
|
-
"
|
|
327
|
-
"
|
|
328
|
-
"
|
|
341
|
+
`${subject}. It uses AI.run with OpenAI-compatible chat-completions wire format; ` +
|
|
342
|
+
"fallback, gateway transport, resume, BYOK, and resume callbacks are gateway-delegate " +
|
|
343
|
+
"features — configure provider plugins to use them " +
|
|
344
|
+
"(createWorkersAI({ binding: env.AI, providers: [openai] })), and for dynamic routes " +
|
|
345
|
+
"set caching/fallback on the route or gateway instead of per call.",
|
|
329
346
|
);
|
|
330
347
|
}
|
|
331
348
|
|
|
@@ -367,7 +384,12 @@ export function createWorkersAI(options: WorkersAISettings): WorkersAI {
|
|
|
367
384
|
|
|
368
385
|
const plugin = options.providers?.find((p) => p.wireFormat === DYNAMIC_ROUTE_WIRE_FORMAT);
|
|
369
386
|
if (!plugin) {
|
|
370
|
-
|
|
387
|
+
// Dynamic routes with providers configured but no OpenAI plugin can't be
|
|
388
|
+
// parsed (they always return openai-wire), so guide the user. The
|
|
389
|
+
// catalog passthrough only reaches here with no providers at all, where
|
|
390
|
+
// the built-in `WorkersAIChatLanguageModel` parser is the intended
|
|
391
|
+
// fallback (#596) — so let it through.
|
|
392
|
+
if (kind === "dynamic" && options.providers?.length) {
|
|
371
393
|
throw new Error(
|
|
372
394
|
`"${modelId}" is an AI Gateway dynamic route. Dynamic routes return OpenAI-compatible ` +
|
|
373
395
|
"chat-completions wire format on the AI.run path, so configure the OpenAI " +
|
|
@@ -465,13 +487,28 @@ export function createWorkersAI(options: WorkersAISettings): WorkersAI {
|
|
|
465
487
|
settings?: WorkersAIChatSettings | DelegateCallOptions,
|
|
466
488
|
): WorkersAIChatLanguageModel => {
|
|
467
489
|
if (typeof modelId === "string" && modelId.startsWith("dynamic/")) {
|
|
468
|
-
return
|
|
490
|
+
return createRunPathModel(
|
|
469
491
|
modelId,
|
|
470
492
|
settings as (WorkersAIChatSettings & DelegateCallOptions) | undefined,
|
|
493
|
+
"dynamic",
|
|
471
494
|
);
|
|
472
495
|
}
|
|
473
496
|
if (isGatewaySlug(modelId)) {
|
|
474
|
-
//
|
|
497
|
+
// Without provider plugins there's no gateway/BYOK routing to do, and a
|
|
498
|
+
// bare `"<vendor>/<model>"` id is a valid Workers AI unified-billing run
|
|
499
|
+
// model — `env.AI.run("deepseek/deepseek-v4-pro")`. Route it through the
|
|
500
|
+
// same bare run path as dynamic routes (defaults the gateway, folds
|
|
501
|
+
// cache/metadata, rejects delegate-only options) instead of erroring.
|
|
502
|
+
// Catalog routing (resume, fallback, caching, BYOK) only kicks in once
|
|
503
|
+
// `providers` are set. (#596)
|
|
504
|
+
if (!options.providers?.length) {
|
|
505
|
+
return createRunPathModel(
|
|
506
|
+
modelId,
|
|
507
|
+
settings as (WorkersAIChatSettings & DelegateCallOptions) | undefined,
|
|
508
|
+
"catalog",
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
// The delegate returns a `LanguageModelV4` built by the configured plugin.
|
|
475
512
|
// It's structurally compatible with the AI SDK consumers this provider is
|
|
476
513
|
// used with; the cast keeps the public return type unchanged.
|
|
477
514
|
return getDelegate(modelId)(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LanguageModelV4FinishReason } from "@ai-sdk/provider";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Map a Workers AI finish reason to the AI SDK unified finish reason.
|
|
@@ -9,7 +9,7 @@ import type { LanguageModelV3FinishReason } from "@ai-sdk/provider";
|
|
|
9
9
|
*/
|
|
10
10
|
export function mapWorkersAIFinishReason(
|
|
11
11
|
finishReasonOrResponse: string | null | undefined | Record<string, unknown>,
|
|
12
|
-
):
|
|
12
|
+
): LanguageModelV4FinishReason {
|
|
13
13
|
let finishReason: string | null | undefined;
|
|
14
14
|
|
|
15
15
|
if (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LanguageModelV4Usage } from "@ai-sdk/provider";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Map Workers AI usage data to the AI SDK
|
|
4
|
+
* Map Workers AI usage data to the AI SDK v4 usage format.
|
|
5
5
|
* Accepts any object that may have a `usage` property with token counts.
|
|
6
6
|
*
|
|
7
7
|
* Workers AI mirrors the OpenAI usage shape, including
|
|
@@ -11,7 +11,7 @@ import type { LanguageModelV3Usage } from "@ai-sdk/provider";
|
|
|
11
11
|
*/
|
|
12
12
|
export function mapWorkersAIUsage(
|
|
13
13
|
output: Record<string, unknown> | AiTextGenerationOutput | AiTextToImageOutput,
|
|
14
|
-
):
|
|
14
|
+
): LanguageModelV4Usage {
|
|
15
15
|
const usage = (
|
|
16
16
|
output as {
|
|
17
17
|
usage?: {
|
|
@@ -30,7 +30,7 @@ export function mapWorkersAIUsage(
|
|
|
30
30
|
const cachedTokens = usage.prompt_tokens_details?.cached_tokens;
|
|
31
31
|
|
|
32
32
|
// Clamp at 0 in case the provider ever reports cached_tokens > prompt_tokens;
|
|
33
|
-
// the
|
|
33
|
+
// the v4 spec expects non-negative counts.
|
|
34
34
|
const noCache =
|
|
35
35
|
cachedTokens !== undefined ? Math.max(0, promptTokens - cachedTokens) : undefined;
|
|
36
36
|
|
package/src/openai.ts
CHANGED
|
@@ -11,8 +11,9 @@ import type { ProviderPlugin } from "./gateway-delegate";
|
|
|
11
11
|
*
|
|
12
12
|
* Requires `@ai-sdk/openai` (an optional peer dependency — install it yourself).
|
|
13
13
|
*
|
|
14
|
-
* Uses `.chat()` (Chat Completions) deliberately:
|
|
15
|
-
* defaults to the Responses API, which the AI Gateway run catalog
|
|
14
|
+
* Uses `.chat()` (Chat Completions) deliberately: the bare `openai()` call
|
|
15
|
+
* (AI SDK v6+) defaults to the Responses API, which the AI Gateway run catalog
|
|
16
|
+
* does not serve.
|
|
16
17
|
*/
|
|
17
18
|
export const openai: ProviderPlugin = {
|
|
18
19
|
wireFormat: "openai",
|