refactor: fold implicit provider injection into resolver

This commit is contained in:
Peter Steinberger
2026-03-08 16:22:01 +00:00
parent 1ec1f0f1f2
commit 95dff166cb
4 changed files with 61 additions and 33 deletions

View File

@@ -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;
}

View File

@@ -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<string, string> }
@@ -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<string, string> } | undefined;
expect(request?.headers?.Authorization).toBe("Bearer ALLCAPS_SAMPLE");

View File

@@ -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<typeof ensureAuthProfileStore>["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<typeof ensureAuthProfileStore>;
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<ProviderConfig> {
export async function resolveImplicitProviders(params: {
agentDir: string;
config?: OpenClawConfig;
env?: NodeJS.ProcessEnv;
explicitProviders?: Record<string, ProviderConfig> | null;
}): Promise<ModelsConfig["providers"]> {
const providers: Record<string, ProviderConfig> = {};
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;
}

View File

@@ -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<Record<string, ProviderConfig>> {
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<string, ProviderConfig> = 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;
}