diff --git a/src/agents/model-auth.profiles.test.ts b/src/agents/model-auth.profiles.test.ts index 5cb35417be2..1fe18bb6d79 100644 --- a/src/agents/model-auth.profiles.test.ts +++ b/src/agents/model-auth.profiles.test.ts @@ -1660,6 +1660,40 @@ describe("resolveApiKeyForProvider — per-entry apiKey as profile ID reference" }); }); + it("keeps env-first precedence ahead of per-entry profile references", async () => { + await withEnvAsync({ OPENAI_API_KEY: "sk-env-first" }, async () => { + const resolved = await resolveApiKeyForProvider({ + provider: "openai", + credentialPrecedence: "env-first", + cfg: { + models: { + providers: { + openai: { + api: "openai-completions" as const, + baseUrl: "https://api.openai.com/v1", + apiKey: "openai:key-b", + models: [], + }, + }, + }, + }, + store: { + version: 1, + profiles: { + "openai:key-b": { + type: "api_key", + provider: "openai", + key: "sk-profile-key", + }, + }, + }, + }); + + expect(resolved.apiKey).toBe("sk-env-first"); + expect(resolved.source).toContain("OPENAI_API_KEY"); + }); + }); + it("does not bleed auth.order canonical provider profiles into a per-entry provider", async () => { // auth.order.openrouter should not be selected when resolving openrouter-minimax // that has its own per-entry apiKey = "openrouter:key-b" profile reference. diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 1c6096296f6..f2ea94da7b9 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -1030,6 +1030,29 @@ export async function resolveApiKeyForProvider(params: { return resolveAwsSdkAuthInfo(); } + if (params.credentialPrecedence === "env-first") { + const envResolved = resolveConfigAwareEnvApiKey(cfg, provider, params.workspaceDir); + if (envResolved) { + const resolvedMode: ResolvedProviderAuth["mode"] = envResolved.source.includes("OAUTH_TOKEN") + ? "oauth" + : "api-key"; + if ( + !isAuthModeAllowedForModel({ + provider, + modelApi: params.modelApi, + mode: resolvedMode, + }) + ) { + return resolveApiKeyForProvider({ ...params, credentialPrecedence: "profile-first" }); + } + return { + apiKey: envResolved.apiKey, + source: envResolved.source, + mode: resolvedMode, + }; + } + } + // Resolve stored profile-id references before literal apiKey fallbacks. // Matched profile references are terminal so bad bindings cannot silently // fall through to a different credential or to the profile id as bearer text. @@ -1086,29 +1109,6 @@ export async function resolveApiKeyForProvider(params: { }; } } - if (params.credentialPrecedence === "env-first") { - const envResolved = resolveConfigAwareEnvApiKey(cfg, provider, params.workspaceDir); - if (envResolved) { - const resolvedMode: ResolvedProviderAuth["mode"] = envResolved.source.includes("OAUTH_TOKEN") - ? "oauth" - : "api-key"; - if ( - !isAuthModeAllowedForModel({ - provider, - modelApi: params.modelApi, - mode: resolvedMode, - }) - ) { - return resolveApiKeyForProvider({ ...params, credentialPrecedence: "profile-first" }); - } - return { - apiKey: envResolved.apiKey, - source: envResolved.source, - mode: resolvedMode, - }; - } - } - const providerConfig = resolveProviderConfig(cfg, provider); const configuredLocalKey = resolveUsableCustomProviderApiKey({ cfg, provider }); if (configuredLocalKey && isNonSecretApiKeyMarker(configuredLocalKey.apiKey)) {