fix: allow default model outside allowlist
This commit is contained in:
@@ -1,146 +1,56 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
|
||||
import {
|
||||
normalizeProviderId,
|
||||
resolveConfiguredModelRef,
|
||||
} from "./model-selection.js";
|
||||
import { buildAllowedModelSet, modelKey } from "./model-selection.js";
|
||||
|
||||
describe("resolveConfiguredModelRef", () => {
|
||||
it("parses provider/model from agent.model.primary", () => {
|
||||
const cfg = {
|
||||
agent: { model: { primary: "openai/gpt-4.1-mini" } },
|
||||
} satisfies ClawdbotConfig;
|
||||
const catalog = [
|
||||
{
|
||||
provider: "openai",
|
||||
id: "gpt-4",
|
||||
name: "GPT-4",
|
||||
},
|
||||
];
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({ provider: "openai", model: "gpt-4.1-mini" });
|
||||
});
|
||||
|
||||
it("falls back to anthropic when agent.model.primary omits provider", () => {
|
||||
const cfg = {
|
||||
agent: { model: { primary: "claude-opus-4-5" } },
|
||||
} satisfies ClawdbotConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to defaults when agent.model is missing", () => {
|
||||
const cfg = {} satisfies ClawdbotConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
provider: DEFAULT_PROVIDER,
|
||||
model: DEFAULT_MODEL,
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves agent.model aliases when configured", () => {
|
||||
describe("buildAllowedModelSet", () => {
|
||||
it("always allows the configured default model", () => {
|
||||
const cfg = {
|
||||
agent: {
|
||||
model: { primary: "Opus" },
|
||||
models: {
|
||||
"anthropic/claude-opus-4-5": { alias: "Opus" },
|
||||
"openai/gpt-4": { alias: "gpt4" },
|
||||
},
|
||||
},
|
||||
} satisfies ClawdbotConfig;
|
||||
} as ClawdbotConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
const allowed = buildAllowedModelSet({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
catalog,
|
||||
defaultProvider: "claude-cli",
|
||||
defaultModel: "opus-4.5",
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
});
|
||||
expect(allowed.allowAny).toBe(false);
|
||||
expect(allowed.allowedKeys.has(modelKey("openai", "gpt-4"))).toBe(true);
|
||||
expect(
|
||||
allowed.allowedKeys.has(modelKey("claude-cli", "opus-4.5")),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("normalizes z.ai provider in agent.model", () => {
|
||||
it("includes the default model when no allowlist is set", () => {
|
||||
const cfg = {
|
||||
agent: { model: "z.ai/glm-4.7" },
|
||||
} satisfies ClawdbotConfig;
|
||||
agent: {},
|
||||
} as ClawdbotConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
const allowed = buildAllowedModelSet({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
catalog,
|
||||
defaultProvider: "claude-cli",
|
||||
defaultModel: "opus-4.5",
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({ provider: "zai", model: "glm-4.7" });
|
||||
});
|
||||
|
||||
it("normalizes z-ai provider in agent.model", () => {
|
||||
const cfg = {
|
||||
agent: { model: "z-ai/glm-4.7" },
|
||||
} satisfies ClawdbotConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({ provider: "zai", model: "glm-4.7" });
|
||||
});
|
||||
|
||||
it("normalizes provider casing in agent.model", () => {
|
||||
const cfg = {
|
||||
agent: { model: "OpenAI/gpt-4.1-mini" },
|
||||
} satisfies ClawdbotConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({ provider: "openai", model: "gpt-4.1-mini" });
|
||||
});
|
||||
|
||||
it("normalizes z.ai casing in agent.model", () => {
|
||||
const cfg = {
|
||||
agent: { model: "Z.AI/glm-4.7" },
|
||||
} satisfies ClawdbotConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({ provider: "zai", model: "glm-4.7" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeProviderId", () => {
|
||||
it("normalizes z.ai aliases to canonical zai", () => {
|
||||
expect(normalizeProviderId("z.ai")).toBe("zai");
|
||||
expect(normalizeProviderId("z-ai")).toBe("zai");
|
||||
});
|
||||
|
||||
it("normalizes provider casing", () => {
|
||||
expect(normalizeProviderId("OpenAI")).toBe("openai");
|
||||
expect(normalizeProviderId("Z.AI")).toBe("zai");
|
||||
expect(allowed.allowAny).toBe(true);
|
||||
expect(allowed.allowedKeys.has(modelKey("openai", "gpt-4"))).toBe(true);
|
||||
expect(
|
||||
allowed.allowedKeys.has(modelKey("claude-cli", "opus-4.5")),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,6 +124,7 @@ export function buildAllowedModelSet(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
catalog: ModelCatalogEntry[];
|
||||
defaultProvider: string;
|
||||
defaultModel?: string;
|
||||
}): {
|
||||
allowAny: boolean;
|
||||
allowedCatalog: ModelCatalogEntry[];
|
||||
@@ -134,11 +135,17 @@ export function buildAllowedModelSet(params: {
|
||||
return Object.keys(modelMap);
|
||||
})();
|
||||
const allowAny = rawAllowlist.length === 0;
|
||||
const defaultModel = params.defaultModel?.trim();
|
||||
const defaultKey =
|
||||
defaultModel && params.defaultProvider
|
||||
? modelKey(params.defaultProvider, defaultModel)
|
||||
: undefined;
|
||||
const catalogKeys = new Set(
|
||||
params.catalog.map((entry) => modelKey(entry.provider, entry.id)),
|
||||
);
|
||||
|
||||
if (allowAny) {
|
||||
if (defaultKey) catalogKeys.add(defaultKey);
|
||||
return {
|
||||
allowAny: true,
|
||||
allowedCatalog: params.catalog,
|
||||
@@ -156,11 +163,16 @@ export function buildAllowedModelSet(params: {
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultKey) {
|
||||
allowedKeys.add(defaultKey);
|
||||
}
|
||||
|
||||
const allowedCatalog = params.catalog.filter((entry) =>
|
||||
allowedKeys.has(modelKey(entry.provider, entry.id)),
|
||||
);
|
||||
|
||||
if (allowedCatalog.length === 0) {
|
||||
if (defaultKey) catalogKeys.add(defaultKey);
|
||||
return {
|
||||
allowAny: true,
|
||||
allowedCatalog: params.catalog,
|
||||
|
||||
Reference in New Issue
Block a user