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:
@@ -41,19 +41,23 @@ Ask the user these questions to determine CLI options:
|
||||
- **not max20** → `--claude=yes`
|
||||
- If **no** → `--claude=no`
|
||||
|
||||
2. **Will you integrate Gemini models?**
|
||||
2. **Do you have an OpenAI/ChatGPT Plus Subscription?**
|
||||
- If **yes** → `--openai=yes` (GPT-5.2 for Oracle agent)
|
||||
- If **no** → `--openai=no` (default)
|
||||
|
||||
3. **Will you integrate Gemini models?**
|
||||
- If **yes** → `--gemini=yes`
|
||||
- If **no** → `--gemini=no`
|
||||
|
||||
3. **Do you have a GitHub Copilot Subscription?**
|
||||
4. **Do you have a GitHub Copilot Subscription?**
|
||||
- If **yes** → `--copilot=yes`
|
||||
- If **no** → `--copilot=no`
|
||||
|
||||
4. **Do you have access to OpenCode Zen (opencode/ models)?**
|
||||
5. **Do you have access to OpenCode Zen (opencode/ models)?**
|
||||
- If **yes** → `--opencode-zen=yes`
|
||||
- If **no** → `--opencode-zen=no` (default)
|
||||
|
||||
5. **Do you have a Z.ai Coding Plan subscription?**
|
||||
6. **Do you have a Z.ai Coding Plan subscription?**
|
||||
- If **yes** → `--zai-coding-plan=yes`
|
||||
- If **no** → `--zai-coding-plan=no` (default)
|
||||
|
||||
@@ -80,12 +84,13 @@ Spawn a subagent to handle installation and report back - to save context.
|
||||
Based on user's answers, run the CLI installer with appropriate flags:
|
||||
|
||||
```bash
|
||||
bunx oh-my-opencode install --no-tui --claude=<yes|no|max20> --gemini=<yes|no> --copilot=<yes|no> [--opencode-zen=<yes|no>] [--zai-coding-plan=<yes|no>]
|
||||
bunx oh-my-opencode install --no-tui --claude=<yes|no|max20> --gemini=<yes|no> --copilot=<yes|no> [--openai=<yes|no>] [--opencode-zen=<yes|no>] [--zai-coding-plan=<yes|no>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
- User has all native subscriptions: `bunx oh-my-opencode install --no-tui --claude=max20 --gemini=yes --copilot=no`
|
||||
- User has all native subscriptions: `bunx oh-my-opencode install --no-tui --claude=max20 --openai=yes --gemini=yes --copilot=no`
|
||||
- User has only Claude: `bunx oh-my-opencode install --no-tui --claude=yes --gemini=no --copilot=no`
|
||||
- User has Claude + OpenAI: `bunx oh-my-opencode install --no-tui --claude=yes --openai=yes --gemini=no --copilot=no`
|
||||
- User has only GitHub Copilot: `bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes`
|
||||
- User has Z.ai for Librarian: `bunx oh-my-opencode install --no-tui --claude=yes --gemini=no --copilot=no --zai-coding-plan=yes`
|
||||
- User has only OpenCode Zen: `bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=no --opencode-zen=yes`
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -575,11 +575,36 @@ export function addProviderConfig(config: InstallConfig): ConfigMergeResult {
|
||||
}
|
||||
}
|
||||
|
||||
function detectProvidersFromOmoConfig(): { hasOpenAI: boolean; hasOpencodeZen: boolean; hasZaiCodingPlan: boolean } {
|
||||
const omoConfigPath = getOmoConfig()
|
||||
if (!existsSync(omoConfigPath)) {
|
||||
return { hasOpenAI: true, hasOpencodeZen: true, hasZaiCodingPlan: false }
|
||||
}
|
||||
|
||||
try {
|
||||
const content = readFileSync(omoConfigPath, "utf-8")
|
||||
const omoConfig = parseJsonc<Record<string, unknown>>(content)
|
||||
if (!omoConfig || typeof omoConfig !== "object") {
|
||||
return { hasOpenAI: true, hasOpencodeZen: true, hasZaiCodingPlan: false }
|
||||
}
|
||||
|
||||
const configStr = JSON.stringify(omoConfig)
|
||||
const hasOpenAI = configStr.includes('"openai/')
|
||||
const hasOpencodeZen = configStr.includes('"opencode/')
|
||||
const hasZaiCodingPlan = configStr.includes('"zai-coding-plan/')
|
||||
|
||||
return { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan }
|
||||
} catch {
|
||||
return { hasOpenAI: true, hasOpencodeZen: true, hasZaiCodingPlan: false }
|
||||
}
|
||||
}
|
||||
|
||||
export function detectCurrentConfig(): DetectedConfig {
|
||||
const result: DetectedConfig = {
|
||||
isInstalled: false,
|
||||
hasClaude: true,
|
||||
isMax20: true,
|
||||
hasOpenAI: true,
|
||||
hasGemini: false,
|
||||
hasCopilot: false,
|
||||
hasOpencodeZen: true,
|
||||
@@ -607,5 +632,10 @@ export function detectCurrentConfig(): DetectedConfig {
|
||||
// Gemini auth plugin detection still works via plugin presence
|
||||
result.hasGemini = plugins.some((p) => p.startsWith("opencode-antigravity-auth"))
|
||||
|
||||
const { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan } = detectProvidersFromOmoConfig()
|
||||
result.hasOpenAI = hasOpenAI
|
||||
result.hasOpencodeZen = hasOpencodeZen
|
||||
result.hasZaiCodingPlan = hasZaiCodingPlan
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ program
|
||||
.description("Install and configure oh-my-opencode with interactive setup")
|
||||
.option("--no-tui", "Run in non-interactive mode (requires all options)")
|
||||
.option("--claude <value>", "Claude subscription: no, yes, max20")
|
||||
.option("--openai <value>", "OpenAI/ChatGPT subscription: no, yes (default: no)")
|
||||
.option("--gemini <value>", "Gemini integration: no, yes")
|
||||
.option("--copilot <value>", "GitHub Copilot subscription: no, yes")
|
||||
.option("--opencode-zen <value>", "OpenCode Zen access: no, yes (default: no)")
|
||||
@@ -32,11 +33,12 @@ program
|
||||
.addHelpText("after", `
|
||||
Examples:
|
||||
$ bunx oh-my-opencode install
|
||||
$ bunx oh-my-opencode install --no-tui --claude=max20 --gemini=yes --copilot=no
|
||||
$ bunx oh-my-opencode install --no-tui --claude=max20 --openai=yes --gemini=yes --copilot=no
|
||||
$ bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes --opencode-zen=yes
|
||||
|
||||
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai):
|
||||
Claude Native anthropic/ models (Opus, Sonnet, Haiku)
|
||||
OpenAI Native openai/ models (GPT-5.2 for Oracle)
|
||||
Gemini Native google/ models (Gemini 3 Pro, Flash)
|
||||
Copilot github-copilot/ models (fallback)
|
||||
OpenCode Zen opencode/ models (opencode/claude-opus-4-5, etc.)
|
||||
@@ -46,6 +48,7 @@ Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai):
|
||||
const args: InstallArgs = {
|
||||
tui: options.tui !== false,
|
||||
claude: options.claude,
|
||||
openai: options.openai,
|
||||
gemini: options.gemini,
|
||||
copilot: options.copilot,
|
||||
opencodeZen: options.opencodeZen,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
addProviderConfig,
|
||||
detectCurrentConfig,
|
||||
} from "./config-manager"
|
||||
import { shouldShowChatGPTOnlyWarning } from "./model-fallback"
|
||||
import packageJson from "../../package.json" with { type: "json" }
|
||||
|
||||
const VERSION = packageJson.version
|
||||
@@ -39,6 +40,7 @@ function formatConfigSummary(config: InstallConfig): string {
|
||||
|
||||
const claudeDetail = config.hasClaude ? (config.isMax20 ? "max20" : "standard") : undefined
|
||||
lines.push(formatProvider("Claude", config.hasClaude, claudeDetail))
|
||||
lines.push(formatProvider("OpenAI/ChatGPT", config.hasOpenAI, "GPT-5.2 for Oracle"))
|
||||
lines.push(formatProvider("Gemini", config.hasGemini))
|
||||
lines.push(formatProvider("GitHub Copilot", config.hasCopilot, "fallback"))
|
||||
lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models"))
|
||||
@@ -127,6 +129,10 @@ function validateNonTuiArgs(args: InstallArgs): { valid: boolean; errors: string
|
||||
errors.push(`Invalid --copilot value: ${args.copilot} (expected: no, yes)`)
|
||||
}
|
||||
|
||||
if (args.openai !== undefined && !["no", "yes"].includes(args.openai)) {
|
||||
errors.push(`Invalid --openai value: ${args.openai} (expected: no, yes)`)
|
||||
}
|
||||
|
||||
if (args.opencodeZen !== undefined && !["no", "yes"].includes(args.opencodeZen)) {
|
||||
errors.push(`Invalid --opencode-zen value: ${args.opencodeZen} (expected: no, yes)`)
|
||||
}
|
||||
@@ -142,6 +148,7 @@ function argsToConfig(args: InstallArgs): InstallConfig {
|
||||
return {
|
||||
hasClaude: args.claude !== "no",
|
||||
isMax20: args.claude === "max20",
|
||||
hasOpenAI: args.openai === "yes",
|
||||
hasGemini: args.gemini === "yes",
|
||||
hasCopilot: args.copilot === "yes",
|
||||
hasOpencodeZen: args.opencodeZen === "yes",
|
||||
@@ -149,7 +156,7 @@ function argsToConfig(args: InstallArgs): InstallConfig {
|
||||
}
|
||||
}
|
||||
|
||||
function detectedToInitialValues(detected: DetectedConfig): { claude: ClaudeSubscription; gemini: BooleanArg; copilot: BooleanArg; opencodeZen: BooleanArg; zaiCodingPlan: BooleanArg } {
|
||||
function detectedToInitialValues(detected: DetectedConfig): { claude: ClaudeSubscription; openai: BooleanArg; gemini: BooleanArg; copilot: BooleanArg; opencodeZen: BooleanArg; zaiCodingPlan: BooleanArg } {
|
||||
let claude: ClaudeSubscription = "no"
|
||||
if (detected.hasClaude) {
|
||||
claude = detected.isMax20 ? "max20" : "yes"
|
||||
@@ -157,6 +164,7 @@ function detectedToInitialValues(detected: DetectedConfig): { claude: ClaudeSubs
|
||||
|
||||
return {
|
||||
claude,
|
||||
openai: detected.hasOpenAI ? "yes" : "no",
|
||||
gemini: detected.hasGemini ? "yes" : "no",
|
||||
copilot: detected.hasCopilot ? "yes" : "no",
|
||||
opencodeZen: detected.hasOpencodeZen ? "yes" : "no",
|
||||
@@ -182,6 +190,20 @@ async function runTuiMode(detected: DetectedConfig): Promise<InstallConfig | nul
|
||||
return null
|
||||
}
|
||||
|
||||
const openai = await p.select({
|
||||
message: "Do you have an OpenAI/ChatGPT Plus subscription?",
|
||||
options: [
|
||||
{ value: "no" as const, label: "No", hint: "Oracle will use fallback models" },
|
||||
{ value: "yes" as const, label: "Yes", hint: "GPT-5.2 for Oracle (high-IQ debugging)" },
|
||||
],
|
||||
initialValue: initial.openai,
|
||||
})
|
||||
|
||||
if (p.isCancel(openai)) {
|
||||
p.cancel("Installation cancelled.")
|
||||
return null
|
||||
}
|
||||
|
||||
const gemini = await p.select({
|
||||
message: "Will you integrate Google Gemini?",
|
||||
options: [
|
||||
@@ -241,6 +263,7 @@ async function runTuiMode(detected: DetectedConfig): Promise<InstallConfig | nul
|
||||
return {
|
||||
hasClaude: claude !== "no",
|
||||
isMax20: claude === "max20",
|
||||
hasOpenAI: openai === "yes",
|
||||
hasGemini: gemini === "yes",
|
||||
hasCopilot: copilot === "yes",
|
||||
hasOpencodeZen: opencodeZen === "yes",
|
||||
@@ -326,7 +349,21 @@ async function runNonTuiInstall(args: InstallArgs): Promise<number> {
|
||||
|
||||
printBox(formatConfigSummary(config), isUpdate ? "Updated Configuration" : "Installation Complete")
|
||||
|
||||
if (!config.hasClaude && !config.hasGemini && !config.hasCopilot) {
|
||||
if (!config.hasClaude) {
|
||||
console.log()
|
||||
console.log(color.bgRed(color.white(color.bold(" ⚠️ CRITICAL WARNING "))))
|
||||
console.log()
|
||||
console.log(color.red(color.bold(" Sisyphus agent is STRONGLY optimized for Claude Opus 4.5.")))
|
||||
console.log(color.red(" Without Claude, you may experience significantly degraded performance:"))
|
||||
console.log(color.dim(" • Reduced orchestration quality"))
|
||||
console.log(color.dim(" • Weaker tool selection and delegation"))
|
||||
console.log(color.dim(" • Less reliable task completion"))
|
||||
console.log()
|
||||
console.log(color.yellow(" Consider subscribing to Claude Pro/Max for the best experience."))
|
||||
console.log()
|
||||
}
|
||||
|
||||
if (!config.hasClaude && !config.hasOpenAI && !config.hasGemini && !config.hasCopilot && !config.hasOpencodeZen) {
|
||||
printWarning("No model providers configured. Using opencode/glm-4.7-free as fallback.")
|
||||
}
|
||||
|
||||
@@ -431,7 +468,21 @@ export async function install(args: InstallArgs): Promise<number> {
|
||||
}
|
||||
s.stop(`Config written to ${color.cyan(omoResult.configPath)}`)
|
||||
|
||||
if (!config.hasClaude && !config.hasGemini && !config.hasCopilot) {
|
||||
if (!config.hasClaude) {
|
||||
console.log()
|
||||
console.log(color.bgRed(color.white(color.bold(" ⚠️ CRITICAL WARNING "))))
|
||||
console.log()
|
||||
console.log(color.red(color.bold(" Sisyphus agent is STRONGLY optimized for Claude Opus 4.5.")))
|
||||
console.log(color.red(" Without Claude, you may experience significantly degraded performance:"))
|
||||
console.log(color.dim(" • Reduced orchestration quality"))
|
||||
console.log(color.dim(" • Weaker tool selection and delegation"))
|
||||
console.log(color.dim(" • Less reliable task completion"))
|
||||
console.log()
|
||||
console.log(color.yellow(" Consider subscribing to Claude Pro/Max for the best experience."))
|
||||
console.log()
|
||||
}
|
||||
|
||||
if (!config.hasClaude && !config.hasOpenAI && !config.hasGemini && !config.hasCopilot && !config.hasOpencodeZen) {
|
||||
p.log.warn("No model providers configured. Using opencode/glm-4.7-free as fallback.")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { InstallConfig } from "./types"
|
||||
|
||||
type ProviderTier = "native" | "github-copilot" | "opencode" | "zai-coding-plan"
|
||||
type NativeProvider = "claude" | "openai" | "gemini"
|
||||
|
||||
type ModelCapability =
|
||||
| "opus-level"
|
||||
| "sonnet-level"
|
||||
| "haiku-level"
|
||||
| "reasoning"
|
||||
| "codex"
|
||||
| "visual"
|
||||
| "fast"
|
||||
| "unspecified-high"
|
||||
| "unspecified-low"
|
||||
| "quick"
|
||||
| "ultrabrain"
|
||||
| "visual-engineering"
|
||||
| "artistry"
|
||||
| "writing"
|
||||
| "glm"
|
||||
|
||||
interface ProviderAvailability {
|
||||
@@ -18,79 +18,127 @@ interface ProviderAvailability {
|
||||
openai: boolean
|
||||
gemini: boolean
|
||||
}
|
||||
opencodeZen: boolean
|
||||
copilot: boolean
|
||||
opencode: boolean
|
||||
zai: boolean
|
||||
isMaxPlan: boolean
|
||||
}
|
||||
|
||||
interface AgentConfig {
|
||||
model: string
|
||||
variant?: string
|
||||
}
|
||||
|
||||
interface CategoryConfig {
|
||||
model: string
|
||||
variant?: string
|
||||
}
|
||||
|
||||
export interface GeneratedOmoConfig {
|
||||
$schema: string
|
||||
agents?: Record<string, { model: string }>
|
||||
categories?: Record<string, { model: string }>
|
||||
agents?: Record<string, AgentConfig>
|
||||
categories?: Record<string, CategoryConfig>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
const MODEL_CATALOG: Record<ProviderTier, Partial<Record<ModelCapability, string>>> = {
|
||||
native: {
|
||||
"opus-level": "anthropic/claude-opus-4-5",
|
||||
"sonnet-level": "anthropic/claude-sonnet-4-5",
|
||||
"haiku-level": "anthropic/claude-haiku-4-5",
|
||||
reasoning: "openai/gpt-5.2",
|
||||
codex: "openai/gpt-5.2-codex",
|
||||
visual: "google/gemini-3-pro-preview",
|
||||
fast: "google/gemini-3-flash-preview",
|
||||
},
|
||||
"github-copilot": {
|
||||
"opus-level": "github-copilot/claude-opus-4.5",
|
||||
"sonnet-level": "github-copilot/claude-sonnet-4.5",
|
||||
"haiku-level": "github-copilot/claude-haiku-4.5",
|
||||
reasoning: "github-copilot/gpt-5.2",
|
||||
codex: "github-copilot/gpt-5.2-codex",
|
||||
visual: "github-copilot/gemini-3-pro-preview",
|
||||
fast: "github-copilot/grok-code-fast-1",
|
||||
},
|
||||
opencode: {
|
||||
"opus-level": "opencode/claude-opus-4-5",
|
||||
"sonnet-level": "opencode/claude-sonnet-4-5",
|
||||
"haiku-level": "opencode/claude-haiku-4-5",
|
||||
reasoning: "opencode/gpt-5.2",
|
||||
codex: "opencode/gpt-5.2-codex",
|
||||
visual: "opencode/gemini-3-pro",
|
||||
fast: "opencode/grok-code",
|
||||
glm: "opencode/glm-4.7-free",
|
||||
},
|
||||
"zai-coding-plan": {
|
||||
"opus-level": "zai-coding-plan/glm-4.7",
|
||||
"sonnet-level": "zai-coding-plan/glm-4.7",
|
||||
"haiku-level": "zai-coding-plan/glm-4.7-flash",
|
||||
reasoning: "zai-coding-plan/glm-4.7",
|
||||
codex: "zai-coding-plan/glm-4.7",
|
||||
visual: "zai-coding-plan/glm-4.7",
|
||||
fast: "zai-coding-plan/glm-4.7-flash",
|
||||
glm: "zai-coding-plan/glm-4.7",
|
||||
},
|
||||
interface NativeFallbackEntry {
|
||||
provider: NativeProvider
|
||||
model: string
|
||||
}
|
||||
|
||||
const AGENT_REQUIREMENTS: Record<string, ModelCapability> = {
|
||||
Sisyphus: "opus-level",
|
||||
oracle: "reasoning",
|
||||
librarian: "glm",
|
||||
explore: "fast",
|
||||
"multimodal-looker": "visual",
|
||||
"Prometheus (Planner)": "opus-level",
|
||||
"Metis (Plan Consultant)": "sonnet-level",
|
||||
"Momus (Plan Reviewer)": "sonnet-level",
|
||||
Atlas: "opus-level",
|
||||
const NATIVE_FALLBACK_CHAINS: Record<ModelCapability, NativeFallbackEntry[]> = {
|
||||
"unspecified-high": [
|
||||
{ provider: "claude", model: "anthropic/claude-opus-4-5" },
|
||||
{ provider: "openai", model: "openai/gpt-5.2" },
|
||||
{ provider: "gemini", model: "google/gemini-3-pro-preview" },
|
||||
],
|
||||
"unspecified-low": [
|
||||
{ provider: "claude", model: "anthropic/claude-sonnet-4-5" },
|
||||
{ provider: "openai", model: "openai/gpt-5.2" },
|
||||
{ provider: "gemini", model: "google/gemini-3-flash-preview" },
|
||||
],
|
||||
quick: [
|
||||
{ provider: "claude", model: "anthropic/claude-haiku-4-5" },
|
||||
{ provider: "openai", model: "openai/gpt-5.1-codex-mini" },
|
||||
{ provider: "gemini", model: "google/gemini-3-flash-preview" },
|
||||
],
|
||||
ultrabrain: [
|
||||
{ provider: "openai", model: "openai/gpt-5.2-codex" },
|
||||
{ provider: "claude", model: "anthropic/claude-opus-4-5" },
|
||||
{ provider: "gemini", model: "google/gemini-3-pro-preview" },
|
||||
],
|
||||
"visual-engineering": [
|
||||
{ provider: "gemini", model: "google/gemini-3-pro-preview" },
|
||||
{ provider: "openai", model: "openai/gpt-5.2" },
|
||||
{ provider: "claude", model: "anthropic/claude-sonnet-4-5" },
|
||||
],
|
||||
artistry: [
|
||||
{ provider: "gemini", model: "google/gemini-3-pro-preview" },
|
||||
{ provider: "openai", model: "openai/gpt-5.2" },
|
||||
{ provider: "claude", model: "anthropic/claude-opus-4-5" },
|
||||
],
|
||||
writing: [
|
||||
{ provider: "gemini", model: "google/gemini-3-flash-preview" },
|
||||
{ provider: "openai", model: "openai/gpt-5.2" },
|
||||
{ provider: "claude", model: "anthropic/claude-sonnet-4-5" },
|
||||
],
|
||||
glm: [],
|
||||
}
|
||||
|
||||
const CATEGORY_REQUIREMENTS: Record<string, ModelCapability> = {
|
||||
"visual-engineering": "visual",
|
||||
ultrabrain: "codex",
|
||||
artistry: "visual",
|
||||
quick: "haiku-level",
|
||||
"unspecified-low": "sonnet-level",
|
||||
"unspecified-high": "opus-level",
|
||||
writing: "fast",
|
||||
const OPENCODE_ZEN_MODELS: Record<ModelCapability, string> = {
|
||||
"unspecified-high": "opencode/claude-opus-4-5",
|
||||
"unspecified-low": "opencode/claude-sonnet-4-5",
|
||||
quick: "opencode/claude-haiku-4-5",
|
||||
ultrabrain: "opencode/gpt-5.2-codex",
|
||||
"visual-engineering": "opencode/gemini-3-pro",
|
||||
artistry: "opencode/gemini-3-pro",
|
||||
writing: "opencode/gemini-3-flash",
|
||||
glm: "opencode/glm-4.7-free",
|
||||
}
|
||||
|
||||
const GITHUB_COPILOT_MODELS: Record<ModelCapability, string> = {
|
||||
"unspecified-high": "github-copilot/claude-opus-4.5",
|
||||
"unspecified-low": "github-copilot/claude-sonnet-4.5",
|
||||
quick: "github-copilot/claude-haiku-4.5",
|
||||
ultrabrain: "github-copilot/gpt-5.2-codex",
|
||||
"visual-engineering": "github-copilot/gemini-3-pro-preview",
|
||||
artistry: "github-copilot/gemini-3-pro-preview",
|
||||
writing: "github-copilot/gemini-3-flash-preview",
|
||||
glm: "github-copilot/gpt-5.2",
|
||||
}
|
||||
|
||||
const ZAI_MODEL = "zai-coding-plan/glm-4.7"
|
||||
|
||||
interface AgentRequirement {
|
||||
capability: ModelCapability
|
||||
variant?: string
|
||||
}
|
||||
|
||||
const AGENT_REQUIREMENTS: Record<string, AgentRequirement> = {
|
||||
Sisyphus: { capability: "unspecified-high" },
|
||||
oracle: { capability: "ultrabrain", variant: "high" },
|
||||
librarian: { capability: "glm" },
|
||||
explore: { capability: "quick" },
|
||||
"multimodal-looker": { capability: "visual-engineering" },
|
||||
"Prometheus (Planner)": { capability: "unspecified-high" },
|
||||
"Metis (Plan Consultant)": { capability: "unspecified-high" },
|
||||
"Momus (Plan Reviewer)": { capability: "ultrabrain", variant: "medium" },
|
||||
Atlas: { capability: "unspecified-high" },
|
||||
}
|
||||
|
||||
interface CategoryRequirement {
|
||||
capability: ModelCapability
|
||||
variant?: string
|
||||
}
|
||||
|
||||
const CATEGORY_REQUIREMENTS: Record<string, CategoryRequirement> = {
|
||||
"visual-engineering": { capability: "visual-engineering" },
|
||||
ultrabrain: { capability: "ultrabrain" },
|
||||
artistry: { capability: "artistry", variant: "max" },
|
||||
quick: { capability: "quick" },
|
||||
"unspecified-low": { capability: "unspecified-low" },
|
||||
"unspecified-high": { capability: "unspecified-high" },
|
||||
writing: { capability: "writing" },
|
||||
}
|
||||
|
||||
const ULTIMATE_FALLBACK = "opencode/glm-4.7-free"
|
||||
@@ -100,73 +148,51 @@ function toProviderAvailability(config: InstallConfig): ProviderAvailability {
|
||||
return {
|
||||
native: {
|
||||
claude: config.hasClaude,
|
||||
openai: config.hasClaude,
|
||||
openai: config.hasOpenAI,
|
||||
gemini: config.hasGemini,
|
||||
},
|
||||
opencodeZen: config.hasOpencodeZen,
|
||||
copilot: config.hasCopilot,
|
||||
opencode: config.hasOpencodeZen,
|
||||
zai: config.hasZaiCodingPlan,
|
||||
isMaxPlan: config.isMax20,
|
||||
}
|
||||
}
|
||||
|
||||
function getProviderPriority(avail: ProviderAvailability): ProviderTier[] {
|
||||
const tiers: ProviderTier[] = []
|
||||
|
||||
if (avail.native.claude || avail.native.openai || avail.native.gemini) {
|
||||
tiers.push("native")
|
||||
}
|
||||
if (avail.copilot) tiers.push("github-copilot")
|
||||
if (avail.opencode) tiers.push("opencode")
|
||||
if (avail.zai) tiers.push("zai-coding-plan")
|
||||
|
||||
return tiers
|
||||
}
|
||||
|
||||
function hasCapability(
|
||||
tier: ProviderTier,
|
||||
capability: ModelCapability,
|
||||
avail: ProviderAvailability
|
||||
): boolean {
|
||||
if (tier === "native") {
|
||||
switch (capability) {
|
||||
case "opus-level":
|
||||
case "sonnet-level":
|
||||
case "haiku-level":
|
||||
return avail.native.claude
|
||||
case "reasoning":
|
||||
case "codex":
|
||||
return avail.native.openai || avail.native.claude
|
||||
case "visual":
|
||||
case "fast":
|
||||
return avail.native.gemini
|
||||
case "glm":
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function resolveModel(capability: ModelCapability, avail: ProviderAvailability): string {
|
||||
const tiers = getProviderPriority(avail)
|
||||
|
||||
for (const tier of tiers) {
|
||||
if (hasCapability(tier, capability, avail)) {
|
||||
const model = MODEL_CATALOG[tier][capability]
|
||||
if (model) return model
|
||||
const nativeChain = NATIVE_FALLBACK_CHAINS[capability]
|
||||
for (const entry of nativeChain) {
|
||||
if (avail.native[entry.provider]) {
|
||||
return entry.model
|
||||
}
|
||||
}
|
||||
|
||||
if (avail.opencodeZen) {
|
||||
return OPENCODE_ZEN_MODELS[capability]
|
||||
}
|
||||
|
||||
if (avail.copilot) {
|
||||
return GITHUB_COPILOT_MODELS[capability]
|
||||
}
|
||||
|
||||
if (avail.zai) {
|
||||
return ZAI_MODEL
|
||||
}
|
||||
|
||||
return ULTIMATE_FALLBACK
|
||||
}
|
||||
|
||||
function resolveClaudeCapability(avail: ProviderAvailability): ModelCapability {
|
||||
return avail.isMaxPlan ? "unspecified-high" : "unspecified-low"
|
||||
}
|
||||
|
||||
export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
const avail = toProviderAvailability(config)
|
||||
const hasAnyProvider =
|
||||
avail.native.claude ||
|
||||
avail.native.openai ||
|
||||
avail.native.gemini ||
|
||||
avail.opencodeZen ||
|
||||
avail.copilot ||
|
||||
avail.opencode ||
|
||||
avail.zai
|
||||
|
||||
if (!hasAnyProvider) {
|
||||
@@ -181,19 +207,31 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
}
|
||||
}
|
||||
|
||||
const agents: Record<string, { model: string }> = {}
|
||||
const categories: Record<string, { model: string }> = {}
|
||||
const agents: Record<string, AgentConfig> = {}
|
||||
const categories: Record<string, CategoryConfig> = {}
|
||||
|
||||
for (const [role, capability] of Object.entries(AGENT_REQUIREMENTS)) {
|
||||
const claudeCapability = resolveClaudeCapability(avail)
|
||||
|
||||
for (const [role, req] of Object.entries(AGENT_REQUIREMENTS)) {
|
||||
if (role === "librarian" && avail.zai) {
|
||||
agents[role] = { model: "zai-coding-plan/glm-4.7" }
|
||||
agents[role] = { model: ZAI_MODEL }
|
||||
} else if (role === "explore") {
|
||||
if (avail.native.claude && avail.isMaxPlan) {
|
||||
agents[role] = { model: "anthropic/claude-haiku-4-5" }
|
||||
} else {
|
||||
agents[role] = { model: "opencode/grok-code" }
|
||||
}
|
||||
} else {
|
||||
agents[role] = { model: resolveModel(capability, avail) }
|
||||
const capability = req.capability === "unspecified-high" ? claudeCapability : req.capability
|
||||
const model = resolveModel(capability, avail)
|
||||
agents[role] = req.variant ? { model, variant: req.variant } : { model }
|
||||
}
|
||||
}
|
||||
|
||||
for (const [cat, capability] of Object.entries(CATEGORY_REQUIREMENTS)) {
|
||||
categories[cat] = { model: resolveModel(capability, avail) }
|
||||
for (const [cat, req] of Object.entries(CATEGORY_REQUIREMENTS)) {
|
||||
const capability = req.capability === "unspecified-high" ? claudeCapability : req.capability
|
||||
const model = resolveModel(capability, avail)
|
||||
categories[cat] = req.variant ? { model, variant: req.variant } : { model }
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -202,3 +240,7 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
categories,
|
||||
}
|
||||
}
|
||||
|
||||
export function shouldShowChatGPTOnlyWarning(config: InstallConfig): boolean {
|
||||
return !config.hasClaude && !config.hasGemini && config.hasOpenAI
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export type BooleanArg = "no" | "yes"
|
||||
export interface InstallArgs {
|
||||
tui: boolean
|
||||
claude?: ClaudeSubscription
|
||||
openai?: BooleanArg
|
||||
gemini?: BooleanArg
|
||||
copilot?: BooleanArg
|
||||
opencodeZen?: BooleanArg
|
||||
@@ -14,6 +15,7 @@ export interface InstallArgs {
|
||||
export interface InstallConfig {
|
||||
hasClaude: boolean
|
||||
isMax20: boolean
|
||||
hasOpenAI: boolean
|
||||
hasGemini: boolean
|
||||
hasCopilot: boolean
|
||||
hasOpencodeZen: boolean
|
||||
@@ -30,6 +32,7 @@ export interface DetectedConfig {
|
||||
isInstalled: boolean
|
||||
hasClaude: boolean
|
||||
isMax20: boolean
|
||||
hasOpenAI: boolean
|
||||
hasGemini: boolean
|
||||
hasCopilot: boolean
|
||||
hasOpencodeZen: boolean
|
||||
|
||||
Reference in New Issue
Block a user