fix(model-auth): keep env-first profile precedence

This commit is contained in:
Peter Steinberger
2026-05-31 11:31:04 +01:00
parent 6a445ac90c
commit f55dec154d
2 changed files with 57 additions and 23 deletions

View File

@@ -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.

View File

@@ -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)) {