diff --git a/src/cli/__snapshots__/model-fallback.test.ts.snap b/src/cli/__snapshots__/model-fallback.test.ts.snap index 819e1b79c..47dba5ebc 100644 --- a/src/cli/__snapshots__/model-fallback.test.ts.snap +++ b/src/cli/__snapshots__/model-fallback.test.ts.snap @@ -195,14 +195,16 @@ exports[`generateModelConfig single native provider uses OpenAI models when only "variant": "medium", }, "explore": { - "model": "opencode/gpt-5-nano", + "model": "openai/gpt-5.4", + "variant": "medium", }, "hephaestus": { "model": "openai/gpt-5.3-codex", "variant": "medium", }, "librarian": { - "model": "opencode/glm-4.7-free", + "model": "openai/gpt-5.4", + "variant": "medium", }, "metis": { "model": "openai/gpt-5.4", @@ -230,12 +232,17 @@ exports[`generateModelConfig single native provider uses OpenAI models when only }, }, "categories": { + "artistry": { + "model": "openai/gpt-5.4", + "variant": "xhigh", + }, "deep": { "model": "openai/gpt-5.3-codex", "variant": "medium", }, "quick": { - "model": "opencode/glm-4.7-free", + "model": "openai/gpt-5.3-codex", + "variant": "low", }, "ultrabrain": { "model": "openai/gpt-5.3-codex", @@ -250,10 +257,12 @@ exports[`generateModelConfig single native provider uses OpenAI models when only "variant": "medium", }, "visual-engineering": { - "model": "opencode/glm-4.7-free", + "model": "openai/gpt-5.4", + "variant": "high", }, "writing": { - "model": "opencode/glm-4.7-free", + "model": "openai/gpt-5.4", + "variant": "medium", }, }, } @@ -268,14 +277,16 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa "variant": "medium", }, "explore": { - "model": "opencode/gpt-5-nano", + "model": "openai/gpt-5.4", + "variant": "medium", }, "hephaestus": { "model": "openai/gpt-5.3-codex", "variant": "medium", }, "librarian": { - "model": "opencode/glm-4.7-free", + "model": "openai/gpt-5.4", + "variant": "medium", }, "metis": { "model": "openai/gpt-5.4", @@ -303,12 +314,17 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa }, }, "categories": { + "artistry": { + "model": "openai/gpt-5.4", + "variant": "xhigh", + }, "deep": { "model": "openai/gpt-5.3-codex", "variant": "medium", }, "quick": { - "model": "opencode/glm-4.7-free", + "model": "openai/gpt-5.3-codex", + "variant": "low", }, "ultrabrain": { "model": "openai/gpt-5.3-codex", @@ -323,10 +339,12 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa "variant": "medium", }, "visual-engineering": { - "model": "opencode/glm-4.7-free", + "model": "openai/gpt-5.4", + "variant": "high", }, "writing": { - "model": "opencode/glm-4.7-free", + "model": "openai/gpt-5.4", + "variant": "medium", }, }, } diff --git a/src/cli/model-fallback.test.ts b/src/cli/model-fallback.test.ts index dcc83e023..39bc73f50 100644 --- a/src/cli/model-fallback.test.ts +++ b/src/cli/model-fallback.test.ts @@ -344,15 +344,16 @@ describe("generateModelConfig", () => { expect(result.agents?.explore?.model).toBe("anthropic/claude-haiku-4-5") }) - test("explore uses gpt-5-nano when only OpenAI available", () => { + test("explore uses OpenAI model when only OpenAI available", () => { // #given only OpenAI is available const config = createConfig({ hasOpenAI: true }) // #when generateModelConfig is called const result = generateModelConfig(config) - // #then explore should use gpt-5-nano (fallback) - expect(result.agents?.explore?.model).toBe("opencode/gpt-5-nano") + // #then explore should use native OpenAI model + expect(result.agents?.explore?.model).toBe("openai/gpt-5.4") + expect(result.agents?.explore?.variant).toBe("medium") }) test("explore uses gpt-5-mini when only Copilot available", () => { diff --git a/src/cli/model-fallback.ts b/src/cli/model-fallback.ts index ffa9925d8..3b9c917ef 100644 --- a/src/cli/model-fallback.ts +++ b/src/cli/model-fallback.ts @@ -5,6 +5,7 @@ import { import type { InstallConfig } from "./types" import type { AgentConfig, CategoryConfig, GeneratedOmoConfig } from "./model-fallback-types" +import { applyOpenAiOnlyModelCatalog, isOpenAiOnlyAvailability } from "./openai-only-model-catalog" import { toProviderAvailability } from "./provider-availability" import { getSisyphusFallbackChain, @@ -122,11 +123,15 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig { } } - return { + const generatedConfig: GeneratedOmoConfig = { $schema: SCHEMA_URL, agents, categories, } + + return isOpenAiOnlyAvailability(avail) + ? applyOpenAiOnlyModelCatalog(generatedConfig) + : generatedConfig } export function shouldShowChatGPTOnlyWarning(config: InstallConfig): boolean { diff --git a/src/cli/openai-only-model-catalog.test.ts b/src/cli/openai-only-model-catalog.test.ts new file mode 100644 index 000000000..ee56d2d06 --- /dev/null +++ b/src/cli/openai-only-model-catalog.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, test } from "bun:test" + +import { generateModelConfig } from "./model-fallback" +import type { InstallConfig } from "./types" + +function createConfig(overrides: Partial = {}): InstallConfig { + return { + hasClaude: false, + isMax20: false, + hasOpenAI: false, + hasGemini: false, + hasCopilot: false, + hasOpencodeZen: false, + hasZaiCodingPlan: false, + hasKimiForCoding: false, + ...overrides, + } +} + +describe("generateModelConfig OpenAI-only model catalog", () => { + test("fills remaining OpenAI-only agent gaps with OpenAI models", () => { + // #given + const config = createConfig({ hasOpenAI: true }) + + // #when + const result = generateModelConfig(config) + + // #then + expect(result.agents?.explore).toEqual({ model: "openai/gpt-5.4", variant: "medium" }) + expect(result.agents?.librarian).toEqual({ model: "openai/gpt-5.4", variant: "medium" }) + }) + + test("fills remaining OpenAI-only category gaps with OpenAI models", () => { + // #given + const config = createConfig({ hasOpenAI: true }) + + // #when + const result = generateModelConfig(config) + + // #then + expect(result.categories?.artistry).toEqual({ model: "openai/gpt-5.4", variant: "xhigh" }) + expect(result.categories?.quick).toEqual({ model: "openai/gpt-5.3-codex", variant: "low" }) + expect(result.categories?.["visual-engineering"]).toEqual({ model: "openai/gpt-5.4", variant: "high" }) + expect(result.categories?.writing).toEqual({ model: "openai/gpt-5.4", variant: "medium" }) + }) +}) diff --git a/src/cli/openai-only-model-catalog.ts b/src/cli/openai-only-model-catalog.ts new file mode 100644 index 000000000..52e8484e1 --- /dev/null +++ b/src/cli/openai-only-model-catalog.ts @@ -0,0 +1,39 @@ +import type { AgentConfig, CategoryConfig, GeneratedOmoConfig, ProviderAvailability } from "./model-fallback-types" + +const OPENAI_ONLY_AGENT_OVERRIDES: Record = { + explore: { model: "openai/gpt-5.4", variant: "medium" }, + librarian: { model: "openai/gpt-5.4", variant: "medium" }, +} + +const OPENAI_ONLY_CATEGORY_OVERRIDES: Record = { + artistry: { model: "openai/gpt-5.4", variant: "xhigh" }, + quick: { model: "openai/gpt-5.3-codex", variant: "low" }, + "visual-engineering": { model: "openai/gpt-5.4", variant: "high" }, + writing: { model: "openai/gpt-5.4", variant: "medium" }, +} + +export function isOpenAiOnlyAvailability(availability: ProviderAvailability): boolean { + return ( + availability.native.openai && + !availability.native.claude && + !availability.native.gemini && + !availability.opencodeZen && + !availability.copilot && + !availability.zai && + !availability.kimiForCoding + ) +} + +export function applyOpenAiOnlyModelCatalog(config: GeneratedOmoConfig): GeneratedOmoConfig { + return { + ...config, + agents: { + ...config.agents, + ...OPENAI_ONLY_AGENT_OVERRIDES, + }, + categories: { + ...config.categories, + ...OPENAI_ONLY_CATEGORY_OVERRIDES, + }, + } +}