diff --git a/src/agents/model-selection.test.ts b/src/agents/model-selection.test.ts index e2d90f355bc..bf4c3aee03e 100644 --- a/src/agents/model-selection.test.ts +++ b/src/agents/model-selection.test.ts @@ -50,6 +50,60 @@ function resolveAnthropicOpusThinking(cfg: OpenClawConfig) { }); } +function createAgentFallbackConfig(params: { + primary?: string; + fallbacks?: string[]; + agentFallbacks?: string[]; +}) { + return { + agents: { + defaults: { + models: { + "openai/gpt-4o": {}, + }, + model: { + primary: params.primary ?? "openai/gpt-4o", + fallbacks: params.fallbacks ?? [], + }, + }, + ...(params.agentFallbacks + ? { + list: [ + { + id: "coder", + model: { + primary: params.primary ?? "openai/gpt-4o", + fallbacks: params.agentFallbacks, + }, + }, + ], + } + : {}), + }, + } as OpenClawConfig; +} + +function createProviderWithModelsConfig(provider: string, models: Array>) { + return { + models: { + providers: { + [provider]: { + baseUrl: `https://${provider}.example.com`, + models, + }, + }, + }, + } as Partial; +} + +function resolveConfiguredRefForTest(cfg: Partial) { + return resolveConfiguredModelRef({ + cfg: cfg as OpenClawConfig, + defaultProvider: "anthropic", + defaultModel: "claude-opus-4-6", + }); +} + describe("model-selection", () => { describe("normalizeProviderId", () => { it("should normalize provider names", () => { @@ -326,19 +380,9 @@ describe("model-selection", () => { }); it("includes fallback models in allowed set", () => { - const cfg: OpenClawConfig = { - agents: { - defaults: { - models: { - "openai/gpt-4o": {}, - }, - model: { - primary: "openai/gpt-4o", - fallbacks: ["anthropic/claude-sonnet-4-6", "google/gemini-3-pro"], - }, - }, - }, - } as OpenClawConfig; + const cfg = createAgentFallbackConfig({ + fallbacks: ["anthropic/claude-sonnet-4-6", "google/gemini-3-pro"], + }); const result = buildAllowedModelSet({ cfg, @@ -354,19 +398,7 @@ describe("model-selection", () => { }); it("handles empty fallbacks gracefully", () => { - const cfg: OpenClawConfig = { - agents: { - defaults: { - models: { - "openai/gpt-4o": {}, - }, - model: { - primary: "openai/gpt-4o", - fallbacks: [], - }, - }, - }, - } as OpenClawConfig; + const cfg = createAgentFallbackConfig({}); const result = buildAllowedModelSet({ cfg, @@ -380,28 +412,10 @@ describe("model-selection", () => { }); it("prefers per-agent fallback overrides when agentId is provided", () => { - const cfg: OpenClawConfig = { - agents: { - defaults: { - models: { - "openai/gpt-4o": {}, - }, - model: { - primary: "openai/gpt-4o", - fallbacks: ["google/gemini-3-pro"], - }, - }, - list: [ - { - id: "coder", - model: { - primary: "openai/gpt-4o", - fallbacks: ["anthropic/claude-sonnet-4-6"], - }, - }, - ], - }, - } as OpenClawConfig; + const cfg = createAgentFallbackConfig({ + fallbacks: ["google/gemini-3-pro"], + agentFallbacks: ["anthropic/claude-sonnet-4-6"], + }); const result = buildAllowedModelSet({ cfg, @@ -632,79 +646,40 @@ describe("model-selection", () => { }); it("should prefer configured custom provider when default provider is not in models.providers", () => { - const cfg: Partial = { - models: { - providers: { - n1n: { - baseUrl: "https://n1n.example.com", - models: [ - { - id: "gpt-5.4", - name: "GPT 5.4", - reasoning: false, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 128000, - maxTokens: 4096, - }, - ], - }, - }, + const cfg = createProviderWithModelsConfig("n1n", [ + { + id: "gpt-5.4", + name: "GPT 5.4", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 4096, }, - }; - const result = resolveConfiguredModelRef({ - cfg: cfg as OpenClawConfig, - defaultProvider: "anthropic", - defaultModel: "claude-opus-4-6", - }); + ]); + const result = resolveConfiguredRefForTest(cfg); expect(result).toEqual({ provider: "n1n", model: "gpt-5.4" }); }); it("should keep default provider when it is in models.providers", () => { - const cfg: Partial = { - models: { - providers: { - anthropic: { - baseUrl: "https://api.anthropic.com", - models: [ - { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - reasoning: true, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 200000, - maxTokens: 4096, - }, - ], - }, - }, + const cfg = createProviderWithModelsConfig("anthropic", [ + { + id: "claude-opus-4-6", + name: "Claude Opus 4.6", + reasoning: true, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 200000, + maxTokens: 4096, }, - }; - const result = resolveConfiguredModelRef({ - cfg: cfg as OpenClawConfig, - defaultProvider: "anthropic", - defaultModel: "claude-opus-4-6", - }); + ]); + const result = resolveConfiguredRefForTest(cfg); expect(result).toEqual({ provider: "anthropic", model: "claude-opus-4-6" }); }); it("should fall back to hardcoded default when no custom providers have models", () => { - const cfg: Partial = { - models: { - providers: { - "empty-provider": { - baseUrl: "https://example.com", - models: [], - }, - }, - }, - }; - const result = resolveConfiguredModelRef({ - cfg: cfg as OpenClawConfig, - defaultProvider: "anthropic", - defaultModel: "claude-opus-4-6", - }); + const cfg = createProviderWithModelsConfig("empty-provider", []); + const result = resolveConfiguredRefForTest(cfg); expect(result).toEqual({ provider: "anthropic", model: "claude-opus-4-6" }); });