fix: include local model list auth markers

This commit is contained in:
Shakker
2026-04-29 16:55:41 +01:00
parent b418c08a22
commit 87bd12b2d3
5 changed files with 62 additions and 22 deletions

View File

@@ -274,6 +274,36 @@ function isManagedSecretRefApiKeyMarker(apiKey: string | undefined): boolean {
return apiKey?.trim() === NON_ENV_SECRETREF_MARKER;
}
export function hasSyntheticLocalProviderAuthConfig(params: {
cfg: OpenClawConfig | undefined;
provider: string;
}): boolean {
const providerConfig = resolveProviderConfig(params.cfg, params.provider);
if (!providerConfig) {
return false;
}
const hasApiConfig =
Boolean(providerConfig.api?.trim()) ||
Boolean(providerConfig.baseUrl?.trim()) ||
(Array.isArray(providerConfig.models) && providerConfig.models.length > 0);
if (!hasApiConfig) {
return false;
}
const authOverride = resolveProviderAuthOverride(params.cfg, params.provider);
if (authOverride && authOverride !== "api-key") {
return false;
}
if (!isCustomLocalProviderConfig(providerConfig)) {
return false;
}
if (hasExplicitProviderApiKeyConfig(providerConfig)) {
return false;
}
return Boolean(providerConfig.baseUrl && isLocalBaseUrl(providerConfig.baseUrl));
}
type SyntheticProviderAuthResolution = {
auth?: ResolvedProviderAuth;
blockedOnManagedSecretRef?: boolean;
@@ -340,29 +370,10 @@ function resolveSyntheticLocalProviderAuth(params: {
return null;
}
const hasApiConfig =
Boolean(providerConfig.api?.trim()) ||
Boolean(providerConfig.baseUrl?.trim()) ||
(Array.isArray(providerConfig.models) && providerConfig.models.length > 0);
if (!hasApiConfig) {
return null;
}
const authOverride = resolveProviderAuthOverride(params.cfg, params.provider);
if (authOverride && authOverride !== "api-key") {
return null;
}
if (!isCustomLocalProviderConfig(providerConfig)) {
return null;
}
if (hasExplicitProviderApiKeyConfig(providerConfig)) {
return null;
}
// Custom providers pointing at a local server (e.g. llama.cpp, vLLM, LocalAI)
// typically don't require auth. Synthesize a local key so the auth resolver
// doesn't reject them when the user left the API key blank during setup.
if (providerConfig.baseUrl && isLocalBaseUrl(providerConfig.baseUrl)) {
if (hasSyntheticLocalProviderAuthConfig(params)) {
return {
apiKey: CUSTOM_LOCAL_AUTH_MARKER,
source: `models.providers.${params.provider} (synthetic local key)`,

View File

@@ -16,6 +16,7 @@ const listProfilesForProvider = vi.fn().mockReturnValue([]);
const resolveEnvApiKey = vi.fn().mockReturnValue(undefined);
const resolveAwsSdkEnvVarName = vi.fn().mockReturnValue(undefined);
const hasUsableCustomProviderApiKey = vi.fn().mockReturnValue(false);
const hasSyntheticLocalProviderAuthConfig = vi.fn().mockReturnValue(false);
const loadModelCatalog = vi.fn(async () => []);
const loadProviderCatalogModelsForList = vi.fn<() => Promise<Array<Record<string, unknown>>>>(
async () => [],
@@ -64,6 +65,7 @@ vi.mock("../agents/auth-profiles/store.js", () => ({
vi.mock("../agents/model-auth.js", () => ({
hasUsableCustomProviderApiKey,
hasSyntheticLocalProviderAuthConfig,
resolveAwsSdkEnvVarName,
resolveEnvApiKey,
}));

View File

@@ -66,6 +66,26 @@ describe("createModelListAuthIndex", () => {
expect(index.hasProviderAuth("custom-openai")).toBe(true);
});
it("records configured local custom provider markers", () => {
const index = createModelListAuthIndex({
cfg: {
models: {
providers: {
"local-openai": {
api: "openai-completions",
baseUrl: "http://127.0.0.1:8080/v1",
models: [{ id: "local-model" }],
},
},
},
},
authStore: emptyStore,
env: {},
});
expect(index.hasProviderAuth("local-openai")).toBe(true);
});
it("uses injected synthetic auth refs without loading provider runtime", () => {
const index = createModelListAuthIndex({
cfg: {},

View File

@@ -2,7 +2,10 @@ 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 {
hasSyntheticLocalProviderAuthConfig,
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";
@@ -65,7 +68,10 @@ export function createModelListAuthIndex(
}
for (const provider of Object.keys(params.cfg.models?.providers ?? {})) {
if (hasUsableCustomProviderApiKey(params.cfg, provider, env)) {
if (
hasUsableCustomProviderApiKey(params.cfg, provider, env) ||
hasSyntheticLocalProviderAuthConfig({ cfg: params.cfg, provider })
) {
addProvider(provider);
}
}

View File

@@ -209,6 +209,7 @@ function installModelsListCommandForwardCompatMocks() {
vi.doMock("../../agents/model-auth.js", () => ({
hasUsableCustomProviderApiKey: vi.fn().mockReturnValue(false),
hasSyntheticLocalProviderAuthConfig: vi.fn().mockReturnValue(false),
}));
vi.doMock("../../plugins/installed-plugin-index-store.js", () => ({