diff --git a/docs/guide/installation.md b/docs/guide/installation.md index c6619e20..eac4c6c6 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -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= --gemini= --copilot= [--opencode-zen=] [--zai-coding-plan=] +bunx oh-my-opencode install --no-tui --claude= --gemini= --copilot= [--openai=] [--opencode-zen=] [--zai-coding-plan=] ``` **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` diff --git a/src/cli/config-manager.test.ts b/src/cli/config-manager.test.ts index a20cb809..504742ed 100644 --- a/src/cli/config-manager.test.ts +++ b/src/cli/config-manager.test.ts @@ -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).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).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).Sisyphus.model).toBe("github-copilot/claude-opus-4.5") + // #then should use github-copilot sonnet models + expect((result.agents as Record).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).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).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).Sisyphus.model).toBe("openai/gpt-5.2") + // #then Oracle should use native OpenAI (primary for ultrabrain) + expect((result.agents as Record).oracle.model).toBe("openai/gpt-5.2-codex") + // #then multimodal-looker should use native OpenAI (fallback within native tier) + expect((result.agents as Record)["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).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).explore.model).toBe("opencode/grok-code") + }) }) diff --git a/src/cli/config-manager.ts b/src/cli/config-manager.ts index 8a9c2e5c..1bfb4e26 100644 --- a/src/cli/config-manager.ts +++ b/src/cli/config-manager.ts @@ -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>(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 } diff --git a/src/cli/index.ts b/src/cli/index.ts index c554875a..7b415d89 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -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 ", "Claude subscription: no, yes, max20") + .option("--openai ", "OpenAI/ChatGPT subscription: no, yes (default: no)") .option("--gemini ", "Gemini integration: no, yes") .option("--copilot ", "GitHub Copilot subscription: no, yes") .option("--opencode-zen ", "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, diff --git a/src/cli/install.ts b/src/cli/install.ts index 8087ff69..30f13ecf 100644 --- a/src/cli/install.ts +++ b/src/cli/install.ts @@ -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 { 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 { } 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.") } diff --git a/src/cli/model-fallback.ts b/src/cli/model-fallback.ts index 0453a9dc..206a7276 100644 --- a/src/cli/model-fallback.ts +++ b/src/cli/model-fallback.ts @@ -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 - categories?: Record + agents?: Record + categories?: Record [key: string]: unknown } -const MODEL_CATALOG: Record>> = { - 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 = { - 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 = { + "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 = { - "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 = { + "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 = { + "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 = { + 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 = { + "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 = {} - const categories: Record = {} + const agents: Record = {} + const categories: Record = {} - 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 +} diff --git a/src/cli/types.ts b/src/cli/types.ts index 521b2f48..6825075a 100644 --- a/src/cli/types.ts +++ b/src/cli/types.ts @@ -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