diff --git a/src/commands/google-gemini-model-default.ts b/src/commands/google-gemini-model-default.ts index a343a29fb3b..385f1cc849d 100644 --- a/src/commands/google-gemini-model-default.ts +++ b/src/commands/google-gemini-model-default.ts @@ -1,44 +1,11 @@ import type { OpenClawConfig } from "../config/config.js"; -import type { AgentModelListConfig } from "../config/types.js"; +import { applyAgentDefaultPrimaryModel } from "./model-default.js"; export const GOOGLE_GEMINI_DEFAULT_MODEL = "google/gemini-3-pro-preview"; -function resolvePrimaryModel(model?: AgentModelListConfig | string): string | undefined { - if (typeof model === "string") { - return model; - } - if (model && typeof model === "object" && typeof model.primary === "string") { - return model.primary; - } - return undefined; -} - export function applyGoogleGeminiModelDefault(cfg: OpenClawConfig): { next: OpenClawConfig; changed: boolean; } { - const current = resolvePrimaryModel(cfg.agents?.defaults?.model)?.trim(); - if (current === GOOGLE_GEMINI_DEFAULT_MODEL) { - return { next: cfg, changed: false }; - } - - return { - next: { - ...cfg, - agents: { - ...cfg.agents, - defaults: { - ...cfg.agents?.defaults, - model: - cfg.agents?.defaults?.model && typeof cfg.agents.defaults.model === "object" - ? { - ...cfg.agents.defaults.model, - primary: GOOGLE_GEMINI_DEFAULT_MODEL, - } - : { primary: GOOGLE_GEMINI_DEFAULT_MODEL }, - }, - }, - }, - changed: true, - }; + return applyAgentDefaultPrimaryModel({ cfg, model: GOOGLE_GEMINI_DEFAULT_MODEL }); } diff --git a/src/commands/model-default.test.ts b/src/commands/model-default.test.ts new file mode 100644 index 00000000000..dab27ae31e6 --- /dev/null +++ b/src/commands/model-default.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../config/config.js"; +import { applyAgentDefaultPrimaryModel } from "./model-default.js"; + +describe("applyAgentDefaultPrimaryModel", () => { + it("does not mutate when already set", () => { + const cfg = { agents: { defaults: { model: { primary: "a/b" } } } } as OpenClawConfig; + const result = applyAgentDefaultPrimaryModel({ cfg, model: "a/b" }); + expect(result.changed).toBe(false); + expect(result.next).toBe(cfg); + }); + + it("normalizes legacy models", () => { + const cfg = { agents: { defaults: { model: { primary: "legacy" } } } } as OpenClawConfig; + const result = applyAgentDefaultPrimaryModel({ + cfg, + model: "a/b", + legacyModels: new Set(["legacy"]), + }); + expect(result.changed).toBe(false); + expect(result.next).toBe(cfg); + }); +}); diff --git a/src/commands/model-default.ts b/src/commands/model-default.ts new file mode 100644 index 00000000000..ce121973da3 --- /dev/null +++ b/src/commands/model-default.ts @@ -0,0 +1,45 @@ +import type { OpenClawConfig } from "../config/config.js"; +import type { AgentModelListConfig } from "../config/types.js"; + +export function resolvePrimaryModel(model?: AgentModelListConfig | string): string | undefined { + if (typeof model === "string") { + return model; + } + if (model && typeof model === "object" && typeof model.primary === "string") { + return model.primary; + } + return undefined; +} + +export function applyAgentDefaultPrimaryModel(params: { + cfg: OpenClawConfig; + model: string; + legacyModels?: Set; +}): { next: OpenClawConfig; changed: boolean } { + const current = resolvePrimaryModel(params.cfg.agents?.defaults?.model)?.trim(); + const normalizedCurrent = current && params.legacyModels?.has(current) ? params.model : current; + if (normalizedCurrent === params.model) { + return { next: params.cfg, changed: false }; + } + + return { + next: { + ...params.cfg, + agents: { + ...params.cfg.agents, + defaults: { + ...params.cfg.agents?.defaults, + model: + params.cfg.agents?.defaults?.model && + typeof params.cfg.agents.defaults.model === "object" + ? { + ...params.cfg.agents.defaults.model, + primary: params.model, + } + : { primary: params.model }, + }, + }, + }, + changed: true, + }; +} diff --git a/src/commands/opencode-zen-model-default.ts b/src/commands/opencode-zen-model-default.ts index 9f3d4b45654..9efb9c17ade 100644 --- a/src/commands/opencode-zen-model-default.ts +++ b/src/commands/opencode-zen-model-default.ts @@ -1,5 +1,5 @@ import type { OpenClawConfig } from "../config/config.js"; -import type { AgentModelListConfig } from "../config/types.js"; +import { applyAgentDefaultPrimaryModel } from "./model-default.js"; export const OPENCODE_ZEN_DEFAULT_MODEL = "opencode/claude-opus-4-6"; const LEGACY_OPENCODE_ZEN_DEFAULT_MODELS = new Set([ @@ -7,46 +7,13 @@ const LEGACY_OPENCODE_ZEN_DEFAULT_MODELS = new Set([ "opencode-zen/claude-opus-4-5", ]); -function resolvePrimaryModel(model?: AgentModelListConfig | string): string | undefined { - if (typeof model === "string") { - return model; - } - if (model && typeof model === "object" && typeof model.primary === "string") { - return model.primary; - } - return undefined; -} - export function applyOpencodeZenModelDefault(cfg: OpenClawConfig): { next: OpenClawConfig; changed: boolean; } { - const current = resolvePrimaryModel(cfg.agents?.defaults?.model)?.trim(); - const normalizedCurrent = - current && LEGACY_OPENCODE_ZEN_DEFAULT_MODELS.has(current) - ? OPENCODE_ZEN_DEFAULT_MODEL - : current; - if (normalizedCurrent === OPENCODE_ZEN_DEFAULT_MODEL) { - return { next: cfg, changed: false }; - } - - return { - next: { - ...cfg, - agents: { - ...cfg.agents, - defaults: { - ...cfg.agents?.defaults, - model: - cfg.agents?.defaults?.model && typeof cfg.agents.defaults.model === "object" - ? { - ...cfg.agents.defaults.model, - primary: OPENCODE_ZEN_DEFAULT_MODEL, - } - : { primary: OPENCODE_ZEN_DEFAULT_MODEL }, - }, - }, - }, - changed: true, - }; + return applyAgentDefaultPrimaryModel({ + cfg, + model: OPENCODE_ZEN_DEFAULT_MODEL, + legacyModels: LEGACY_OPENCODE_ZEN_DEFAULT_MODELS, + }); }