workers-ai-provider 3.1.13 → 3.2.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 +183 -31
- package/dist/anthropic.d.mts +14 -0
- package/dist/anthropic.mjs +21 -0
- package/dist/anthropic.mjs.map +1 -0
- package/dist/gateway-delegate-BfaUTwDZ.d.mts +385 -0
- package/dist/gateway-provider-1USFWm7c.mjs +583 -0
- package/dist/gateway-provider-1USFWm7c.mjs.map +1 -0
- package/dist/gateway-provider.d.mts +80 -0
- package/dist/gateway-provider.mjs +2 -0
- package/dist/google.d.mts +14 -0
- package/dist/google.mjs +21 -0
- package/dist/google.mjs.map +1 -0
- package/dist/index.d.mts +64 -7
- package/dist/index.mjs +967 -327
- package/dist/index.mjs.map +1 -1
- package/dist/openai.d.mts +20 -0
- package/dist/openai.mjs +27 -0
- package/dist/openai.mjs.map +1 -0
- package/package.json +47 -6
- package/src/anthropic.ts +17 -0
- package/src/client-fallback.ts +70 -0
- package/src/convert-to-workersai-chat-messages.ts +33 -7
- package/src/errors.ts +216 -0
- package/src/gateway-delegate.ts +696 -0
- package/src/gateway-provider.ts +167 -0
- package/src/gateway-providers.ts +457 -0
- package/src/google.ts +19 -0
- package/src/index.ts +180 -9
- package/src/openai.ts +25 -0
- package/src/resumable-stream.ts +223 -0
- package/src/streaming.ts +103 -30
- package/src/utils.ts +206 -6
- package/src/workersai-chat-language-model.ts +87 -26
- package/src/workersai-chat-settings.ts +1 -1
- package/src/workersai-models.ts +11 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { WorkersAIGatewayError } from "./errors";
|
|
2
|
+
import { detectProviderByUrl, type GatewayProviderInfo } from "./gateway-providers";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bring-your-own-provider: route any `@ai-sdk/*` provider's HTTP traffic through
|
|
6
|
+
* Cloudflare AI Gateway, without the catalog slug delegate. The provider keeps
|
|
7
|
+
* its own request/response shaping; this only swaps the transport.
|
|
8
|
+
*
|
|
9
|
+
* Use it for providers the slug delegate cannot auto-wire (bedrock, replicate,
|
|
10
|
+
* audio/image providers, anything provider-native), or when you want full control
|
|
11
|
+
* of the underlying `@ai-sdk` provider. This is the gateway path only — BYOK and
|
|
12
|
+
* caching are available, resume (`cf-aig-run-id`) is not.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { createOpenAI } from "@ai-sdk/openai";
|
|
17
|
+
* import { createGatewayFetch } from "workers-ai-provider/gateway";
|
|
18
|
+
*
|
|
19
|
+
* const openai = createOpenAI({
|
|
20
|
+
* apiKey: env.OPENAI_API_KEY, // forwarded when byok: true
|
|
21
|
+
* fetch: createGatewayFetch({ binding: env.AI, gateway: "my-gw", byok: true }),
|
|
22
|
+
* });
|
|
23
|
+
* const model = openai("gpt-5");
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export interface GatewayFetchConfig {
|
|
27
|
+
/** A Cloudflare AI binding (e.g. `env.AI`). */
|
|
28
|
+
binding: Ai;
|
|
29
|
+
/** Gateway id (or options). */
|
|
30
|
+
gateway: GatewayOptions | string;
|
|
31
|
+
/**
|
|
32
|
+
* Force a gateway provider id instead of detecting it from the request URL.
|
|
33
|
+
* Required when the wrapped provider's host is not in the registry.
|
|
34
|
+
*/
|
|
35
|
+
provider?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Forward the upstream provider key (Authorization / x-api-key / …) instead of
|
|
38
|
+
* stripping it. Required for BYOK providers. Defaults to `false` (strip, so
|
|
39
|
+
* unified billing / the gateway's stored key applies).
|
|
40
|
+
*/
|
|
41
|
+
byok?: boolean;
|
|
42
|
+
/** Extra headers added to every gateway entry. */
|
|
43
|
+
extraHeaders?: Record<string, string>;
|
|
44
|
+
/** Gateway-path response caching (seconds). */
|
|
45
|
+
cacheTtl?: number;
|
|
46
|
+
/** Bypass gateway cache. */
|
|
47
|
+
skipCache?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const STRIP_HEADERS_BASE = new Set(["content-length", "host"]);
|
|
51
|
+
|
|
52
|
+
interface AiGatewayRunner {
|
|
53
|
+
run(body: unknown, options?: Record<string, unknown>): Promise<Response>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function asText(body: BodyInit | null | undefined): string {
|
|
57
|
+
if (typeof body === "string") return body;
|
|
58
|
+
if (body instanceof Uint8Array) return new TextDecoder().decode(body);
|
|
59
|
+
if (body instanceof ArrayBuffer) return new TextDecoder().decode(body);
|
|
60
|
+
return "{}";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function headersToObject(h: HeadersInit | undefined): Record<string, string> {
|
|
64
|
+
const out: Record<string, string> = {};
|
|
65
|
+
if (!h) return out;
|
|
66
|
+
if (h instanceof Headers) {
|
|
67
|
+
for (const [k, v] of h) out[k] = v;
|
|
68
|
+
} else if (Array.isArray(h)) {
|
|
69
|
+
for (const [k, v] of h) out[k] = v;
|
|
70
|
+
} else {
|
|
71
|
+
Object.assign(out, h);
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A `fetch` that dispatches the wrapped provider's request through AI Gateway.
|
|
78
|
+
* Detects the gateway provider id from the request URL (or uses `config.provider`),
|
|
79
|
+
* strips the provider host to the endpoint path, and forwards the body verbatim.
|
|
80
|
+
*/
|
|
81
|
+
export function createGatewayFetch(config: GatewayFetchConfig): typeof globalThis.fetch {
|
|
82
|
+
if (!config?.binding) {
|
|
83
|
+
throw new WorkersAIGatewayError(
|
|
84
|
+
"gateway-error",
|
|
85
|
+
"createGatewayFetch requires a `binding` (e.g. { binding: env.AI }).",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const gatewayId = typeof config.gateway === "string" ? config.gateway : config.gateway?.id;
|
|
89
|
+
if (!gatewayId) {
|
|
90
|
+
throw new WorkersAIGatewayError(
|
|
91
|
+
"gateway-error",
|
|
92
|
+
'createGatewayFetch requires a `gateway` id (e.g. gateway: "my-gateway").',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
97
|
+
const rawUrl = typeof input === "string" ? input : input.toString();
|
|
98
|
+
|
|
99
|
+
let info: GatewayProviderInfo | undefined;
|
|
100
|
+
if (config.provider) {
|
|
101
|
+
info = undefined; // explicit provider id; no registry lookup needed
|
|
102
|
+
} else {
|
|
103
|
+
info = detectProviderByUrl(rawUrl);
|
|
104
|
+
if (!info) {
|
|
105
|
+
throw new WorkersAIGatewayError(
|
|
106
|
+
"gateway-error",
|
|
107
|
+
`Could not detect a gateway provider from URL "${rawUrl}". ` +
|
|
108
|
+
'Pass `provider: "<gateway-provider-id>"` explicitly.',
|
|
109
|
+
{ recoverable: false },
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const providerId = config.provider ?? (info as GatewayProviderInfo).gatewayProviderId;
|
|
115
|
+
const endpoint = info?.transformEndpoint
|
|
116
|
+
? info.transformEndpoint(rawUrl)
|
|
117
|
+
: rawUrl.replace(/^https?:\/\/[^/]+\//, "");
|
|
118
|
+
const body = JSON.parse(asText(init?.body)) as Record<string, unknown>;
|
|
119
|
+
|
|
120
|
+
const strip = new Set(STRIP_HEADERS_BASE);
|
|
121
|
+
if (!config.byok && info) for (const h of info.authHeaders) strip.add(h.toLowerCase());
|
|
122
|
+
|
|
123
|
+
const headers: Record<string, string> = {};
|
|
124
|
+
for (const [k, v] of Object.entries(headersToObject(init?.headers))) {
|
|
125
|
+
if (!strip.has(k.toLowerCase())) headers[k] = v;
|
|
126
|
+
}
|
|
127
|
+
if (config.extraHeaders) Object.assign(headers, config.extraHeaders);
|
|
128
|
+
if (config.cacheTtl !== undefined) headers["cf-aig-cache-ttl"] = String(config.cacheTtl);
|
|
129
|
+
if (config.skipCache) headers["cf-aig-skip-cache"] = "true";
|
|
130
|
+
|
|
131
|
+
const entry = { provider: providerId, endpoint, headers, query: body };
|
|
132
|
+
const gw = (config.binding as unknown as { gateway(id: string): AiGatewayRunner }).gateway(
|
|
133
|
+
gatewayId,
|
|
134
|
+
);
|
|
135
|
+
const runOptions: Record<string, unknown> = {};
|
|
136
|
+
if (init?.signal) runOptions.signal = init.signal;
|
|
137
|
+
return gw.run([entry], runOptions);
|
|
138
|
+
}) as typeof globalThis.fetch;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Wrap an `@ai-sdk/*` provider factory so its traffic flows through AI Gateway.
|
|
143
|
+
* A thin convenience over {@link createGatewayFetch} — it injects the gateway
|
|
144
|
+
* `fetch` (and a placeholder `apiKey` unless you supply one for BYOK).
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { createOpenAI } from "@ai-sdk/openai";
|
|
149
|
+
* import { createGatewayProvider } from "workers-ai-provider/gateway";
|
|
150
|
+
*
|
|
151
|
+
* const openai = createGatewayProvider(createOpenAI, {
|
|
152
|
+
* binding: env.AI,
|
|
153
|
+
* gateway: "my-gw",
|
|
154
|
+
* });
|
|
155
|
+
* const model = openai("gpt-5");
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export function createGatewayProvider<T>(
|
|
159
|
+
factory: (opts: { apiKey?: string; baseURL?: string; fetch: typeof globalThis.fetch }) => T,
|
|
160
|
+
config: GatewayFetchConfig & { apiKey?: string; baseURL?: string },
|
|
161
|
+
): T {
|
|
162
|
+
return factory({
|
|
163
|
+
apiKey: config.apiKey ?? "unused",
|
|
164
|
+
...(config.baseURL ? { baseURL: config.baseURL } : {}),
|
|
165
|
+
fetch: createGatewayFetch(config),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of Cloudflare AI Gateway providers.
|
|
3
|
+
*
|
|
4
|
+
* One table drives both delegate surfaces:
|
|
5
|
+
*
|
|
6
|
+
* - **Slug delegate** (`wai("openai/gpt-5")`): `resolverKey` is the slug prefix
|
|
7
|
+
* the user types. `runCatalog` providers dispatch through the resumable run
|
|
8
|
+
* path (`env.AI.run`, unified billing, `cf-aig-run-id`); the rest go through
|
|
9
|
+
* the gateway path (`env.AI.gateway().run`, BYOK, no resume). `wireFormat`
|
|
10
|
+
* selects the built-in `@ai-sdk/*` parser; absent ⇒ the provider is reachable
|
|
11
|
+
* only via the bring-your-own-provider wrapper (it isn't chat/completions
|
|
12
|
+
* shaped, e.g. audio/image providers).
|
|
13
|
+
* - **Bring-your-own-provider** (`createGatewayProvider`): `hostPattern` +
|
|
14
|
+
* `transformEndpoint` map a wrapped provider's request URL to the gateway
|
|
15
|
+
* `provider` id + endpoint path.
|
|
16
|
+
*
|
|
17
|
+
* Slugs mirror the AI Gateway provider directory
|
|
18
|
+
* (developers.cloudflare.com/ai-gateway/usage/providers/); endpoint transforms
|
|
19
|
+
* mirror `ai-gateway-provider`'s provider table. `runCatalog` / `billing` flags
|
|
20
|
+
* follow the documented unified-billing list (OpenAI, Anthropic, Google AI
|
|
21
|
+
* Studio, Google Vertex, xAI/Grok, Groq) and are otherwise conservative — the
|
|
22
|
+
* e2e suite confirms them live, since resume is undocumented upstream.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Response wire format the slug delegate can parse with a built-in `@ai-sdk/*` provider. */
|
|
26
|
+
export type WireFormat = "openai" | "anthropic" | "google";
|
|
27
|
+
|
|
28
|
+
/** How a provider is billed + keyed when reached through the gateway. */
|
|
29
|
+
export type Billing = "unified" | "byok";
|
|
30
|
+
|
|
31
|
+
export interface GatewayProviderInfo {
|
|
32
|
+
/**
|
|
33
|
+
* Slug prefix the user types in `wai("<resolverKey>/<model>")`. For
|
|
34
|
+
* `runCatalog` providers this is also the run-catalog author (so
|
|
35
|
+
* `env.AI.run("<resolverKey>/<model>")` resolves).
|
|
36
|
+
*/
|
|
37
|
+
resolverKey: string;
|
|
38
|
+
/** Provider id for the gateway universal endpoint (`env.AI.gateway().run([{ provider }])`). */
|
|
39
|
+
gatewayProviderId: string;
|
|
40
|
+
/**
|
|
41
|
+
* Built-in parser wire format. `openai` covers the whole OpenAI-compatible
|
|
42
|
+
* long tail (deepseek, grok, groq, mistral, perplexity, …). Absent ⇒ reachable
|
|
43
|
+
* only via the bring-your-own-provider wrapper (provider-native, non-chat, or a
|
|
44
|
+
* gateway-path URL shape we don't reproduce reliably from the slug delegate).
|
|
45
|
+
*/
|
|
46
|
+
wireFormat?: WireFormat;
|
|
47
|
+
/**
|
|
48
|
+
* Wire format the unified-billing **run path** (`env.AI.run`) emits for this
|
|
49
|
+
* provider — which is NOT always the provider's native format. Cloudflare's
|
|
50
|
+
* unified catalog normalizes most providers to OpenAI chat-completions (so
|
|
51
|
+
* `google` is parsed with the `openai` plugin on the run path), but passes
|
|
52
|
+
* **Anthropic through natively** (`content[].text`, native tool shape), so
|
|
53
|
+
* anthropic must be parsed with the `anthropic` plugin. Defaults to `"openai"`
|
|
54
|
+
* for run-catalog providers when omitted. Only meaningful when `runCatalog`.
|
|
55
|
+
*/
|
|
56
|
+
runWireFormat?: WireFormat;
|
|
57
|
+
/**
|
|
58
|
+
* Base URL the wire-format builder should target so the request URL it
|
|
59
|
+
* generates host-strips (via {@link transformEndpoint}) to the provider's
|
|
60
|
+
* gateway-native endpoint. Omit to use the `@ai-sdk` provider's default (the
|
|
61
|
+
* provider's own host — correct for `openai`/`anthropic`/`google`). Required
|
|
62
|
+
* for OpenAI-wire providers that share the `openai` plugin but live on a
|
|
63
|
+
* different host (deepseek, grok, groq, mistral, perplexity, …).
|
|
64
|
+
*/
|
|
65
|
+
baseURL?: string;
|
|
66
|
+
/** On the unified-billing resumable run catalog (`env.AI.run`, `cf-aig-run-id`). */
|
|
67
|
+
runCatalog: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether the provider has a gateway path (`env.AI.gateway().run`). `false` ⇒
|
|
70
|
+
* **run-path only**: the provider is on the unified run catalog but is not a
|
|
71
|
+
* native gateway provider, so caching, server-side fallback, and
|
|
72
|
+
* `transport: "gateway"` are unavailable and the delegate rejects them with a
|
|
73
|
+
* clear error (rather than failing upstream). Defaults to `true`.
|
|
74
|
+
*/
|
|
75
|
+
gatewayPath?: boolean;
|
|
76
|
+
/** Billing model when reached through the gateway. */
|
|
77
|
+
billing: Billing;
|
|
78
|
+
/** Header(s) carrying the upstream provider key (stripped on the gateway path unless BYOK-forwarded). */
|
|
79
|
+
authHeaders: string[];
|
|
80
|
+
/** Host matcher for bring-your-own-provider URL detection. */
|
|
81
|
+
hostPattern?: RegExp;
|
|
82
|
+
/** Strip the provider host, leaving the gateway endpoint path (+ query). */
|
|
83
|
+
transformEndpoint?: (url: string) => string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Strip a leading `https://<host>/` prefix, leaving the endpoint path + query. */
|
|
87
|
+
function hostStrip(pattern: RegExp): (url: string) => string {
|
|
88
|
+
return (url: string) => url.replace(pattern, "");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const OPENAI_HOST = /^https:\/\/api\.openai\.com\//;
|
|
92
|
+
const ANTHROPIC_HOST = /^https:\/\/api\.anthropic\.com\//;
|
|
93
|
+
const GOOGLE_HOST = /^https:\/\/generativelanguage\.googleapis\.com\//;
|
|
94
|
+
const VERTEX_HOST = /^https:\/\/(?:[a-z0-9-]+-)?aiplatform\.googleapis\.com\//;
|
|
95
|
+
const XAI_HOST = /^https:\/\/api\.x\.ai\//;
|
|
96
|
+
const GROQ_HOST = /^https:\/\/api\.groq\.com\/openai\/v1\//;
|
|
97
|
+
const DEEPSEEK_HOST = /^https:\/\/api\.deepseek\.com\//;
|
|
98
|
+
const MISTRAL_HOST = /^https:\/\/api\.mistral\.ai\//;
|
|
99
|
+
const PERPLEXITY_HOST = /^https:\/\/api\.perplexity\.ai\//;
|
|
100
|
+
const CEREBRAS_HOST = /^https:\/\/api\.cerebras\.ai\//;
|
|
101
|
+
const OPENROUTER_HOST = /^https:\/\/openrouter\.ai\/api\//;
|
|
102
|
+
const FIREWORKS_HOST = /^https:\/\/api\.fireworks\.ai\/inference\/v1\//;
|
|
103
|
+
const COHERE_HOST = /^https:\/\/api\.cohere\.(?:com|ai)\//;
|
|
104
|
+
const REPLICATE_HOST = /^https:\/\/api\.replicate\.com\//;
|
|
105
|
+
const HUGGINGFACE_HOST = /^https:\/\/api-inference\.huggingface\.co\/models\//;
|
|
106
|
+
const CARTESIA_HOST = /^https:\/\/api\.cartesia\.ai\//;
|
|
107
|
+
const FAL_HOST = /^https:\/\/fal\.run\//;
|
|
108
|
+
const IDEOGRAM_HOST = /^https:\/\/api\.ideogram\.ai\//;
|
|
109
|
+
const DEEPGRAM_HOST = /^https:\/\/api\.deepgram\.com\//;
|
|
110
|
+
const ELEVENLABS_HOST = /^https:\/\/api\.elevenlabs\.io\//;
|
|
111
|
+
const GROK_KEY = "grok";
|
|
112
|
+
|
|
113
|
+
// Bedrock's URL carries the AWS region, which the gateway endpoint preserves as
|
|
114
|
+
// `bedrock-runtime/<region>/<rest>` (mirrors ai-gateway-provider).
|
|
115
|
+
const BEDROCK_HOST = /^https:\/\/bedrock-runtime\.(?<region>[^.]+)\.amazonaws\.com\//;
|
|
116
|
+
function bedrockTransform(url: string): string {
|
|
117
|
+
const m = url.match(
|
|
118
|
+
/^https:\/\/bedrock-runtime\.(?<region>[^.]+)\.amazonaws\.com\/(?<rest>.*)$/,
|
|
119
|
+
);
|
|
120
|
+
if (!m?.groups) return url;
|
|
121
|
+
const { region, rest } = m.groups;
|
|
122
|
+
if (!region || rest === undefined) return url;
|
|
123
|
+
return `bedrock-runtime/${region}/${rest}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Azure's URL carries the resource + deployment, so it needs a bespoke transform
|
|
127
|
+
// (mirrors ai-gateway-provider). Only used for bring-your-own-provider detection.
|
|
128
|
+
const AZURE_HOST =
|
|
129
|
+
/^https:\/\/(?<resource>[^.]+)\.openai\.azure\.com\/openai\/deployments\/(?<deployment>[^/]+)\/(?<rest>.*)$/;
|
|
130
|
+
function azureTransform(url: string): string {
|
|
131
|
+
const m = url.match(AZURE_HOST);
|
|
132
|
+
if (!m?.groups) return url;
|
|
133
|
+
const { resource, deployment, rest } = m.groups;
|
|
134
|
+
if (!resource || !deployment || !rest) return url;
|
|
135
|
+
return `${resource}/${deployment}/${rest}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The provider table. Order matters only for `detectProviderByUrl` (first match
|
|
140
|
+
* wins); slugs are looked up by `resolverKey`.
|
|
141
|
+
*/
|
|
142
|
+
export const GATEWAY_PROVIDERS: GatewayProviderInfo[] = [
|
|
143
|
+
// ---- Unified-billing run-catalog providers (resumable run path) ----
|
|
144
|
+
{
|
|
145
|
+
resolverKey: "openai",
|
|
146
|
+
gatewayProviderId: "openai",
|
|
147
|
+
wireFormat: "openai",
|
|
148
|
+
runCatalog: true,
|
|
149
|
+
billing: "unified",
|
|
150
|
+
authHeaders: ["authorization"],
|
|
151
|
+
hostPattern: OPENAI_HOST,
|
|
152
|
+
transformEndpoint: hostStrip(OPENAI_HOST),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
resolverKey: "anthropic",
|
|
156
|
+
gatewayProviderId: "anthropic",
|
|
157
|
+
wireFormat: "anthropic",
|
|
158
|
+
// Unified billing passes Anthropic through natively (unlike google, which it
|
|
159
|
+
// normalizes to openai-wire), so the run path also speaks Anthropic Messages.
|
|
160
|
+
runWireFormat: "anthropic",
|
|
161
|
+
runCatalog: true,
|
|
162
|
+
billing: "unified",
|
|
163
|
+
authHeaders: ["x-api-key", "authorization"],
|
|
164
|
+
hostPattern: ANTHROPIC_HOST,
|
|
165
|
+
transformEndpoint: hostStrip(ANTHROPIC_HOST),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
resolverKey: "google",
|
|
169
|
+
gatewayProviderId: "google-ai-studio",
|
|
170
|
+
// Gateway path hits Gemini's native endpoint (google-wire); the unified run
|
|
171
|
+
// path, however, returns openai-wire — so runWireFormat defaults to "openai".
|
|
172
|
+
wireFormat: "google",
|
|
173
|
+
runCatalog: true,
|
|
174
|
+
billing: "unified",
|
|
175
|
+
authHeaders: ["x-goog-api-key", "authorization"],
|
|
176
|
+
hostPattern: GOOGLE_HOST,
|
|
177
|
+
transformEndpoint: hostStrip(GOOGLE_HOST),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
resolverKey: "xai",
|
|
181
|
+
gatewayProviderId: GROK_KEY,
|
|
182
|
+
wireFormat: "openai",
|
|
183
|
+
// Targeted so a forced gateway-path request host-strips correctly (the run
|
|
184
|
+
// path, the default for xai, ignores this).
|
|
185
|
+
baseURL: "https://api.x.ai/v1",
|
|
186
|
+
runCatalog: true,
|
|
187
|
+
billing: "unified",
|
|
188
|
+
authHeaders: ["authorization"],
|
|
189
|
+
hostPattern: XAI_HOST,
|
|
190
|
+
transformEndpoint: hostStrip(XAI_HOST),
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
resolverKey: "groq",
|
|
194
|
+
gatewayProviderId: "groq",
|
|
195
|
+
wireFormat: "openai",
|
|
196
|
+
// Groq's gateway-native endpoint strips `/openai/v1/`, so the builder must
|
|
197
|
+
// target that base or a forced gateway request doubles the prefix.
|
|
198
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
199
|
+
runCatalog: true,
|
|
200
|
+
billing: "unified",
|
|
201
|
+
authHeaders: ["authorization"],
|
|
202
|
+
hostPattern: GROQ_HOST,
|
|
203
|
+
transformEndpoint: hostStrip(GROQ_HOST),
|
|
204
|
+
},
|
|
205
|
+
// Unified-catalog chat providers that are NOT in the native gateway directory:
|
|
206
|
+
// they exist only on the resumable run path (env.AI.run, unified billing), so
|
|
207
|
+
// there's no BYOK gateway path. Both return OpenAI chat-completions wire (so the
|
|
208
|
+
// `openai` plugin parses them) and emit `cf-aig-run-id` on streams (resumable),
|
|
209
|
+
// verified live against the default gateway. Forcing transport:"gateway" for
|
|
210
|
+
// these errors upstream (no native provider) — that's expected.
|
|
211
|
+
{
|
|
212
|
+
// Alibaba Qwen, served via DashScope's OpenAI-compatible endpoint.
|
|
213
|
+
resolverKey: "alibaba",
|
|
214
|
+
gatewayProviderId: "alibaba",
|
|
215
|
+
wireFormat: "openai",
|
|
216
|
+
runCatalog: true,
|
|
217
|
+
gatewayPath: false,
|
|
218
|
+
billing: "unified",
|
|
219
|
+
authHeaders: ["authorization"],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
// MiniMax (M-series). OpenAI-wire with extra fields (reasoning_content,
|
|
223
|
+
// audio_content) the openai parser ignores; core choices[].delta.content is standard.
|
|
224
|
+
resolverKey: "minimax",
|
|
225
|
+
gatewayProviderId: "minimax",
|
|
226
|
+
wireFormat: "openai",
|
|
227
|
+
runCatalog: true,
|
|
228
|
+
gatewayPath: false,
|
|
229
|
+
billing: "unified",
|
|
230
|
+
authHeaders: ["authorization"],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
resolverKey: "google-vertex",
|
|
234
|
+
gatewayProviderId: "google-vertex-ai",
|
|
235
|
+
// Vertex's URL carries project/location/publisher segments that the
|
|
236
|
+
// `@ai-sdk/google` default (AI Studio) does not produce, so the slug
|
|
237
|
+
// delegate can't shape it reliably — reach Vertex via createGatewayProvider.
|
|
238
|
+
runCatalog: false,
|
|
239
|
+
billing: "unified",
|
|
240
|
+
authHeaders: ["authorization"],
|
|
241
|
+
hostPattern: VERTEX_HOST,
|
|
242
|
+
transformEndpoint: hostStrip(VERTEX_HOST),
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
// ---- OpenAI-compatible long tail (gateway path, BYOK) ----
|
|
246
|
+
{
|
|
247
|
+
resolverKey: "deepseek",
|
|
248
|
+
gatewayProviderId: "deepseek",
|
|
249
|
+
wireFormat: "openai",
|
|
250
|
+
baseURL: "https://api.deepseek.com",
|
|
251
|
+
runCatalog: false,
|
|
252
|
+
billing: "byok",
|
|
253
|
+
authHeaders: ["authorization"],
|
|
254
|
+
hostPattern: DEEPSEEK_HOST,
|
|
255
|
+
transformEndpoint: hostStrip(DEEPSEEK_HOST),
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
resolverKey: "mistral",
|
|
259
|
+
gatewayProviderId: "mistral",
|
|
260
|
+
wireFormat: "openai",
|
|
261
|
+
baseURL: "https://api.mistral.ai/v1",
|
|
262
|
+
runCatalog: false,
|
|
263
|
+
billing: "byok",
|
|
264
|
+
authHeaders: ["authorization"],
|
|
265
|
+
hostPattern: MISTRAL_HOST,
|
|
266
|
+
transformEndpoint: hostStrip(MISTRAL_HOST),
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
resolverKey: "perplexity",
|
|
270
|
+
gatewayProviderId: "perplexity-ai",
|
|
271
|
+
wireFormat: "openai",
|
|
272
|
+
baseURL: "https://api.perplexity.ai",
|
|
273
|
+
runCatalog: false,
|
|
274
|
+
billing: "byok",
|
|
275
|
+
authHeaders: ["authorization"],
|
|
276
|
+
hostPattern: PERPLEXITY_HOST,
|
|
277
|
+
transformEndpoint: hostStrip(PERPLEXITY_HOST),
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
resolverKey: "cerebras",
|
|
281
|
+
gatewayProviderId: "cerebras",
|
|
282
|
+
wireFormat: "openai",
|
|
283
|
+
baseURL: "https://api.cerebras.ai/v1",
|
|
284
|
+
runCatalog: false,
|
|
285
|
+
billing: "byok",
|
|
286
|
+
authHeaders: ["authorization"],
|
|
287
|
+
hostPattern: CEREBRAS_HOST,
|
|
288
|
+
transformEndpoint: hostStrip(CEREBRAS_HOST),
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
resolverKey: "openrouter",
|
|
292
|
+
gatewayProviderId: "openrouter",
|
|
293
|
+
wireFormat: "openai",
|
|
294
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
295
|
+
runCatalog: false,
|
|
296
|
+
billing: "byok",
|
|
297
|
+
authHeaders: ["authorization"],
|
|
298
|
+
hostPattern: OPENROUTER_HOST,
|
|
299
|
+
transformEndpoint: hostStrip(OPENROUTER_HOST),
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
// Fireworks is OpenAI-compatible. Present on ai-gateway-provider (#409) but
|
|
303
|
+
// not the current provider directory — treat as BYOK long-tail.
|
|
304
|
+
resolverKey: "fireworks",
|
|
305
|
+
gatewayProviderId: "fireworks",
|
|
306
|
+
wireFormat: "openai",
|
|
307
|
+
baseURL: "https://api.fireworks.ai/inference/v1",
|
|
308
|
+
runCatalog: false,
|
|
309
|
+
billing: "byok",
|
|
310
|
+
authHeaders: ["authorization"],
|
|
311
|
+
hostPattern: FIREWORKS_HOST,
|
|
312
|
+
transformEndpoint: hostStrip(FIREWORKS_HOST),
|
|
313
|
+
},
|
|
314
|
+
// Providers whose gateway-path URL shape isn't reliably reproducible from the
|
|
315
|
+
// shared openai builder (cohere's /compat surface, baseten's per-deployment
|
|
316
|
+
// hosts, parallel, azure's resource/deployment path) are bring-your-own-provider
|
|
317
|
+
// only — set your own @ai-sdk provider baseURL and route via createGatewayProvider.
|
|
318
|
+
{
|
|
319
|
+
resolverKey: "cohere",
|
|
320
|
+
gatewayProviderId: "cohere",
|
|
321
|
+
runCatalog: false,
|
|
322
|
+
billing: "byok",
|
|
323
|
+
authHeaders: ["authorization"],
|
|
324
|
+
hostPattern: COHERE_HOST,
|
|
325
|
+
transformEndpoint: hostStrip(COHERE_HOST),
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
// Baseten serves per-deployment hosts, so there's no single detectable URL
|
|
329
|
+
// shape — reach it with an explicit `provider` via createGatewayProvider.
|
|
330
|
+
resolverKey: "baseten",
|
|
331
|
+
gatewayProviderId: "baseten",
|
|
332
|
+
runCatalog: false,
|
|
333
|
+
billing: "byok",
|
|
334
|
+
authHeaders: ["authorization"],
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
resolverKey: "parallel",
|
|
338
|
+
gatewayProviderId: "parallel",
|
|
339
|
+
runCatalog: false,
|
|
340
|
+
billing: "byok",
|
|
341
|
+
authHeaders: ["authorization", "x-api-key"],
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
resolverKey: "azure-openai",
|
|
345
|
+
gatewayProviderId: "azure-openai",
|
|
346
|
+
runCatalog: false,
|
|
347
|
+
billing: "byok",
|
|
348
|
+
authHeaders: ["api-key", "authorization"],
|
|
349
|
+
hostPattern: AZURE_HOST,
|
|
350
|
+
transformEndpoint: azureTransform,
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
// ---- Provider-native only: reachable via the bring-your-own-provider wrapper ----
|
|
354
|
+
// (no `wireFormat` ⇒ not auto-wired by the slug delegate)
|
|
355
|
+
{
|
|
356
|
+
resolverKey: "aws-bedrock",
|
|
357
|
+
gatewayProviderId: "aws-bedrock",
|
|
358
|
+
runCatalog: false,
|
|
359
|
+
billing: "byok",
|
|
360
|
+
authHeaders: ["authorization"],
|
|
361
|
+
hostPattern: BEDROCK_HOST,
|
|
362
|
+
transformEndpoint: bedrockTransform,
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
resolverKey: "huggingface",
|
|
366
|
+
gatewayProviderId: "huggingface",
|
|
367
|
+
runCatalog: false,
|
|
368
|
+
billing: "byok",
|
|
369
|
+
authHeaders: ["authorization"],
|
|
370
|
+
hostPattern: HUGGINGFACE_HOST,
|
|
371
|
+
transformEndpoint: hostStrip(HUGGINGFACE_HOST),
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
resolverKey: "replicate",
|
|
375
|
+
gatewayProviderId: "replicate",
|
|
376
|
+
runCatalog: false,
|
|
377
|
+
billing: "byok",
|
|
378
|
+
authHeaders: ["authorization"],
|
|
379
|
+
hostPattern: REPLICATE_HOST,
|
|
380
|
+
transformEndpoint: hostStrip(REPLICATE_HOST),
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
resolverKey: "fal",
|
|
384
|
+
gatewayProviderId: "fal",
|
|
385
|
+
runCatalog: false,
|
|
386
|
+
billing: "byok",
|
|
387
|
+
authHeaders: ["authorization"],
|
|
388
|
+
hostPattern: FAL_HOST,
|
|
389
|
+
transformEndpoint: hostStrip(FAL_HOST),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
resolverKey: "ideogram",
|
|
393
|
+
gatewayProviderId: "ideogram",
|
|
394
|
+
runCatalog: false,
|
|
395
|
+
billing: "byok",
|
|
396
|
+
authHeaders: ["authorization"],
|
|
397
|
+
hostPattern: IDEOGRAM_HOST,
|
|
398
|
+
transformEndpoint: hostStrip(IDEOGRAM_HOST),
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
resolverKey: "cartesia",
|
|
402
|
+
gatewayProviderId: "cartesia",
|
|
403
|
+
runCatalog: false,
|
|
404
|
+
billing: "byok",
|
|
405
|
+
authHeaders: ["authorization", "x-api-key"],
|
|
406
|
+
hostPattern: CARTESIA_HOST,
|
|
407
|
+
transformEndpoint: hostStrip(CARTESIA_HOST),
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
resolverKey: "deepgram",
|
|
411
|
+
gatewayProviderId: "deepgram",
|
|
412
|
+
runCatalog: false,
|
|
413
|
+
billing: "byok",
|
|
414
|
+
authHeaders: ["authorization", "token"],
|
|
415
|
+
hostPattern: DEEPGRAM_HOST,
|
|
416
|
+
transformEndpoint: hostStrip(DEEPGRAM_HOST),
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
resolverKey: "elevenlabs",
|
|
420
|
+
gatewayProviderId: "elevenlabs",
|
|
421
|
+
runCatalog: false,
|
|
422
|
+
billing: "byok",
|
|
423
|
+
authHeaders: ["xi-api-key", "authorization"],
|
|
424
|
+
hostPattern: ELEVENLABS_HOST,
|
|
425
|
+
transformEndpoint: hostStrip(ELEVENLABS_HOST),
|
|
426
|
+
},
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
/** Aliases that map a friendly slug prefix to a canonical `resolverKey`. */
|
|
430
|
+
const RESOLVER_ALIASES: Record<string, string> = {
|
|
431
|
+
// xAI's run-catalog author is `xai`, but `grok` is the common name.
|
|
432
|
+
grok: "xai",
|
|
433
|
+
"google-ai-studio": "google",
|
|
434
|
+
"google-vertex-ai": "google-vertex",
|
|
435
|
+
bedrock: "aws-bedrock",
|
|
436
|
+
azure: "azure-openai",
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const BY_RESOLVER_KEY = new Map<string, GatewayProviderInfo>(
|
|
440
|
+
GATEWAY_PROVIDERS.map((p) => [p.resolverKey, p]),
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
/** Look up a provider by the slug prefix the user typed (honoring aliases). */
|
|
444
|
+
export function findProviderBySlug(resolverKey: string): GatewayProviderInfo | undefined {
|
|
445
|
+
const canonical = RESOLVER_ALIASES[resolverKey] ?? resolverKey;
|
|
446
|
+
return BY_RESOLVER_KEY.get(canonical);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Detect the gateway provider from a wrapped provider's request URL (BYOG). */
|
|
450
|
+
export function detectProviderByUrl(url: string): GatewayProviderInfo | undefined {
|
|
451
|
+
return GATEWAY_PROVIDERS.find((p) => p.hostPattern?.test(url));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** All slug keys with a built-in parser (auto-wireable by the slug delegate). */
|
|
455
|
+
export function wireableProviders(): GatewayProviderInfo[] {
|
|
456
|
+
return GATEWAY_PROVIDERS.filter((p) => p.wireFormat !== undefined);
|
|
457
|
+
}
|
package/src/google.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
2
|
+
import type { ProviderPlugin } from "./gateway-delegate";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Google (Gemini) provider plugin for the gateway delegate. Pass to
|
|
6
|
+
* `createGatewayDelegate({ providers: [google] })` to handle `"google/<model>"`
|
|
7
|
+
* (Google AI Studio) and `"google-vertex/<model>"` slugs.
|
|
8
|
+
*
|
|
9
|
+
* Requires `@ai-sdk/google` (an optional peer dependency — install it yourself).
|
|
10
|
+
*/
|
|
11
|
+
export const google: ProviderPlugin = {
|
|
12
|
+
wireFormat: "google",
|
|
13
|
+
create: ({ modelId, fetch, baseURL }) =>
|
|
14
|
+
// apiKey is a placeholder — the gateway handles auth (unified billing / BYOK)
|
|
15
|
+
// and the delegate strips the x-goog-api-key header on the gateway path.
|
|
16
|
+
createGoogleGenerativeAI({ apiKey: "unused", fetch, ...(baseURL ? { baseURL } : {}) })(
|
|
17
|
+
modelId,
|
|
18
|
+
),
|
|
19
|
+
};
|