fix(model-fallback): align OpenAI fallback resolution across CLI and runtime

Keep install-time and runtime model tables in sync, stop OpenAI-only misrouting when OpenCode Go is present, and add valid OpenAI fallbacks for atlas, metis, and sisyphus-junior.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-03-14 13:46:26 +09:00
parent b63082a3bb
commit 532995bb51
11 changed files with 276 additions and 355 deletions

View File

@@ -966,6 +966,28 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
cacheSpy.mockRestore()
}
})
test("atlas and metis resolve to OpenAI in an OpenAI-only environment without a system default", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set(["openai/gpt-5.4"]))
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, undefined, undefined, undefined, [], {})
// #then
expect(agents.atlas).toBeDefined()
expect(agents.atlas.model).toBe("openai/gpt-5.4")
expect(agents.atlas.variant).toBe("medium")
expect(agents.metis).toBeDefined()
expect(agents.metis.model).toBe("openai/gpt-5.4")
expect(agents.metis.variant).toBe("high")
} finally {
fetchSpy.mockRestore()
cacheSpy.mockRestore()
}
})
})
describe("buildAgent with category and skills", () => {

View File

@@ -69,7 +69,7 @@ exports[`generateModelConfig single native provider uses Claude models when only
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -110,17 +110,17 @@ exports[`generateModelConfig single native provider uses Claude models when only
"variant": "max",
},
"unspecified-high": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"unspecified-low": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "anthropic/claude-opus-4-6",
"variant": "max",
},
"writing": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
},
}
@@ -131,7 +131,7 @@ exports[`generateModelConfig single native provider uses Claude models with isMa
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -176,14 +176,14 @@ exports[`generateModelConfig single native provider uses Claude models with isMa
"variant": "max",
},
"unspecified-low": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "anthropic/claude-opus-4-6",
"variant": "max",
},
"writing": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
},
}
@@ -194,7 +194,8 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/glm-4.7-free",
"model": "openai/gpt-5.4",
"variant": "medium",
},
"explore": {
"model": "openai/gpt-5.4",
@@ -209,7 +210,8 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
"variant": "medium",
},
"metis": {
"model": "opencode/glm-4.7-free",
"model": "openai/gpt-5.4",
"variant": "high",
},
"momus": {
"model": "openai/gpt-5.4",
@@ -232,7 +234,8 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
"variant": "medium",
},
"sisyphus-junior": {
"model": "opencode/glm-4.7-free",
"model": "openai/gpt-5.4",
"variant": "medium",
},
},
"categories": {
@@ -249,7 +252,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
"variant": "low",
},
"ultrabrain": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
@@ -277,7 +280,8 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/glm-4.7-free",
"model": "openai/gpt-5.4",
"variant": "medium",
},
"explore": {
"model": "openai/gpt-5.4",
@@ -292,7 +296,8 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
"variant": "medium",
},
"metis": {
"model": "opencode/glm-4.7-free",
"model": "openai/gpt-5.4",
"variant": "high",
},
"momus": {
"model": "openai/gpt-5.4",
@@ -315,7 +320,8 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
"variant": "medium",
},
"sisyphus-junior": {
"model": "opencode/glm-4.7-free",
"model": "openai/gpt-5.4",
"variant": "medium",
},
},
"categories": {
@@ -332,7 +338,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
"variant": "low",
},
"ultrabrain": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
@@ -480,7 +486,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -530,14 +536,14 @@ exports[`generateModelConfig all native providers uses preferred models from fal
"model": "anthropic/claude-haiku-4-5",
},
"ultrabrain": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"unspecified-low": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3.1-pro-preview",
@@ -555,7 +561,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -605,15 +611,15 @@ exports[`generateModelConfig all native providers uses preferred models with isM
"model": "anthropic/claude-haiku-4-5",
},
"ultrabrain": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
"model": "openai/gpt-5.4",
"variant": "high",
"model": "anthropic/claude-opus-4-6",
"variant": "max",
},
"unspecified-low": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3.1-pro-preview",
@@ -631,7 +637,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/claude-sonnet-4-5",
"model": "opencode/claude-sonnet-4-6",
},
"explore": {
"model": "opencode/claude-haiku-4-5",
@@ -681,14 +687,14 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
"model": "opencode/claude-haiku-4-5",
},
"ultrabrain": {
"model": "opencode/gpt-5.3-codex",
"model": "opencode/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
"model": "opencode/claude-sonnet-4-5",
"model": "opencode/claude-sonnet-4-6",
},
"unspecified-low": {
"model": "opencode/claude-sonnet-4-5",
"model": "opencode/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "opencode/gemini-3.1-pro",
@@ -706,7 +712,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "opencode/claude-sonnet-4-5",
"model": "opencode/claude-sonnet-4-6",
},
"explore": {
"model": "opencode/claude-haiku-4-5",
@@ -756,15 +762,15 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
"model": "opencode/claude-haiku-4-5",
},
"ultrabrain": {
"model": "opencode/gpt-5.3-codex",
"model": "opencode/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
"model": "opencode/gpt-5.4",
"variant": "high",
"model": "opencode/claude-opus-4-6",
"variant": "max",
},
"unspecified-low": {
"model": "opencode/claude-sonnet-4-5",
"model": "opencode/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "opencode/gemini-3.1-pro",
@@ -782,11 +788,15 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"explore": {
"model": "github-copilot/gpt-5-mini",
},
"hephaestus": {
"model": "github-copilot/gpt-5.4",
"variant": "medium",
},
"metis": {
"model": "github-copilot/claude-opus-4.6",
"variant": "max",
@@ -796,7 +806,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when
"variant": "xhigh",
},
"multimodal-looker": {
"model": "opencode/glm-4.7-free",
"model": "github-copilot/gpt-5-nano",
},
"oracle": {
"model": "github-copilot/gpt-5.4",
@@ -827,10 +837,10 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when
"variant": "high",
},
"unspecified-high": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"unspecified-low": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"visual-engineering": {
"model": "github-copilot/gemini-3.1-pro-preview",
@@ -848,11 +858,15 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"explore": {
"model": "github-copilot/gpt-5-mini",
},
"hephaestus": {
"model": "github-copilot/gpt-5.4",
"variant": "medium",
},
"metis": {
"model": "github-copilot/claude-opus-4.6",
"variant": "max",
@@ -862,7 +876,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with
"variant": "xhigh",
},
"multimodal-looker": {
"model": "opencode/glm-4.7-free",
"model": "github-copilot/gpt-5-nano",
},
"oracle": {
"model": "github-copilot/gpt-5.4",
@@ -893,11 +907,11 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with
"variant": "high",
},
"unspecified-high": {
"model": "github-copilot/gpt-5.4",
"variant": "high",
"model": "github-copilot/claude-opus-4.6",
"variant": "max",
},
"unspecified-low": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"visual-engineering": {
"model": "github-copilot/gemini-3.1-pro-preview",
@@ -1031,7 +1045,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -1081,14 +1095,14 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
"model": "anthropic/claude-haiku-4-5",
},
"ultrabrain": {
"model": "opencode/gpt-5.3-codex",
"model": "opencode/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"unspecified-low": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "opencode/gemini-3.1-pro",
@@ -1106,7 +1120,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"explore": {
"model": "github-copilot/gpt-5-mini",
@@ -1156,14 +1170,14 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
"model": "github-copilot/claude-haiku-4.5",
},
"ultrabrain": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"unspecified-low": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"visual-engineering": {
"model": "github-copilot/gemini-3.1-pro-preview",
@@ -1181,7 +1195,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + ZAI combinat
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -1225,16 +1239,16 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + ZAI combinat
"variant": "max",
},
"unspecified-high": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"unspecified-low": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "zai-coding-plan/glm-5",
},
"writing": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
},
}
@@ -1245,7 +1259,7 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -1290,10 +1304,10 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi
"variant": "high",
},
"unspecified-high": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"unspecified-low": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3.1-pro-preview",
@@ -1311,7 +1325,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"explore": {
"model": "opencode/claude-haiku-4-5",
@@ -1364,14 +1378,14 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
"model": "github-copilot/claude-haiku-4.5",
},
"ultrabrain": {
"model": "opencode/gpt-5.3-codex",
"model": "opencode/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"unspecified-low": {
"model": "github-copilot/claude-sonnet-4.5",
"model": "github-copilot/claude-sonnet-4.6",
},
"visual-engineering": {
"model": "github-copilot/gemini-3.1-pro-preview",
@@ -1389,7 +1403,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -1442,14 +1456,14 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
"model": "anthropic/claude-haiku-4-5",
},
"ultrabrain": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"unspecified-low": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3.1-pro-preview",
@@ -1467,7 +1481,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
"agents": {
"atlas": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"explore": {
"model": "anthropic/claude-haiku-4-5",
@@ -1520,15 +1534,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
"model": "anthropic/claude-haiku-4-5",
},
"ultrabrain": {
"model": "openai/gpt-5.3-codex",
"model": "openai/gpt-5.4",
"variant": "xhigh",
},
"unspecified-high": {
"model": "openai/gpt-5.4",
"variant": "high",
"model": "anthropic/claude-opus-4-6",
"variant": "max",
},
"unspecified-low": {
"model": "anthropic/claude-sonnet-4-5",
"model": "anthropic/claude-sonnet-4-6",
},
"visual-engineering": {
"model": "google/gemini-3.1-pro-preview",

View File

@@ -0,0 +1,31 @@
import { describe, expect, test } from "bun:test"
import {
CLI_AGENT_MODEL_REQUIREMENTS,
CLI_CATEGORY_MODEL_REQUIREMENTS,
} from "./model-fallback-requirements"
import { AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS } from "../shared/model-requirements"
describe("CLI model fallback requirements", () => {
test("agent requirements stay aligned with runtime requirements", () => {
// #given
const runtimeAgents = AGENT_MODEL_REQUIREMENTS
// #when
const cliAgents = CLI_AGENT_MODEL_REQUIREMENTS
// #then
expect(cliAgents).toEqual(runtimeAgents)
})
test("category requirements stay aligned with runtime requirements", () => {
// #given
const runtimeCategories = CATEGORY_MODEL_REQUIREMENTS
// #when
const cliCategories = CLI_CATEGORY_MODEL_REQUIREMENTS
// #then
expect(cliCategories).toEqual(runtimeCategories)
})
})

View File

@@ -1,283 +1,9 @@
import type { ModelRequirement } from "../shared/model-requirements";
import {
AGENT_MODEL_REQUIREMENTS,
CATEGORY_MODEL_REQUIREMENTS,
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-only providers like `nvidia`.
export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = AGENT_MODEL_REQUIREMENTS
export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
sisyphus: {
fallbackChain: [
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6",
variant: "max",
},
{ providers: ["opencode-go"], model: "kimi-k2.5" },
{ providers: ["kimi-for-coding"], model: "k2p5" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4", variant: "medium" },
{ providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
],
requiresAnyModel: true,
},
hephaestus: {
fallbackChain: [
{
providers: ["openai", "opencode"],
model: "gpt-5.3-codex",
variant: "medium",
},
],
requiresProvider: ["openai", "opencode"],
},
oracle: {
fallbackChain: [
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "high",
},
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3.1-pro",
variant: "high",
},
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6",
variant: "max",
},
{ providers: ["opencode-go"], model: "glm-5" },
],
},
librarian: {
fallbackChain: [
{ providers: ["opencode-go"], model: "minimax-m2.5" },
{ providers: ["opencode"], model: "minimax-m2.5-free" },
{ providers: ["anthropic", "opencode"], model: "claude-haiku-4-5" },
{ providers: ["opencode"], model: "gpt-5-nano" },
],
},
explore: {
fallbackChain: [
{ providers: ["github-copilot"], model: "grok-code-fast-1" },
{ providers: ["opencode-go"], model: "minimax-m2.5" },
{ providers: ["anthropic", "opencode"], model: "claude-haiku-4-5" },
{ providers: ["opencode"], model: "gpt-5-nano" },
],
},
"multimodal-looker": {
fallbackChain: [
{ providers: ["openai", "opencode"], model: "gpt-5.4", variant: "medium" },
{ providers: ["opencode-go"], model: "kimi-k2.5" },
{ providers: ["zai-coding-plan"], model: "glm-4.6v" },
{ 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: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "high",
},
{ providers: ["opencode-go"], model: "glm-5" },
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3.1-pro",
},
],
},
metis: {
fallbackChain: [
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6",
variant: "max",
},
{ providers: ["opencode-go"], model: "glm-5" },
{ providers: ["kimi-for-coding"], model: "k2p5" },
],
},
momus: {
fallbackChain: [
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "xhigh",
},
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6",
variant: "max",
},
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3.1-pro",
variant: "high",
},
{ providers: ["opencode-go"], model: "glm-5" },
],
},
atlas: {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
{ providers: ["opencode-go"], model: "kimi-k2.5" },
],
},
"sisyphus-junior": {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" },
{ providers: ["opencode-go"], model: "kimi-k2.5" },
{ providers: ["opencode"], model: "big-pickle" },
],
},
};
export const CLI_CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
"visual-engineering": {
fallbackChain: [
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3.1-pro",
variant: "high",
},
{ providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6",
variant: "max",
},
{ providers: ["kimi-for-coding"], model: "k2p5" },
{ providers: ["opencode-go"], model: "glm-5" },
],
},
ultrabrain: {
fallbackChain: [
{
providers: ["openai", "opencode"],
model: "gpt-5.3-codex",
variant: "xhigh",
},
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3.1-pro",
variant: "high",
},
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6",
variant: "max",
},
{ providers: ["opencode-go"], model: "glm-5" },
],
},
deep: {
fallbackChain: [
{
providers: ["openai", "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.1-pro",
variant: "high",
},
],
requiresModel: "gpt-5.3-codex",
},
artistry: {
fallbackChain: [
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3.1-pro",
variant: "high",
},
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6",
variant: "max",
},
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
},
],
requiresModel: "gemini-3.1-pro",
},
quick: {
fallbackChain: [
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-haiku-4-5",
},
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3-flash",
},
{ providers: ["opencode-go"], model: "minimax-m2.5" },
{ providers: ["opencode"], model: "gpt-5-nano" },
],
},
"unspecified-low": {
fallbackChain: [
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-sonnet-4-5",
},
{
providers: ["openai", "opencode"],
model: "gpt-5.3-codex",
variant: "medium",
},
{ providers: ["opencode-go"], model: "kimi-k2.5" },
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3-flash",
},
],
},
"unspecified-high": {
fallbackChain: [
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "high",
},
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6",
variant: "max",
},
{ providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
{ providers: ["kimi-for-coding"], model: "k2p5" },
{ providers: ["opencode"], model: "kimi-k2.5" },
{ providers: ["opencode-go"], model: "glm-5" },
],
},
writing: {
fallbackChain: [
{ providers: ["kimi-for-coding"], model: "k2p5" },
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3-flash",
},
{ providers: ["opencode-go"], model: "kimi-k2.5" },
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-sonnet-4-5",
},
],
},
};
export const CLI_CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = CATEGORY_MODEL_REQUIREMENTS

View File

@@ -13,6 +13,7 @@ function createConfig(overrides: Partial<InstallConfig> = {}): InstallConfig {
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
...overrides,
}
}
@@ -410,6 +411,44 @@ describe("generateModelConfig", () => {
})
})
describe("OpenAI fallback coverage", () => {
test("Atlas resolves to OpenAI when only OpenAI is available", () => {
// #given
const config = createConfig({ hasOpenAI: true })
// #when
const result = generateModelConfig(config)
// #then
expect(result.agents?.atlas?.model).toBe("openai/gpt-5.4")
expect(result.agents?.atlas?.variant).toBe("medium")
})
test("Metis resolves to OpenAI when only OpenAI is available", () => {
// #given
const config = createConfig({ hasOpenAI: true })
// #when
const result = generateModelConfig(config)
// #then
expect(result.agents?.metis?.model).toBe("openai/gpt-5.4")
expect(result.agents?.metis?.variant).toBe("high")
})
test("Sisyphus-Junior resolves to OpenAI when only OpenAI is available", () => {
// #given
const config = createConfig({ hasOpenAI: true })
// #when
const result = generateModelConfig(config)
// #then
expect(result.agents?.["sisyphus-junior"]?.model).toBe("openai/gpt-5.4")
expect(result.agents?.["sisyphus-junior"]?.variant).toBe("medium")
})
})
describe("Hephaestus agent special cases", () => {
test("Hephaestus is created when OpenAI is available (openai provider connected)", () => {
// #given
@@ -423,15 +462,18 @@ describe("generateModelConfig", () => {
expect(result.agents?.hephaestus?.variant).toBe("medium")
})
test("Hephaestus is NOT created when only Copilot is available (gpt-5.3-codex unavailable on github-copilot)", () => {
test("Hephaestus falls back to Copilot GPT-5.4 when only Copilot is available", () => {
// #given
const config = createConfig({ hasCopilot: true })
// #when
const result = generateModelConfig(config)
// #then - hephaestus is omitted because gpt-5.3-codex is not available on github-copilot
expect(result.agents?.hephaestus).toBeUndefined()
// #then
expect(result.agents?.hephaestus).toEqual({
model: "github-copilot/gpt-5.4",
variant: "medium",
})
})
test("Hephaestus is created when OpenCode Zen is available (opencode provider connected)", () => {

View File

@@ -13,6 +13,7 @@ function createConfig(overrides: Partial<InstallConfig> = {}): InstallConfig {
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
...overrides,
}
}
@@ -43,4 +44,17 @@ describe("generateModelConfig OpenAI-only model catalog", () => {
expect(result.categories?.["visual-engineering"]).toEqual({ model: "openai/gpt-5.4", variant: "high" })
expect(result.categories?.writing).toEqual({ model: "openai/gpt-5.4", variant: "medium" })
})
test("does not apply OpenAI-only overrides when OpenCode Go is also available", () => {
// #given
const config = createConfig({ hasOpenAI: true, hasOpencodeGo: true })
// #when
const result = generateModelConfig(config)
// #then
expect(result.agents?.explore).toEqual({ model: "opencode-go/minimax-m2.5" })
expect(result.agents?.librarian).toEqual({ model: "opencode-go/minimax-m2.5" })
expect(result.categories?.quick).toEqual({ model: "opencode-go/minimax-m2.5" })
})
})

View File

@@ -17,6 +17,7 @@ export function isOpenAiOnlyAvailability(availability: ProviderAvailability): bo
availability.native.openai &&
!availability.native.claude &&
!availability.native.gemini &&
!availability.opencodeGo &&
!availability.opencodeZen &&
!availability.copilot &&
!availability.zai &&

View File

@@ -74,6 +74,13 @@ describe("applyAgentConfig builtin override protection", () => {
mode: "subagent",
}
const builtinAtlasConfig: AgentConfig = {
name: "atlas",
prompt: "atlas prompt",
mode: "all",
model: "openai/gpt-5.4",
}
const sisyphusJuniorConfig: AgentConfig = {
name: "Sisyphus-Junior",
prompt: "junior prompt",
@@ -85,6 +92,7 @@ describe("applyAgentConfig builtin override protection", () => {
sisyphus: builtinSisyphusConfig,
oracle: builtinOracleConfig,
"multimodal-looker": builtinMultimodalLookerConfig,
atlas: builtinAtlasConfig,
})
createSisyphusJuniorAgentSpy = spyOn(
@@ -255,4 +263,19 @@ describe("applyAgentConfig builtin override protection", () => {
})
})
})
test("passes the resolved Atlas model to Sisyphus-Junior as its fallback default", async () => {
// given
// when
await applyAgentConfig({
config: createBaseConfig(),
pluginConfig: createPluginConfig(),
ctx: { directory: "/tmp" },
pluginComponents: createPluginComponents(),
})
// then
expect(createSisyphusJuniorAgentSpy).toHaveBeenCalledWith(undefined, "openai/gpt-5.4", false)
})
})

View File

@@ -158,7 +158,7 @@ export async function applyAgentConfig(params: {
agentConfig["sisyphus-junior"] = createSisyphusJuniorAgentWithOverrides(
params.pluginConfig.agents?.["sisyphus-junior"],
undefined,
(builtinAgents.atlas as { model?: string } | undefined)?.model,
useTaskSystem,
);

View File

@@ -178,6 +178,13 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
expect(primary.model).toBe("claude-opus-4-6")
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode"])
expect(primary.variant).toBe("max")
const openAiFallback = metis.fallbackChain.find((entry) => entry.providers.includes("openai"))
expect(openAiFallback).toEqual({
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "high",
})
})
test("momus has valid fallbackChain with gpt-5.4 as primary", () => {
@@ -213,6 +220,32 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
const secondary = atlas.fallbackChain[1]
expect(secondary.model).toBe("kimi-k2.5")
expect(secondary.providers[0]).toBe("opencode-go")
const tertiary = atlas.fallbackChain[2]
expect(tertiary).toEqual({
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "medium",
})
})
test("sisyphus-junior has an OpenAI fallback before big-pickle", () => {
// given - sisyphus-junior agent requirement
const sisyphusJunior = AGENT_MODEL_REQUIREMENTS["sisyphus-junior"]
// when - locating the OpenAI fallback entry
const openAiFallback = sisyphusJunior.fallbackChain.find((entry) => entry.providers.includes("openai"))
const openAiFallbackIndex = sisyphusJunior.fallbackChain.findIndex((entry) => entry.providers.includes("openai"))
const bigPickleIndex = sisyphusJunior.fallbackChain.findIndex((entry) => entry.model === "big-pickle")
// then
expect(openAiFallback).toEqual({
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "medium",
})
expect(openAiFallbackIndex).toBeGreaterThan(-1)
expect(bigPickleIndex).toBeGreaterThan(openAiFallbackIndex)
})
test("hephaestus supports openai, github-copilot, venice, and opencode providers", () => {

View File

@@ -121,6 +121,11 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
model: "claude-opus-4-6",
variant: "max",
},
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "high",
},
{ providers: ["opencode-go"], model: "glm-5" },
{ providers: ["kimi-for-coding"], model: "k2p5" },
],
@@ -149,12 +154,22 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" },
{ providers: ["opencode-go"], model: "kimi-k2.5" },
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "medium",
},
],
},
"sisyphus-junior": {
fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" },
{ providers: ["opencode-go"], model: "kimi-k2.5" },
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.4",
variant: "medium",
},
{ providers: ["opencode"], model: "big-pickle" },
],
},