2026-01-15 04:41:50 +00:00
import path from "node:path" ;
2026-01-06 00:56:29 +00:00
import { type Api , getEnvApiKey , type Model } from "@mariozechner/pi-ai" ;
import type { ClawdbotConfig } from "../config/config.js" ;
import type { ModelProviderConfig } from "../config/types.js" ;
import { getShellEnvAppliedKeys } from "../infra/shell-env.js" ;
2026-01-04 19:35:00 +01:00
import {
2026-01-06 00:56:29 +00:00
type AuthProfileStore ,
ensureAuthProfileStore ,
2026-01-06 02:48:53 +01:00
listProfilesForProvider ,
2026-01-06 00:56:29 +00:00
resolveApiKeyForProfile ,
resolveAuthProfileOrder ,
2026-01-15 04:41:50 +00:00
resolveAuthStorePathForDisplay ,
2026-01-06 00:56:29 +00:00
} from "./auth-profiles.js" ;
2026-01-10 20:55:50 +00:00
import { normalizeProviderId } from "./model-selection.js" ;
2026-01-06 00:56:29 +00:00
2026-01-14 14:31:43 +00:00
export { ensureAuthProfileStore , resolveAuthProfileOrder } from "./auth-profiles.js" ;
2026-01-06 00:56:29 +00:00
export function getCustomProviderApiKey (
cfg : ClawdbotConfig | undefined ,
provider : string ,
) : string | undefined {
const providers = cfg ? . models ? . providers ? ? { } ;
const entry = providers [ provider ] as ModelProviderConfig | undefined ;
const key = entry ? . apiKey ? . trim ( ) ;
return key || undefined ;
2026-01-04 19:35:00 +01:00
}
2026-01-06 00:56:29 +00:00
export async function resolveApiKeyForProvider ( params : {
provider : string ;
cfg? : ClawdbotConfig ;
profileId? : string ;
preferredProfile? : string ;
store? : AuthProfileStore ;
2026-01-06 18:25:37 +00:00
agentDir? : string ;
2026-01-06 00:56:29 +00:00
} ) : Promise < { apiKey : string ; profileId? : string ; source : string } > {
const { provider , cfg , profileId , preferredProfile } = params ;
2026-01-06 18:25:37 +00:00
const store = params . store ? ? ensureAuthProfileStore ( params . agentDir ) ;
2026-01-06 00:56:29 +00:00
if ( profileId ) {
const resolved = await resolveApiKeyForProfile ( {
cfg ,
store ,
profileId ,
2026-01-06 18:25:37 +00:00
agentDir : params.agentDir ,
2026-01-06 00:56:29 +00:00
} ) ;
if ( ! resolved ) {
throw new Error ( ` No credentials found for profile " ${ profileId } ". ` ) ;
}
return {
apiKey : resolved.apiKey ,
profileId ,
source : ` profile: ${ profileId } ` ,
} ;
2026-01-04 19:35:00 +01:00
}
2026-01-06 00:56:29 +00:00
const order = resolveAuthProfileOrder ( {
cfg ,
store ,
provider ,
preferredProfile ,
} ) ;
for ( const candidate of order ) {
try {
const resolved = await resolveApiKeyForProfile ( {
cfg ,
store ,
profileId : candidate ,
2026-01-06 18:25:37 +00:00
agentDir : params.agentDir ,
2026-01-06 00:56:29 +00:00
} ) ;
if ( resolved ) {
return {
apiKey : resolved.apiKey ,
profileId : candidate ,
source : ` profile: ${ candidate } ` ,
} ;
2026-01-04 19:35:00 +01:00
}
2026-01-06 00:56:29 +00:00
} catch { }
}
2026-01-04 19:35:00 +01:00
2026-01-06 00:56:29 +00:00
const envResolved = resolveEnvApiKey ( provider ) ;
if ( envResolved ) {
return { apiKey : envResolved.apiKey , source : envResolved.source } ;
2026-01-04 19:35:00 +01:00
}
2026-01-06 00:56:29 +00:00
const customKey = getCustomProviderApiKey ( cfg , provider ) ;
if ( customKey ) {
return { apiKey : customKey , source : "models.json" } ;
2026-01-04 19:35:00 +01:00
}
2026-01-06 02:48:53 +01:00
if ( provider === "openai" ) {
const hasCodex = listProfilesForProvider ( store , "openai-codex" ) . length > 0 ;
if ( hasCodex ) {
throw new Error (
'No API key found for provider "openai". You are authenticated with OpenAI Codex OAuth. Use openai-codex/gpt-5.2 (ChatGPT OAuth) or set OPENAI_API_KEY for openai/gpt-5.2.' ,
) ;
}
}
2026-01-15 04:41:50 +00:00
const authStorePath = resolveAuthStorePathForDisplay ( params . agentDir ) ;
const resolvedAgentDir = path . dirname ( authStorePath ) ;
throw new Error (
[
` No API key found for provider " ${ provider } ". ` ,
` Auth store: ${ authStorePath } (agentDir: ${ resolvedAgentDir } ). ` ,
"Configure auth for this agent (clawdbot agents add <id>) or copy auth-profiles.json from the main agentDir." ,
] . join ( " " ) ,
) ;
2026-01-04 19:35:00 +01:00
}
2026-01-06 00:56:29 +00:00
export type EnvApiKeyResult = { apiKey : string ; source : string } ;
2026-01-09 07:51:47 +01:00
export type ModelAuthMode = "api-key" | "oauth" | "token" | "mixed" | "unknown" ;
2026-01-06 00:56:29 +00:00
export function resolveEnvApiKey ( provider : string ) : EnvApiKeyResult | null {
2026-01-10 21:37:38 +01:00
const normalized = normalizeProviderId ( provider ) ;
2026-01-06 00:56:29 +00:00
const applied = new Set ( getShellEnvAppliedKeys ( ) ) ;
const pick = ( envVar : string ) : EnvApiKeyResult | null = > {
const value = process . env [ envVar ] ? . trim ( ) ;
if ( ! value ) return null ;
2026-01-14 14:31:43 +00:00
const source = applied . has ( envVar ) ? ` shell env: ${ envVar } ` : ` env: ${ envVar } ` ;
2026-01-06 00:56:29 +00:00
return { apiKey : value , source } ;
} ;
2026-01-10 21:37:38 +01:00
if ( normalized === "github-copilot" ) {
2026-01-14 14:31:43 +00:00
return pick ( "COPILOT_GITHUB_TOKEN" ) ? ? pick ( "GH_TOKEN" ) ? ? pick ( "GITHUB_TOKEN" ) ;
2026-01-06 00:56:29 +00:00
}
2026-01-05 06:31:45 +01:00
2026-01-10 21:37:38 +01:00
if ( normalized === "anthropic" ) {
2026-01-06 00:56:29 +00:00
return pick ( "ANTHROPIC_OAUTH_TOKEN" ) ? ? pick ( "ANTHROPIC_API_KEY" ) ;
2026-01-05 06:31:45 +01:00
}
2026-01-11 15:06:54 +01:00
if ( normalized === "chutes" ) {
return pick ( "CHUTES_OAUTH_TOKEN" ) ? ? pick ( "CHUTES_API_KEY" ) ;
}
2026-01-10 21:37:38 +01:00
if ( normalized === "zai" ) {
2026-01-10 19:45:51 +00:00
return pick ( "ZAI_API_KEY" ) ? ? pick ( "Z_AI_API_KEY" ) ;
}
2026-01-10 21:37:38 +01:00
if ( normalized === "google-vertex" ) {
const envKey = getEnvApiKey ( normalized ) ;
2026-01-06 00:56:29 +00:00
if ( ! envKey ) return null ;
return { apiKey : envKey , source : "gcloud adc" } ;
}
2026-01-05 13:49:25 +00:00
2026-01-10 21:37:38 +01:00
if ( normalized === "opencode" ) {
return pick ( "OPENCODE_API_KEY" ) ? ? pick ( "OPENCODE_ZEN_API_KEY" ) ;
}
2026-01-06 00:56:29 +00:00
const envMap : Record < string , string > = {
openai : "OPENAI_API_KEY" ,
google : "GEMINI_API_KEY" ,
groq : "GROQ_API_KEY" ,
cerebras : "CEREBRAS_API_KEY" ,
xai : "XAI_API_KEY" ,
openrouter : "OPENROUTER_API_KEY" ,
2026-01-12 06:47:52 +00:00
moonshot : "MOONSHOT_API_KEY" ,
2026-01-08 15:10:18 +01:00
minimax : "MINIMAX_API_KEY" ,
2026-01-13 00:22:03 +00:00
synthetic : "SYNTHETIC_API_KEY" ,
2026-01-06 00:56:29 +00:00
mistral : "MISTRAL_API_KEY" ,
2026-01-10 21:37:38 +01:00
opencode : "OPENCODE_API_KEY" ,
2026-01-06 00:56:29 +00:00
} ;
2026-01-10 21:37:38 +01:00
const envVar = envMap [ normalized ] ;
2026-01-06 00:56:29 +00:00
if ( ! envVar ) return null ;
return pick ( envVar ) ;
2026-01-04 19:35:00 +01:00
}
2026-01-09 02:21:17 +00:00
export function resolveModelAuthMode (
provider? : string ,
cfg? : ClawdbotConfig ,
store? : AuthProfileStore ,
) : ModelAuthMode | undefined {
const resolved = provider ? . trim ( ) ;
if ( ! resolved ) return undefined ;
const authStore = store ? ? ensureAuthProfileStore ( ) ;
const profiles = listProfilesForProvider ( authStore , resolved ) ;
if ( profiles . length > 0 ) {
const modes = new Set (
profiles
. map ( ( id ) = > authStore . profiles [ id ] ? . type )
2026-01-09 07:51:47 +01:00
. filter ( ( mode ) : mode is "api_key" | "oauth" | "token" = > Boolean ( mode ) ) ,
2026-01-09 02:21:17 +00:00
) ;
2026-01-09 07:51:47 +01:00
const distinct = [ "oauth" , "token" , "api_key" ] . filter ( ( k ) = >
modes . has ( k as "oauth" | "token" | "api_key" ) ,
) ;
if ( distinct . length >= 2 ) return "mixed" ;
2026-01-09 02:21:17 +00:00
if ( modes . has ( "oauth" ) ) return "oauth" ;
2026-01-09 07:51:47 +01:00
if ( modes . has ( "token" ) ) return "token" ;
2026-01-09 02:21:17 +00:00
if ( modes . has ( "api_key" ) ) return "api-key" ;
}
const envKey = resolveEnvApiKey ( resolved ) ;
if ( envKey ? . apiKey ) {
return envKey . source . includes ( "OAUTH_TOKEN" ) ? "oauth" : "api-key" ;
}
if ( getCustomProviderApiKey ( cfg , resolved ) ) return "api-key" ;
return "unknown" ;
}
2026-01-06 00:56:29 +00:00
export async function getApiKeyForModel ( params : {
model : Model < Api > ;
cfg? : ClawdbotConfig ;
profileId? : string ;
preferredProfile? : string ;
store? : AuthProfileStore ;
2026-01-06 18:25:37 +00:00
agentDir? : string ;
2026-01-06 00:56:29 +00:00
} ) : Promise < { apiKey : string ; profileId? : string ; source : string } > {
return resolveApiKeyForProvider ( {
provider : params.model.provider ,
cfg : params.cfg ,
profileId : params.profileId ,
preferredProfile : params.preferredProfile ,
store : params.store ,
2026-01-06 18:25:37 +00:00
agentDir : params.agentDir ,
2026-01-06 00:56:29 +00:00
} ) ;
2026-01-04 19:35:00 +01:00
}