diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index d91c99916..9bd3396ce 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -1666,6 +1666,7 @@ async function loadModelsDevData(): Promise[]> { } // Process Cloudflare AI Gateway models + const cloudflareAIGatewayModelIds = new Set(); if (data["cloudflare-ai-gateway"]?.models) { for (const [prefixedId, model] of Object.entries(data["cloudflare-ai-gateway"].models)) { const m = model as ModelsDevModel; @@ -1700,6 +1701,7 @@ async function loadModelsDevData(): Promise[]> { const compat = upstream === "anthropic" || upstream === "workers-ai" ? { sendSessionAffinityHeaders: true } : undefined; + cloudflareAIGatewayModelIds.add(id); models.push({ id, name: m.name || id, @@ -1722,6 +1724,41 @@ async function loadModelsDevData(): Promise[]> { } } + // models.dev may omit Workers AI passthroughs from the AI Gateway provider + // list even though the gateway /compat endpoint supports routing to them. + // Mirror the Workers AI catalog under the documented workers-ai/ prefix so + // the gateway keeps its OpenAI-compatible /compat models stable. + if (data["cloudflare-workers-ai"]?.models) { + for (const [modelId, model] of Object.entries(data["cloudflare-workers-ai"].models)) { + const m = model as ModelsDevModel; + if (m.tool_call !== true) continue; + + const id = `workers-ai/${modelId}`; + if (cloudflareAIGatewayModelIds.has(id)) continue; + cloudflareAIGatewayModelIds.add(id); + + models.push({ + id, + name: m.name || id, + api: "openai-completions", + provider: "cloudflare-ai-gateway", + baseUrl: CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL, + reasoning: m.reasoning === true, + input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], + cost: { + input: m.cost?.input || 0, + output: m.cost?.output || 0, + cacheRead: m.cost?.cache_read || 0, + cacheWrite: m.cost?.cache_write || 0, + }, + contextWindow: m.limit?.context || 4096, + maxTokens: m.limit?.output || 4096, + compat: { sendSessionAffinityHeaders: true }, + }); + recordModelsDevReasoningOptions("cloudflare-ai-gateway", id, m); + } + } + // Process xAi models if (data.xai?.models) { for (const [modelId, model] of Object.entries(data.xai.models)) { diff --git a/packages/ai/src/providers/cloudflare-ai-gateway.ts b/packages/ai/src/providers/cloudflare-ai-gateway.ts index 50c10569f..7c1cc3b78 100644 --- a/packages/ai/src/providers/cloudflare-ai-gateway.ts +++ b/packages/ai/src/providers/cloudflare-ai-gateway.ts @@ -6,10 +6,10 @@ import { CLOUDFLARE_AI_GATEWAY_MODELS } from "./cloudflare-ai-gateway.models.ts" import { cloudflareAIGatewayAuth } from "./cloudflare-auth.ts"; import { cloudflareStreams } from "./cloudflare-stream.ts"; -export function cloudflareAIGatewayProvider(): Provider< - "anthropic-messages" | "openai-completions" | "openai-responses" -> { - return createProvider({ +type CloudflareAIGatewayApi = "anthropic-messages" | "openai-completions" | "openai-responses"; + +export function cloudflareAIGatewayProvider(): Provider { + return createProvider({ id: "cloudflare-ai-gateway", name: "Cloudflare AI Gateway", auth: { apiKey: cloudflareAIGatewayAuth() },