feat(cli): add model fallback system with provider priority
Implements intelligent model selection based on available providers. Priority order: Native > Copilot > OpenCode Zen > Z.ai > Ultimate Fallback. - Add model-fallback.ts with MODEL_CATALOG and capability-based resolution - Extend InstallConfig with hasOpencodeZen and hasZaiCodingPlan - Add TUI prompts and CLI args for new provider options - Z.ai gets Librarian priority (zai-coding-plan/glm-4.7) 🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
parent
6865995227
commit
c00f210922
@ -33,7 +33,7 @@ follow these steps:
|
|||||||
|
|
||||||
### Step 0: Ask user about subscriptions
|
### Step 0: Ask user about subscriptions
|
||||||
|
|
||||||
Ask the user these 3 questions to determine CLI options:
|
Ask the user these questions to determine CLI options:
|
||||||
|
|
||||||
1. **Do you have a Claude Pro/Max Subscription?**
|
1. **Do you have a Claude Pro/Max Subscription?**
|
||||||
- If **yes**: **Are you on max20 (20x mode)?**
|
- If **yes**: **Are you on max20 (20x mode)?**
|
||||||
@ -41,18 +41,24 @@ Ask the user these 3 questions to determine CLI options:
|
|||||||
- **not max20** → `--claude=yes`
|
- **not max20** → `--claude=yes`
|
||||||
- If **no** → `--claude=no`
|
- If **no** → `--claude=no`
|
||||||
|
|
||||||
2. **Do you have a ChatGPT Subscription?**
|
2. **Will you integrate Gemini models?**
|
||||||
- If **yes** → `--chatgpt=yes`
|
|
||||||
- If **no** → `--chatgpt=no`
|
|
||||||
|
|
||||||
3. **Will you integrate Gemini models?**
|
|
||||||
- If **yes** → `--gemini=yes`
|
- If **yes** → `--gemini=yes`
|
||||||
- If **no** → `--gemini=no`
|
- If **no** → `--gemini=no`
|
||||||
|
|
||||||
4. **Do you have a GitHub Copilot Subscription?**
|
3. **Do you have a GitHub Copilot Subscription?**
|
||||||
- If **yes** → `--copilot=yes`
|
- If **yes** → `--copilot=yes`
|
||||||
- If **no** → `--copilot=no`
|
- If **no** → `--copilot=no`
|
||||||
|
|
||||||
|
4. **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?**
|
||||||
|
- If **yes** → `--zai-coding-plan=yes`
|
||||||
|
- If **no** → `--zai-coding-plan=no` (default)
|
||||||
|
|
||||||
|
**Provider Priority**: Native (anthropic/, openai/, google/) > GitHub Copilot > OpenCode Zen > Z.ai Coding Plan
|
||||||
|
|
||||||
MUST STRONGLY WARNING, WHEN USER SAID THEY DON'T HAVE CLAUDE SUBSCRIPTION, SISYPHUS AGENT MIGHT NOT WORK IDEALY.
|
MUST STRONGLY WARNING, WHEN USER SAID THEY DON'T HAVE CLAUDE SUBSCRIPTION, SISYPHUS AGENT MIGHT NOT WORK IDEALY.
|
||||||
|
|
||||||
### Step 1: Install OpenCode (if not installed)
|
### Step 1: Install OpenCode (if not installed)
|
||||||
@ -74,14 +80,16 @@ Spawn a subagent to handle installation and report back - to save context.
|
|||||||
Based on user's answers, run the CLI installer with appropriate flags:
|
Based on user's answers, run the CLI installer with appropriate flags:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bunx oh-my-opencode install --no-tui --claude=<yes|no|max20> --chatgpt=<yes|no> --gemini=<yes|no> --copilot=<yes|no>
|
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>]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
- User has all subscriptions with max20: `bunx oh-my-opencode install --no-tui --claude=max20 --chatgpt=yes --gemini=yes --copilot=no`
|
- User has all native subscriptions: `bunx oh-my-opencode install --no-tui --claude=max20 --gemini=yes --copilot=no`
|
||||||
- User has only Claude (no max20): `bunx oh-my-opencode install --no-tui --claude=yes --chatgpt=no --gemini=no --copilot=no`
|
- User has only Claude: `bunx oh-my-opencode install --no-tui --claude=yes --gemini=no --copilot=no`
|
||||||
- User has only GitHub Copilot: `bunx oh-my-opencode install --no-tui --claude=no --chatgpt=no --gemini=no --copilot=yes`
|
- User has only GitHub Copilot: `bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes`
|
||||||
- User has no subscriptions: `bunx oh-my-opencode install --no-tui --claude=no --chatgpt=no --gemini=no`
|
- 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`
|
||||||
|
- User has no subscriptions: `bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=no`
|
||||||
|
|
||||||
The CLI will:
|
The CLI will:
|
||||||
- Register the plugin in `opencode.json`
|
- Register the plugin in `opencode.json`
|
||||||
@ -158,23 +166,49 @@ opencode auth login
|
|||||||
|
|
||||||
#### GitHub Copilot (Fallback Provider)
|
#### GitHub Copilot (Fallback Provider)
|
||||||
|
|
||||||
GitHub Copilot is supported as a **fallback provider** when native providers (Claude, ChatGPT, Gemini) are unavailable. The installer configures Copilot with lower priority than native providers.
|
GitHub Copilot is supported as a **fallback provider** when native providers are unavailable.
|
||||||
|
|
||||||
**Priority**: Native providers (Claude/ChatGPT/Gemini) > GitHub Copilot > Free models
|
**Priority**: Native (anthropic/, openai/, google/) > GitHub Copilot > OpenCode Zen > Z.ai Coding Plan
|
||||||
|
|
||||||
##### Model Mappings
|
##### Model Mappings
|
||||||
|
|
||||||
When GitHub Copilot is enabled, oh-my-opencode uses these model assignments:
|
When GitHub Copilot is the best available provider, oh-my-opencode uses these model assignments:
|
||||||
|
|
||||||
| Agent | Model |
|
| Agent | Model |
|
||||||
| ------------- | -------------------------------- |
|
| ------------- | -------------------------------- |
|
||||||
| **Sisyphus** | `github-copilot/claude-opus-4.5` |
|
| **Sisyphus** | `github-copilot/claude-opus-4.5` |
|
||||||
| **Oracle** | `github-copilot/gpt-5.2` |
|
| **Oracle** | `github-copilot/gpt-5.2` |
|
||||||
| **Explore** | `grok code` (default) |
|
| **Explore** | `github-copilot/grok-code-fast-1`|
|
||||||
| **Librarian** | `glm 4.7 free` (default) |
|
| **Librarian** | `zai-coding-plan/glm-4.7` (if Z.ai available) or fallback |
|
||||||
|
|
||||||
GitHub Copilot acts as a proxy provider, routing requests to underlying models based on your subscription.
|
GitHub Copilot acts as a proxy provider, routing requests to underlying models based on your subscription.
|
||||||
|
|
||||||
|
#### Z.ai Coding Plan
|
||||||
|
|
||||||
|
Z.ai Coding Plan provides access to GLM-4.7 models. When enabled, the **Librarian agent always uses `zai-coding-plan/glm-4.7`** regardless of other available providers.
|
||||||
|
|
||||||
|
If Z.ai is the only provider available, all agents will use GLM models:
|
||||||
|
|
||||||
|
| Agent | Model |
|
||||||
|
| ------------- | -------------------------------- |
|
||||||
|
| **Sisyphus** | `zai-coding-plan/glm-4.7` |
|
||||||
|
| **Oracle** | `zai-coding-plan/glm-4.7` |
|
||||||
|
| **Explore** | `zai-coding-plan/glm-4.7-flash` |
|
||||||
|
| **Librarian** | `zai-coding-plan/glm-4.7` |
|
||||||
|
|
||||||
|
#### OpenCode Zen
|
||||||
|
|
||||||
|
OpenCode Zen provides access to `opencode/` prefixed models including `opencode/claude-opus-4-5`, `opencode/gpt-5.2`, `opencode/grok-code`, and `opencode/glm-4.7-free`.
|
||||||
|
|
||||||
|
When OpenCode Zen is the best available provider (no native or Copilot), these models are used:
|
||||||
|
|
||||||
|
| Agent | Model |
|
||||||
|
| ------------- | -------------------------------- |
|
||||||
|
| **Sisyphus** | `opencode/claude-opus-4-5` |
|
||||||
|
| **Oracle** | `opencode/gpt-5.2` |
|
||||||
|
| **Explore** | `opencode/grok-code` |
|
||||||
|
| **Librarian** | `opencode/glm-4.7-free` |
|
||||||
|
|
||||||
##### Setup
|
##### Setup
|
||||||
|
|
||||||
Run the installer and select "Yes" for GitHub Copilot:
|
Run the installer and select "Yes" for GitHub Copilot:
|
||||||
|
|||||||
@ -200,57 +200,81 @@ describe("config-manager ANTIGRAVITY_PROVIDER_CONFIG", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("generateOmoConfig - v3 beta: no hardcoded models", () => {
|
describe("generateOmoConfig - model fallback system", () => {
|
||||||
test("generates minimal config with only $schema", () => {
|
test("generates native models when Claude available", () => {
|
||||||
// #given any install config
|
// #given user has Claude subscription
|
||||||
const config: InstallConfig = {
|
const config: InstallConfig = {
|
||||||
hasClaude: true,
|
hasClaude: true,
|
||||||
isMax20: false,
|
isMax20: false,
|
||||||
hasGemini: false,
|
hasGemini: false,
|
||||||
hasCopilot: false,
|
hasCopilot: false,
|
||||||
|
hasOpencodeZen: false,
|
||||||
|
hasZaiCodingPlan: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #when generating config
|
// #when generating config
|
||||||
const result = generateOmoConfig(config)
|
const result = generateOmoConfig(config)
|
||||||
|
|
||||||
// #then should only contain $schema, no agents or categories
|
// #then should use native anthropic models
|
||||||
expect(result.$schema).toBe("https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json")
|
expect(result.$schema).toBe("https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json")
|
||||||
expect(result.agents).toBeUndefined()
|
expect(result.agents).toBeDefined()
|
||||||
expect(result.categories).toBeUndefined()
|
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("anthropic/claude-opus-4-5")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("does not include model fields regardless of provider config", () => {
|
test("uses github-copilot fallback when only copilot available", () => {
|
||||||
// #given user has multiple providers
|
// #given user has only copilot
|
||||||
const config: InstallConfig = {
|
const config: InstallConfig = {
|
||||||
hasClaude: true,
|
hasClaude: false,
|
||||||
isMax20: true,
|
isMax20: false,
|
||||||
hasGemini: true,
|
hasGemini: false,
|
||||||
hasCopilot: true,
|
hasCopilot: true,
|
||||||
|
hasOpencodeZen: false,
|
||||||
|
hasZaiCodingPlan: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #when generating config
|
// #when generating config
|
||||||
const result = generateOmoConfig(config)
|
const result = generateOmoConfig(config)
|
||||||
|
|
||||||
// #then should not have agents or categories with model fields
|
// #then should use github-copilot models
|
||||||
expect(result.agents).toBeUndefined()
|
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("github-copilot/claude-opus-4.5")
|
||||||
expect(result.categories).toBeUndefined()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("does not include model fields when no providers configured", () => {
|
test("uses ultimate fallback when no providers configured", () => {
|
||||||
// #given user has no providers
|
// #given user has no providers
|
||||||
const config: InstallConfig = {
|
const config: InstallConfig = {
|
||||||
hasClaude: false,
|
hasClaude: false,
|
||||||
isMax20: false,
|
isMax20: false,
|
||||||
hasGemini: false,
|
hasGemini: false,
|
||||||
hasCopilot: false,
|
hasCopilot: false,
|
||||||
|
hasOpencodeZen: false,
|
||||||
|
hasZaiCodingPlan: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #when generating config
|
// #when generating config
|
||||||
const result = generateOmoConfig(config)
|
const result = generateOmoConfig(config)
|
||||||
|
|
||||||
// #then should still only contain $schema
|
// #then should use ultimate fallback for all agents
|
||||||
expect(result.$schema).toBe("https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json")
|
expect(result.$schema).toBe("https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json")
|
||||||
expect(result.agents).toBeUndefined()
|
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("opencode/glm-4.7-free")
|
||||||
expect(result.categories).toBeUndefined()
|
})
|
||||||
|
|
||||||
|
test("uses zai-coding-plan/glm-4.7 for librarian when Z.ai available", () => {
|
||||||
|
// #given user has Z.ai and Claude
|
||||||
|
const config: InstallConfig = {
|
||||||
|
hasClaude: true,
|
||||||
|
isMax20: false,
|
||||||
|
hasGemini: false,
|
||||||
|
hasCopilot: false,
|
||||||
|
hasOpencodeZen: false,
|
||||||
|
hasZaiCodingPlan: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when generating config
|
||||||
|
const result = generateOmoConfig(config)
|
||||||
|
|
||||||
|
// #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
|
||||||
|
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("anthropic/claude-opus-4-5")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
type OpenCodeConfigPaths,
|
type OpenCodeConfigPaths,
|
||||||
} from "../shared"
|
} from "../shared"
|
||||||
import type { ConfigMergeResult, DetectedConfig, InstallConfig } from "./types"
|
import type { ConfigMergeResult, DetectedConfig, InstallConfig } from "./types"
|
||||||
|
import { generateModelConfig } from "./model-fallback"
|
||||||
|
|
||||||
const OPENCODE_BINARIES = ["opencode", "opencode-desktop"] as const
|
const OPENCODE_BINARIES = ["opencode", "opencode-desktop"] as const
|
||||||
|
|
||||||
@ -306,14 +307,8 @@ function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateOmoConfig(_installConfig: InstallConfig): Record<string, unknown> {
|
export function generateOmoConfig(installConfig: InstallConfig): Record<string, unknown> {
|
||||||
// v3 beta: No hardcoded model strings - users rely on their OpenCode configured model
|
return generateModelConfig(installConfig)
|
||||||
// Users who want specific models configure them explicitly after install
|
|
||||||
const config: Record<string, unknown> = {
|
|
||||||
$schema: "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json",
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writeOmoConfig(installConfig: InstallConfig): ConfigMergeResult {
|
export function writeOmoConfig(installConfig: InstallConfig): ConfigMergeResult {
|
||||||
@ -581,14 +576,14 @@ export function addProviderConfig(config: InstallConfig): ConfigMergeResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function detectCurrentConfig(): DetectedConfig {
|
export function detectCurrentConfig(): DetectedConfig {
|
||||||
// v3 beta: Since we no longer generate hardcoded model strings,
|
|
||||||
// detection only checks for plugin installation and Gemini auth plugin
|
|
||||||
const result: DetectedConfig = {
|
const result: DetectedConfig = {
|
||||||
isInstalled: false,
|
isInstalled: false,
|
||||||
hasClaude: true,
|
hasClaude: true,
|
||||||
isMax20: true,
|
isMax20: true,
|
||||||
hasGemini: false,
|
hasGemini: false,
|
||||||
hasCopilot: false,
|
hasCopilot: false,
|
||||||
|
hasOpencodeZen: true,
|
||||||
|
hasZaiCodingPlan: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const { format, path } = detectConfigFormat()
|
const { format, path } = detectConfigFormat()
|
||||||
|
|||||||
@ -26,16 +26,21 @@ program
|
|||||||
.option("--claude <value>", "Claude subscription: no, yes, max20")
|
.option("--claude <value>", "Claude subscription: no, yes, max20")
|
||||||
.option("--gemini <value>", "Gemini integration: no, yes")
|
.option("--gemini <value>", "Gemini integration: no, yes")
|
||||||
.option("--copilot <value>", "GitHub Copilot subscription: no, yes")
|
.option("--copilot <value>", "GitHub Copilot subscription: no, yes")
|
||||||
|
.option("--opencode-zen <value>", "OpenCode Zen access: no, yes (default: no)")
|
||||||
|
.option("--zai-coding-plan <value>", "Z.ai Coding Plan subscription: no, yes (default: no)")
|
||||||
.option("--skip-auth", "Skip authentication setup hints")
|
.option("--skip-auth", "Skip authentication setup hints")
|
||||||
.addHelpText("after", `
|
.addHelpText("after", `
|
||||||
Examples:
|
Examples:
|
||||||
$ bunx oh-my-opencode install
|
$ 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 --gemini=yes --copilot=no
|
||||||
$ bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes
|
$ bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes --opencode-zen=yes
|
||||||
|
|
||||||
Model Providers:
|
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai):
|
||||||
Claude Required for Sisyphus (main orchestrator) and Librarian agents
|
Claude Native anthropic/ models (Opus, Sonnet, Haiku)
|
||||||
Gemini Powers frontend, documentation, and multimodal agents
|
Gemini Native google/ models (Gemini 3 Pro, Flash)
|
||||||
|
Copilot github-copilot/ models (fallback)
|
||||||
|
OpenCode Zen opencode/ models (opencode/claude-opus-4-5, etc.)
|
||||||
|
Z.ai zai-coding-plan/glm-4.7 (Librarian priority)
|
||||||
`)
|
`)
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
const args: InstallArgs = {
|
const args: InstallArgs = {
|
||||||
@ -43,6 +48,8 @@ Model Providers:
|
|||||||
claude: options.claude,
|
claude: options.claude,
|
||||||
gemini: options.gemini,
|
gemini: options.gemini,
|
||||||
copilot: options.copilot,
|
copilot: options.copilot,
|
||||||
|
opencodeZen: options.opencodeZen,
|
||||||
|
zaiCodingPlan: options.zaiCodingPlan,
|
||||||
skipAuth: options.skipAuth ?? false,
|
skipAuth: options.skipAuth ?? false,
|
||||||
}
|
}
|
||||||
const exitCode = await install(args)
|
const exitCode = await install(args)
|
||||||
|
|||||||
@ -40,17 +40,18 @@ function formatConfigSummary(config: InstallConfig): string {
|
|||||||
const claudeDetail = config.hasClaude ? (config.isMax20 ? "max20" : "standard") : undefined
|
const claudeDetail = config.hasClaude ? (config.isMax20 ? "max20" : "standard") : undefined
|
||||||
lines.push(formatProvider("Claude", config.hasClaude, claudeDetail))
|
lines.push(formatProvider("Claude", config.hasClaude, claudeDetail))
|
||||||
lines.push(formatProvider("Gemini", config.hasGemini))
|
lines.push(formatProvider("Gemini", config.hasGemini))
|
||||||
lines.push(formatProvider("GitHub Copilot", config.hasCopilot, "fallback provider"))
|
lines.push(formatProvider("GitHub Copilot", config.hasCopilot, "fallback"))
|
||||||
|
lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models"))
|
||||||
|
lines.push(formatProvider("Z.ai Coding Plan", config.hasZaiCodingPlan, "Librarian: glm-4.7"))
|
||||||
|
|
||||||
lines.push("")
|
lines.push("")
|
||||||
lines.push(color.dim("─".repeat(40)))
|
lines.push(color.dim("─".repeat(40)))
|
||||||
lines.push("")
|
lines.push("")
|
||||||
|
|
||||||
// v3 beta: No hardcoded models - agents use OpenCode's configured default model
|
lines.push(color.bold(color.white("Model Assignment")))
|
||||||
lines.push(color.bold(color.white("Agent Models")))
|
|
||||||
lines.push("")
|
lines.push("")
|
||||||
lines.push(` ${SYMBOLS.info} Agents will use your OpenCode default model`)
|
lines.push(` ${SYMBOLS.info} Models auto-configured based on provider priority`)
|
||||||
lines.push(` ${SYMBOLS.bullet} Configure specific models in ${color.cyan("oh-my-opencode.json")} if needed`)
|
lines.push(` ${SYMBOLS.bullet} Priority: Native > Copilot > OpenCode Zen > Z.ai`)
|
||||||
|
|
||||||
return lines.join("\n")
|
return lines.join("\n")
|
||||||
}
|
}
|
||||||
@ -126,6 +127,14 @@ function validateNonTuiArgs(args: InstallArgs): { valid: boolean; errors: string
|
|||||||
errors.push(`Invalid --copilot value: ${args.copilot} (expected: no, yes)`)
|
errors.push(`Invalid --copilot value: ${args.copilot} (expected: no, yes)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.opencodeZen !== undefined && !["no", "yes"].includes(args.opencodeZen)) {
|
||||||
|
errors.push(`Invalid --opencode-zen value: ${args.opencodeZen} (expected: no, yes)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.zaiCodingPlan !== undefined && !["no", "yes"].includes(args.zaiCodingPlan)) {
|
||||||
|
errors.push(`Invalid --zai-coding-plan value: ${args.zaiCodingPlan} (expected: no, yes)`)
|
||||||
|
}
|
||||||
|
|
||||||
return { valid: errors.length === 0, errors }
|
return { valid: errors.length === 0, errors }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,10 +144,12 @@ function argsToConfig(args: InstallArgs): InstallConfig {
|
|||||||
isMax20: args.claude === "max20",
|
isMax20: args.claude === "max20",
|
||||||
hasGemini: args.gemini === "yes",
|
hasGemini: args.gemini === "yes",
|
||||||
hasCopilot: args.copilot === "yes",
|
hasCopilot: args.copilot === "yes",
|
||||||
|
hasOpencodeZen: args.opencodeZen === "yes",
|
||||||
|
hasZaiCodingPlan: args.zaiCodingPlan === "yes",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function detectedToInitialValues(detected: DetectedConfig): { claude: ClaudeSubscription; gemini: BooleanArg; copilot: BooleanArg } {
|
function detectedToInitialValues(detected: DetectedConfig): { claude: ClaudeSubscription; gemini: BooleanArg; copilot: BooleanArg; opencodeZen: BooleanArg; zaiCodingPlan: BooleanArg } {
|
||||||
let claude: ClaudeSubscription = "no"
|
let claude: ClaudeSubscription = "no"
|
||||||
if (detected.hasClaude) {
|
if (detected.hasClaude) {
|
||||||
claude = detected.isMax20 ? "max20" : "yes"
|
claude = detected.isMax20 ? "max20" : "yes"
|
||||||
@ -148,6 +159,8 @@ function detectedToInitialValues(detected: DetectedConfig): { claude: ClaudeSubs
|
|||||||
claude,
|
claude,
|
||||||
gemini: detected.hasGemini ? "yes" : "no",
|
gemini: detected.hasGemini ? "yes" : "no",
|
||||||
copilot: detected.hasCopilot ? "yes" : "no",
|
copilot: detected.hasCopilot ? "yes" : "no",
|
||||||
|
opencodeZen: detected.hasOpencodeZen ? "yes" : "no",
|
||||||
|
zaiCodingPlan: detected.hasZaiCodingPlan ? "yes" : "no",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,11 +210,41 @@ async function runTuiMode(detected: DetectedConfig): Promise<InstallConfig | nul
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const opencodeZen = await p.select({
|
||||||
|
message: "Do you have access to OpenCode Zen (opencode/ models)?",
|
||||||
|
options: [
|
||||||
|
{ value: "no" as const, label: "No", hint: "Will use other configured providers" },
|
||||||
|
{ value: "yes" as const, label: "Yes", hint: "opencode/claude-opus-4-5, opencode/gpt-5.2, etc." },
|
||||||
|
],
|
||||||
|
initialValue: initial.opencodeZen,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (p.isCancel(opencodeZen)) {
|
||||||
|
p.cancel("Installation cancelled.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const zaiCodingPlan = await p.select({
|
||||||
|
message: "Do you have a Z.ai Coding Plan subscription?",
|
||||||
|
options: [
|
||||||
|
{ value: "no" as const, label: "No", hint: "Will use other configured providers" },
|
||||||
|
{ value: "yes" as const, label: "Yes", hint: "zai-coding-plan/glm-4.7 for Librarian" },
|
||||||
|
],
|
||||||
|
initialValue: initial.zaiCodingPlan,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (p.isCancel(zaiCodingPlan)) {
|
||||||
|
p.cancel("Installation cancelled.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasClaude: claude !== "no",
|
hasClaude: claude !== "no",
|
||||||
isMax20: claude === "max20",
|
isMax20: claude === "max20",
|
||||||
hasGemini: gemini === "yes",
|
hasGemini: gemini === "yes",
|
||||||
hasCopilot: copilot === "yes",
|
hasCopilot: copilot === "yes",
|
||||||
|
hasOpencodeZen: opencodeZen === "yes",
|
||||||
|
hasZaiCodingPlan: zaiCodingPlan === "yes",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
204
src/cli/model-fallback.ts
Normal file
204
src/cli/model-fallback.ts
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import type { InstallConfig } from "./types"
|
||||||
|
|
||||||
|
type ProviderTier = "native" | "github-copilot" | "opencode" | "zai-coding-plan"
|
||||||
|
|
||||||
|
type ModelCapability =
|
||||||
|
| "opus-level"
|
||||||
|
| "sonnet-level"
|
||||||
|
| "haiku-level"
|
||||||
|
| "reasoning"
|
||||||
|
| "codex"
|
||||||
|
| "visual"
|
||||||
|
| "fast"
|
||||||
|
| "glm"
|
||||||
|
|
||||||
|
interface ProviderAvailability {
|
||||||
|
native: {
|
||||||
|
claude: boolean
|
||||||
|
openai: boolean
|
||||||
|
gemini: boolean
|
||||||
|
}
|
||||||
|
copilot: boolean
|
||||||
|
opencode: boolean
|
||||||
|
zai: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeneratedOmoConfig {
|
||||||
|
$schema: string
|
||||||
|
agents?: Record<string, { model: string }>
|
||||||
|
categories?: Record<string, { model: string }>
|
||||||
|
[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",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 ULTIMATE_FALLBACK = "opencode/glm-4.7-free"
|
||||||
|
const SCHEMA_URL = "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json"
|
||||||
|
|
||||||
|
function toProviderAvailability(config: InstallConfig): ProviderAvailability {
|
||||||
|
return {
|
||||||
|
native: {
|
||||||
|
claude: config.hasClaude,
|
||||||
|
openai: config.hasClaude,
|
||||||
|
gemini: config.hasGemini,
|
||||||
|
},
|
||||||
|
copilot: config.hasCopilot,
|
||||||
|
opencode: config.hasOpencodeZen,
|
||||||
|
zai: config.hasZaiCodingPlan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ULTIMATE_FALLBACK
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||||
|
const avail = toProviderAvailability(config)
|
||||||
|
const hasAnyProvider =
|
||||||
|
avail.native.claude ||
|
||||||
|
avail.native.openai ||
|
||||||
|
avail.native.gemini ||
|
||||||
|
avail.copilot ||
|
||||||
|
avail.opencode ||
|
||||||
|
avail.zai
|
||||||
|
|
||||||
|
if (!hasAnyProvider) {
|
||||||
|
return {
|
||||||
|
$schema: SCHEMA_URL,
|
||||||
|
agents: Object.fromEntries(
|
||||||
|
Object.keys(AGENT_REQUIREMENTS).map((role) => [role, { model: ULTIMATE_FALLBACK }])
|
||||||
|
),
|
||||||
|
categories: Object.fromEntries(
|
||||||
|
Object.keys(CATEGORY_REQUIREMENTS).map((cat) => [cat, { model: ULTIMATE_FALLBACK }])
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const agents: Record<string, { model: string }> = {}
|
||||||
|
const categories: Record<string, { model: string }> = {}
|
||||||
|
|
||||||
|
for (const [role, capability] of Object.entries(AGENT_REQUIREMENTS)) {
|
||||||
|
if (role === "librarian" && avail.zai) {
|
||||||
|
agents[role] = { model: "zai-coding-plan/glm-4.7" }
|
||||||
|
} else {
|
||||||
|
agents[role] = { model: resolveModel(capability, avail) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [cat, capability] of Object.entries(CATEGORY_REQUIREMENTS)) {
|
||||||
|
categories[cat] = { model: resolveModel(capability, avail) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
$schema: SCHEMA_URL,
|
||||||
|
agents,
|
||||||
|
categories,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,8 @@ export interface InstallArgs {
|
|||||||
claude?: ClaudeSubscription
|
claude?: ClaudeSubscription
|
||||||
gemini?: BooleanArg
|
gemini?: BooleanArg
|
||||||
copilot?: BooleanArg
|
copilot?: BooleanArg
|
||||||
|
opencodeZen?: BooleanArg
|
||||||
|
zaiCodingPlan?: BooleanArg
|
||||||
skipAuth?: boolean
|
skipAuth?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,6 +16,8 @@ export interface InstallConfig {
|
|||||||
isMax20: boolean
|
isMax20: boolean
|
||||||
hasGemini: boolean
|
hasGemini: boolean
|
||||||
hasCopilot: boolean
|
hasCopilot: boolean
|
||||||
|
hasOpencodeZen: boolean
|
||||||
|
hasZaiCodingPlan: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigMergeResult {
|
export interface ConfigMergeResult {
|
||||||
@ -28,4 +32,6 @@ export interface DetectedConfig {
|
|||||||
isMax20: boolean
|
isMax20: boolean
|
||||||
hasGemini: boolean
|
hasGemini: boolean
|
||||||
hasCopilot: boolean
|
hasCopilot: boolean
|
||||||
|
hasOpencodeZen: boolean
|
||||||
|
hasZaiCodingPlan: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user