2026-01-06 00:56:29 +00:00
import { type Api , getEnvApiKey , type Model } from "@mariozechner/pi-ai" ;
2026-02-01 10:03:47 +09:00
import path from "node:path" ;
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-01-20 07:42:21 +00:00
import { formatCliCommand } from "../cli/command-format.js" ;
2026-02-01 10:03:47 +09:00
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
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-01-06 00:56:29 +00:00
const key = entry ? . apiKey ? . trim ( ) ;
return key || undefined ;
2026-01-04 19:35:00 +01:00
}
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 ;
}
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-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 ) {
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-01-06 00:56:29 +00:00
const customKey = getCustomProviderApiKey ( cfg , provider ) ;
if ( customKey ) {
2026-01-20 07:53:25 +00:00
return { apiKey : customKey , source : "models.json" , mode : "api-key" } ;
}
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 (
'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 } ). ` ,
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
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 ( ) ;
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-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-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-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-17 20:20:20 +00:00
if ( normalized === "qwen-portal" ) {
return pick ( "QWEN_OAUTH_TOKEN" ) ? ? pick ( "QWEN_PORTAL_API_KEY" ) ;
}
2026-01-31 12:42:45 +01:00
if ( normalized === "minimax-portal" ) {
return pick ( "MINIMAX_OAUTH_TOKEN" ) ? ? pick ( "MINIMAX_API_KEY" ) ;
}
2026-01-31 06:04:10 +01:00
if ( normalized === "kimi-coding" ) {
return pick ( "KIMI_API_KEY" ) ? ? pick ( "KIMICODE_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" ,
2026-01-17 08:46:40 +00:00
deepgram : "DEEPGRAM_API_KEY" ,
2026-01-06 00:56:29 +00:00
cerebras : "CEREBRAS_API_KEY" ,
xai : "XAI_API_KEY" ,
openrouter : "OPENROUTER_API_KEY" ,
2026-01-16 14:40:56 +01:00
"vercel-ai-gateway" : "AI_GATEWAY_API_KEY" ,
2026-02-04 04:10:13 -08:00
"cloudflare-ai-gateway" : "CLOUDFLARE_AI_GATEWAY_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-29 00:30:17 +08:00
xiaomi : "XIAOMI_API_KEY" ,
2026-01-13 00:22:03 +00:00
synthetic : "SYNTHETIC_API_KEY" ,
feat: add Venice AI provider integration
Venice AI is a privacy-focused AI inference provider with support for
uncensored models and access to major proprietary models via their
anonymized proxy.
This integration adds:
- Complete model catalog with 25 models:
- 15 private models (Llama, Qwen, DeepSeek, Venice Uncensored, etc.)
- 10 anonymized models (Claude, GPT-5.2, Gemini, Grok, Kimi, MiniMax)
- Auto-discovery from Venice API with fallback to static catalog
- VENICE_API_KEY environment variable support
- Interactive onboarding via 'venice-api-key' auth choice
- Model selection prompt showing all available Venice models
- Provider auto-registration when API key is detected
- Comprehensive documentation covering:
- Privacy modes (private vs anonymized)
- All 25 models with context windows and features
- Streaming, function calling, and vision support
- Model selection recommendations
Privacy modes:
- Private: Fully private, no logging (open-source models)
- Anonymized: Proxied through Venice (proprietary models)
Default model: venice/llama-3.3-70b (good balance of capability + privacy)
Venice API: https://api.venice.ai/api/v1 (OpenAI-compatible)
2026-01-24 16:56:42 -07:00
venice : "VENICE_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-31 16:19:20 +09:00
if ( ! envVar ) {
return null ;
}
2026-01-06 00:56:29 +00:00
return pick ( envVar ) ;
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-01-31 16:19:20 +09:00
if ( getCustomProviderApiKey ( cfg , resolved ) ) {
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 {
const key = auth . apiKey ? . trim ( ) ;
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
}