Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9c02a74
feat(core): register native packages from models.dev
rekram1-node Sep 12, 2026
da01d84
feat(core): route models.dev providers to their dedicated native pack…
rekram1-node Sep 12, 2026
4e9a0d5
refactor(core): move reasoning variant generation into Variant
rekram1-node Sep 12, 2026
7365850
chore(core): trim variant comments
rekram1-node Sep 13, 2026
1755f0d
refactor(core): group variant spellings by family
rekram1-node Sep 13, 2026
bca964c
refactor(core): one variant format per package
rekram1-node Sep 13, 2026
84fcf5f
refactor(core): one variant protocol per package
rekram1-node Sep 13, 2026
0a8b556
fix(core): match native packages in provider plugins
rekram1-node Sep 14, 2026
b87611e
fix(core): write native packages for Copilot Anthropic models and tes…
rekram1-node Sep 14, 2026
fa97400
fix(core): native packages in Cloudflare, Vertex and Modal providers
rekram1-node Sep 14, 2026
9b0ee17
refactor(core): rewrite AI SDK packages to native on catalog write
rekram1-node Sep 14, 2026
0c1e30c
fix(core): generate variants after package rewrite
rekram1-node Sep 14, 2026
1dff31a
fix(core): specialize Messages reasoning variants
rekram1-node Sep 14, 2026
2c79990
fix(core): specialize direct chat reasoning variants
rekram1-node Sep 14, 2026
fddf7d2
fix(core): specialize hosted reasoning variants
rekram1-node Sep 14, 2026
949ca21
fix(core): restore Workers AI environment auth
rekram1-node Sep 14, 2026
25e1e84
fix(core): generate configured variants in model registry
rekram1-node Sep 14, 2026
00e277a
fix(core): preserve Workers AI account configuration
rekram1-node Sep 14, 2026
ba9bf91
refactor(core): remove obsolete AI SDK provider paths
rekram1-node Sep 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 1 addition & 117 deletions bun.lock

Large diffs are not rendered by default.

129 changes: 88 additions & 41 deletions packages/ai/src/providers/cloudflare-ai-gateway.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { Config, Redacted } from "effect"
import { Schema, type Config, type Redacted } from "effect"
import type { ProviderPackage } from "../provider-package.js"
import { OpenAIChat } from "../protocols/openai-chat.js"
import { AnthropicMessages, OpenAIChat, OpenAIResponses } from "../protocols/index.js"
import { Auth } from "../route/auth.js"
import type { AtLeastOne, ProviderAuthOption } from "../route/auth-options.js"
import { Route, type RouteDefaultsInput } from "../route/client.js"
import { Endpoint } from "../route/endpoint.js"
import type { RouteDefaultsInput } from "../route/client.js"
import { ProviderConfigurationError, ProviderID, type ModelID } from "../schema/index.js"
import type { OpenAIProviderOptionsInput } from "./openai-options.js"

Expand All @@ -14,21 +13,29 @@ export const authEnvVars = ["CLOUDFLARE_API_TOKEN", "CF_AIG_TOKEN"] as const
type GatewayURL = AtLeastOne<{
readonly accountId: string
readonly baseURL: string
}> & {
}>

type GatewayOptions = {
readonly gatewayId?: string
readonly metadata?: unknown
readonly cacheTtl?: number
readonly cacheKey?: string
readonly skipCache?: boolean
readonly collectLog?: boolean
}

export type LanguageModelOptions = GatewayURL &
GatewayOptions &
Omit<RouteDefaultsInput, "providerOptions"> &
ProviderAuthOption<"optional"> & {
/** Cloudflare AI Gateway authentication token. Sent as `cf-aig-authorization`. */
readonly gatewayApiKey?: string | Redacted.Redacted | Config.Config<string | Redacted.Redacted>
readonly providerOptions?: OpenAIProviderOptionsInput
}

export type Settings = ProviderPackage.Settings &
OpenAIProviderOptionsInput &
GatewayURL & {
GatewayURL &
GatewayOptions & {
readonly apiKey?: string
readonly gatewayApiKey?: string
}
Expand All @@ -40,73 +47,113 @@ export const baseURL = (input: GatewayURL) => {
provider: id,
message: "CloudflareAIGateway.configure requires accountId unless baseURL is supplied",
})
return `https://gateway.ai.cloudflare.com/v1/${encodeURIComponent(input.accountId)}/${encodeURIComponent(input.gatewayId?.trim() || "default")}/compat`
return `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(input.accountId)}/ai/v1`
}

export const responsesRoute = OpenAIResponses.route.with({
id: "cloudflare-ai-gateway-responses",
provider: id,
endpoint: { baseURL: undefined },
})

export const messagesRoute = AnthropicMessages.route.with({
id: "cloudflare-ai-gateway-messages",
provider: id,
endpoint: { baseURL: undefined },
})

export const route = OpenAIChat.route.with({
id: "cloudflare-ai-gateway-chat",
provider: id,
endpoint: { baseURL: undefined },
})

export const routes = [responsesRoute, messagesRoute, route]

const auth = (input: LanguageModelOptions) => {
if ("auth" in input && input.auth) return input.auth
const gateway = Auth.optional(input.gatewayApiKey, "gatewayApiKey")
return Auth.optional(input.gatewayApiKey ?? ("apiKey" in input ? input.apiKey : undefined), "apiKey")
.orElse(Auth.config(authEnvVars[0]))
.orElse(Auth.config(authEnvVars[1]))
.pipe(Auth.bearerHeader("cf-aig-authorization"))
if (!("apiKey" in input) || input.apiKey === undefined) return gateway
if (input.gatewayApiKey === undefined) return Auth.bearer(input.apiKey)
return Auth.bearerHeader("cf-aig-authorization", input.gatewayApiKey).andThen(Auth.bearer(input.apiKey))
.bearer()
}

export const route = Route.make({
id: "cloudflare-ai-gateway",
provider: id,
providerMetadataKey: "cloudflare-ai-gateway",
protocol: OpenAIChat.protocol,
endpoint: Endpoint.path("/chat/completions"),
framing: OpenAIChat.framing,
const headers = (input: LanguageModelOptions) => ({
...(input.gatewayId === undefined ? {} : { "cf-aig-gateway-id": input.gatewayId.trim() || "default" }),
...(input.metadata === undefined
? {}
: { "cf-aig-metadata": Schema.encodeSync(Schema.fromJsonString(Schema.Unknown))(input.metadata) }),
...(input.cacheTtl === undefined ? {} : { "cf-aig-cache-ttl": String(input.cacheTtl) }),
...(input.cacheKey === undefined ? {} : { "cf-aig-cache-key": input.cacheKey }),
...(input.skipCache === undefined ? {} : { "cf-aig-skip-cache": String(input.skipCache) }),
...(input.collectLog === undefined ? {} : { "cf-aig-collect-log": String(input.collectLog) }),
...input.headers,
})

export const routes = [route]
const modelID = (input: string | ModelID) => {
const value = String(input)
if (value.startsWith("workers-ai/")) return value.slice("workers-ai/".length)
if (value.startsWith("anthropic/")) return `anthropic/${value.slice("anthropic/".length).replaceAll(".", "-")}`
return value
}

export const configure = (input: LanguageModelOptions) => {
const {
accountId: _accountId,
gatewayId: _gatewayId,
apiKey: _apiKey,
gatewayApiKey: _gatewayApiKey,
baseURL: _baseURL,
auth: _auth,
...defaults
} = input
const configured = route.with({
...defaults,
const defaults = {
endpoint: { baseURL: baseURL(input) },
auth: auth(input),
})
headers: headers(input),
http: input.http,
providerOptions: input.providerOptions,
}
const responses = responsesRoute.with(defaults)
const messages = messagesRoute.with(defaults)
const chat = route.with(defaults)
return {
id,
model: (modelID: string | ModelID) => configured.model<OpenAIProviderOptionsInput>({ id: modelID }),
model: (input: string | ModelID) => {
const wire = modelID(input)
if (String(input).startsWith("openai/")) return responses.model<OpenAIProviderOptionsInput>({ id: wire })
if (String(input).startsWith("anthropic/")) return messages.model<OpenAIProviderOptionsInput>({ id: wire })
return chat.model<OpenAIProviderOptionsInput>({ id: wire })
},
configure,
}
}

export const provider = { id, configure }

export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"] = (modelID, settings) => {
const {
accountId: _,
export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"] = (
modelID,
{
accountId,
apiKey,
baseURL: _url,
baseURL: configuredBaseURL,
body,
cacheKey,
cacheTtl,
collectLog,
gatewayApiKey,
gatewayId: _id,
gatewayId,
headers,
metadata,
skipCache,
...providerOptions
} = settings
},
) => {
const connection = configuredBaseURL === undefined ? { accountId: accountId ?? "" } : { baseURL: configuredBaseURL }
return configure({
...connection,
apiKey,
cacheKey,
cacheTtl,
collectLog,
gatewayApiKey,
baseURL: baseURL(settings),
gatewayId,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
metadata,
providerOptions,
skipCache,
}).model(modelID)
}

Expand Down
2 changes: 1 addition & 1 deletion packages/ai/src/providers/cloudflare-workers-ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ProviderConfigurationError, ProviderID, type ModelID } from "../schema/
import type { OpenAIProviderOptionsInput } from "./openai-options.js"

export const id = ProviderID.make("cloudflare-workers-ai")
export const authEnvVars = ["CLOUDFLARE_API_KEY", "CLOUDFLARE_WORKERS_AI_TOKEN"] as const
export const authEnvVars = ["CLOUDFLARE_API_KEY", "CLOUDFLARE_WORKERS_AI_TOKEN", "CLOUDFLARE_API_TOKEN"] as const

type WorkersAIURL = AtLeastOne<{
readonly accountId: string
Expand Down
Loading
Loading