Skip to content

Commit c280aa9

Browse files
elithrarmvclaudianobj
authored andcommitted
fix(opencode): use official ai-gateway-provider package for Cloudflare AI Gateway (anomalyco#12014)
1 parent bdca631 commit c280aa9

3 files changed

Lines changed: 50 additions & 36 deletions

File tree

bun.lock

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/opencode/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"@standard-schema/spec": "1.0.0",
9393
"@zip.js/zip.js": "2.7.62",
9494
"ai": "catalog:",
95+
"ai-gateway-provider": "2.3.1",
9596
"bonjour-service": "1.3.0",
9697
"bun-pty": "0.4.4",
9798
"chokidar": "4.0.3",

packages/opencode/src/provider/provider.ts

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -461,52 +461,36 @@ export namespace Provider {
461461

462462
if (!accountId || !gateway) return { autoload: false }
463463

464-
// Get API token from env or auth prompt
464+
// Get API token from env or auth - required for authenticated gateways
465465
const apiToken = await (async () => {
466-
const envToken = Env.get("CLOUDFLARE_API_TOKEN")
466+
const envToken = Env.get("CLOUDFLARE_API_TOKEN") || Env.get("CF_AIG_TOKEN")
467467
if (envToken) return envToken
468468
const auth = await Auth.get(input.id)
469469
if (auth?.type === "api") return auth.key
470470
return undefined
471471
})()
472472

473+
if (!apiToken) {
474+
throw new Error(
475+
"CLOUDFLARE_API_TOKEN (or CF_AIG_TOKEN) is required for Cloudflare AI Gateway. " +
476+
"Set it via environment variable or run `opencode auth cloudflare-ai-gateway`.",
477+
)
478+
}
479+
480+
// Use official ai-gateway-provider package (v2.x for AI SDK v5 compatibility)
481+
const { createAiGateway } = await import("ai-gateway-provider")
482+
const { createUnified } = await import("ai-gateway-provider/providers/unified")
483+
484+
const aigateway = createAiGateway({ accountId, gateway, apiKey: apiToken })
485+
const unified = createUnified()
486+
473487
return {
474488
autoload: true,
475-
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
476-
return sdk.languageModel(modelID)
477-
},
478-
options: {
479-
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gateway}/compat`,
480-
headers: {
481-
// Cloudflare AI Gateway uses cf-aig-authorization for authenticated gateways
482-
// This enables Unified Billing where Cloudflare handles upstream provider auth
483-
...(apiToken ? { "cf-aig-authorization": `Bearer ${apiToken}` } : {}),
484-
"HTTP-Referer": "https://opencode.ai/",
485-
"X-Title": "opencode",
486-
},
487-
// Custom fetch to handle parameter transformation and auth
488-
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
489-
const headers = new Headers(init?.headers)
490-
// Strip Authorization header - AI Gateway uses cf-aig-authorization instead
491-
headers.delete("Authorization")
492-
493-
// Transform max_tokens to max_completion_tokens for newer models
494-
if (init?.body && init.method === "POST") {
495-
try {
496-
const body = JSON.parse(init.body as string)
497-
if (body.max_tokens !== undefined && !body.max_completion_tokens) {
498-
body.max_completion_tokens = body.max_tokens
499-
delete body.max_tokens
500-
init = { ...init, body: JSON.stringify(body) }
501-
}
502-
} catch (e) {
503-
// If body parsing fails, continue with original request
504-
}
505-
}
506-
507-
return fetch(input, { ...init, headers })
508-
},
489+
async getModel(_sdk: any, modelID: string, _options?: Record<string, any>) {
490+
// Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5")
491+
return aigateway(unified(modelID))
509492
},
493+
options: {},
510494
}
511495
},
512496
cerebras: async () => {

0 commit comments

Comments
 (0)