Fix model fallback across main/background/sync agents
This commit is contained in:
@@ -15,7 +15,7 @@ describe("model-resolution check", () => {
|
||||
const sisyphus = info.agents.find((a) => a.name === "sisyphus")
|
||||
expect(sisyphus).toBeDefined()
|
||||
expect(sisyphus!.requirement.fallbackChain[0]?.model).toBe("claude-opus-4-6")
|
||||
expect(sisyphus!.requirement.fallbackChain[0]?.providers).toContain("anthropic")
|
||||
expect(sisyphus!.requirement.fallbackChain[0]?.providers).toContain("quotio")
|
||||
})
|
||||
|
||||
it("returns category requirements with provider chains", async () => {
|
||||
@@ -26,8 +26,8 @@ describe("model-resolution check", () => {
|
||||
// then: Should have category entries
|
||||
const visual = info.categories.find((c) => c.name === "visual-engineering")
|
||||
expect(visual).toBeDefined()
|
||||
expect(visual!.requirement.fallbackChain[0]?.model).toBe("gemini-3-pro")
|
||||
expect(visual!.requirement.fallbackChain[0]?.providers).toContain("google")
|
||||
expect(visual!.requirement.fallbackChain[0]?.model).toBe("claude-opus-4-6-thinking")
|
||||
expect(visual!.requirement.fallbackChain[0]?.providers).toContain("quotio")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -87,7 +87,7 @@ describe("model-resolution check", () => {
|
||||
expect(sisyphus).toBeDefined()
|
||||
expect(sisyphus!.userOverride).toBeUndefined()
|
||||
expect(sisyphus!.effectiveResolution).toContain("Provider fallback:")
|
||||
expect(sisyphus!.effectiveResolution).toContain("anthropic")
|
||||
expect(sisyphus!.effectiveResolution).toContain("quotio")
|
||||
})
|
||||
|
||||
it("captures user variant for agent when configured", async () => {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import {
|
||||
AGENT_MODEL_REQUIREMENTS,
|
||||
type FallbackEntry,
|
||||
} from "../shared/model-requirements"
|
||||
import type { FallbackEntry } from "../shared/model-requirements"
|
||||
import type { ProviderAvailability } from "./model-fallback-types"
|
||||
import { CLI_AGENT_MODEL_REQUIREMENTS } from "./model-fallback-requirements"
|
||||
import { isProviderAvailable } from "./provider-availability"
|
||||
import { transformModelForProvider } from "./provider-model-id-transform"
|
||||
|
||||
@@ -25,7 +23,7 @@ export function resolveModelFromChain(
|
||||
}
|
||||
|
||||
export function getSisyphusFallbackChain(): FallbackEntry[] {
|
||||
return AGENT_MODEL_REQUIREMENTS.sisyphus.fallbackChain
|
||||
return CLI_AGENT_MODEL_REQUIREMENTS.sisyphus.fallbackChain
|
||||
}
|
||||
|
||||
export function isAnyFallbackEntryAvailable(
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import type { ModelRequirement } from "../shared/model-requirements"
|
||||
|
||||
// NOTE: These requirements are used by the CLI config generator (`generateModelConfig`).
|
||||
// They intentionally use "install-time" provider IDs (anthropic/openai/google/opencode/etc),
|
||||
// not runtime providers like `quotio`/`nvidia`.
|
||||
|
||||
export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
sisyphus: {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||
{ providers: ["zai-coding-plan"], model: "glm-4.7" },
|
||||
{ providers: ["opencode"], model: "glm-4.7-free" },
|
||||
],
|
||||
requiresAnyModel: true,
|
||||
},
|
||||
hephaestus: {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
|
||||
],
|
||||
requiresProvider: ["openai", "github-copilot", "opencode"],
|
||||
},
|
||||
oracle: {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
],
|
||||
},
|
||||
librarian: {
|
||||
fallbackChain: [
|
||||
{ providers: ["zai-coding-plan"], model: "glm-4.7" },
|
||||
{ providers: ["opencode"], model: "glm-4.7-free" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
|
||||
],
|
||||
},
|
||||
explore: {
|
||||
fallbackChain: [
|
||||
{ providers: ["github-copilot"], model: "grok-code-fast-1" },
|
||||
{ providers: ["anthropic", "opencode"], model: "claude-haiku-4-5" },
|
||||
{ providers: ["opencode"], model: "gpt-5-nano" },
|
||||
],
|
||||
},
|
||||
"multimodal-looker": {
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
|
||||
{ providers: ["zai-coding-plan"], model: "glm-4.6v" },
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-haiku-4-5" },
|
||||
{ providers: ["opencode"], model: "gpt-5-nano" },
|
||||
],
|
||||
},
|
||||
prometheus: {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
||||
],
|
||||
},
|
||||
metis: {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
],
|
||||
},
|
||||
momus: {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "medium" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
],
|
||||
},
|
||||
atlas: {
|
||||
fallbackChain: [
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const CLI_CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
"visual-engineering": {
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["zai-coding-plan"], model: "glm-5" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
],
|
||||
},
|
||||
ultrabrain: {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.3-codex", variant: "xhigh" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
],
|
||||
},
|
||||
deep: {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
],
|
||||
requiresModel: "gpt-5.3-codex",
|
||||
},
|
||||
artistry: {
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
|
||||
],
|
||||
requiresModel: "gemini-3-pro",
|
||||
},
|
||||
quick: {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-haiku-4-5" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
|
||||
{ providers: ["opencode"], model: "gpt-5-nano" },
|
||||
],
|
||||
},
|
||||
"unspecified-low": {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
|
||||
],
|
||||
},
|
||||
"unspecified-high": {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
||||
],
|
||||
},
|
||||
writing: {
|
||||
fallbackChain: [
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
+12
-13
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
AGENT_MODEL_REQUIREMENTS,
|
||||
CATEGORY_MODEL_REQUIREMENTS,
|
||||
} from "../shared/model-requirements"
|
||||
CLI_AGENT_MODEL_REQUIREMENTS,
|
||||
CLI_CATEGORY_MODEL_REQUIREMENTS,
|
||||
} from "./model-fallback-requirements"
|
||||
import type { InstallConfig } from "./types"
|
||||
|
||||
import type { AgentConfig, CategoryConfig, GeneratedOmoConfig } from "./model-fallback-types"
|
||||
@@ -16,9 +16,9 @@ import {
|
||||
|
||||
export type { GeneratedOmoConfig } from "./model-fallback-types"
|
||||
|
||||
const LIBRARIAN_MODEL = "opencode/minimax-m2.5-free"
|
||||
const ZAI_MODEL = "zai-coding-plan/glm-4.7"
|
||||
|
||||
const ULTIMATE_FALLBACK = "opencode/big-pickle"
|
||||
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"
|
||||
|
||||
|
||||
@@ -38,12 +38,12 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
return {
|
||||
$schema: SCHEMA_URL,
|
||||
agents: Object.fromEntries(
|
||||
Object.entries(AGENT_MODEL_REQUIREMENTS)
|
||||
Object.entries(CLI_AGENT_MODEL_REQUIREMENTS)
|
||||
.filter(([role, req]) => !(role === "sisyphus" && req.requiresAnyModel))
|
||||
.map(([role]) => [role, { model: ULTIMATE_FALLBACK }])
|
||||
),
|
||||
categories: Object.fromEntries(
|
||||
Object.keys(CATEGORY_MODEL_REQUIREMENTS).map((cat) => [cat, { model: ULTIMATE_FALLBACK }])
|
||||
Object.keys(CLI_CATEGORY_MODEL_REQUIREMENTS).map((cat) => [cat, { model: ULTIMATE_FALLBACK }])
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -51,9 +51,9 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
const agents: Record<string, AgentConfig> = {}
|
||||
const categories: Record<string, CategoryConfig> = {}
|
||||
|
||||
for (const [role, req] of Object.entries(AGENT_MODEL_REQUIREMENTS)) {
|
||||
if (role === "librarian") {
|
||||
agents[role] = { model: LIBRARIAN_MODEL }
|
||||
for (const [role, req] of Object.entries(CLI_AGENT_MODEL_REQUIREMENTS)) {
|
||||
if (role === "librarian" && avail.zai) {
|
||||
agents[role] = { model: ZAI_MODEL }
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
if (req.requiresAnyModel && !isAnyFallbackEntryAvailable(fallbackChain, avail)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const resolved = resolveModelFromChain(fallbackChain, avail)
|
||||
if (resolved) {
|
||||
const variant = resolved.variant ?? req.variant
|
||||
@@ -100,11 +99,11 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
}
|
||||
}
|
||||
|
||||
for (const [cat, req] of Object.entries(CATEGORY_MODEL_REQUIREMENTS)) {
|
||||
for (const [cat, req] of Object.entries(CLI_CATEGORY_MODEL_REQUIREMENTS)) {
|
||||
// Special case: unspecified-high downgrades to unspecified-low when not isMaxPlan
|
||||
const fallbackChain =
|
||||
cat === "unspecified-high" && !avail.isMaxPlan
|
||||
? CATEGORY_MODEL_REQUIREMENTS["unspecified-low"].fallbackChain
|
||||
? CLI_CATEGORY_MODEL_REQUIREMENTS["unspecified-low"].fallbackChain
|
||||
: req.fallbackChain
|
||||
|
||||
if (req.requiresModel && !isRequiredModelAvailable(req.requiresModel, req.fallbackChain, avail)) {
|
||||
|
||||
Reference in New Issue
Block a user