@@ -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