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:
@@ -33,7 +33,7 @@ follow these steps:
|
||||
|
||||
### 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?**
|
||||
- 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`
|
||||
- If **no** → `--claude=no`
|
||||
|
||||
2. **Do you have a ChatGPT Subscription?**
|
||||
- If **yes** → `--chatgpt=yes`
|
||||
- If **no** → `--chatgpt=no`
|
||||
|
||||
3. **Will you integrate Gemini models?**
|
||||
2. **Will you integrate Gemini models?**
|
||||
- If **yes** → `--gemini=yes`
|
||||
- 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 **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.
|
||||
|
||||
### 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:
|
||||
|
||||
```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:**
|
||||
- User has all subscriptions with max20: `bunx oh-my-opencode install --no-tui --claude=max20 --chatgpt=yes --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 GitHub Copilot: `bunx oh-my-opencode install --no-tui --claude=no --chatgpt=no --gemini=no --copilot=yes`
|
||||
- User has no subscriptions: `bunx oh-my-opencode install --no-tui --claude=no --chatgpt=no --gemini=no`
|
||||
- User has all native subscriptions: `bunx oh-my-opencode install --no-tui --claude=max20 --gemini=yes --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 --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`
|
||||
- User has no subscriptions: `bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=no`
|
||||
|
||||
The CLI will:
|
||||
- Register the plugin in `opencode.json`
|
||||
@@ -158,23 +166,49 @@ opencode auth login
|
||||
|
||||
#### 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
|
||||
|
||||
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 |
|
||||
| ------------- | -------------------------------- |
|
||||
| **Sisyphus** | `github-copilot/claude-opus-4.5` |
|
||||
| **Oracle** | `github-copilot/gpt-5.2` |
|
||||
| **Explore** | `grok code` (default) |
|
||||
| **Librarian** | `glm 4.7 free` (default) |
|
||||
| **Explore** | `github-copilot/grok-code-fast-1`|
|
||||
| **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.
|
||||
|
||||
#### 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
|
||||
|
||||
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", () => {
|
||||
test("generates minimal config with only $schema", () => {
|
||||
// #given any install config
|
||||
describe("generateOmoConfig - model fallback system", () => {
|
||||
test("generates native models when Claude available", () => {
|
||||
// #given user has Claude subscription
|
||||
const config: InstallConfig = {
|
||||
hasClaude: true,
|
||||
isMax20: false,
|
||||
hasGemini: false,
|
||||
hasCopilot: false,
|
||||
hasOpencodeZen: false,
|
||||
hasZaiCodingPlan: false,
|
||||
}
|
||||
|
||||
// #when generating 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.agents).toBeUndefined()
|
||||
expect(result.categories).toBeUndefined()
|
||||
expect(result.agents).toBeDefined()
|
||||
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", () => {
|
||||
// #given user has multiple providers
|
||||
test("uses github-copilot fallback when only copilot available", () => {
|
||||
// #given user has only copilot
|
||||
const config: InstallConfig = {
|
||||
hasClaude: true,
|
||||
isMax20: true,
|
||||
hasGemini: true,
|
||||
hasClaude: false,
|
||||
isMax20: false,
|
||||
hasGemini: false,
|
||||
hasCopilot: true,
|
||||
hasOpencodeZen: false,
|
||||
hasZaiCodingPlan: false,
|
||||
}
|
||||
|
||||
// #when generating config
|
||||
const result = generateOmoConfig(config)
|
||||
|
||||
// #then should not have agents or categories with model fields
|
||||
expect(result.agents).toBeUndefined()
|
||||
expect(result.categories).toBeUndefined()
|
||||
// #then should use github-copilot models
|
||||
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("github-copilot/claude-opus-4.5")
|
||||
})
|
||||
|
||||
test("does not include model fields when no providers configured", () => {
|
||||
test("uses ultimate fallback when no providers configured", () => {
|
||||
// #given user has no providers
|
||||
const config: InstallConfig = {
|
||||
hasClaude: false,
|
||||
isMax20: false,
|
||||
hasGemini: false,
|
||||
hasCopilot: false,
|
||||
hasOpencodeZen: false,
|
||||
hasZaiCodingPlan: false,
|
||||
}
|
||||
|
||||
// #when generating 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.agents).toBeUndefined()
|
||||
expect(result.categories).toBeUndefined()
|
||||
expect((result.agents as Record<string, { model: string }>).Sisyphus.model).toBe("opencode/glm-4.7-free")
|
||||
})
|
||||
|
||||
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,
|
||||
} from "../shared"
|
||||
import type { ConfigMergeResult, DetectedConfig, InstallConfig } from "./types"
|
||||
import { generateModelConfig } from "./model-fallback"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export function generateOmoConfig(_installConfig: InstallConfig): Record<string, unknown> {
|
||||
// v3 beta: No hardcoded model strings - users rely on their OpenCode configured model
|
||||
// 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 generateOmoConfig(installConfig: InstallConfig): Record<string, unknown> {
|
||||
return generateModelConfig(installConfig)
|
||||
}
|
||||
|
||||
export function writeOmoConfig(installConfig: InstallConfig): ConfigMergeResult {
|
||||
@@ -581,14 +576,14 @@ export function addProviderConfig(config: InstallConfig): ConfigMergeResult {
|
||||
}
|
||||
|
||||
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 = {
|
||||
isInstalled: false,
|
||||
hasClaude: true,
|
||||
isMax20: true,
|
||||
hasGemini: false,
|
||||
hasCopilot: false,
|
||||
hasOpencodeZen: true,
|
||||
hasZaiCodingPlan: false,
|
||||
}
|
||||
|
||||
const { format, path } = detectConfigFormat()
|
||||
|
||||
@@ -26,16 +26,21 @@ program
|
||||
.option("--claude <value>", "Claude subscription: no, yes, max20")
|
||||
.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)")
|
||||
.option("--zai-coding-plan <value>", "Z.ai Coding Plan subscription: no, yes (default: no)")
|
||||
.option("--skip-auth", "Skip authentication setup hints")
|
||||
.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=no --gemini=no --copilot=yes
|
||||
$ bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes --opencode-zen=yes
|
||||
|
||||
Model Providers:
|
||||
Claude Required for Sisyphus (main orchestrator) and Librarian agents
|
||||
Gemini Powers frontend, documentation, and multimodal agents
|
||||
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai):
|
||||
Claude Native anthropic/ models (Opus, Sonnet, Haiku)
|
||||
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) => {
|
||||
const args: InstallArgs = {
|
||||
@@ -43,6 +48,8 @@ Model Providers:
|
||||
claude: options.claude,
|
||||
gemini: options.gemini,
|
||||
copilot: options.copilot,
|
||||
opencodeZen: options.opencodeZen,
|
||||
zaiCodingPlan: options.zaiCodingPlan,
|
||||
skipAuth: options.skipAuth ?? false,
|
||||
}
|
||||
const exitCode = await install(args)
|
||||
|
||||
@@ -40,17 +40,18 @@ function formatConfigSummary(config: InstallConfig): string {
|
||||
const claudeDetail = config.hasClaude ? (config.isMax20 ? "max20" : "standard") : undefined
|
||||
lines.push(formatProvider("Claude", config.hasClaude, claudeDetail))
|
||||
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(color.dim("─".repeat(40)))
|
||||
lines.push("")
|
||||
|
||||
// v3 beta: No hardcoded models - agents use OpenCode's configured default model
|
||||
lines.push(color.bold(color.white("Agent Models")))
|
||||
lines.push(color.bold(color.white("Model Assignment")))
|
||||
lines.push("")
|
||||
lines.push(` ${SYMBOLS.info} Agents will use your OpenCode default model`)
|
||||
lines.push(` ${SYMBOLS.bullet} Configure specific models in ${color.cyan("oh-my-opencode.json")} if needed`)
|
||||
lines.push(` ${SYMBOLS.info} Models auto-configured based on provider priority`)
|
||||
lines.push(` ${SYMBOLS.bullet} Priority: Native > Copilot > OpenCode Zen > Z.ai`)
|
||||
|
||||
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)`)
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -135,10 +144,12 @@ function argsToConfig(args: InstallArgs): InstallConfig {
|
||||
isMax20: args.claude === "max20",
|
||||
hasGemini: args.gemini === "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"
|
||||
if (detected.hasClaude) {
|
||||
claude = detected.isMax20 ? "max20" : "yes"
|
||||
@@ -148,6 +159,8 @@ function detectedToInitialValues(detected: DetectedConfig): { claude: ClaudeSubs
|
||||
claude,
|
||||
gemini: detected.hasGemini ? "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
|
||||
}
|
||||
|
||||
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 {
|
||||
hasClaude: claude !== "no",
|
||||
isMax20: claude === "max20",
|
||||
hasGemini: gemini === "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
|
||||
gemini?: BooleanArg
|
||||
copilot?: BooleanArg
|
||||
opencodeZen?: BooleanArg
|
||||
zaiCodingPlan?: BooleanArg
|
||||
skipAuth?: boolean
|
||||
}
|
||||
|
||||
@@ -14,6 +16,8 @@ export interface InstallConfig {
|
||||
isMax20: boolean
|
||||
hasGemini: boolean
|
||||
hasCopilot: boolean
|
||||
hasOpencodeZen: boolean
|
||||
hasZaiCodingPlan: boolean
|
||||
}
|
||||
|
||||
export interface ConfigMergeResult {
|
||||
@@ -28,4 +32,6 @@ export interface DetectedConfig {
|
||||
isMax20: boolean
|
||||
hasGemini: boolean
|
||||
hasCopilot: boolean
|
||||
hasOpencodeZen: boolean
|
||||
hasZaiCodingPlan: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user