feat: add model list auth index

This commit is contained in:
Shakker
2026-04-29 16:46:57 +01:00
parent 0e0ade80a0
commit a777b82da0
3 changed files with 174 additions and 2 deletions

View File

@@ -7,12 +7,18 @@ import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js
import { resolveProviderEnvApiKeyCandidates } from "./model-auth-env-vars.js";
import { GCP_VERTEX_CREDENTIALS_MARKER } from "./model-auth-markers.js";
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
import { normalizeProviderIdForAuth } from "./provider-id.js";
export type EnvApiKeyResult = {
apiKey: string;
source: string;
};
export type EnvApiKeyLookupOptions = {
aliasMap?: Readonly<Record<string, string>>;
candidateMap?: Readonly<Record<string, readonly string[]>>;
};
function hasGoogleVertexAdcCredentials(env: NodeJS.ProcessEnv): boolean {
const explicitCredentialsPath = normalizeOptionalSecretInput(env.GOOGLE_APPLICATION_CREDENTIALS);
if (explicitCredentialsPath) {
@@ -39,9 +45,13 @@ function resolveGoogleVertexEnvApiKey(env: NodeJS.ProcessEnv): string | undefine
export function resolveEnvApiKey(
provider: string,
env: NodeJS.ProcessEnv = process.env,
options: EnvApiKeyLookupOptions = {},
): EnvApiKeyResult | null {
const normalized = resolveProviderIdForAuth(provider, { env });
const candidateMap = resolveProviderEnvApiKeyCandidates({ env });
const normalizedProvider = normalizeProviderIdForAuth(provider);
const normalized = options.aliasMap
? (options.aliasMap[normalizedProvider] ?? normalizedProvider)
: resolveProviderIdForAuth(provider, { env });
const candidateMap = options.candidateMap ?? resolveProviderEnvApiKeyCandidates({ env });
const applied = new Set(getShellEnvAppliedKeys());
const pick = (envVar: string): EnvApiKeyResult | null => {
const value = normalizeOptionalSecretInput(env[envVar]);

View File

@@ -0,0 +1,79 @@
import { describe, expect, it, vi } from "vitest";
import type { AuthProfileStore } from "../../agents/auth-profiles/types.js";
import { createModelListAuthIndex } from "./list.auth-index.js";
vi.mock("../../plugins/installed-plugin-index-store.js", () => ({
readPersistedInstalledPluginIndexSync: vi.fn(() => null),
}));
const emptyStore: AuthProfileStore = {
version: 1,
profiles: {},
};
describe("createModelListAuthIndex", () => {
it("normalizes auth aliases from profiles", () => {
const index = createModelListAuthIndex({
cfg: {},
authStore: {
version: 1,
profiles: {
"byteplus:default": {
type: "api_key",
provider: "byteplus",
key: "sk-test",
},
},
},
env: {},
});
expect(index.hasProviderAuth("byteplus")).toBe(true);
expect(index.hasProviderAuth("byteplus-plan")).toBe(true);
});
it("records env-backed providers without resolving env candidates per row", () => {
const index = createModelListAuthIndex({
cfg: {},
authStore: emptyStore,
env: {
MOONSHOT_API_KEY: "sk-test",
},
});
expect(index.hasProviderAuth("moonshot")).toBe(true);
expect(index.hasProviderAuth("openai")).toBe(false);
});
it("records configured provider API keys", () => {
const index = createModelListAuthIndex({
cfg: {
models: {
providers: {
"custom-openai": {
api: "openai-completions",
apiKey: "sk-configured",
baseUrl: "https://custom.example/v1",
models: [{ id: "local-model" }],
},
},
},
},
authStore: emptyStore,
env: {},
});
expect(index.hasProviderAuth("custom-openai")).toBe(true);
});
it("uses injected synthetic auth refs without loading provider runtime", () => {
const index = createModelListAuthIndex({
cfg: {},
authStore: emptyStore,
env: {},
syntheticAuthProviderRefs: ["codex"],
});
expect(index.hasProviderAuth("codex")).toBe(true);
});
});

View File

@@ -0,0 +1,83 @@
import type { AuthProfileStore } from "../../agents/auth-profiles/types.js";
import { resolveProviderEnvApiKeyCandidates } from "../../agents/model-auth-env-vars.js";
import { resolveEnvApiKey } from "../../agents/model-auth-env.js";
import { resolveAwsSdkEnvVarName } from "../../agents/model-auth-runtime-shared.js";
import { hasUsableCustomProviderApiKey } from "../../agents/model-auth.js";
import { resolveProviderAuthAliasMap } from "../../agents/provider-auth-aliases.js";
import { normalizeProviderIdForAuth } from "../../agents/provider-id.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { readPersistedInstalledPluginIndexSync } from "../../plugins/installed-plugin-index-store.js";
export type ModelListAuthIndex = {
hasProviderAuth(provider: string): boolean;
};
export type CreateModelListAuthIndexParams = {
cfg: OpenClawConfig;
authStore: AuthProfileStore;
env?: NodeJS.ProcessEnv;
syntheticAuthProviderRefs?: readonly string[];
};
export const EMPTY_MODEL_LIST_AUTH_INDEX: ModelListAuthIndex = {
hasProviderAuth: () => false,
};
function normalizeAuthProvider(
provider: string,
aliasMap: Readonly<Record<string, string>>,
): string {
const normalized = normalizeProviderIdForAuth(provider);
return aliasMap[normalized] ?? normalized;
}
function listPersistedSyntheticAuthProviderRefs(): readonly string[] {
const index = readPersistedInstalledPluginIndexSync();
return index?.plugins.flatMap((plugin) => plugin.syntheticAuthRefs ?? []) ?? [];
}
export function createModelListAuthIndex(
params: CreateModelListAuthIndexParams,
): ModelListAuthIndex {
const env = params.env ?? process.env;
const aliasMap = resolveProviderAuthAliasMap({ config: params.cfg, env });
const envCandidateMap = resolveProviderEnvApiKeyCandidates({ config: params.cfg, env });
const authenticatedProviders = new Set<string>();
const addProvider = (provider: string | undefined) => {
if (!provider?.trim()) {
return;
}
authenticatedProviders.add(normalizeAuthProvider(provider, aliasMap));
};
for (const credential of Object.values(params.authStore.profiles ?? {})) {
addProvider(credential.provider);
}
for (const provider of Object.keys(envCandidateMap)) {
if (resolveEnvApiKey(provider, env, { aliasMap, candidateMap: envCandidateMap })) {
addProvider(provider);
}
}
if (resolveAwsSdkEnvVarName(env)) {
addProvider("amazon-bedrock");
}
for (const provider of Object.keys(params.cfg.models?.providers ?? {})) {
if (hasUsableCustomProviderApiKey(params.cfg, provider, env)) {
addProvider(provider);
}
}
for (const provider of params.syntheticAuthProviderRefs ??
listPersistedSyntheticAuthProviderRefs()) {
addProvider(provider);
}
return {
hasProviderAuth(provider: string): boolean {
return authenticatedProviders.has(normalizeAuthProvider(provider, aliasMap));
},
};
}