mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:00:43 +00:00
fix: The one-line picker change hides unauthenticated catalog rows in (#74530)
Co-authored-by: openclaw-clawsweeper[bot] <280122609+openclaw-clawsweeper[bot]@users.noreply.github.com>
This commit is contained in:
@@ -304,6 +304,31 @@ export function hasSyntheticLocalProviderAuthConfig(params: {
|
||||
return Boolean(providerConfig.baseUrl && isLocalBaseUrl(providerConfig.baseUrl));
|
||||
}
|
||||
|
||||
export function hasRuntimeAvailableProviderAuth(params: {
|
||||
provider: string;
|
||||
cfg?: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): boolean {
|
||||
const provider = normalizeProviderId(params.provider);
|
||||
const authOverride = resolveProviderAuthOverride(params.cfg, provider);
|
||||
if (authOverride === "aws-sdk") {
|
||||
return true;
|
||||
}
|
||||
if (resolveEnvApiKey(provider, params.env)) {
|
||||
return true;
|
||||
}
|
||||
if (resolveUsableCustomProviderApiKey({ cfg: params.cfg, provider, env: params.env })) {
|
||||
return true;
|
||||
}
|
||||
if (resolveSyntheticLocalProviderAuth({ cfg: params.cfg, provider })) {
|
||||
return true;
|
||||
}
|
||||
if (authOverride === undefined && provider === "amazon-bedrock") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
type SyntheticProviderAuthResolution = {
|
||||
auth?: ResolvedProviderAuth;
|
||||
blockedOnManagedSecretRef?: boolean;
|
||||
|
||||
80
src/agents/model-provider-auth.test.ts
Normal file
80
src/agents/model-provider-auth.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { AuthProfileStore } from "./auth-profiles.js";
|
||||
import { hasAuthForModelProvider } from "./model-provider-auth.js";
|
||||
|
||||
const emptyStore: AuthProfileStore = {
|
||||
version: 1,
|
||||
profiles: {},
|
||||
};
|
||||
|
||||
function modelDefinition(id: string) {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
reasoning: false,
|
||||
input: ["text" as const],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 8192,
|
||||
};
|
||||
}
|
||||
|
||||
describe("model provider auth availability", () => {
|
||||
it("accepts implicit Bedrock AWS SDK auth without an API key", () => {
|
||||
expect(
|
||||
hasAuthForModelProvider({
|
||||
provider: "amazon-bedrock",
|
||||
cfg: {} as OpenClawConfig,
|
||||
env: {},
|
||||
store: emptyStore,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts local no-key custom providers", () => {
|
||||
const cfg = {
|
||||
models: {
|
||||
providers: {
|
||||
vllm: {
|
||||
api: "openai-completions",
|
||||
baseUrl: "http://127.0.0.1:8000/v1",
|
||||
models: [modelDefinition("meta-llama/Meta-Llama-3-8B-Instruct")],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(
|
||||
hasAuthForModelProvider({
|
||||
provider: "vllm",
|
||||
cfg,
|
||||
env: {},
|
||||
store: emptyStore,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps remote no-key custom providers unavailable", () => {
|
||||
const cfg = {
|
||||
models: {
|
||||
providers: {
|
||||
remote: {
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://remote.example.com/v1",
|
||||
models: [modelDefinition("remote-model")],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(
|
||||
hasAuthForModelProvider({
|
||||
provider: "remote",
|
||||
cfg,
|
||||
env: {},
|
||||
store: emptyStore,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
listProfilesForProvider,
|
||||
type AuthProfileStore,
|
||||
} from "./auth-profiles.js";
|
||||
import { hasUsableCustomProviderApiKey, resolveEnvApiKey } from "./model-auth.js";
|
||||
import { hasRuntimeAvailableProviderAuth } from "./model-auth.js";
|
||||
import { normalizeProviderId } from "./model-selection.js";
|
||||
|
||||
export function hasAuthForModelProvider(params: {
|
||||
@@ -15,6 +15,15 @@ export function hasAuthForModelProvider(params: {
|
||||
store?: AuthProfileStore;
|
||||
}): boolean {
|
||||
const provider = normalizeProviderId(params.provider);
|
||||
if (
|
||||
hasRuntimeAvailableProviderAuth({
|
||||
provider,
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const store =
|
||||
params.store ??
|
||||
ensureAuthProfileStore(params.agentDir, {
|
||||
@@ -23,12 +32,6 @@ export function hasAuthForModelProvider(params: {
|
||||
if (listProfilesForProvider(store, provider).length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (resolveEnvApiKey(provider, params.env)?.apiKey) {
|
||||
return true;
|
||||
}
|
||||
if (hasUsableCustomProviderApiKey(params.cfg, provider, params.env)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,15 +36,53 @@ vi.mock("../agents/auth-profiles.js", () => ({
|
||||
}));
|
||||
|
||||
const resolveEnvApiKey = vi.hoisted(() =>
|
||||
vi.fn<(_provider: string) => { apiKey: string; source: string } | null>((_provider: string) => ({
|
||||
apiKey: "test-key",
|
||||
source: "test",
|
||||
})),
|
||||
vi.fn<(_provider: string, _env?: NodeJS.ProcessEnv) => { apiKey: string; source: string } | null>(
|
||||
(_provider: string) => ({
|
||||
apiKey: "test-key",
|
||||
source: "test",
|
||||
}),
|
||||
),
|
||||
);
|
||||
const hasUsableCustomProviderApiKey = vi.hoisted(() =>
|
||||
vi.fn<(_cfg?: OpenClawConfig, _provider?: string, _env?: NodeJS.ProcessEnv) => boolean>(
|
||||
() => false,
|
||||
),
|
||||
);
|
||||
const hasRuntimeAvailableProviderAuth = vi.hoisted(() =>
|
||||
vi.fn(
|
||||
({
|
||||
provider,
|
||||
cfg,
|
||||
env,
|
||||
}: {
|
||||
provider: string;
|
||||
cfg?: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) => {
|
||||
if (provider === "amazon-bedrock") {
|
||||
const auth = cfg?.models?.providers?.["amazon-bedrock"]?.auth;
|
||||
return auth === undefined || auth === "aws-sdk";
|
||||
}
|
||||
if (resolveEnvApiKey(provider, env)?.apiKey) {
|
||||
return true;
|
||||
}
|
||||
if (hasUsableCustomProviderApiKey(cfg, provider, env)) {
|
||||
return true;
|
||||
}
|
||||
const providerConfig = cfg?.models?.providers?.[provider];
|
||||
return Boolean(
|
||||
providerConfig?.baseUrl?.startsWith("http://127.0.0.1") &&
|
||||
providerConfig.api &&
|
||||
providerConfig.models?.length &&
|
||||
!providerConfig.apiKey,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
const hasUsableCustomProviderApiKey = vi.hoisted(() => vi.fn(() => false));
|
||||
vi.mock("../agents/model-auth.js", () => ({
|
||||
resolveEnvApiKey,
|
||||
hasUsableCustomProviderApiKey,
|
||||
hasRuntimeAvailableProviderAuth,
|
||||
}));
|
||||
|
||||
const resolveOwningPluginIdsForProvider = vi.hoisted(() =>
|
||||
@@ -208,6 +246,30 @@ describe("promptDefaultModel", () => {
|
||||
expect(values).toEqual(["anthropic/claude-sonnet-4-6"]);
|
||||
});
|
||||
|
||||
it("keeps implicit Bedrock AWS SDK models visible without API-key auth", async () => {
|
||||
resolveEnvApiKey.mockReturnValue(null);
|
||||
loadModelCatalog.mockResolvedValue([
|
||||
{ provider: "amazon-bedrock", id: "us.anthropic.claude-sonnet-4-5", name: "Claude Sonnet" },
|
||||
{ provider: "openai", id: "gpt-5.5", name: "GPT-5.5" },
|
||||
]);
|
||||
|
||||
const select = vi.fn(async (params) => params.initialValue as never);
|
||||
const prompter = makePrompter({ select });
|
||||
|
||||
await promptDefaultModel({
|
||||
config: { agents: { defaults: {} } } as OpenClawConfig,
|
||||
prompter,
|
||||
allowKeep: false,
|
||||
includeManual: false,
|
||||
ignoreAllowlist: true,
|
||||
});
|
||||
|
||||
const values = (select.mock.calls[0]?.[0]?.options ?? []).map(
|
||||
(option: { value: string }) => option.value,
|
||||
);
|
||||
expect(values).toEqual(["amazon-bedrock/us.anthropic.claude-sonnet-4-5"]);
|
||||
});
|
||||
|
||||
it("hides legacy runtime providers from default model choices", async () => {
|
||||
loadModelCatalog.mockResolvedValue([
|
||||
{ provider: "codex", id: "gpt-5.5", name: "GPT-5.5" },
|
||||
@@ -899,6 +961,44 @@ describe("promptModelAllowlist", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps local no-key provider models visible in allowlist choices", async () => {
|
||||
resolveEnvApiKey.mockReturnValue(null);
|
||||
loadModelCatalog.mockResolvedValue([
|
||||
{
|
||||
provider: "vllm",
|
||||
id: "meta-llama/Meta-Llama-3-8B-Instruct",
|
||||
name: "Meta Llama",
|
||||
},
|
||||
{
|
||||
provider: "openai",
|
||||
id: "gpt-5.5",
|
||||
name: "GPT-5.5",
|
||||
},
|
||||
]);
|
||||
|
||||
const multiselect = createSelectAllMultiselect();
|
||||
const prompter = makePrompter({ multiselect });
|
||||
const config = {
|
||||
models: {
|
||||
providers: {
|
||||
vllm: {
|
||||
api: "openai-completions",
|
||||
baseUrl: "http://127.0.0.1:8000/v1",
|
||||
models: [configuredTextModel("meta-llama/Meta-Llama-3-8B-Instruct", "Meta Llama")],
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: { defaults: {} },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await promptModelAllowlist({ config, prompter });
|
||||
|
||||
expect(
|
||||
multiselect.mock.calls[0]?.[0]?.options.map((option: { value: string }) => option.value),
|
||||
).toEqual(["vllm/meta-llama/Meta-Llama-3-8B-Instruct"]);
|
||||
expect(result.models).toEqual(["vllm/meta-llama/Meta-Llama-3-8B-Instruct"]);
|
||||
});
|
||||
|
||||
it("seeds existing model fallbacks into unscoped allowlist selections", async () => {
|
||||
loadModelCatalog.mockResolvedValue([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user