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:
justsisyphus
2026-01-21 00:05:51 +09:00
parent 6865995227
commit c00f210922
7 changed files with 368 additions and 55 deletions

View File

@@ -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:

View File

@@ -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")
})
})

View File

@@ -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()

View File

@@ -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)

View File

@@ -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
View 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,
}
}

View File

@@ -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
}