2026-02-18 01:29:02 +00:00
import path from "node:path" ;
2026-02-18 01:34:35 +00:00
import { type Api , getEnvApiKey , type Model } from "@mariozechner/pi-ai" ;
import { formatCliCommand } from "../cli/command-format.js" ;
2026-01-30 03:15:10 +01:00
import type { OpenClawConfig } from "../config/config.js" ;
2026-01-20 07:53:25 +00:00
import type { ModelProviderAuthMode , ModelProviderConfig } from "../config/types.js" ;
2026-02-01 10:03:47 +09:00
import { getShellEnvAppliedKeys } from "../infra/shell-env.js" ;
2026-03-11 01:38:49 +08:00
import { createSubsystemLogger } from "../logging/subsystem.js" ;
2026-02-09 11:25:54 -06:00
import {
normalizeOptionalSecretInput ,
normalizeSecretInput ,
} from "../utils/normalize-secret-input.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-03-07 11:28:39 -06:00
import { PROVIDER_ENV_API_KEY_CANDIDATES } from "./model-auth-env-vars.js" ;
2026-03-10 18:46:47 -05:00
import {
isKnownEnvApiKeyMarker ,
isNonSecretApiKeyMarker ,
OLLAMA_LOCAL_AUTH_MARKER ,
} from "./model-auth-markers.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
2026-03-11 01:38:49 +08:00
const log = createSubsystemLogger ( "model-auth" ) ;
2026-01-20 07:53:25 +00:00
const AWS_BEARER_ENV = "AWS_BEARER_TOKEN_BEDROCK" ;
const AWS_ACCESS_KEY_ENV = "AWS_ACCESS_KEY_ID" ;
const AWS_SECRET_KEY_ENV = "AWS_SECRET_ACCESS_KEY" ;
const AWS_PROFILE_ENV = "AWS_PROFILE" ;
function resolveProviderConfig (
2026-01-30 03:15:10 +01:00
cfg : OpenClawConfig | undefined ,
2026-01-20 07:53:25 +00:00
provider : string ,
) : ModelProviderConfig | undefined {
const providers = cfg ? . models ? . providers ? ? { } ;
const direct = providers [ provider ] as ModelProviderConfig | undefined ;
2026-01-31 16:19:20 +09:00
if ( direct ) {
return direct ;
}
2026-01-20 07:53:25 +00:00
const normalized = normalizeProviderId ( provider ) ;
if ( normalized === provider ) {
const matched = Object . entries ( providers ) . find (
( [ key ] ) = > normalizeProviderId ( key ) === normalized ,
) ;
2026-01-31 16:03:28 +09:00
return matched ? . [ 1 ] ;
2026-01-20 07:53:25 +00:00
}
return (
( providers [ normalized ] as ModelProviderConfig | undefined ) ? ?
2026-01-31 16:03:28 +09:00
Object . entries ( providers ) . find ( ( [ key ] ) = > normalizeProviderId ( key ) === normalized ) ? . [ 1 ]
2026-01-20 07:53:25 +00:00
) ;
}
2026-01-06 00:56:29 +00:00
export function getCustomProviderApiKey (
2026-01-30 03:15:10 +01:00
cfg : OpenClawConfig | undefined ,
2026-01-06 00:56:29 +00:00
provider : string ,
) : string | undefined {
2026-01-20 07:53:25 +00:00
const entry = resolveProviderConfig ( cfg , provider ) ;
2026-02-09 11:25:54 -06:00
return normalizeOptionalSecretInput ( entry ? . apiKey ) ;
2026-01-04 19:35:00 +01:00
}
2026-03-10 18:46:47 -05:00
type ResolvedCustomProviderApiKey = {
apiKey : string ;
source : string ;
} ;
export function resolveUsableCustomProviderApiKey ( params : {
cfg : OpenClawConfig | undefined ;
provider : string ;
env? : NodeJS.ProcessEnv ;
} ) : ResolvedCustomProviderApiKey | null {
const customKey = getCustomProviderApiKey ( params . cfg , params . provider ) ;
if ( ! customKey ) {
return null ;
}
if ( ! isNonSecretApiKeyMarker ( customKey ) ) {
return { apiKey : customKey , source : "models.json" } ;
}
if ( ! isKnownEnvApiKeyMarker ( customKey ) ) {
return null ;
}
const envValue = normalizeOptionalSecretInput ( ( params . env ? ? process . env ) [ customKey ] ) ;
if ( ! envValue ) {
return null ;
}
const applied = new Set ( getShellEnvAppliedKeys ( ) ) ;
return {
apiKey : envValue ,
source : resolveEnvSourceLabel ( {
applied ,
envVars : [ customKey ] ,
label : ` ${ customKey } (models.json marker) ` ,
} ) ,
} ;
}
export function hasUsableCustomProviderApiKey (
cfg : OpenClawConfig | undefined ,
provider : string ,
env? : NodeJS.ProcessEnv ,
) : boolean {
return Boolean ( resolveUsableCustomProviderApiKey ( { cfg , provider , env } ) ) ;
}
2026-01-20 07:53:25 +00:00
function resolveProviderAuthOverride (
2026-01-30 03:15:10 +01:00
cfg : OpenClawConfig | undefined ,
2026-01-20 07:53:25 +00:00
provider : string ,
) : ModelProviderAuthMode | undefined {
const entry = resolveProviderConfig ( cfg , provider ) ;
const auth = entry ? . auth ;
if ( auth === "api-key" || auth === "aws-sdk" || auth === "oauth" || auth === "token" ) {
return auth ;
}
return undefined ;
}
2026-03-05 20:13:26 -08:00
function resolveSyntheticLocalProviderAuth ( params : {
cfg : OpenClawConfig | undefined ;
provider : string ;
} ) : ResolvedProviderAuth | null {
const normalizedProvider = normalizeProviderId ( params . provider ) ;
if ( normalizedProvider !== "ollama" ) {
return null ;
}
const providerConfig = resolveProviderConfig ( params . cfg , params . provider ) ;
if ( ! providerConfig ) {
return null ;
}
const hasApiConfig =
Boolean ( providerConfig . api ? . trim ( ) ) ||
Boolean ( providerConfig . baseUrl ? . trim ( ) ) ||
( Array . isArray ( providerConfig . models ) && providerConfig . models . length > 0 ) ;
if ( ! hasApiConfig ) {
return null ;
}
return {
2026-03-07 11:28:39 -06:00
apiKey : OLLAMA_LOCAL_AUTH_MARKER ,
2026-03-05 20:13:26 -08:00
source : "models.providers.ollama (synthetic local key)" ,
mode : "api-key" ,
} ;
}
2026-01-20 07:53:25 +00:00
function resolveEnvSourceLabel ( params : {
applied : Set < string > ;
envVars : string [ ] ;
label : string ;
} ) : string {
const shellApplied = params . envVars . some ( ( envVar ) = > params . applied . has ( envVar ) ) ;
const prefix = shellApplied ? "shell env: " : "env: " ;
return ` ${ prefix } ${ params . label } ` ;
}
2026-01-24 01:14:32 +00:00
export function resolveAwsSdkEnvVarName ( env : NodeJS.ProcessEnv = process . env ) : string | undefined {
2026-01-31 16:19:20 +09:00
if ( env [ AWS_BEARER_ENV ] ? . trim ( ) ) {
return AWS_BEARER_ENV ;
}
2026-01-24 01:14:32 +00:00
if ( env [ AWS_ACCESS_KEY_ENV ] ? . trim ( ) && env [ AWS_SECRET_KEY_ENV ] ? . trim ( ) ) {
2026-01-20 07:53:25 +00:00
return AWS_ACCESS_KEY_ENV ;
}
2026-01-31 16:19:20 +09:00
if ( env [ AWS_PROFILE_ENV ] ? . trim ( ) ) {
return AWS_PROFILE_ENV ;
}
2026-01-20 07:53:25 +00:00
return undefined ;
}
function resolveAwsSdkAuthInfo ( ) : { mode : "aws-sdk" ; source : string } {
const applied = new Set ( getShellEnvAppliedKeys ( ) ) ;
if ( process . env [ AWS_BEARER_ENV ] ? . trim ( ) ) {
return {
mode : "aws-sdk" ,
source : resolveEnvSourceLabel ( {
applied ,
envVars : [ AWS_BEARER_ENV ] ,
label : AWS_BEARER_ENV ,
} ) ,
} ;
}
if ( process . env [ AWS_ACCESS_KEY_ENV ] ? . trim ( ) && process . env [ AWS_SECRET_KEY_ENV ] ? . trim ( ) ) {
return {
mode : "aws-sdk" ,
source : resolveEnvSourceLabel ( {
applied ,
envVars : [ AWS_ACCESS_KEY_ENV , AWS_SECRET_KEY_ENV ] ,
label : ` ${ AWS_ACCESS_KEY_ENV } + ${ AWS_SECRET_KEY_ENV } ` ,
} ) ,
} ;
}
if ( process . env [ AWS_PROFILE_ENV ] ? . trim ( ) ) {
return {
mode : "aws-sdk" ,
source : resolveEnvSourceLabel ( {
applied ,
envVars : [ AWS_PROFILE_ENV ] ,
label : AWS_PROFILE_ENV ,
} ) ,
} ;
}
return { mode : "aws-sdk" , source : "aws-sdk default chain" } ;
}
export type ResolvedProviderAuth = {
apiKey? : string ;
profileId? : string ;
source : string ;
mode : "api-key" | "oauth" | "token" | "aws-sdk" ;
} ;
2026-01-06 00:56:29 +00:00
export async function resolveApiKeyForProvider ( params : {
provider : string ;
2026-01-30 03:15:10 +01:00
cfg? : OpenClawConfig ;
2026-01-06 00:56:29 +00:00
profileId? : string ;
preferredProfile? : string ;
store? : AuthProfileStore ;
2026-01-06 18:25:37 +00:00
agentDir? : string ;
2026-01-20 07:53:25 +00:00
} ) : Promise < ResolvedProviderAuth > {
2026-01-06 00:56:29 +00:00
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 } ". ` ) ;
}
2026-01-20 07:53:25 +00:00
const mode = store . profiles [ profileId ] ? . type ;
2026-01-06 00:56:29 +00:00
return {
apiKey : resolved.apiKey ,
profileId ,
source : ` profile: ${ profileId } ` ,
2026-01-20 07:53:25 +00:00
mode : mode === "oauth" ? "oauth" : mode === "token" ? "token" : "api-key" ,
2026-01-06 00:56:29 +00:00
} ;
2026-01-04 19:35:00 +01:00
}
2026-01-20 07:53:25 +00:00
const authOverride = resolveProviderAuthOverride ( cfg , provider ) ;
if ( authOverride === "aws-sdk" ) {
return resolveAwsSdkAuthInfo ( ) ;
}
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 ) {
2026-01-20 07:53:25 +00:00
const mode = store . profiles [ candidate ] ? . type ;
2026-01-06 00:56:29 +00:00
return {
apiKey : resolved.apiKey ,
profileId : candidate ,
source : ` profile: ${ candidate } ` ,
2026-01-20 07:53:25 +00:00
mode : mode === "oauth" ? "oauth" : mode === "token" ? "token" : "api-key" ,
2026-01-06 00:56:29 +00:00
} ;
2026-01-04 19:35:00 +01:00
}
2026-03-11 01:38:49 +08:00
} catch ( err ) {
log . debug ? . ( ` auth profile " ${ candidate } " failed for provider " ${ provider } ": ${ String ( err ) } ` ) ;
}
2026-01-06 00:56:29 +00:00
}
2026-01-04 19:35:00 +01:00
2026-01-06 00:56:29 +00:00
const envResolved = resolveEnvApiKey ( provider ) ;
if ( envResolved ) {
2026-01-20 07:53:25 +00:00
return {
apiKey : envResolved.apiKey ,
source : envResolved.source ,
mode : envResolved.source.includes ( "OAUTH_TOKEN" ) ? "oauth" : "api-key" ,
} ;
2026-01-04 19:35:00 +01:00
}
2026-03-10 18:46:47 -05:00
const customKey = resolveUsableCustomProviderApiKey ( { cfg , provider } ) ;
2026-01-06 00:56:29 +00:00
if ( customKey ) {
2026-03-10 18:46:47 -05:00
return { apiKey : customKey.apiKey , source : customKey.source , mode : "api-key" } ;
2026-01-20 07:53:25 +00:00
}
2026-03-05 20:13:26 -08:00
const syntheticLocalAuth = resolveSyntheticLocalProviderAuth ( { cfg , provider } ) ;
if ( syntheticLocalAuth ) {
return syntheticLocalAuth ;
}
2026-01-20 07:53:25 +00:00
const normalized = normalizeProviderId ( provider ) ;
if ( authOverride === undefined && normalized === "amazon-bedrock" ) {
return resolveAwsSdkAuthInfo ( ) ;
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 (
2026-03-06 08:01:37 +03:00
'No API key found for provider "openai". You are authenticated with OpenAI Codex OAuth. Use openai-codex/gpt-5.4 (OAuth) or set OPENAI_API_KEY to use openai/gpt-5.4.' ,
2026-01-06 02:48:53 +01:00
) ;
}
}
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 } ). ` ,
2026-01-30 03:15:10 +01:00
` Configure auth for this agent ( ${ formatCliCommand ( "openclaw agents add <id>" ) } ) or copy auth-profiles.json from the main agentDir. ` ,
2026-01-15 04:41:50 +00:00
] . 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-20 07:53:25 +00:00
export type ModelAuthMode = "api-key" | "oauth" | "token" | "mixed" | "aws-sdk" | "unknown" ;
2026-01-06 00:56:29 +00:00
2026-03-08 16:22:01 +00:00
export function resolveEnvApiKey (
provider : string ,
env : NodeJS.ProcessEnv = process . env ,
) : 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 = > {
2026-03-08 16:22:01 +00:00
const value = normalizeOptionalSecretInput ( env [ envVar ] ) ;
2026-01-31 16:19:20 +09:00
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-03-07 11:28:39 -06:00
const candidates = PROVIDER_ENV_API_KEY_CANDIDATES [ normalized ] ;
if ( candidates ) {
for ( const envVar of candidates ) {
const resolved = pick ( envVar ) ;
if ( resolved ) {
return resolved ;
}
}
2026-01-10 19:45:51 +00:00
}
2026-01-10 21:37:38 +01:00
if ( normalized === "google-vertex" ) {
const envKey = getEnvApiKey ( normalized ) ;
2026-01-31 16:19:20 +09:00
if ( ! envKey ) {
return null ;
}
2026-01-06 00:56:29 +00:00
return { apiKey : envKey , source : "gcloud adc" } ;
}
2026-03-07 11:28:39 -06:00
return null ;
2026-01-04 19:35:00 +01:00
}
2026-01-09 02:21:17 +00:00
export function resolveModelAuthMode (
provider? : string ,
2026-01-30 03:15:10 +01:00
cfg? : OpenClawConfig ,
2026-01-09 02:21:17 +00:00
store? : AuthProfileStore ,
) : ModelAuthMode | undefined {
const resolved = provider ? . trim ( ) ;
2026-01-31 16:19:20 +09:00
if ( ! resolved ) {
return undefined ;
}
2026-01-09 02:21:17 +00:00
2026-01-20 07:53:25 +00:00
const authOverride = resolveProviderAuthOverride ( cfg , resolved ) ;
2026-01-31 16:19:20 +09:00
if ( authOverride === "aws-sdk" ) {
return "aws-sdk" ;
}
2026-01-20 07:53:25 +00:00
2026-01-09 02:21:17 +00:00
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" ) ,
) ;
2026-01-31 16:19:20 +09:00
if ( distinct . length >= 2 ) {
return "mixed" ;
}
if ( modes . has ( "oauth" ) ) {
return "oauth" ;
}
if ( modes . has ( "token" ) ) {
return "token" ;
}
if ( modes . has ( "api_key" ) ) {
return "api-key" ;
}
2026-01-09 02:21:17 +00:00
}
2026-01-20 07:53:25 +00:00
if ( authOverride === undefined && normalizeProviderId ( resolved ) === "amazon-bedrock" ) {
return "aws-sdk" ;
}
2026-01-09 02:21:17 +00:00
const envKey = resolveEnvApiKey ( resolved ) ;
if ( envKey ? . apiKey ) {
return envKey . source . includes ( "OAUTH_TOKEN" ) ? "oauth" : "api-key" ;
}
2026-03-10 18:46:47 -05:00
if ( hasUsableCustomProviderApiKey ( cfg , resolved ) ) {
2026-01-31 16:19:20 +09:00
return "api-key" ;
}
2026-01-09 02:21:17 +00:00
return "unknown" ;
}
2026-01-06 00:56:29 +00:00
export async function getApiKeyForModel ( params : {
model : Model < Api > ;
2026-01-30 03:15:10 +01:00
cfg? : OpenClawConfig ;
2026-01-06 00:56:29 +00:00
profileId? : string ;
preferredProfile? : string ;
store? : AuthProfileStore ;
2026-01-06 18:25:37 +00:00
agentDir? : string ;
2026-01-20 07:53:25 +00:00
} ) : Promise < ResolvedProviderAuth > {
2026-01-06 00:56:29 +00:00
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
}
2026-01-20 07:53:25 +00:00
export function requireApiKey ( auth : ResolvedProviderAuth , provider : string ) : string {
2026-02-09 11:25:54 -06:00
const key = normalizeSecretInput ( auth . apiKey ) ;
2026-01-31 16:19:20 +09:00
if ( key ) {
return key ;
}
2026-01-20 13:52:59 +00:00
throw new Error ( ` No API key resolved for provider " ${ provider } " (auth mode: ${ auth . mode } ). ` ) ;
2026-01-20 07:53:25 +00:00
}