feat(cli): redesign model fallback with native cross-fallback and OpenAI separation

- Add OpenAI/ChatGPT as separate subscription option (--openai flag)
- Implement native tier cross-fallback (Claude → OpenAI → Gemini)
- Change fallback order: Native → OpenCode Zen → GitHub Copilot → Z.ai
- Add explore agent special logic: max20 → haiku, else → grok-code
- Add critical warning when Claude is not configured
- Add tests for ChatGPT-only and explore agent cases
This commit is contained in:
justsisyphus
2026-01-21 11:14:00 +09:00
parent 137add8b83
commit c5f7fdbaf0
7 changed files with 359 additions and 141 deletions
+94 -10
View File
@@ -201,11 +201,12 @@ describe("config-manager ANTIGRAVITY_PROVIDER_CONFIG", () => {
})
describe("generateOmoConfig - model fallback system", () => {
test("generates native models when Claude available", () => {
// #given user has Claude subscription
test("generates native sonnet models when Claude standard subscription", () => {
// #given user has Claude standard subscription (not max20)
const config: InstallConfig = {
hasClaude: true,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
@@ -215,17 +216,37 @@ describe("generateOmoConfig - model fallback system", () => {
// #when generating config
const result = generateOmoConfig(config)
// #then should use native anthropic models
// #then should use native anthropic sonnet (cost-efficient for standard plan)
expect(result.$schema).toBe("https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json")
expect(result.agents).toBeDefined()
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("anthropic/claude-sonnet-4-5")
})
test("generates native opus models when Claude max20 subscription", () => {
// #given user has Claude max20 subscription
const config: InstallConfig = {
hasClaude: true,
isMax20: true,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then should use native anthropic opus (max power for max20 plan)
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("anthropic/claude-opus-4-5")
})
test("uses github-copilot fallback when only copilot available", () => {
// #given user has only copilot
test("uses github-copilot sonnet fallback when only copilot available", () => {
// #given user has only copilot (no max plan)
const config: InstallConfig = {
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: true,
hasOpencodeZen: false,
@@ -235,8 +256,8 @@ describe("generateOmoConfig - model fallback system", () => {
// #when generating config
const result = generateOmoConfig(config)
// #then should use github-copilot models
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("github-copilot/claude-opus-4.5")
// #then should use github-copilot sonnet models
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("github-copilot/claude-sonnet-4.5")
})
test("uses ultimate fallback when no providers configured", () => {
@@ -244,6 +265,7 @@ describe("generateOmoConfig - model fallback system", () => {
const config: InstallConfig = {
hasClaude: false,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
@@ -259,10 +281,11 @@ describe("generateOmoConfig - model fallback system", () => {
})
test("uses zai-coding-plan/glm-4.7 for librarian when Z.ai available", () => {
// #given user has Z.ai and Claude
// #given user has Z.ai and Claude max20
const config: InstallConfig = {
hasClaude: true,
isMax20: false,
isMax20: true,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
@@ -274,7 +297,68 @@ describe("generateOmoConfig - model fallback system", () => {
// #then librarian should use zai-coding-plan/glm-4.7
expect((result.agents as Record<string, { model: string }>).librarian.model).toBe("zai-coding-plan/glm-4.7")
// #then other agents should use native
// #then other agents should use native opus (max20 plan)
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("anthropic/claude-opus-4-5")
})
test("uses native OpenAI models when only ChatGPT available", () => {
// #given user has only ChatGPT subscription
const config: InstallConfig = {
hasClaude: false,
isMax20: false,
hasOpenAI: true,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then Sisyphus should use native OpenAI (fallback within native tier)
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("openai/gpt-5.2")
// #then Oracle should use native OpenAI (primary for ultrabrain)
expect((result.agents as Record<string, { model: string }>).oracle.model).toBe("openai/gpt-5.2-codex")
// #then multimodal-looker should use native OpenAI (fallback within native tier)
expect((result.agents as Record<string, { model: string }>)["multimodal-looker"].model).toBe("openai/gpt-5.2")
})
test("uses haiku for explore when Claude max20", () => {
// #given user has Claude max20
const config: InstallConfig = {
hasClaude: true,
isMax20: true,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then explore should use haiku (max20 plan uses Claude quota)
expect((result.agents as Record<string, { model: string }>).explore.model).toBe("anthropic/claude-haiku-4-5")
})
test("uses grok-code for explore when not max20", () => {
// #given user has Claude but not max20
const config: InstallConfig = {
hasClaude: true,
isMax20: false,
hasOpenAI: false,
hasGemini: false,
hasCopilot: false,
hasOpencodeZen: false,
hasZaiCodingPlan: false,
}
// #when generating config
const result = generateOmoConfig(config)
// #then explore should use grok-code (preserve Claude quota)
expect((result.agents as Record<string, { model: string }>).explore.model).toBe("opencode/grok-code")
})
})