diff --git a/src/agents/model-auth-env-vars.ts b/src/agents/model-auth-env-vars.ts index 1f401ccc250..75754b462c6 100644 --- a/src/agents/model-auth-env-vars.ts +++ b/src/agents/model-auth-env-vars.ts @@ -20,14 +20,22 @@ export function resolveProviderEnvAuthEvidence( return resolveProviderAuthEvidence(params); } -export function resolveProviderEnvAuthLookupKeys(params?: ProviderEnvVarLookupParams): string[] { - const envCandidateMap = resolveProviderEnvApiKeyCandidates(params); - const authEvidenceMap = resolveProviderEnvAuthEvidence(params); +export function listProviderEnvAuthLookupKeys(params: { + envCandidateMap: Readonly>; + authEvidenceMap: Readonly>; +}): string[] { return Array.from( - new Set([...Object.keys(envCandidateMap), ...Object.keys(authEvidenceMap)]), + new Set([...Object.keys(params.envCandidateMap), ...Object.keys(params.authEvidenceMap)]), ).toSorted((a, b) => a.localeCompare(b)); } +export function resolveProviderEnvAuthLookupKeys(params?: ProviderEnvVarLookupParams): string[] { + return listProviderEnvAuthLookupKeys({ + envCandidateMap: resolveProviderEnvApiKeyCandidates(params), + authEvidenceMap: resolveProviderEnvAuthEvidence(params), + }); +} + export const PROVIDER_ENV_API_KEY_CANDIDATES = resolveProviderEnvApiKeyCandidates(); export function listKnownProviderEnvApiKeyNames(): string[] { diff --git a/src/agents/pi-auth-discovery-core.ts b/src/agents/pi-auth-discovery-core.ts index ff274deb1de..4074c970883 100644 --- a/src/agents/pi-auth-discovery-core.ts +++ b/src/agents/pi-auth-discovery-core.ts @@ -2,9 +2,9 @@ import fs from "node:fs"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { isRecord } from "../utils.js"; import { + listProviderEnvAuthLookupKeys, resolveProviderEnvApiKeyCandidates, resolveProviderEnvAuthEvidence, - resolveProviderEnvAuthLookupKeys, } from "./model-auth-env-vars.js"; import { resolveEnvApiKey } from "./model-auth-env.js"; import type { PiCredentialMap } from "./pi-auth-credentials.js"; @@ -31,7 +31,10 @@ export function addEnvBackedPiCredentials( // pi-coding-agent hides providers from its registry when auth storage lacks // a matching credential entry. Mirror env-backed provider auth here so // live/model discovery sees the same providers runtime auth can use. - for (const provider of resolveProviderEnvAuthLookupKeys(lookupParams)) { + for (const provider of listProviderEnvAuthLookupKeys({ + envCandidateMap: candidateMap, + authEvidenceMap, + })) { if (next[provider]) { continue; } diff --git a/src/agents/pi-model-discovery.auth.test.ts b/src/agents/pi-model-discovery.auth.test.ts index 43b1ca68545..c12a1e74800 100644 --- a/src/agents/pi-model-discovery.auth.test.ts +++ b/src/agents/pi-model-discovery.auth.test.ts @@ -9,6 +9,7 @@ import { } from "./pi-auth-discovery-core.js"; vi.mock("./model-auth-env-vars.js", () => ({ + listProviderEnvAuthLookupKeys: () => ["mistral", "workspace-cloud"], resolveProviderEnvApiKeyCandidates: () => ({ mistral: ["MISTRAL_API_KEY"], }), @@ -21,7 +22,6 @@ vi.mock("./model-auth-env-vars.js", () => ({ }, ], }), - resolveProviderEnvAuthLookupKeys: () => ["mistral", "workspace-cloud"], })); vi.mock("./model-auth-env.js", () => ({ diff --git a/src/commands/models/list.auth-index.ts b/src/commands/models/list.auth-index.ts index 4eea5987a81..98846bdf130 100644 --- a/src/commands/models/list.auth-index.ts +++ b/src/commands/models/list.auth-index.ts @@ -1,8 +1,8 @@ import type { AuthProfileStore } from "../../agents/auth-profiles/types.js"; import { + listProviderEnvAuthLookupKeys, resolveProviderEnvAuthEvidence, resolveProviderEnvApiKeyCandidates, - resolveProviderEnvAuthLookupKeys, } from "../../agents/model-auth-env-vars.js"; import { resolveEnvApiKey } from "../../agents/model-auth-env.js"; import { resolveAwsSdkEnvVarName } from "../../agents/model-auth-runtime-shared.js"; @@ -27,10 +27,6 @@ export type CreateModelListAuthIndexParams = { syntheticAuthProviderRefs?: readonly string[]; }; -export const EMPTY_MODEL_LIST_AUTH_INDEX: ModelListAuthIndex = { - hasProviderAuth: () => false, -}; - function normalizeAuthProvider( provider: string, aliasMap: Readonly>, @@ -90,12 +86,13 @@ export function createModelListAuthIndex( addProvider(credential.provider); } - for (const provider of resolveProviderEnvAuthLookupKeys(lookupParams)) { + for (const provider of listProviderEnvAuthLookupKeys({ envCandidateMap, authEvidenceMap })) { if ( resolveEnvApiKey(provider, env, { aliasMap, candidateMap: envCandidateMap, authEvidenceMap, + config: params.cfg, workspaceDir: params.workspaceDir, }) ) { @@ -131,8 +128,16 @@ export function createModelListAuthIndex( if (cached !== undefined) { return cached; } + const hasPrecomputedCandidates = Object.hasOwn(envCandidateMap, normalized); + const hasPrecomputedEvidence = Object.hasOwn(authEvidenceMap, normalized); const hasAuth = Boolean( - resolveEnvApiKey(provider, env, { aliasMap, candidateMap: envCandidateMap }), + resolveEnvApiKey(provider, env, { + aliasMap, + candidateMap: hasPrecomputedCandidates ? envCandidateMap : undefined, + authEvidenceMap: hasPrecomputedEvidence ? authEvidenceMap : undefined, + config: params.cfg, + workspaceDir: params.workspaceDir, + }), ); envProviderAuthCache.set(normalized, hasAuth); if (hasAuth) { diff --git a/src/commands/models/list.status-command.ts b/src/commands/models/list.status-command.ts index a7a298b80f5..7203108f94c 100644 --- a/src/commands/models/list.status-command.ts +++ b/src/commands/models/list.status-command.ts @@ -17,9 +17,9 @@ import { ensureAuthProfileStoreWithoutExternalProfiles as ensureAuthProfileStore import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js"; import { resolveProfileUnusableUntilForDisplay } from "../../agents/auth-profiles/usage.js"; import { + listProviderEnvAuthLookupKeys, resolveProviderEnvApiKeyCandidates, resolveProviderEnvAuthEvidence, - resolveProviderEnvAuthLookupKeys, } from "../../agents/model-auth-env-vars.js"; import { resolveEnvApiKey } from "../../agents/model-auth.js"; import { @@ -257,7 +257,7 @@ export async function modelsStatusCommand( }; const envCandidateMap = resolveProviderEnvApiKeyCandidates(envLookupParams); const authEvidenceMap = resolveProviderEnvAuthEvidence(envLookupParams); - for (const provider of resolveProviderEnvAuthLookupKeys(envLookupParams)) { + for (const provider of listProviderEnvAuthLookupKeys({ envCandidateMap, authEvidenceMap })) { if ( resolveEnvApiKey(provider, process.env, { config: cfg, diff --git a/src/commands/models/list.status.test.ts b/src/commands/models/list.status.test.ts index 138ac241f77..b79dc65907d 100644 --- a/src/commands/models/list.status.test.ts +++ b/src/commands/models/list.status.test.ts @@ -92,7 +92,7 @@ const mocks = vi.hoisted(() => { fal: ["FAL_KEY"], }), resolveProviderEnvAuthEvidence: vi.fn().mockReturnValue({}), - resolveProviderEnvAuthLookupKeys: vi + listProviderEnvAuthLookupKeys: vi .fn() .mockImplementation(() => [ "anthropic", @@ -206,9 +206,9 @@ vi.mock("../../agents/model-auth.js", () => ({ getCustomProviderApiKey: mocks.getCustomProviderApiKey, })); vi.mock("../../agents/model-auth-env-vars.js", () => ({ + listProviderEnvAuthLookupKeys: mocks.listProviderEnvAuthLookupKeys, resolveProviderEnvApiKeyCandidates: mocks.resolveProviderEnvApiKeyCandidates, resolveProviderEnvAuthEvidence: mocks.resolveProviderEnvAuthEvidence, - resolveProviderEnvAuthLookupKeys: mocks.resolveProviderEnvAuthLookupKeys, listKnownProviderEnvApiKeyNames: mocks.listKnownProviderEnvApiKeyNames, })); vi.mock("../../agents/model-selection-cli.js", () => ({ @@ -543,11 +543,11 @@ describe("modelsStatusCommand auth overview", () => { it("includes auth-evidence-only providers in the auth overview", async () => { const localRuntime = createRuntime(); - const originalKeysImpl = mocks.resolveProviderEnvAuthLookupKeys.getMockImplementation(); + const originalKeysImpl = mocks.listProviderEnvAuthLookupKeys.getMockImplementation(); const originalEvidenceImpl = mocks.resolveProviderEnvAuthEvidence.getMockImplementation(); const originalEnvImpl = mocks.resolveEnvApiKey.getMockImplementation(); - mocks.resolveProviderEnvAuthLookupKeys.mockReturnValue(["workspace-cloud"]); + mocks.listProviderEnvAuthLookupKeys.mockReturnValue(["workspace-cloud"]); mocks.resolveProviderEnvAuthEvidence.mockReturnValue({ "workspace-cloud": [ { @@ -581,7 +581,7 @@ describe("modelsStatusCommand auth overview", () => { ); } finally { if (originalKeysImpl) { - mocks.resolveProviderEnvAuthLookupKeys.mockImplementation(originalKeysImpl); + mocks.listProviderEnvAuthLookupKeys.mockImplementation(originalKeysImpl); } if (originalEvidenceImpl) { mocks.resolveProviderEnvAuthEvidence.mockImplementation(originalEvidenceImpl);