From 95dff166cbbb18ae099723e48313545af06f2657 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 8 Mar 2026 16:22:01 +0000 Subject: [PATCH] refactor: fold implicit provider injection into resolver --- src/agents/model-auth.ts | 7 ++- ...ls-config.providers.discovery-auth.test.ts | 6 +- src/agents/models-config.providers.ts | 59 +++++++++++++++---- src/agents/models-config.ts | 22 ++----- 4 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index b8b0ac9336b..51ba332ed7f 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -271,11 +271,14 @@ export async function resolveApiKeyForProvider(params: { export type EnvApiKeyResult = { apiKey: string; source: string }; export type ModelAuthMode = "api-key" | "oauth" | "token" | "mixed" | "aws-sdk" | "unknown"; -export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { +export function resolveEnvApiKey( + provider: string, + env: NodeJS.ProcessEnv = process.env, +): EnvApiKeyResult | null { const normalized = normalizeProviderId(provider); const applied = new Set(getShellEnvAppliedKeys()); const pick = (envVar: string): EnvApiKeyResult | null => { - const value = normalizeOptionalSecretInput(process.env[envVar]); + const value = normalizeOptionalSecretInput(env[envVar]); if (!value) { return null; } diff --git a/src/agents/models-config.providers.discovery-auth.test.ts b/src/agents/models-config.providers.discovery-auth.test.ts index 6e8ebfbc0ac..c5c79b2fb95 100644 --- a/src/agents/models-config.providers.discovery-auth.test.ts +++ b/src/agents/models-config.providers.discovery-auth.test.ts @@ -63,7 +63,7 @@ describe("provider discovery auth marker guardrails", () => { "utf8", ); - const providers = await resolveImplicitProviders({ agentDir }); + const providers = await resolveImplicitProviders({ agentDir, env: {} }); expect(providers?.vllm?.apiKey).toBe(NON_ENV_SECRETREF_MARKER); const request = fetchMock.mock.calls[0]?.[1] as | { headers?: Record } @@ -96,7 +96,7 @@ describe("provider discovery auth marker guardrails", () => { "utf8", ); - const providers = await resolveImplicitProviders({ agentDir }); + const providers = await resolveImplicitProviders({ agentDir, env: {} }); expect(providers?.huggingface?.apiKey).toBe(NON_ENV_SECRETREF_MARKER); const huggingfaceCalls = fetchMock.mock.calls.filter(([url]) => String(url).includes("router.huggingface.co"), @@ -132,7 +132,7 @@ describe("provider discovery auth marker guardrails", () => { "utf8", ); - await resolveImplicitProviders({ agentDir }); + await resolveImplicitProviders({ agentDir, env: {} }); const vllmCall = fetchMock.mock.calls.find(([url]) => String(url).includes(":8000")); const request = vllmCall?.[1] as { headers?: Record } | undefined; expect(request?.headers?.Authorization).toBe("Bearer ALLCAPS_SAMPLE"); diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 9232e45ae85..8ae03757bea 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -403,8 +403,11 @@ function normalizeApiKeyConfig(value: string): string { return match?.[1] ?? trimmed; } -function resolveEnvApiKeyVarName(provider: string): string | undefined { - const resolved = resolveEnvApiKey(provider); +function resolveEnvApiKeyVarName( + provider: string, + env: NodeJS.ProcessEnv = process.env, +): string | undefined { + const resolved = resolveEnvApiKey(provider, env); if (!resolved) { return undefined; } @@ -470,6 +473,7 @@ function toDiscoveryApiKey(value: string | undefined): string | undefined { function resolveApiKeyFromCredential( cred: ReturnType["profiles"][string] | undefined, + env: NodeJS.ProcessEnv = process.env, ): ProfileApiKeyResolution | undefined { if (!cred) { return undefined; @@ -482,7 +486,7 @@ function resolveApiKeyFromCredential( return { apiKey: envVar, source: "env-ref", - discoveryApiKey: toDiscoveryApiKey(process.env[envVar]), + discoveryApiKey: toDiscoveryApiKey(env[envVar]), }; } return { @@ -507,7 +511,7 @@ function resolveApiKeyFromCredential( return { apiKey: envVar, source: "env-ref", - discoveryApiKey: toDiscoveryApiKey(process.env[envVar]), + discoveryApiKey: toDiscoveryApiKey(env[envVar]), }; } return { @@ -529,10 +533,11 @@ function resolveApiKeyFromCredential( function resolveApiKeyFromProfiles(params: { provider: string; store: ReturnType; + env?: NodeJS.ProcessEnv; }): ProfileApiKeyResolution | undefined { const ids = listProfilesForProvider(params.store, params.provider); for (const id of ids) { - const resolved = resolveApiKeyFromCredential(params.store.profiles[id]); + const resolved = resolveApiKeyFromCredential(params.store.profiles[id], params.env); if (resolved) { return resolved; } @@ -1117,23 +1122,26 @@ async function buildKilocodeProviderWithDiscovery(): Promise { export async function resolveImplicitProviders(params: { agentDir: string; + config?: OpenClawConfig; + env?: NodeJS.ProcessEnv; explicitProviders?: Record | null; }): Promise { const providers: Record = {}; + const env = params.env ?? process.env; const authStore = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false, }); const resolveProviderApiKey = ( provider: string, ): { apiKey: string | undefined; discoveryApiKey?: string } => { - const envVar = resolveEnvApiKeyVarName(provider); + const envVar = resolveEnvApiKeyVarName(provider, env); if (envVar) { return { apiKey: envVar, - discoveryApiKey: toDiscoveryApiKey(process.env[envVar]), + discoveryApiKey: toDiscoveryApiKey(env[envVar]), }; } - const fromProfiles = resolveApiKeyFromProfiles({ provider, store: authStore }); + const fromProfiles = resolveApiKeyFromProfiles({ provider, store: authStore, env }); return { apiKey: fromProfiles?.apiKey, discoveryApiKey: fromProfiles?.discoveryApiKey, @@ -1145,7 +1153,7 @@ export async function resolveImplicitProviders(params: { providers.minimax = { ...buildMinimaxProvider(), apiKey: minimaxKey }; } - const minimaxPortalEnvKey = resolveEnvApiKeyVarName("minimax-portal"); + const minimaxPortalEnvKey = resolveEnvApiKeyVarName("minimax-portal", env); const minimaxOauthProfile = listProfilesForProvider(authStore, "minimax-portal"); if (minimaxPortalEnvKey || minimaxOauthProfile.length > 0) { providers["minimax-portal"] = { @@ -1220,8 +1228,8 @@ export async function resolveImplicitProviders(params: { if (!baseUrl) { continue; } - const envVarApiKey = resolveEnvApiKeyVarName("cloudflare-ai-gateway"); - const profileApiKey = resolveApiKeyFromCredential(cred)?.apiKey; + const envVarApiKey = resolveEnvApiKeyVarName("cloudflare-ai-gateway", env); + const profileApiKey = resolveApiKeyFromCredential(cred, env)?.apiKey; const apiKey = envVarApiKey ?? profileApiKey ?? ""; if (!apiKey) { continue; @@ -1329,6 +1337,35 @@ export async function resolveImplicitProviders(params: { providers.kilocode = { ...(await buildKilocodeProviderWithDiscovery()), apiKey: kilocodeKey }; } + if (!providers["github-copilot"]) { + const implicitCopilot = await resolveImplicitCopilotProvider({ + agentDir: params.agentDir, + env, + }); + if (implicitCopilot) { + providers["github-copilot"] = implicitCopilot; + } + } + + const implicitBedrock = await resolveImplicitBedrockProvider({ + agentDir: params.agentDir, + config: params.config, + env, + }); + if (implicitBedrock) { + const existing = providers["amazon-bedrock"]; + providers["amazon-bedrock"] = existing + ? { + ...implicitBedrock, + ...existing, + models: + Array.isArray(existing.models) && existing.models.length > 0 + ? existing.models + : implicitBedrock.models, + } + : implicitBedrock; + } + return providers; } diff --git a/src/agents/models-config.ts b/src/agents/models-config.ts index 9145c5089b9..9a470ee5cdc 100644 --- a/src/agents/models-config.ts +++ b/src/agents/models-config.ts @@ -11,15 +11,12 @@ import { isRecord } from "../utils.js"; import { resolveOpenClawAgentDir } from "./agent-paths.js"; import { mergeProviders, - mergeProviderModels, mergeWithExistingProviderSecrets, type ExistingProviderConfig, } from "./models-config.merge.js"; import { normalizeProviders, type ProviderConfig, - resolveImplicitBedrockProvider, - resolveImplicitCopilotProvider, resolveImplicitProviders, } from "./models-config.providers.js"; @@ -52,24 +49,15 @@ async function resolveProvidersForModelsJson(params: { }): Promise> { const { cfg, agentDir } = params; const explicitProviders = cfg.models?.providers ?? {}; - const implicitProviders = await resolveImplicitProviders({ agentDir, explicitProviders }); + const implicitProviders = await resolveImplicitProviders({ + agentDir, + config: cfg, + explicitProviders, + }); const providers: Record = mergeProviders({ implicit: implicitProviders, explicit: explicitProviders, }); - - const implicitBedrock = await resolveImplicitBedrockProvider({ agentDir, config: cfg }); - if (implicitBedrock) { - const existing = providers["amazon-bedrock"]; - providers["amazon-bedrock"] = existing - ? mergeProviderModels(implicitBedrock, existing) - : implicitBedrock; - } - - const implicitCopilot = await resolveImplicitCopilotProvider({ agentDir }); - if (implicitCopilot && !providers["github-copilot"]) { - providers["github-copilot"] = implicitCopilot; - } return providers; }