import type { HarnessV1RequestTransformation } from '@ai-sdk/harness'; import { createCredentialRequestTransformation, getAiGatewayAuthFromEnv, } from '@ai-sdk/harness/utils'; export const OPENCODE_CREDENTIAL_ENVIRONMENT_VARIABLES = [ 'AI_GATEWAY_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', ] as const; export function createOpenCodeRequestTransformations( env: Record, auth: OpenCodeResolvedAuthenticationMode, ): HarnessV1RequestTransformation[] { switch (auth) { case 'ai-gateway': return env.AI_GATEWAY_API_KEY ? [ createCredentialRequestTransformation({ baseUrl: env.AI_GATEWAY_BASE_URL, headers: { Authorization: `Bearer ${env.AI_GATEWAY_API_KEY}`, }, }), ] : []; case 'openai': return env.OPENAI_API_KEY ? [ createCredentialRequestTransformation({ baseUrl: env.OPENAI_BASE_URL ?? 'https://api.openai.com/v1', headers: { Authorization: `Bearer ${env.OPENAI_API_KEY}` }, }), ] : []; case 'anthropic': { const headers: Record = {}; if (env.ANTHROPIC_API_KEY) { headers['x-api-key'] = env.ANTHROPIC_API_KEY; } if (env.ANTHROPIC_AUTH_TOKEN) { headers.Authorization = `Bearer ${env.ANTHROPIC_AUTH_TOKEN}`; } return Object.keys(headers).length === 0 ? [] : [ createCredentialRequestTransformation({ baseUrl: env.ANTHROPIC_BASE_URL ?? 'https://api.anthropic.com', headers, }), ]; } } } export type OpenCodeResolvedAuthenticationMode = | 'anthropic' | 'openai' | 'ai-gateway'; export type OpenCodeAuthenticationMode = | OpenCodeResolvedAuthenticationMode | 'auto'; /** * @deprecated Passing an object to auth options is deprecated. Use an `OpenCodeAuthenticationMode` string value ("auto" | "anthropic" | "openai" | "ai-gateway") instead, and pass credentials via environment variables. */ export type LegacyOpenCodeAuthOptions = { readonly gateway?: { readonly apiKey?: string; readonly baseUrl?: string; }; readonly anthropic?: { readonly apiKey?: string; readonly authToken?: string; readonly baseUrl?: string; }; readonly openai?: { readonly apiKey?: string; readonly baseUrl?: string; readonly organization?: string; readonly project?: string; }; readonly openaiCompatible?: { readonly apiKey?: string; readonly baseUrl?: string; readonly name?: string; readonly queryParams?: Record; }; }; export type OpenCodeAuthOptions = | OpenCodeAuthenticationMode | LegacyOpenCodeAuthOptions; export function resolveOpenCodeProvider({ model, provider, }: { model?: string; provider?: string; }): 'anthropic' | 'openai' { if (provider === 'anthropic' || provider === 'openai') { return provider; } if (model?.includes('/')) { const [modelProvider] = model.split('/'); if (modelProvider === 'anthropic' || modelProvider === 'openai') { return modelProvider; } } return 'anthropic'; } export function splitOpenCodeModel( model: string | undefined, provider: string | undefined, ): { providerID?: string; modelID?: string; model?: string } { if (!model) return {}; if (model.includes('/')) { const [providerID, ...rest] = model.split('/'); return { providerID, modelID: rest.join('/'), model, }; } return { providerID: provider, modelID: model, model: provider ? `${provider}/${model}` : model, }; } export function resolveOpenCodeEnv({ auth, model, provider, processEnv = process.env, }: { auth: OpenCodeAuthOptions | undefined; model?: string; provider?: string; processEnv?: Record; }): Record { const normalizedAuth = normalizeOpenCodeAuthToLegacyAuth(auth); const selectedProvider = resolveOpenCodeProvider({ model, provider }); if (normalizedAuth?.openaiCompatible) { return pickOpenAICompatible(normalizedAuth.openaiCompatible, processEnv); } if (selectedProvider === 'openai') { if (normalizedAuth?.openai) { return pickOpenAI({ explicit: normalizedAuth.openai, processEnv }); } } else if (normalizedAuth?.anthropic) { return pickAnthropic({ explicit: normalizedAuth.anthropic, processEnv }); } const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env: processEnv }); if (normalizedAuth?.gateway) { return pickGateway({ explicit: normalizedAuth.gateway, gatewayAuthFromEnv, }); } if (gatewayAuthFromEnv.apiKey) { return pickGateway({ explicit: {}, gatewayAuthFromEnv }); } return selectedProvider === 'openai' ? pickOpenAI({ processEnv }) : pickAnthropic({ processEnv }); } export function resolveOpenCodeAuthenticationMode({ auth, model, provider, processEnv = process.env, }: { auth: OpenCodeAuthOptions | undefined; model?: string; provider?: string; processEnv?: Record; }): OpenCodeResolvedAuthenticationMode { if (typeof auth !== 'string' && auth?.openaiCompatible) { return 'openai'; } const selectedProvider = resolveOpenCodeProvider({ model, provider }); if ( selectedProvider === 'openai' && (auth === 'openai' || (typeof auth !== 'string' && auth?.openai)) ) { return 'openai'; } if ( selectedProvider === 'anthropic' && (auth === 'anthropic' || (typeof auth !== 'string' && auth?.anthropic)) ) { return 'anthropic'; } if (auth === 'ai-gateway' || (typeof auth !== 'string' && auth?.gateway)) { return 'ai-gateway'; } if (getAiGatewayAuthFromEnv({ env: processEnv }).apiKey) { return 'ai-gateway'; } return selectedProvider; } function normalizeOpenCodeAuthToLegacyAuth( auth: OpenCodeAuthOptions | undefined, ): LegacyOpenCodeAuthOptions | undefined { if (auth == null || auth === 'auto') { return undefined; } if (typeof auth === 'string') { switch (auth) { case 'anthropic': return { anthropic: {} }; case 'openai': return { openai: {} }; case 'ai-gateway': return { gateway: {} }; default: return undefined; } } console.warn( '[opencode] Passing an object to auth options is deprecated. Use a string mode ("auto" | "anthropic" | "openai" | "ai-gateway") instead, and pass credentials via environment variables.', ); return auth; } function pickOpenAICompatible( explicit: NonNullable, processEnv: Record, ): Record { const env: Record = {}; const apiKey = explicit.apiKey ?? processEnv.OPENAI_API_KEY; if (apiKey) env.OPENAI_API_KEY = apiKey; const baseUrl = explicit.baseUrl ?? processEnv.OPENAI_BASE_URL; if (baseUrl) env.OPENAI_BASE_URL = baseUrl; const name = explicit.name ?? processEnv.OPENAI_NAME; if (name) env.OPENAI_NAME = name; if (explicit.queryParams && Object.keys(explicit.queryParams).length > 0) { env.OPENAI_QUERY_PARAMS_JSON = JSON.stringify(explicit.queryParams); } else if (processEnv.OPENAI_QUERY_PARAMS_JSON) { env.OPENAI_QUERY_PARAMS_JSON = processEnv.OPENAI_QUERY_PARAMS_JSON; } return env; } function pickOpenAI({ explicit, processEnv, }: { explicit?: NonNullable; processEnv: Record; }): Record { const env: Record = {}; const apiKey = explicit?.apiKey ?? processEnv.OPENAI_API_KEY; if (apiKey) env.OPENAI_API_KEY = apiKey; const baseUrl = explicit?.baseUrl ?? processEnv.OPENAI_BASE_URL; if (baseUrl) env.OPENAI_BASE_URL = baseUrl; const organization = explicit?.organization ?? processEnv.OPENAI_ORGANIZATION; if (organization) env.OPENAI_ORGANIZATION = organization; const project = explicit?.project ?? processEnv.OPENAI_PROJECT; if (project) env.OPENAI_PROJECT = project; return env; } function pickAnthropic({ explicit, processEnv, }: { explicit?: NonNullable; processEnv: Record; }): Record { const env: Record = {}; const apiKey = explicit?.apiKey ?? processEnv.ANTHROPIC_API_KEY; if (apiKey) env.ANTHROPIC_API_KEY = apiKey; const authToken = explicit?.authToken ?? processEnv.ANTHROPIC_AUTH_TOKEN; if (authToken) env.ANTHROPIC_AUTH_TOKEN = authToken; const baseUrl = explicit?.baseUrl ?? processEnv.ANTHROPIC_BASE_URL; if (baseUrl) env.ANTHROPIC_BASE_URL = baseUrl; return env; } function pickGateway({ explicit, gatewayAuthFromEnv, }: { explicit: NonNullable; gatewayAuthFromEnv: ReturnType; }): Record { const env: Record = {}; const apiKey = explicit.apiKey ?? gatewayAuthFromEnv.apiKey; if (apiKey) env.AI_GATEWAY_API_KEY = apiKey; const baseUrl = toOpenCodeGatewayBaseUrl( explicit.baseUrl ?? gatewayAuthFromEnv.baseUrl, ); env.AI_GATEWAY_BASE_URL = baseUrl; return env; } export function toOpenCodeGatewayBaseUrl(baseUrl: string): string { const trimmed = baseUrl.replace(/\/+$/, ''); return trimmed.endsWith('/v1') ? trimmed : `${trimmed}/v1`; }