chore: update GPT-5.2 references to GPT-5.4

Align runtime defaults, tests, docs, and generated artifacts with the newer GPT-5.4 baseline. Keep think-mode and prompt-routing expectations consistent after the model version bump.
This commit is contained in:
YeonGyu-Kim
2026-03-07 02:33:32 +09:00
parent 8a1352fc9b
commit fade6740ae
62 changed files with 478 additions and 479 deletions
+3 -3
View File
@@ -11,12 +11,12 @@ Agent factories following `createXXXAgent(model) → AgentConfig` pattern. Each
| Agent | Model | Temp | Mode | Fallback Chain | Purpose |
|-------|-------|------|------|----------------|---------|
| **Sisyphus** | claude-opus-4-6 max | 0.1 | all | glm-5 → big-pickle | Main orchestrator, plans + delegates |
| **Hephaestus** | gpt-5.3-codex medium | 0.1 | all | gpt-5.2 medium (copilot) | Autonomous deep worker |
| **Oracle** | gpt-5.2 high | 0.1 | subagent | gemini-3.1-pro high → claude-opus-4-6 max | Read-only consultation |
| **Hephaestus** | gpt-5.3-codex medium | 0.1 | all | gpt-5.4 medium (copilot) | Autonomous deep worker |
| **Oracle** | gpt-5.4 high | 0.1 | subagent | gemini-3.1-pro high → claude-opus-4-6 max | Read-only consultation |
| **Librarian** | gemini-3-flash | 0.1 | subagent | minimax-m2.5-free → big-pickle | External docs/code search |
| **Explore** | grok-code-fast-1 | 0.1 | subagent | minimax-m2.5-free → claude-haiku-4-5 → gpt-5-nano | Contextual grep |
| **Multimodal-Looker** | gpt-5.3-codex medium | 0.1 | subagent | k2p5 → gemini-3-flash → glm-4.6v → gpt-5-nano | PDF/image analysis |
| **Metis** | claude-opus-4-6 max | **0.3** | subagent | gpt-5.2 high → gemini-3.1-pro high | Pre-planning consultant |
| **Metis** | claude-opus-4-6 max | **0.3** | subagent | gpt-5.4 high → gemini-3.1-pro high | Pre-planning consultant |
| **Momus** | gpt-5.4 xhigh | 0.1 | subagent | claude-opus-4-6 max → gemini-3.1-pro high | Plan reviewer |
| **Atlas** | claude-sonnet-4-6 | 0.1 | primary | gpt-5.4 medium | Todo-list orchestrator |
| **Prometheus** | claude-opus-4-6 max | 0.1 | — | gpt-5.4 high → gemini-3.1-pro | Strategic planner (internal) |
+1 -1
View File
@@ -5,7 +5,7 @@
* You are the conductor of a symphony of specialized agents.
*
* Routing:
* 1. GPT models (openai/*, github-copilot/gpt-*) → gpt.ts (GPT-5.2 optimized)
* 1. GPT models (openai/*, github-copilot/gpt-*) → gpt.ts (GPT-5.4 optimized)
* 2. Gemini models (google/*, google-vertex/*) → gemini.ts (Gemini-optimized)
* 3. Default (Claude, etc.) → default.ts (Claude-optimized)
*/
@@ -197,7 +197,7 @@ describe("buildParallelDelegationSection", () => {
it("#given non-Claude model with unspecified-high category #when building #then returns aggressive delegation section", () => {
//#given
const model = "openai/gpt-5.2"
const model = "openai/gpt-5.4"
const categories = [unspecifiedHighCategory, otherCategory]
//#when
@@ -223,7 +223,7 @@ describe("buildParallelDelegationSection", () => {
it("#given non-Claude model without deep or unspecified-high category #when building #then returns empty", () => {
//#given
const model = "openai/gpt-5.2"
const model = "openai/gpt-5.4"
const categories = [otherCategory]
//#when
@@ -261,7 +261,7 @@ describe("buildNonClaudePlannerSection", () => {
it("#given GPT model #when building #then returns plan agent section", () => {
//#given
const model = "openai/gpt-5.2"
const model = "openai/gpt-5.4"
//#when
const result = buildNonClaudePlannerSection(model)
+3 -3
View File
@@ -39,8 +39,8 @@ describe("getHephaestusPromptSource", () => {
test("returns 'gpt' for generic GPT models", () => {
// given
const model1 = "openai/gpt-5.2";
const model2 = "github-copilot/gpt-5.2";
const model1 = "openai/gpt-4o";
const model2 = "github-copilot/gpt-4o";
const model3 = "openai/gpt-4o";
// when
@@ -111,7 +111,7 @@ describe("getHephaestusPrompt", () => {
test("generic GPT model returns generic GPT prompt", () => {
// given
const model = "openai/gpt-5.2";
const model = "openai/gpt-4o";
// when
const prompt = getHephaestusPrompt(model);
+1 -1
View File
@@ -522,7 +522,7 @@ export function createHephaestusAgent(
return {
description:
"Autonomous Deep Worker - goal-oriented execution with GPT 5.2 Codex. Explores thoroughly before acting, uses explore/librarian agents for comprehensive context, completes tasks end-to-end. Inspired by AmpCode deep mode. (Hephaestus - OhMyOpenCode)",
"Autonomous Deep Worker - goal-oriented execution with GPT 5.4 Codex. Explores thoroughly before acting, uses explore/librarian agents for comprehensive context, completes tasks end-to-end. Inspired by AmpCode deep mode. (Hephaestus - OhMyOpenCode)",
mode: MODE,
model,
maxTokens: 32000,
+1 -1
View File
@@ -48,7 +48,7 @@ export function getPrometheusPromptSource(model?: string): PrometheusPromptSourc
/**
* Gets the appropriate Prometheus prompt based on model.
* GPT models → GPT-5.2 optimized prompt (XML-tagged, principle-driven)
* GPT models → GPT-5.4 optimized prompt (XML-tagged, principle-driven)
* Gemini models → Gemini-optimized prompt (aggressive tool-call enforcement, thinking checkpoints)
* Default (Claude, etc.) → Claude-optimized prompt (modular sections)
*/
+1 -1
View File
@@ -5,7 +5,7 @@
* Category-spawned executor with domain-specific configurations.
*
* Routing:
* 1. GPT models (openai/*, github-copilot/gpt-*) -> gpt.ts (GPT-5.2 optimized)
* 1. GPT models (openai/*, github-copilot/gpt-*) -> gpt.ts (GPT-5.4 optimized)
* 2. Gemini models (google/*, google-vertex/*) -> gemini.ts (Gemini-optimized)
* 3. Default (Claude, etc.) -> default.ts (Claude-optimized)
*/
+8 -8
View File
@@ -10,13 +10,13 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
describe("honored fields", () => {
test("applies model override", () => {
// given
const override = { model: "openai/gpt-5.2" }
const override = { model: "openai/gpt-5.4" }
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// then
expect(result.model).toBe("openai/gpt-5.2")
expect(result.model).toBe("openai/gpt-5.4")
})
test("applies temperature override", () => {
@@ -105,7 +105,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
// given
const override = {
disable: true,
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
temperature: 0.9,
}
@@ -216,7 +216,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
test("useTaskSystem=true produces Task Discipline prompt for GPT", () => {
//#given
const override = { model: "openai/gpt-5.2" }
const override = { model: "openai/gpt-5.4" }
//#when
const result = createSisyphusJuniorAgentWithOverrides(override, undefined, true)
@@ -253,7 +253,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
test("useTaskSystem=true includes task_create/task_update in GPT prompt", () => {
//#given
const override = { model: "openai/gpt-5.2" }
const override = { model: "openai/gpt-5.4" }
//#when
const result = createSisyphusJuniorAgentWithOverrides(override, undefined, true)
@@ -303,7 +303,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
test("GPT model uses GPT-optimized prompt with Hephaestus-style sections", () => {
// given
const override = { model: "openai/gpt-5.2" }
const override = { model: "openai/gpt-5.4" }
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
@@ -401,7 +401,7 @@ describe("getSisyphusJuniorPromptSource", () => {
test("returns 'gpt' for generic GPT models", () => {
// given
const model = "openai/gpt-5.2"
const model = "openai/gpt-4o"
// when
const source = getSisyphusJuniorPromptSource(model)
@@ -473,7 +473,7 @@ describe("buildSisyphusJuniorPrompt", () => {
test("generic GPT model uses generic GPT prompt", () => {
// given
const model = "openai/gpt-5.2"
const model = "openai/gpt-5.4"
// when
const prompt = buildSisyphusJuniorPrompt(model, false)
+8 -8
View File
@@ -12,9 +12,9 @@ describe("isGpt5_4Model", () => {
test("does not match other GPT models", () => {
expect(isGpt5_4Model("openai/gpt-5.3-codex")).toBe(false);
expect(isGpt5_4Model("openai/gpt-5.2")).toBe(false);
expect(isGpt5_4Model("openai/gpt-5.1")).toBe(false);
expect(isGpt5_4Model("openai/gpt-4o")).toBe(false);
expect(isGpt5_4Model("github-copilot/gpt-5.2")).toBe(false);
expect(isGpt5_4Model("github-copilot/gpt-4o")).toBe(false);
});
test("does not match non-GPT models", () => {
@@ -26,7 +26,7 @@ describe("isGpt5_4Model", () => {
describe("isGptModel", () => {
test("standard openai provider gpt models", () => {
expect(isGptModel("openai/gpt-5.2")).toBe(true);
expect(isGptModel("openai/gpt-5.4")).toBe(true);
expect(isGptModel("openai/gpt-4o")).toBe(true);
});
@@ -39,22 +39,22 @@ describe("isGptModel", () => {
});
test("github copilot gpt models", () => {
expect(isGptModel("github-copilot/gpt-5.2")).toBe(true);
expect(isGptModel("github-copilot/gpt-5.4")).toBe(true);
expect(isGptModel("github-copilot/gpt-4o")).toBe(true);
});
test("litellm proxied gpt models", () => {
expect(isGptModel("litellm/gpt-5.2")).toBe(true);
expect(isGptModel("litellm/gpt-5.4")).toBe(true);
expect(isGptModel("litellm/gpt-4o")).toBe(true);
});
test("other proxied gpt models", () => {
expect(isGptModel("ollama/gpt-4o")).toBe(true);
expect(isGptModel("custom-provider/gpt-5.2")).toBe(true);
expect(isGptModel("custom-provider/gpt-5.4")).toBe(true);
});
test("venice provider gpt models", () => {
expect(isGptModel("venice/gpt-5.2")).toBe(true);
expect(isGptModel("venice/gpt-5.4")).toBe(true);
expect(isGptModel("venice/gpt-4o")).toBe(true);
});
@@ -108,7 +108,7 @@ describe("isGeminiModel", () => {
});
test("#given gpt models #then returns false", () => {
expect(isGeminiModel("openai/gpt-5.2")).toBe(false);
expect(isGeminiModel("openai/gpt-5.4")).toBe(false);
expect(isGeminiModel("openai/o3-mini")).toBe(false);
expect(isGeminiModel("litellm/gpt-4o")).toBe(false);
});
+30 -30
View File
@@ -39,14 +39,14 @@ describe("createBuiltinAgents with model overrides", () => {
test("Sisyphus with GPT model override has reasoningEffort, no thinking", async () => {
// #given
const overrides = {
sisyphus: { model: "github-copilot/gpt-5.2" },
sisyphus: { model: "github-copilot/gpt-5.4" },
}
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
// #then
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.2")
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.4")
expect(agents.sisyphus.reasoningEffort).toBe("medium")
expect(agents.sisyphus.thinking).toBeUndefined()
})
@@ -54,9 +54,9 @@ describe("createBuiltinAgents with model overrides", () => {
test("Atlas uses uiSelectedModel", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.2", "anthropic/claude-sonnet-4-6"])
new Set(["openai/gpt-5.4", "anthropic/claude-sonnet-4-6"])
)
const uiSelectedModel = "openai/gpt-5.2"
const uiSelectedModel = "openai/gpt-5.4"
try {
// #when
@@ -75,7 +75,7 @@ describe("createBuiltinAgents with model overrides", () => {
// #then
expect(agents.atlas).toBeDefined()
expect(agents.atlas.model).toBe("openai/gpt-5.2")
expect(agents.atlas.model).toBe("openai/gpt-5.4")
} finally {
fetchSpy.mockRestore()
}
@@ -84,9 +84,9 @@ describe("createBuiltinAgents with model overrides", () => {
test("user config model takes priority over uiSelectedModel for sisyphus", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.2", "anthropic/claude-sonnet-4-6"])
new Set(["openai/gpt-5.4", "anthropic/claude-sonnet-4-6"])
)
const uiSelectedModel = "openai/gpt-5.2"
const uiSelectedModel = "openai/gpt-5.4"
const overrides = {
sisyphus: { model: "google/antigravity-claude-opus-4-5-thinking" },
}
@@ -117,9 +117,9 @@ describe("createBuiltinAgents with model overrides", () => {
test("user config model takes priority over uiSelectedModel for atlas", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.2", "anthropic/claude-sonnet-4-6"])
new Set(["openai/gpt-5.4", "anthropic/claude-sonnet-4-6"])
)
const uiSelectedModel = "openai/gpt-5.2"
const uiSelectedModel = "openai/gpt-5.4"
const overrides = {
atlas: { model: "google/antigravity-claude-opus-4-5-thinking" },
}
@@ -173,8 +173,8 @@ describe("createBuiltinAgents with model overrides", () => {
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
// #then - oracle resolves via connected cache fallback to openai/gpt-5.2 (not system default)
expect(agents.oracle.model).toBe("openai/gpt-5.2")
// #then - oracle resolves via connected cache fallback to openai/gpt-5.4 (not system default)
expect(agents.oracle.model).toBe("openai/gpt-5.4")
expect(agents.oracle.reasoningEffort).toBe("medium")
expect(agents.oracle.thinking).toBeUndefined()
cacheSpy.mockRestore?.()
@@ -196,14 +196,14 @@ describe("createBuiltinAgents with model overrides", () => {
test("Oracle with GPT model override has reasoningEffort, no thinking", async () => {
// #given
const overrides = {
oracle: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
}
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
// #then
expect(agents.oracle.model).toBe("openai/gpt-5.2")
expect(agents.oracle.model).toBe("openai/gpt-5.4")
expect(agents.oracle.reasoningEffort).toBe("medium")
expect(agents.oracle.textVerbosity).toBe("high")
expect(agents.oracle.thinking).toBeUndefined()
@@ -228,14 +228,14 @@ describe("createBuiltinAgents with model overrides", () => {
test("non-model overrides are still applied after factory rebuild", async () => {
// #given
const overrides = {
sisyphus: { model: "github-copilot/gpt-5.2", temperature: 0.5 },
sisyphus: { model: "github-copilot/gpt-5.4", temperature: 0.5 },
}
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
// #then
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.2")
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.4")
expect(agents.sisyphus.temperature).toBe(0.5)
})
@@ -261,7 +261,7 @@ describe("createBuiltinAgents with model overrides", () => {
"opencode/kimi-k2.5-free",
"zai-coding-plan/glm-5",
"opencode/big-pickle",
"openai/gpt-5.2",
"openai/gpt-5.4",
])
)
@@ -298,7 +298,7 @@ describe("createBuiltinAgents with model overrides", () => {
test("excludes hidden custom agents from orchestrator prompts", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
)
const customAgentSummaries = [
@@ -334,7 +334,7 @@ describe("createBuiltinAgents with model overrides", () => {
test("excludes disabled custom agents from orchestrator prompts", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
)
const customAgentSummaries = [
@@ -370,7 +370,7 @@ describe("createBuiltinAgents with model overrides", () => {
test("excludes custom agents when disabledAgents contains their name (case-insensitive)", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
)
const disabledAgents = ["ReSeArChEr"]
@@ -406,7 +406,7 @@ describe("createBuiltinAgents with model overrides", () => {
test("deduplicates custom agents case-insensitively", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
)
const customAgentSummaries = [
@@ -438,7 +438,7 @@ describe("createBuiltinAgents with model overrides", () => {
test("sanitizes custom agent strings for markdown tables", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
)
const customAgentSummaries = [
@@ -479,7 +479,7 @@ describe("createBuiltinAgents without systemDefaultModel", () => {
// #then - connected cache enables model resolution despite no systemDefaultModel
expect(agents.oracle).toBeDefined()
expect(agents.oracle.model).toBe("openai/gpt-5.2")
expect(agents.oracle.model).toBe("openai/gpt-5.4")
cacheSpy.mockRestore?.()
})
@@ -787,7 +787,7 @@ describe("Atlas is unaffected by environment context toggle", () => {
beforeEach(() => {
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
)
})
@@ -891,9 +891,9 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
})
test("sisyphus is not created when no fallback model is available and provider not connected", async () => {
// #given - only openai/gpt-5.2 available, not in sisyphus fallback chain
// #given - only openai/gpt-5.4 available, not in sisyphus fallback chain
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.2"])
new Set(["openai/gpt-5.4"])
)
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue([])
@@ -913,7 +913,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
// #given - user configures a model from a plugin provider (like antigravity)
// that is NOT in the availableModels cache and NOT in the fallback chain
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.2"])
new Set(["openai/gpt-5.4"])
)
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(
["openai"]
@@ -1021,7 +1021,7 @@ describe("buildAgent with category and skills", () => {
const categories = {
"custom-category": {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
variant: "xhigh",
},
}
@@ -1030,7 +1030,7 @@ describe("buildAgent with category and skills", () => {
const agent = buildAgent(source["test-agent"], TEST_MODEL, categories)
// #then
expect(agent.model).toBe("openai/gpt-5.2")
expect(agent.model).toBe("openai/gpt-5.4")
expect(agent.variant).toBe("xhigh")
})
@@ -1247,7 +1247,7 @@ describe("override.category expansion in createBuiltinAgents", () => {
// #given - custom category has reasoningEffort=xhigh, direct override says "low"
const categories = {
"test-cat": {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
reasoningEffort: "xhigh" as const,
},
}
@@ -1267,7 +1267,7 @@ describe("override.category expansion in createBuiltinAgents", () => {
// #given - custom category has reasoningEffort, no direct reasoningEffort in override
const categories = {
"reasoning-cat": {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
reasoningEffort: "high" as const,
},
}
@@ -205,7 +205,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
"model": "opencode/glm-4.7-free",
},
"metis": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.4",
"variant": "high",
},
"momus": {
@@ -217,7 +217,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -274,7 +274,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
"model": "opencode/glm-4.7-free",
},
"metis": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.4",
"variant": "high",
},
"momus": {
@@ -286,7 +286,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -476,7 +476,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -551,7 +551,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -627,7 +627,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
"variant": "medium",
},
"oracle": {
"model": "opencode/gpt-5.2",
"model": "opencode/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -702,7 +702,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
"variant": "medium",
},
"oracle": {
"model": "opencode/gpt-5.2",
"model": "opencode/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -773,7 +773,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when
"model": "github-copilot/gemini-3-flash-preview",
},
"oracle": {
"model": "github-copilot/gpt-5.2",
"model": "github-copilot/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -839,7 +839,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with
"model": "github-copilot/gemini-3-flash-preview",
},
"oracle": {
"model": "github-copilot/gpt-5.2",
"model": "github-copilot/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -1021,7 +1021,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
"variant": "medium",
},
"oracle": {
"model": "opencode/gpt-5.2",
"model": "opencode/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -1096,7 +1096,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -1298,7 +1298,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
"variant": "medium",
},
"oracle": {
"model": "github-copilot/gpt-5.2",
"model": "github-copilot/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -1373,7 +1373,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.4",
"variant": "high",
},
"prometheus": {
@@ -1448,7 +1448,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
"variant": "medium",
},
"oracle": {
"model": "openai/gpt-5.2",
"model": "openai/gpt-5.4",
"variant": "high",
},
"prometheus": {
+1 -1
View File
@@ -40,7 +40,7 @@ Examples:
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi):
Claude Native anthropic/ models (Opus, Sonnet, Haiku)
OpenAI Native openai/ models (GPT-5.2 for Oracle)
OpenAI Native openai/ models (GPT-5.4 for Oracle)
Gemini Native google/ models (Gemini 3 Pro, Flash)
Copilot github-copilot/ models (fallback)
OpenCode Zen opencode/ models (opencode/claude-opus-4-6, etc.)
+1 -1
View File
@@ -252,7 +252,7 @@ describe("generateOmoConfig - model fallback system", () => {
// #then Sisyphus is omitted (requires all fallback providers)
expect((result.agents as Record<string, { model: string }>).sisyphus).toBeUndefined()
// #then Oracle should use native OpenAI (first fallback entry)
expect((result.agents as Record<string, { model: string }>).oracle.model).toBe("openai/gpt-5.2")
expect((result.agents as Record<string, { model: string }>).oracle.model).toBe("openai/gpt-5.4")
// #then multimodal-looker should use native OpenAI (first fallback entry is gpt-5.3-codex)
expect((result.agents as Record<string, { model: string }>)["multimodal-looker"].model).toBe("openai/gpt-5.3-codex")
})
@@ -61,7 +61,7 @@ describe("model-resolution check", () => {
// given: User has override for visual-engineering category
const mockConfig = {
categories: {
"visual-engineering": { model: "openai/gpt-5.2" },
"visual-engineering": { model: "openai/gpt-5.4" },
},
}
@@ -70,8 +70,8 @@ describe("model-resolution check", () => {
// then: visual-engineering should show the override
const visual = info.categories.find((c) => c.name === "visual-engineering")
expect(visual).toBeDefined()
expect(visual!.userOverride).toBe("openai/gpt-5.2")
expect(visual!.effectiveResolution).toBe("User override: openai/gpt-5.2")
expect(visual!.userOverride).toBe("openai/gpt-5.4")
expect(visual!.effectiveResolution).toBe("User override: openai/gpt-5.4")
})
it("shows provider fallback when no override exists", async () => {
@@ -96,7 +96,7 @@ describe("model-resolution check", () => {
//#given User has model with variant override for oracle agent
const mockConfig = {
agents: {
oracle: { model: "openai/gpt-5.2", variant: "xhigh" },
oracle: { model: "openai/gpt-5.4", variant: "xhigh" },
},
}
@@ -106,7 +106,7 @@ describe("model-resolution check", () => {
//#then Oracle should have userVariant set
const oracle = info.agents.find((a) => a.name === "oracle")
expect(oracle).toBeDefined()
expect(oracle!.userOverride).toBe("openai/gpt-5.2")
expect(oracle!.userOverride).toBe("openai/gpt-5.4")
expect(oracle!.userVariant).toBe("xhigh")
})
+1 -1
View File
@@ -32,7 +32,7 @@ export function formatConfigSummary(config: InstallConfig): string {
const claudeDetail = config.hasClaude ? (config.isMax20 ? "max20" : "standard") : undefined
lines.push(formatProvider("Claude", config.hasClaude, claudeDetail))
lines.push(formatProvider("OpenAI/ChatGPT", config.hasOpenAI, "GPT-5.2 for Oracle"))
lines.push(formatProvider("OpenAI/ChatGPT", config.hasOpenAI, "GPT-5.4 for Oracle"))
lines.push(formatProvider("Gemini", config.hasGemini))
lines.push(formatProvider("GitHub Copilot", config.hasCopilot, "fallback"))
lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models"))
+3 -3
View File
@@ -31,7 +31,7 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
fallbackChain: [
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.2",
model: "gpt-5.4",
variant: "high",
},
{
@@ -108,7 +108,7 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
{ providers: ["kimi-for-coding"], model: "k2p5" },
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.2",
model: "gpt-5.4",
variant: "high",
},
{
@@ -224,7 +224,7 @@ export const CLI_CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> =
},
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.2",
model: "gpt-5.4",
},
],
requiresModel: "gemini-3.1-pro",
+2 -2
View File
@@ -44,7 +44,7 @@ export async function promptInstallConfig(detected: DetectedConfig): Promise<Ins
message: "Do you have an OpenAI/ChatGPT Plus subscription?",
options: [
{ value: "no", label: "No", hint: "Oracle will use fallback models" },
{ value: "yes", label: "Yes", hint: "GPT-5.2 for Oracle (high-IQ debugging)" },
{ value: "yes", label: "Yes", hint: "GPT-5.4 for Oracle (high-IQ debugging)" },
],
initialValue: initial.openai,
})
@@ -74,7 +74,7 @@ export async function promptInstallConfig(detected: DetectedConfig): Promise<Ins
message: "Do you have access to OpenCode Zen (opencode/ models)?",
options: [
{ value: "no", label: "No", hint: "Will use other configured providers" },
{ value: "yes", label: "Yes", hint: "opencode/claude-opus-4-6, opencode/gpt-5.2, etc." },
{ value: "yes", label: "Yes", hint: "opencode/claude-opus-4-6, opencode/gpt-5.4, etc." },
],
initialValue: initial.opencodeZen,
})
+8 -8
View File
@@ -266,7 +266,7 @@ describe("AgentOverrideConfigSchema", () => {
describe("backward compatibility", () => {
test("still accepts model field (deprecated)", () => {
// given
const config = { model: "openai/gpt-5.2" }
const config = { model: "openai/gpt-5.4" }
// when
const result = AgentOverrideConfigSchema.safeParse(config)
@@ -274,14 +274,14 @@ describe("AgentOverrideConfigSchema", () => {
// then
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.model).toBe("openai/gpt-5.2")
expect(result.data.model).toBe("openai/gpt-5.4")
}
})
test("accepts both model and category (deprecated usage)", () => {
// given - category should take precedence at runtime, but both should validate
const config = {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
category: "ultrabrain"
}
@@ -291,7 +291,7 @@ describe("AgentOverrideConfigSchema", () => {
// then
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.model).toBe("openai/gpt-5.2")
expect(result.data.model).toBe("openai/gpt-5.4")
expect(result.data.category).toBe("ultrabrain")
}
})
@@ -343,7 +343,7 @@ describe("AgentOverrideConfigSchema", () => {
describe("CategoryConfigSchema", () => {
test("accepts variant as optional string", () => {
// given
const config = { model: "openai/gpt-5.2", variant: "xhigh" }
const config = { model: "openai/gpt-5.4", variant: "xhigh" }
// when
const result = CategoryConfigSchema.safeParse(config)
@@ -371,7 +371,7 @@ describe("CategoryConfigSchema", () => {
test("rejects non-string variant", () => {
// given
const config = { model: "openai/gpt-5.2", variant: 123 }
const config = { model: "openai/gpt-5.4", variant: 123 }
// when
const result = CategoryConfigSchema.safeParse(config)
@@ -413,7 +413,7 @@ describe("Sisyphus-Junior agent override", () => {
const config = {
agents: {
"sisyphus-junior": {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
temperature: 0.2,
},
},
@@ -426,7 +426,7 @@ describe("Sisyphus-Junior agent override", () => {
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.agents?.["sisyphus-junior"]).toBeDefined()
expect(result.data.agents?.["sisyphus-junior"]?.model).toBe("openai/gpt-5.2")
expect(result.data.agents?.["sisyphus-junior"]?.model).toBe("openai/gpt-5.4")
expect(result.data.agents?.["sisyphus-junior"]?.temperature).toBe(0.2)
}
})
@@ -2078,7 +2078,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
description: "Task 2",
prompt: "Do something else",
agent: "test-agent",
model: { providerID: "openai", modelID: "gpt-5.2" },
model: { providerID: "openai", modelID: "gpt-5.4" },
parentSessionID: "parent-session",
parentMessageID: "parent-message",
}
+1 -1
View File
@@ -162,7 +162,7 @@ describe("createAnthropicEffortHook", () => {
const hook = createAnthropicEffortHook()
const { input, output } = createMockParams({
providerID: "openai",
modelID: "gpt-5.2",
modelID: "gpt-5.4",
})
//#when chat.params hook is called
@@ -3,7 +3,7 @@
*
* Routing logic:
* 1. Planner agents (prometheus, plan) → planner.ts
* 2. GPT 5.2 models → gpt5.2.ts
* 2. GPT 5.4 models → gpt5.4.ts
* 3. Gemini models → gemini.ts
* 4. Everything else (Claude, etc.) → default.ts
*/
+2 -2
View File
@@ -104,7 +104,7 @@ describe("no-sisyphus-gpt hook", () => {
await hook["chat.message"]?.({
sessionID: "ses_3",
agent: HEPHAESTUS_DISPLAY,
model: { providerID: "openai", modelID: "gpt-5.2" },
model: { providerID: "openai", modelID: "gpt-5.4" },
}, output)
// then - no toast
@@ -126,7 +126,7 @@ describe("no-sisyphus-gpt hook", () => {
// when - chat.message runs without input.agent
await hook["chat.message"]?.({
sessionID: "ses_4",
model: { providerID: "openai", modelID: "gpt-5.2" },
model: { providerID: "openai", modelID: "gpt-4o" },
}, output)
// then - toast shown via session-agent fallback
+28 -28
View File
@@ -103,7 +103,7 @@ describe("runtime-fallback", () => {
await hook.event({
event: {
type: "session.created",
properties: { info: { id: sessionID, model: "openai/gpt-5.2" } },
properties: { info: { id: sessionID, model: "openai/gpt-5.4" } },
},
})
@@ -202,7 +202,7 @@ describe("runtime-fallback", () => {
test("should trigger fallback for missing API key errors when fallback models are configured", async () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
})
const sessionID = "test-session-missing-api-key-fallback"
SessionCategoryRegistry.register(sessionID, "test")
@@ -230,7 +230,7 @@ describe("runtime-fallback", () => {
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
expect(fallbackLog).toBeDefined()
expect(fallbackLog?.data).toMatchObject({ from: "google/gemini-2.5-pro", to: "openai/gpt-5.2" })
expect(fallbackLog?.data).toMatchObject({ from: "google/gemini-2.5-pro", to: "openai/gpt-5.4" })
})
test("should detect retryable error from message pattern 'rate limit'", async () => {
@@ -260,7 +260,7 @@ describe("runtime-fallback", () => {
config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback([
"anthropic/claude-opus-4.6",
"openai/gpt-5.2",
"openai/gpt-5.4",
]),
})
const sessionID = "test-session-model-not-found"
@@ -302,7 +302,7 @@ describe("runtime-fallback", () => {
const fallbackLogs = logCalls.filter((c) => c.msg.includes("Preparing fallback"))
expect(fallbackLogs.length).toBeGreaterThanOrEqual(2)
expect(fallbackLogs[1]?.data).toMatchObject({ from: "anthropic/claude-opus-4.6", to: "openai/gpt-5.2" })
expect(fallbackLogs[1]?.data).toMatchObject({ from: "anthropic/claude-opus-4.6", to: "openai/gpt-5.4" })
const nonRetryLog = logCalls.find(
(c) => c.msg.includes("Error not retryable") && (c.data as { sessionID?: string } | undefined)?.sessionID === sessionID
@@ -313,7 +313,7 @@ describe("runtime-fallback", () => {
test("should trigger fallback on Copilot auto-retry signal in message.updated", async () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
})
const sessionID = "test-session-copilot-auto-retry"
@@ -346,7 +346,7 @@ describe("runtime-fallback", () => {
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
expect(fallbackLog).toBeDefined()
expect(fallbackLog?.data).toMatchObject({ from: "github-copilot/claude-opus-4.6", to: "openai/gpt-5.2" })
expect(fallbackLog?.data).toMatchObject({ from: "github-copilot/claude-opus-4.6", to: "openai/gpt-5.4" })
})
test("should trigger fallback on OpenAI auto-retry signal in message.updated", async () => {
@@ -658,7 +658,7 @@ describe("runtime-fallback", () => {
test("should trigger fallback when message.updated has missing API key error without model", async () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
})
const sessionID = "test-message-updated-missing-model"
SessionCategoryRegistry.register(sessionID, "test")
@@ -689,7 +689,7 @@ describe("runtime-fallback", () => {
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
expect(fallbackLog).toBeDefined()
expect(fallbackLog?.data).toMatchObject({ from: "google/gemini-2.5-pro", to: "openai/gpt-5.2" })
expect(fallbackLog?.data).toMatchObject({ from: "google/gemini-2.5-pro", to: "openai/gpt-5.4" })
})
test("should not advance fallback state from message.updated while retry is already in flight", async () => {
@@ -709,7 +709,7 @@ describe("runtime-fallback", () => {
pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6",
"anthropic/claude-opus-4-6",
"openai/gpt-5.2",
"openai/gpt-5.4",
]),
}
)
@@ -799,7 +799,7 @@ describe("runtime-fallback", () => {
pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6",
"anthropic/claude-opus-4-6",
"openai/gpt-5.2",
"openai/gpt-5.4",
]),
}
)
@@ -883,7 +883,7 @@ describe("runtime-fallback", () => {
pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6",
"anthropic/claude-opus-4-6",
"openai/gpt-5.2",
"openai/gpt-5.4",
]),
session_timeout_ms: 20,
}
@@ -949,7 +949,7 @@ describe("runtime-fallback", () => {
pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6",
"anthropic/claude-opus-4-6",
"openai/gpt-5.2",
"openai/gpt-5.4",
]),
session_timeout_ms: 20,
}
@@ -1034,7 +1034,7 @@ describe("runtime-fallback", () => {
pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6",
"anthropic/claude-opus-4-6",
"openai/gpt-5.2",
"openai/gpt-5.4",
]),
session_timeout_ms: 20,
}
@@ -1099,7 +1099,7 @@ describe("runtime-fallback", () => {
pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6",
"anthropic/claude-opus-4-6",
"openai/gpt-5.2",
"openai/gpt-5.4",
]),
session_timeout_ms: 20,
}
@@ -1637,7 +1637,7 @@ describe("runtime-fallback", () => {
}),
{
config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
}
)
@@ -1665,7 +1665,7 @@ describe("runtime-fallback", () => {
},
})
expect(retriedModels).toContain("openai/gpt-5.2")
expect(retriedModels).toContain("openai/gpt-5.4")
})
test("triggers fallback when message has mixed text and error parts", async () => {
@@ -1745,7 +1745,7 @@ describe("runtime-fallback", () => {
}),
{
config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
}
)
@@ -1841,7 +1841,7 @@ describe("runtime-fallback", () => {
test("should apply fallback model on next chat.message after error", async () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2", "google/gemini-3.1-pro"]),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4", "google/gemini-3.1-pro"]),
})
const sessionID = "test-session-switch"
SessionCategoryRegistry.register(sessionID, "test")
@@ -1871,13 +1871,13 @@ describe("runtime-fallback", () => {
output
)
expect(output.message.model).toEqual({ providerID: "openai", modelID: "gpt-5.2" })
expect(output.message.model).toEqual({ providerID: "openai", modelID: "gpt-5.4" })
})
test("should notify when fallback occurs", async () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
config: createMockConfig({ notify_on_fallback: true }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
})
const sessionID = "test-session-notify"
SessionCategoryRegistry.register(sessionID, "test")
@@ -1897,7 +1897,7 @@ describe("runtime-fallback", () => {
})
expect(toastCalls.length).toBe(1)
expect(toastCalls[0]?.message.includes("gpt-5.2")).toBe(true)
expect(toastCalls[0]?.message.includes("gpt-5.4")).toBe(true)
})
})
@@ -1916,7 +1916,7 @@ describe("runtime-fallback", () => {
const input = createMockPluginInput()
const hook = createRuntimeFallbackHook(input, {
config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithAgentFallback("oracle", ["openai/gpt-5.2", "google/gemini-3.1-pro"]),
pluginConfig: createMockPluginConfigWithAgentFallback("oracle", ["openai/gpt-5.4", "google/gemini-3.1-pro"]),
})
const sessionID = "test-agent-fallback"
@@ -1936,16 +1936,16 @@ describe("runtime-fallback", () => {
},
})
//#then - should prepare fallback to openai/gpt-5.2
//#then - should prepare fallback to openai/gpt-5.4
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
expect(fallbackLog).toBeDefined()
expect(fallbackLog?.data).toMatchObject({ from: "anthropic/claude-opus-4-5", to: "openai/gpt-5.2" })
expect(fallbackLog?.data).toMatchObject({ from: "anthropic/claude-opus-4-5", to: "openai/gpt-5.4" })
})
test("should detect agent from sessionID pattern", async () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithAgentFallback("sisyphus", ["openai/gpt-5.2"]),
pluginConfig: createMockPluginConfigWithAgentFallback("sisyphus", ["openai/gpt-5.4"]),
})
const sessionID = "sisyphus-session-123"
@@ -1966,7 +1966,7 @@ describe("runtime-fallback", () => {
//#then - should detect sisyphus from sessionID and use its fallback
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
expect(fallbackLog).toBeDefined()
expect(fallbackLog?.data).toMatchObject({ to: "openai/gpt-5.2" })
expect(fallbackLog?.data).toMatchObject({ to: "openai/gpt-5.4" })
})
test("should preserve resolved agent during auto-retry", async () => {
@@ -2019,7 +2019,7 @@ describe("runtime-fallback", () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
config: createMockConfig({ cooldown_seconds: 60, notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback([
"openai/gpt-5.2",
"openai/gpt-5.4",
"anthropic/claude-opus-4-5",
]),
})
+2 -2
View File
@@ -70,7 +70,7 @@ describe("createThinkModeHook", () => {
const input = createHookInput({
sessionID,
providerID: "github-copilot",
modelID: "gpt-5.2",
modelID: "gpt-5.4",
})
const output = createHookOutput("ultrathink about this")
@@ -81,7 +81,7 @@ describe("createThinkModeHook", () => {
expect(output.message.variant).toBe("high")
expect(output.message.model).toEqual({
providerID: "github-copilot",
modelID: "gpt-5-2-high",
modelID: "gpt-5-4-high",
})
})
+14 -14
View File
@@ -32,11 +32,11 @@ describe("think-mode switcher", () => {
})
it("should handle dots in GPT version numbers", () => {
// given a GPT model ID with dot format (gpt-5.2)
const variant = getHighVariant("gpt-5.2")
// given a GPT model ID with dot format (gpt-5.4)
const variant = getHighVariant("gpt-5.4")
// then should return high variant
expect(variant).toBe("gpt-5-2-high")
expect(variant).toBe("gpt-5-4-high")
})
it("should handle dots in GPT-5.1 codex variants", () => {
@@ -60,7 +60,7 @@ describe("think-mode switcher", () => {
it("should return null for already-high variants", () => {
// given model IDs that are already high variants
expect(getHighVariant("claude-opus-4-6-high")).toBeNull()
expect(getHighVariant("gpt-5-2-high")).toBeNull()
expect(getHighVariant("gpt-5-4-high")).toBeNull()
expect(getHighVariant("gemini-3-1-pro-high")).toBeNull()
})
@@ -76,20 +76,20 @@ describe("think-mode switcher", () => {
it("should detect -high suffix", () => {
// given model IDs with -high suffix
expect(isAlreadyHighVariant("claude-opus-4-6-high")).toBe(true)
expect(isAlreadyHighVariant("gpt-5-2-high")).toBe(true)
expect(isAlreadyHighVariant("gpt-5-4-high")).toBe(true)
expect(isAlreadyHighVariant("gemini-3.1-pro-high")).toBe(true)
})
it("should detect -high suffix after normalization", () => {
// given model IDs with dots that end in -high
expect(isAlreadyHighVariant("gpt-5.2-high")).toBe(true)
expect(isAlreadyHighVariant("gpt-5.4-high")).toBe(true)
})
it("should return false for base models", () => {
// given base model IDs without -high suffix
expect(isAlreadyHighVariant("claude-opus-4-6")).toBe(false)
expect(isAlreadyHighVariant("claude-opus-4.6")).toBe(false)
expect(isAlreadyHighVariant("gpt-5.2")).toBe(false)
expect(isAlreadyHighVariant("gpt-5.4")).toBe(false)
expect(isAlreadyHighVariant("gemini-3.1-pro")).toBe(false)
})
@@ -111,10 +111,10 @@ describe("think-mode switcher", () => {
it("should preserve openai/ prefix when getting high variant", () => {
// given a model ID with openai/ prefix
const variant = getHighVariant("openai/gpt-5-2")
const variant = getHighVariant("openai/gpt-5-4")
// then should return high variant with prefix preserved
expect(variant).toBe("openai/gpt-5-2-high")
expect(variant).toBe("openai/gpt-5-4-high")
})
it("should handle prefixes with dots in version numbers", () => {
@@ -141,7 +141,7 @@ describe("think-mode switcher", () => {
it("should return null for already-high prefixed models", () => {
// given prefixed model IDs that are already high
expect(getHighVariant("vertex_ai/claude-opus-4-6-high")).toBeNull()
expect(getHighVariant("openai/gpt-5-2-high")).toBeNull()
expect(getHighVariant("openai/gpt-5-4-high")).toBeNull()
})
})
@@ -149,20 +149,20 @@ describe("think-mode switcher", () => {
it("should detect -high suffix in prefixed models", () => {
// given prefixed model IDs with -high suffix
expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-6-high")).toBe(true)
expect(isAlreadyHighVariant("openai/gpt-5-2-high")).toBe(true)
expect(isAlreadyHighVariant("openai/gpt-5-4-high")).toBe(true)
expect(isAlreadyHighVariant("custom/gemini-3.1-pro-high")).toBe(true)
})
it("should return false for prefixed base models", () => {
// given prefixed base model IDs without -high suffix
expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-6")).toBe(false)
expect(isAlreadyHighVariant("openai/gpt-5-2")).toBe(false)
expect(isAlreadyHighVariant("openai/gpt-5-4")).toBe(false)
})
it("should handle prefixed models with dots", () => {
// given prefixed model IDs with dots
expect(isAlreadyHighVariant("vertex_ai/gpt-5.2")).toBe(false)
expect(isAlreadyHighVariant("vertex_ai/gpt-5.2-high")).toBe(true)
expect(isAlreadyHighVariant("vertex_ai/gpt-5.4")).toBe(false)
expect(isAlreadyHighVariant("vertex_ai/gpt-5.4-high")).toBe(true)
})
})
})
+5 -6
View File
@@ -25,7 +25,7 @@ import { normalizeModelID } from "../../shared"
* @example
* extractModelPrefix("vertex_ai/claude-sonnet-4-6") // { prefix: "vertex_ai/", base: "claude-sonnet-4-6" }
* extractModelPrefix("claude-sonnet-4-6") // { prefix: "", base: "claude-sonnet-4-6" }
* extractModelPrefix("openai/gpt-5.2") // { prefix: "openai/", base: "gpt-5.2" }
* extractModelPrefix("openai/gpt-5.4") // { prefix: "openai/", base: "gpt-5.4" }
*/
function extractModelPrefix(modelID: string): { prefix: string; base: string } {
const slashIndex = modelID.indexOf("/")
@@ -61,10 +61,10 @@ const HIGH_VARIANT_MAP: Record<string, string> = {
"gpt-5-1-codex": "gpt-5-1-codex-high",
"gpt-5-1-codex-mini": "gpt-5-1-codex-mini-high",
"gpt-5-1-codex-max": "gpt-5-1-codex-max-high",
// GPT-5.2
"gpt-5-2": "gpt-5-2-high",
"gpt-5-2-chat-latest": "gpt-5-2-chat-latest-high",
"gpt-5-2-pro": "gpt-5-2-pro-high",
// GPT-5.4
"gpt-5-4": "gpt-5-4-high",
"gpt-5-4-chat-latest": "gpt-5-4-chat-latest-high",
"gpt-5-4-pro": "gpt-5-4-pro-high",
// Antigravity (Google)
"antigravity-gemini-3-1-pro": "antigravity-gemini-3-1-pro-high",
"antigravity-gemini-3-flash": "antigravity-gemini-3-flash-high",
@@ -97,4 +97,3 @@ export function isAlreadyHighVariant(modelID: string): boolean {
const { base } = extractModelPrefix(normalized)
return ALREADY_HIGH.has(base) || base.endsWith("-high")
}
@@ -1345,8 +1345,8 @@ describe("todo-continuation-enforcer", () => {
// OpenCode returns assistant messages with flat modelID/providerID, not nested model object
const mockMessagesWithAssistant = [
{ info: { id: "msg-1", role: "user", agent: "sisyphus", model: { providerID: "openai", modelID: "gpt-5.2" } } },
{ info: { id: "msg-2", role: "assistant", agent: "sisyphus", modelID: "gpt-5.2", providerID: "openai" } },
{ info: { id: "msg-1", role: "user", agent: "sisyphus", model: { providerID: "openai", modelID: "gpt-5.4" } } },
{ info: { id: "msg-2", role: "assistant", agent: "sisyphus", modelID: "gpt-5.4", providerID: "openai" } },
]
const mockInput = {
@@ -1390,7 +1390,7 @@ describe("todo-continuation-enforcer", () => {
// then - model should be extracted from assistant message's flat modelID/providerID
expect(promptCalls.length).toBe(1)
expect(promptCalls[0].model).toEqual({ providerID: "openai", modelID: "gpt-5.2" })
expect(promptCalls[0].model).toEqual({ providerID: "openai", modelID: "gpt-5.4" })
})
// ============================================================
+18 -18
View File
@@ -12,7 +12,7 @@ describe("mergeConfigs", () => {
const base = {
categories: {
general: {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
temperature: 0.5,
},
quick: {
@@ -35,7 +35,7 @@ describe("mergeConfigs", () => {
const result = mergeConfigs(base, override);
// then general.model should be preserved from base
expect(result.categories?.general?.model).toBe("openai/gpt-5.2");
expect(result.categories?.general?.model).toBe("openai/gpt-5.4");
// then general.temperature should be overridden
expect(result.categories?.general?.temperature).toBe(0.3);
// then quick should be preserved from base
@@ -48,7 +48,7 @@ describe("mergeConfigs", () => {
const base: OhMyOpenCodeConfig = {
categories: {
general: {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
},
},
};
@@ -57,7 +57,7 @@ describe("mergeConfigs", () => {
const result = mergeConfigs(base, override);
expect(result.categories?.general?.model).toBe("openai/gpt-5.2");
expect(result.categories?.general?.model).toBe("openai/gpt-5.4");
});
it("should use override categories when base has no categories", () => {
@@ -66,14 +66,14 @@ describe("mergeConfigs", () => {
const override: OhMyOpenCodeConfig = {
categories: {
general: {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
},
},
};
const result = mergeConfigs(base, override);
expect(result.categories?.general?.model).toBe("openai/gpt-5.2");
expect(result.categories?.general?.model).toBe("openai/gpt-5.4");
});
});
@@ -81,7 +81,7 @@ describe("mergeConfigs", () => {
it("should deep merge agents", () => {
const base: OhMyOpenCodeConfig = {
agents: {
oracle: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
},
};
@@ -94,7 +94,7 @@ describe("mergeConfigs", () => {
const result = mergeConfigs(base, override);
expect(result.agents?.oracle?.model).toBe("openai/gpt-5.2");
expect(result.agents?.oracle?.model).toBe("openai/gpt-5.4");
expect(result.agents?.oracle?.temperature).toBe(0.5);
expect(result.agents?.explore?.model).toBe("anthropic/claude-haiku-4-5");
});
@@ -127,8 +127,8 @@ describe("parseConfigPartially", () => {
it("should return the full config when everything is valid", () => {
const rawConfig = {
agents: {
oracle: { model: "openai/gpt-5.2" },
momus: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
momus: { model: "openai/gpt-5.4" },
},
disabled_hooks: ["comment-checker"],
};
@@ -136,8 +136,8 @@ describe("parseConfigPartially", () => {
const result = parseConfigPartially(rawConfig);
expect(result).not.toBeNull();
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.2");
expect(result!.agents?.momus?.model).toBe("openai/gpt-5.2");
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.4");
expect(result!.agents?.momus?.model).toBe("openai/gpt-5.4");
expect(result!.disabled_hooks).toEqual(["comment-checker"]);
});
});
@@ -150,8 +150,8 @@ describe("parseConfigPartially", () => {
it("should preserve valid agent overrides when another section is invalid", () => {
const rawConfig = {
agents: {
oracle: { model: "openai/gpt-5.2" },
momus: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
momus: { model: "openai/gpt-5.4" },
prometheus: {
permission: {
edit: { "*": "ask", ".sisyphus/**": "allow" },
@@ -171,7 +171,7 @@ describe("parseConfigPartially", () => {
it("should preserve valid agents when a non-agent section is invalid", () => {
const rawConfig = {
agents: {
oracle: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
},
disabled_hooks: ["not-a-real-hook"],
};
@@ -179,7 +179,7 @@ describe("parseConfigPartially", () => {
const result = parseConfigPartially(rawConfig);
expect(result).not.toBeNull();
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.2");
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.4");
expect(result!.disabled_hooks).toEqual(["not-a-real-hook"]);
});
});
@@ -224,7 +224,7 @@ describe("parseConfigPartially", () => {
it("should ignore unknown keys and return valid sections", () => {
const rawConfig = {
agents: {
oracle: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
},
some_future_key: { foo: "bar" },
};
@@ -232,7 +232,7 @@ describe("parseConfigPartially", () => {
const result = parseConfigPartially(rawConfig);
expect(result).not.toBeNull();
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.2");
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.4");
expect((result as Record<string, unknown>)["some_future_key"]).toBeUndefined();
});
});
+9 -9
View File
@@ -656,7 +656,7 @@ describe("Prometheus direct override priority over category", () => {
},
categories: {
"test-planning": {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
reasoningEffort: "xhigh",
},
},
@@ -698,7 +698,7 @@ describe("Prometheus direct override priority over category", () => {
},
categories: {
"reasoning-cat": {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
reasoningEffort: "high",
},
},
@@ -739,7 +739,7 @@ describe("Prometheus direct override priority over category", () => {
},
categories: {
"temp-cat": {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
temperature: 0.8,
},
},
@@ -860,7 +860,7 @@ describe("Plan agent model inheritance from prometheus", () => {
test("plan agent inherits temperature, reasoningEffort, and other model settings from prometheus", async () => {
//#given - prometheus configured with category that has temperature and reasoningEffort
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
provenance: "override",
variant: "high",
})
@@ -871,7 +871,7 @@ describe("Plan agent model inheritance from prometheus", () => {
},
agents: {
prometheus: {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
variant: "high",
temperature: 0.3,
top_p: 0.9,
@@ -902,7 +902,7 @@ describe("Plan agent model inheritance from prometheus", () => {
const agents = config.agent as Record<string, Record<string, unknown>>
expect(agents.plan).toBeDefined()
expect(agents.plan.mode).toBe("subagent")
expect(agents.plan.model).toBe("openai/gpt-5.2")
expect(agents.plan.model).toBe("openai/gpt-5.4")
expect(agents.plan.variant).toBe("high")
expect(agents.plan.temperature).toBe(0.3)
expect(agents.plan.top_p).toBe(0.9)
@@ -913,7 +913,7 @@ describe("Plan agent model inheritance from prometheus", () => {
})
test("plan agent user override takes priority over prometheus inherited settings", async () => {
//#given - prometheus resolves to opus, but user has plan override for gpt-5.2
//#given - prometheus resolves to opus, but user has plan override for gpt-5.4
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
model: "anthropic/claude-opus-4-6",
provenance: "provider-fallback",
@@ -926,7 +926,7 @@ describe("Plan agent model inheritance from prometheus", () => {
},
agents: {
plan: {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
variant: "high",
temperature: 0.5,
},
@@ -950,7 +950,7 @@ describe("Plan agent model inheritance from prometheus", () => {
//#then - plan uses its own override, not prometheus settings
const agents = config.agent as Record<string, Record<string, unknown>>
expect(agents.plan.model).toBe("openai/gpt-5.2")
expect(agents.plan.model).toBe("openai/gpt-5.4")
expect(agents.plan.variant).toBe("high")
expect(agents.plan.temperature).toBe(0.5)
})
@@ -64,7 +64,7 @@ describe("buildPlanDemoteConfig", () => {
reasoningEffort: "high",
}
const planOverride = {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
variant: "high",
temperature: 0.5,
reasoningEffort: "low",
@@ -74,7 +74,7 @@ describe("buildPlanDemoteConfig", () => {
const result = buildPlanDemoteConfig(prometheusConfig, planOverride)
//#then
expect(result.model).toBe("openai/gpt-5.2")
expect(result.model).toBe("openai/gpt-5.4")
expect(result.variant).toBe("high")
expect(result.temperature).toBe(0.5)
expect(result.reasoningEffort).toBe("low")
@@ -89,14 +89,14 @@ describe("buildPlanDemoteConfig", () => {
reasoningEffort: "high",
}
const planOverride = {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
}
//#when
const result = buildPlanDemoteConfig(prometheusConfig, planOverride)
//#then - plan model wins, rest inherits from prometheus
expect(result.model).toBe("openai/gpt-5.2")
expect(result.model).toBe("openai/gpt-5.4")
expect(result.variant).toBe("max")
expect(result.temperature).toBe(0.1)
expect(result.reasoningEffort).toBe("high")
+2 -2
View File
@@ -45,7 +45,7 @@ describe("Agent Config Integration", () => {
// given - config with lowercase keys
const config = {
sisyphus: { model: "anthropic/claude-opus-4-6" },
oracle: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
librarian: { model: "opencode/big-pickle" },
}
@@ -63,7 +63,7 @@ describe("Agent Config Integration", () => {
// given - config with mixed old and new format
const mixedConfig = {
Sisyphus: { model: "anthropic/claude-opus-4-6" },
oracle: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
"Prometheus (Planner)": { model: "anthropic/claude-opus-4-6" },
librarian: { model: "opencode/big-pickle" },
}
+3 -3
View File
@@ -36,7 +36,7 @@ describe("resolveAgentVariant", () => {
sisyphus: { category: "ultrabrain" },
},
categories: {
ultrabrain: { model: "openai/gpt-5.2", variant: "xhigh" },
ultrabrain: { model: "openai/gpt-5.4", variant: "xhigh" },
},
} as OhMyOpenCodeConfig
@@ -127,7 +127,7 @@ describe("resolveVariantForModel", () => {
test("returns undefined for provider not in sisyphus chain", () => {
// #given openai is not in sisyphus fallback chain anymore
const config = {} as OhMyOpenCodeConfig
const model = { providerID: "openai", modelID: "gpt-5.2" }
const model = { providerID: "openai", modelID: "gpt-5.4" }
// when
const variant = resolveVariantForModel(config, "sisyphus", model)
@@ -191,7 +191,7 @@ describe("resolveVariantForModel", () => {
test("returns correct variant for oracle agent with openai", () => {
// given
const config = {} as OhMyOpenCodeConfig
const model = { providerID: "openai", modelID: "gpt-5.2" }
const model = { providerID: "openai", modelID: "gpt-5.4" }
// when
const variant = resolveVariantForModel(config, "oracle", model)
+2 -2
View File
@@ -38,7 +38,7 @@ describe("updateConnectedProvidersCache", () => {
env: [],
models: {
"gpt-5.3-codex": { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
"gpt-5.2": { id: "gpt-5.2", name: "GPT-5.2" },
"gpt-5.4": { id: "gpt-5.4", name: "GPT-5.4" },
},
},
{
@@ -64,7 +64,7 @@ describe("updateConnectedProvidersCache", () => {
expect(cache).not.toBeNull()
expect(cache!.connected).toEqual(["openai", "anthropic"])
expect(cache!.models).toEqual({
openai: ["gpt-5.3-codex", "gpt-5.2"],
openai: ["gpt-5.3-codex", "gpt-5.4"],
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6"],
})
})
+2 -2
View File
@@ -105,7 +105,7 @@ describe("parseJsonc", () => {
const jsonc = `{
// This is an example config
"agents": {
"oracle": { "model": "openai/gpt-5.2" }, // GPT for strategic reasoning
"oracle": { "model": "openai/gpt-5.4" }, // GPT for strategic reasoning
},
/* Agent overrides */
"disabled_agents": [],
@@ -118,7 +118,7 @@ describe("parseJsonc", () => {
}>(jsonc)
// then
expect(result.agents.oracle.model).toBe("openai/gpt-5.2")
expect(result.agents.oracle.model).toBe("openai/gpt-5.4")
expect(result.disabled_agents).toEqual([])
})
+3 -3
View File
@@ -44,7 +44,7 @@ describe("mergeCategories", () => {
it("allows user to add custom categories", () => {
//#given
const userCategories = {
"my-custom": { model: "openai/gpt-5.2", description: "Custom category" },
"my-custom": { model: "openai/gpt-5.4", description: "Custom category" },
}
//#when
@@ -52,13 +52,13 @@ describe("mergeCategories", () => {
//#then
expect(result["my-custom"]).toBeDefined()
expect(result["my-custom"].model).toBe("openai/gpt-5.2")
expect(result["my-custom"].model).toBe("openai/gpt-5.4")
})
it("allows user to disable custom categories", () => {
//#given
const userCategories = {
"my-custom": { model: "openai/gpt-5.2", disable: true },
"my-custom": { model: "openai/gpt-5.4", disable: true },
}
//#when
+68 -68
View File
@@ -37,7 +37,7 @@ describe("migrateAgentNames", () => {
test("preserves current agent names unchanged", () => {
// given: Config with current agent names
const agents = {
oracle: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
librarian: { model: "google/gemini-3-flash" },
explore: { model: "opencode/gpt-5-nano" },
}
@@ -47,7 +47,7 @@ describe("migrateAgentNames", () => {
// then: Current names should remain unchanged
expect(changed).toBe(false)
expect(migrated["oracle"]).toEqual({ model: "openai/gpt-5.2" })
expect(migrated["oracle"]).toEqual({ model: "openai/gpt-5.4" })
expect(migrated["librarian"]).toEqual({ model: "google/gemini-3-flash" })
expect(migrated["explore"]).toEqual({ model: "opencode/gpt-5-nano" })
})
@@ -57,7 +57,7 @@ describe("migrateAgentNames", () => {
const agents = {
SISYPHUS: { model: "test" },
"planner-sisyphus": { prompt: "test" },
"Orchestrator-Sisyphus": { model: "openai/gpt-5.2" },
"Orchestrator-Sisyphus": { model: "openai/gpt-5.4" },
}
// when: Migrate agent names
@@ -66,7 +66,7 @@ describe("migrateAgentNames", () => {
// then: Case-insensitive lookup should migrate correctly
expect(migrated["sisyphus"]).toEqual({ model: "test" })
expect(migrated["prometheus"]).toEqual({ prompt: "test" })
expect(migrated["atlas"]).toEqual({ model: "openai/gpt-5.2" })
expect(migrated["atlas"]).toEqual({ model: "openai/gpt-5.4" })
})
test("passes through unknown agent names unchanged", () => {
@@ -441,11 +441,11 @@ describe("migrateConfigFile", () => {
expect(rawConfig.disabled_hooks).toContain("anthropic-context-window-limit-recovery")
})
test("does not migrate gpt-5.2-codex model versions in agents", () => {
test("does not migrate gpt-5.4-codex model versions in agents", () => {
// given: Config with old model version in agents
const rawConfig: Record<string, unknown> = {
agents: {
sisyphus: { model: "openai/gpt-5.2-codex", temperature: 0.1 },
sisyphus: { model: "openai/gpt-5.4-codex", temperature: 0.1 },
},
}
@@ -455,7 +455,7 @@ describe("migrateConfigFile", () => {
// then: Model version should remain unchanged
expect(needsWrite).toBe(false)
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
expect(agents["sisyphus"].model).toBe("openai/gpt-5.2-codex")
expect(agents["sisyphus"].model).toBe("openai/gpt-5.4-codex")
})
test("migrates model versions in categories", () => {
@@ -479,7 +479,7 @@ describe("migrateConfigFile", () => {
// given: Config with current model versions
const rawConfig: Record<string, unknown> = {
agents: {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
},
categories: {
"my-category": { model: "anthropic/claude-opus-4-6" },
@@ -514,10 +514,10 @@ describe("migration maps", () => {
})
describe("MODEL_VERSION_MAP", () => {
test("does not include openai/gpt-5.2-codex migration", () => {
test("does not include openai/gpt-5.4-codex migration", () => {
// given/when: Check MODEL_VERSION_MAP
// then: openai/gpt-5.2-codex should not be migrated
expect(MODEL_VERSION_MAP["openai/gpt-5.2-codex"]).toBeUndefined()
// then: openai/gpt-5.4-codex should not be migrated
expect(MODEL_VERSION_MAP["openai/gpt-5.4-codex"]).toBeUndefined()
})
test("maps anthropic/claude-opus-4-5 to anthropic/claude-opus-4-6", () => {
@@ -528,10 +528,10 @@ describe("MODEL_VERSION_MAP", () => {
})
describe("migrateModelVersions", () => {
test("#given a config with gpt-5.2-codex model #when migrating model versions #then does not overwrite with non-existent gpt-5.3-codex", () => {
// given: Agent config with gpt-5.2-codex model
test("#given a config with gpt-5.4-codex model #when migrating model versions #then does not overwrite with non-existent gpt-5.3-codex", () => {
// given: Agent config with gpt-5.4-codex model
const agents = {
sisyphus: { model: "openai/gpt-5.2-codex", temperature: 0.1 },
sisyphus: { model: "openai/gpt-5.4-codex", temperature: 0.1 },
}
// when: Migrate model versions
@@ -540,7 +540,7 @@ describe("migrateModelVersions", () => {
// then: Model should remain unchanged
expect(changed).toBe(false)
const sisyphus = migrated["sisyphus"] as Record<string, unknown>
expect(sisyphus.model).toBe("openai/gpt-5.2-codex")
expect(sisyphus.model).toBe("openai/gpt-5.4-codex")
expect(sisyphus.temperature).toBe(0.1)
})
@@ -562,7 +562,7 @@ describe("migrateModelVersions", () => {
test("leaves unknown model strings untouched", () => {
// given: Agent config with unknown model
const agents = {
oracle: { model: "openai/gpt-5.2", temperature: 0.5 },
oracle: { model: "openai/gpt-5.4", temperature: 0.5 },
}
// when: Migrate model versions
@@ -571,7 +571,7 @@ describe("migrateModelVersions", () => {
// then: Config should remain unchanged
expect(changed).toBe(false)
const oracle = migrated["oracle"] as Record<string, unknown>
expect(oracle.model).toBe("openai/gpt-5.2")
expect(oracle.model).toBe("openai/gpt-5.4")
})
test("handles agent config with no model field", () => {
@@ -605,9 +605,9 @@ describe("migrateModelVersions", () => {
test("migrates multiple agents in one pass", () => {
// given: Multiple agents with old models
const agents = {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
prometheus: { model: "anthropic/claude-opus-4-5" },
oracle: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
}
// when: Migrate model versions
@@ -615,9 +615,9 @@ describe("migrateModelVersions", () => {
// then: Only mapped models should be updated
expect(changed).toBe(true)
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
expect((migrated["prometheus"] as Record<string, unknown>).model).toBe("anthropic/claude-opus-4-6")
expect((migrated["oracle"] as Record<string, unknown>).model).toBe("openai/gpt-5.2")
expect((migrated["oracle"] as Record<string, unknown>).model).toBe("openai/gpt-5.4")
})
test("handles empty object", () => {
@@ -635,9 +635,9 @@ describe("migrateModelVersions", () => {
test("skips already-applied migrations", () => {
// given: Agent config with old model, but migration already applied
const agents = {
sisyphus: { model: "openai/gpt-5.2-codex", temperature: 0.1 },
sisyphus: { model: "openai/gpt-5.4-codex", temperature: 0.1 },
}
const appliedMigrations = new Set(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
const appliedMigrations = new Set(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
// when: Migrate with applied migrations
const { migrated, changed, newMigrations } = migrateModelVersions(agents, appliedMigrations)
@@ -646,32 +646,32 @@ describe("migrateModelVersions", () => {
expect(changed).toBe(false)
expect(newMigrations).toHaveLength(0)
const sisyphus = migrated["sisyphus"] as Record<string, unknown>
expect(sisyphus.model).toBe("openai/gpt-5.2-codex")
expect(sisyphus.model).toBe("openai/gpt-5.4-codex")
})
test("applies new migrations and records them", () => {
// given: Agent config with old model, no prior migrations
const agents = {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
}
// when: Migrate without applied migrations
const { migrated, changed, newMigrations } = migrateModelVersions(agents)
// then: No migration should be applied for gpt-5.2-codex
// then: No migration should be applied for gpt-5.4-codex
expect(changed).toBe(false)
expect(newMigrations).toEqual([])
const sisyphus = migrated["sisyphus"] as Record<string, unknown>
expect(sisyphus.model).toBe("openai/gpt-5.2-codex")
expect(sisyphus.model).toBe("openai/gpt-5.4-codex")
})
test("handles mixed: some applied, some new", () => {
// given: Multiple agents, one migration already applied
const agents = {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
prometheus: { model: "anthropic/claude-opus-4-5" },
}
const appliedMigrations = new Set(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
const appliedMigrations = new Set(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
// when: Migrate with partial history
const { migrated, changed, newMigrations } = migrateModelVersions(agents, appliedMigrations)
@@ -679,23 +679,23 @@ describe("migrateModelVersions", () => {
// then: Only prometheus should be migrated
expect(changed).toBe(true)
expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"])
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
expect((migrated["prometheus"] as Record<string, unknown>).model).toBe("anthropic/claude-opus-4-6")
})
test("backward compatible without appliedMigrations param", () => {
// given: Agent config with old model, no appliedMigrations param
const agents = {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
}
// when: Migrate without the param (backward compat)
const { migrated, changed, newMigrations } = migrateModelVersions(agents)
// then: Should keep gpt-5.2-codex unchanged
// then: Should keep gpt-5.4-codex unchanged
expect(changed).toBe(false)
expect(newMigrations).toHaveLength(0)
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
})
})
@@ -706,14 +706,14 @@ describe("migrateConfigFile _migrations tracking", () => {
const configPath = `${tmpDir}/oh-my-opencode.json`
const rawConfig: Record<string, unknown> = {
agents: {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
},
}
// when: Migrate config file
const result = migrateConfigFile(configPath, rawConfig)
// then: gpt-5.2-codex should not produce migrations
// then: gpt-5.4-codex should not produce migrations
expect(result).toBe(false)
expect(rawConfig._migrations).toBeUndefined()
@@ -727,9 +727,9 @@ describe("migrateConfigFile _migrations tracking", () => {
const configPath = `${tmpDir}/oh-my-opencode.json`
const rawConfig: Record<string, unknown> = {
agents: {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
},
_migrations: ["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"],
_migrations: ["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"],
}
// when: Migrate config file
@@ -738,7 +738,7 @@ describe("migrateConfigFile _migrations tracking", () => {
// then: Should NOT rewrite (model stays as user set it)
// Note: result may be true due to other migrations, but model should NOT change
const sisyphus = (rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus
expect(sisyphus.model).toBe("openai/gpt-5.2-codex")
expect(sisyphus.model).toBe("openai/gpt-5.4-codex")
// cleanup
fs.rmSync(tmpDir, { recursive: true })
@@ -752,7 +752,7 @@ describe("migrateConfigFile _migrations tracking", () => {
agents: {
prometheus: { model: "anthropic/claude-opus-4-5" },
},
_migrations: ["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"],
_migrations: ["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"],
}
// when: Migrate config file
@@ -761,7 +761,7 @@ describe("migrateConfigFile _migrations tracking", () => {
// then: New migration appended, old one preserved
expect(result).toBe(true)
expect(rawConfig._migrations).toEqual([
"model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex",
"model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex",
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6",
])
@@ -825,7 +825,7 @@ describe("migrateAgentConfigToCategory", () => {
const configs = [
{ model: "google/gemini-3.1-pro" },
{ model: "google/gemini-3-flash" },
{ model: "openai/gpt-5.2" },
{ model: "openai/gpt-5.4" },
{ model: "anthropic/claude-haiku-4-5" },
{ model: "anthropic/claude-opus-4-6" },
{ model: "anthropic/claude-sonnet-4-6" },
@@ -847,7 +847,7 @@ describe("migrateAgentConfigToCategory", () => {
test("preserves non-model fields during migration", () => {
// given: Config with multiple fields
const config = {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
temperature: 0.1,
top_p: 0.95,
maxTokens: 4096,
@@ -1020,7 +1020,7 @@ describe("migrateConfigFile with backup", () => {
const rawConfig: Record<string, unknown> = {
agents: {
"multimodal-looker": { model: "anthropic/claude-haiku-4-5" },
oracle: { model: "openai/gpt-5.2" },
oracle: { model: "openai/gpt-5.4" },
"my-custom-agent": { model: "google/gemini-3.1-pro" },
},
}
@@ -1036,7 +1036,7 @@ describe("migrateConfigFile with backup", () => {
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
expect(agents["multimodal-looker"].model).toBe("anthropic/claude-haiku-4-5")
expect(agents.oracle.model).toBe("openai/gpt-5.2")
expect(agents.oracle.model).toBe("openai/gpt-5.4")
expect(agents["my-custom-agent"].model).toBe("google/gemini-3.1-pro")
})
@@ -1140,9 +1140,9 @@ describe("migrateModelVersions with applied migrations", () => {
test("skips already-applied migrations", () => {
// given: Config with old model and migration already applied
const configs = {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
}
const appliedMigrations = new Set(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
const appliedMigrations = new Set(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
// when: Migrate model versions
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
@@ -1150,32 +1150,32 @@ describe("migrateModelVersions with applied migrations", () => {
// then: Migration should be skipped (user reverted)
expect(changed).toBe(false)
expect(newMigrations).toEqual([])
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
})
test("applies new migrations not in history", () => {
// given: Config with old model, no migration history
const configs = {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
}
const appliedMigrations = new Set<string>()
// when: Migrate model versions
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
// then: gpt-5.2-codex should not be migrated
// then: gpt-5.4-codex should not be migrated
expect(changed).toBe(false)
expect(newMigrations).toEqual([])
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
})
test("handles mixed: skip applied, apply new", () => {
// given: Config with 2 old models, 1 already migrated
const configs = {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
oracle: { model: "anthropic/claude-opus-4-5" },
}
const appliedMigrations = new Set(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
const appliedMigrations = new Set(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
// when: Migrate model versions
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
@@ -1183,29 +1183,29 @@ describe("migrateModelVersions with applied migrations", () => {
// then: Skip sisyphus (already applied), apply oracle
expect(changed).toBe(true)
expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"])
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
expect((migrated.oracle as Record<string, unknown>).model).toBe("anthropic/claude-opus-4-6")
})
test("backward compatible: no appliedMigrations param", () => {
// given: Config with old model, no appliedMigrations param (legacy call)
const configs = {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
}
// when: Migrate model versions (without appliedMigrations)
const { migrated, changed, newMigrations } = migrateModelVersions(configs)
// then: gpt-5.2-codex remains unchanged
// then: gpt-5.4-codex remains unchanged
expect(changed).toBe(false)
expect(newMigrations).toEqual([])
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
})
test("returns empty newMigrations when no migrations applied", () => {
// given: Config with no old models
const configs = {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
}
// when: Migrate model versions
@@ -1235,7 +1235,7 @@ describe("migrateConfigFile with _migrations tracking", () => {
const testConfigPath = "/tmp/test-config-migrations-1.json"
const rawConfig: Record<string, unknown> = {
agents: {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
},
}
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
@@ -1244,10 +1244,10 @@ describe("migrateConfigFile with _migrations tracking", () => {
// when: Migrate config file
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
// then: gpt-5.2-codex should not create migration history
// then: gpt-5.4-codex should not create migration history
expect(needsWrite).toBe(false)
expect(rawConfig._migrations).toBeUndefined()
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.2-codex")
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.4-codex")
})
test("skips re-applying already-recorded migrations", () => {
@@ -1255,9 +1255,9 @@ describe("migrateConfigFile with _migrations tracking", () => {
const testConfigPath = "/tmp/test-config-migrations-2.json"
const rawConfig: Record<string, unknown> = {
agents: {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
},
_migrations: ["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"],
_migrations: ["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"],
}
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
cleanupPaths.push(testConfigPath)
@@ -1267,8 +1267,8 @@ describe("migrateConfigFile with _migrations tracking", () => {
// then: Should not migrate (user reverted)
expect(needsWrite).toBe(false)
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.2-codex")
expect(rawConfig._migrations).toEqual(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.4-codex")
expect(rawConfig._migrations).toEqual(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
})
test("preserves existing _migrations and appends new ones", () => {
@@ -1276,10 +1276,10 @@ describe("migrateConfigFile with _migrations tracking", () => {
const testConfigPath = "/tmp/test-config-migrations-3.json"
const rawConfig: Record<string, unknown> = {
agents: {
sisyphus: { model: "openai/gpt-5.2-codex" },
sisyphus: { model: "openai/gpt-5.4-codex" },
oracle: { model: "anthropic/claude-opus-4-5" },
},
_migrations: ["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"],
_migrations: ["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"],
}
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
cleanupPaths.push(testConfigPath)
@@ -1289,10 +1289,10 @@ describe("migrateConfigFile with _migrations tracking", () => {
// then: Should skip sisyphus, migrate oracle, append to _migrations
expect(needsWrite).toBe(true)
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.2-codex")
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.4-codex")
expect((rawConfig.agents as Record<string, Record<string, unknown>>).oracle.model).toBe("anthropic/claude-opus-4-6")
expect(rawConfig._migrations).toEqual([
"model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex",
"model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex",
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6",
])
})
+1 -1
View File
@@ -14,7 +14,7 @@
export const MODEL_TO_CATEGORY_MAP: Record<string, string> = {
"google/gemini-3.1-pro": "visual-engineering",
"google/gemini-3-flash": "writing",
"openai/gpt-5.2": "ultrabrain",
"openai/gpt-5.4": "ultrabrain",
"anthropic/claude-haiku-4-5": "quick",
"anthropic/claude-opus-4-6": "unspecified-high",
"anthropic/claude-sonnet-4-6": "unspecified-low",
+52 -52
View File
@@ -61,7 +61,7 @@ describe("fetchAvailableModels", () => {
it("#given cache file with models #when fetchAvailableModels called with connectedProviders #then returns Set of model IDs", async () => {
writeModelsCache({
openai: { id: "openai", models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { id: "openai", models: { "gpt-5.4": { id: "gpt-5.4" } } },
anthropic: { id: "anthropic", models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
google: { id: "google", models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
})
@@ -72,14 +72,14 @@ describe("fetchAvailableModels", () => {
expect(result).toBeInstanceOf(Set)
expect(result.size).toBe(3)
expect(result.has("openai/gpt-5.2")).toBe(true)
expect(result.has("openai/gpt-5.4")).toBe(true)
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
expect(result.has("google/gemini-3.1-pro")).toBe(true)
})
it("#given connectedProviders unknown #when fetchAvailableModels called without options #then returns empty Set", async () => {
writeModelsCache({
openai: { id: "openai", models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { id: "openai", models: { "gpt-5.4": { id: "gpt-5.4" } } },
})
const result = await fetchAvailableModels()
@@ -141,7 +141,7 @@ describe("fetchAvailableModels", () => {
it("#given cache read twice #when second call made with same providers #then reads fresh each time", async () => {
writeModelsCache({
openai: { id: "openai", models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { id: "openai", models: { "gpt-5.4": { id: "gpt-5.4" } } },
anthropic: { id: "anthropic", models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
})
@@ -149,7 +149,7 @@ describe("fetchAvailableModels", () => {
const result2 = await fetchAvailableModels(undefined, { connectedProviders: ["openai"] })
expect(result1.size).toBe(result2.size)
expect(result1.has("openai/gpt-5.2")).toBe(true)
expect(result1.has("openai/gpt-5.4")).toBe(true)
})
it("#given empty providers in cache #when fetchAvailableModels called with connectedProviders #then returns empty Set", async () => {
@@ -187,12 +187,12 @@ describe("fuzzyMatchModel", () => {
// then return the matching model
it("should match substring in model name", () => {
const available = new Set([
"openai/gpt-5.2",
"openai/gpt-5.4",
"openai/gpt-5.3-codex",
"anthropic/claude-opus-4-6",
])
const result = fuzzyMatchModel("gpt-5.2", available)
expect(result).toBe("openai/gpt-5.2")
const result = fuzzyMatchModel("gpt-5.4", available)
expect(result).toBe("openai/gpt-5.4")
})
// given available model with preview suffix
@@ -213,12 +213,12 @@ describe("fuzzyMatchModel", () => {
// then return exact match if it exists
it("should prefer exact match over substring match", () => {
const available = new Set([
"openai/gpt-5.2",
"openai/gpt-5.4",
"openai/gpt-5.3-codex",
"openai/gpt-5.2-ultra",
"openai/gpt-5.4-ultra",
])
const result = fuzzyMatchModel("gpt-5.2", available)
expect(result).toBe("openai/gpt-5.2")
const result = fuzzyMatchModel("gpt-5.4", available)
expect(result).toBe("openai/gpt-5.4")
})
// given available models with multiple substring matches
@@ -226,11 +226,11 @@ describe("fuzzyMatchModel", () => {
// then return the shorter model name (more specific)
it("should prefer shorter model name when multiple matches exist", () => {
const available = new Set([
"openai/gpt-5.2-ultra",
"openai/gpt-5.2-ultra-mega",
"openai/gpt-5.4-ultra",
"openai/gpt-5.4-ultra-mega",
])
const result = fuzzyMatchModel("gpt-5.2", available)
expect(result).toBe("openai/gpt-5.2-ultra")
const result = fuzzyMatchModel("gpt-5.4", available)
expect(result).toBe("openai/gpt-5.4-ultra")
})
// given available models with claude variants
@@ -271,12 +271,12 @@ describe("fuzzyMatchModel", () => {
// then only search models from specified providers
it("should filter by provider when providers array is given", () => {
const available = new Set([
"openai/gpt-5.2",
"openai/gpt-5.4",
"anthropic/claude-opus-4-6",
"google/gemini-3",
])
const result = fuzzyMatchModel("gpt", available, ["openai"])
expect(result).toBe("openai/gpt-5.2")
expect(result).toBe("openai/gpt-5.4")
})
// given available models from multiple providers
@@ -284,7 +284,7 @@ describe("fuzzyMatchModel", () => {
// then return null
it("should return null when provider filter excludes all matches", () => {
const available = new Set([
"openai/gpt-5.2",
"openai/gpt-5.4",
"anthropic/claude-opus-4-6",
])
const result = fuzzyMatchModel("claude", available, ["openai"])
@@ -296,7 +296,7 @@ describe("fuzzyMatchModel", () => {
// then return null
it("should return null when no match found", () => {
const available = new Set([
"openai/gpt-5.2",
"openai/gpt-5.4",
"anthropic/claude-opus-4-6",
])
const result = fuzzyMatchModel("gemini", available)
@@ -308,11 +308,11 @@ describe("fuzzyMatchModel", () => {
// then match case-insensitively
it("should match case-insensitively", () => {
const available = new Set([
"openai/gpt-5.2",
"openai/gpt-5.4",
"anthropic/claude-opus-4-6",
])
const result = fuzzyMatchModel("GPT-5.2", available)
expect(result).toBe("openai/gpt-5.2")
const result = fuzzyMatchModel("GPT-5.4", available)
expect(result).toBe("openai/gpt-5.4")
})
// given available models with exact match and longer variants
@@ -356,11 +356,11 @@ describe("fuzzyMatchModel", () => {
// then return shortest full string (preserves tie-break behavior)
it("should use shortest tie-break when multiple providers have same model ID", () => {
const available = new Set([
"opencode/gpt-5.2",
"openai/gpt-5.2",
"opencode/gpt-5.4",
"openai/gpt-5.4",
])
const result = fuzzyMatchModel("gpt-5.2", available)
expect(result).toBe("openai/gpt-5.2")
const result = fuzzyMatchModel("gpt-5.4", available)
expect(result).toBe("openai/gpt-5.4")
})
// given available models with multiple providers
@@ -368,12 +368,12 @@ describe("fuzzyMatchModel", () => {
// then search all specified providers
it("should search all specified providers", () => {
const available = new Set([
"openai/gpt-5.2",
"openai/gpt-5.4",
"anthropic/claude-opus-4-6",
"google/gemini-3",
])
const result = fuzzyMatchModel("gpt", available, ["openai", "google"])
expect(result).toBe("openai/gpt-5.2")
expect(result).toBe("openai/gpt-5.4")
})
// given available models with provider prefix
@@ -381,11 +381,11 @@ describe("fuzzyMatchModel", () => {
// then only match models with correct provider prefix
it("should only match models with correct provider prefix", () => {
const available = new Set([
"openai/gpt-5.2",
"openai/gpt-5.4",
"anthropic/gpt-something",
])
const result = fuzzyMatchModel("gpt", available, ["openai"])
expect(result).toBe("openai/gpt-5.2")
expect(result).toBe("openai/gpt-5.4")
})
// given empty available set
@@ -513,7 +513,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
// then only returns models from that provider
it("should filter models by connected providers", async () => {
writeModelsCache({
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
})
@@ -524,7 +524,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
expect(result.size).toBe(1)
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
expect(result.has("openai/gpt-5.2")).toBe(false)
expect(result.has("openai/gpt-5.4")).toBe(false)
expect(result.has("google/gemini-3.1-pro")).toBe(false)
})
@@ -533,7 +533,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
// then returns models from all specified providers
it("should filter models by multiple connected providers", async () => {
writeModelsCache({
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
})
@@ -545,7 +545,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
expect(result.size).toBe(2)
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
expect(result.has("google/gemini-3.1-pro")).toBe(true)
expect(result.has("openai/gpt-5.2")).toBe(false)
expect(result.has("openai/gpt-5.4")).toBe(false)
})
// given cache with models
@@ -553,7 +553,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
// then returns empty set
it("should return empty set when connectedProviders is empty", async () => {
writeModelsCache({
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
})
@@ -569,7 +569,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
// then returns empty set (triggers fallback in resolver)
it("should return empty set when connectedProviders not specified", async () => {
writeModelsCache({
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
})
@@ -583,7 +583,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
// then returns empty set for that provider
it("should handle provider not in cache gracefully", async () => {
writeModelsCache({
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
})
const result = await fetchAvailableModels(undefined, {
@@ -598,7 +598,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
// then returns models only from matching providers
it("should return models from providers that exist in both cache and connected list", async () => {
writeModelsCache({
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
})
@@ -615,7 +615,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
// then does NOT use cache (dynamic per-session)
it("should not cache filtered results", async () => {
writeModelsCache({
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
})
@@ -630,7 +630,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
connectedProviders: ["openai"]
})
expect(result2.size).toBe(1)
expect(result2.has("openai/gpt-5.2")).toBe(true)
expect(result2.has("openai/gpt-5.4")).toBe(true)
})
// given connectedProviders unknown
@@ -638,7 +638,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
// then always returns empty set (triggers fallback)
it("should return empty set when connectedProviders unknown", async () => {
writeModelsCache({
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
})
const result1 = await fetchAvailableModels()
@@ -696,7 +696,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
connected: ["opencode", "anthropic"]
})
writeModelsCache({
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.2": {} } },
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.4": {} } },
anthropic: { models: { "claude-opus-4-6": {}, "claude-sonnet-4-6": {} } }
})
@@ -708,7 +708,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
expect(result.has("opencode/big-pickle")).toBe(true)
expect(result.has("opencode/gpt-5-nano")).toBe(true)
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
expect(result.has("opencode/gpt-5.2")).toBe(false)
expect(result.has("opencode/gpt-5.4")).toBe(false)
expect(result.has("anthropic/claude-sonnet-4-6")).toBe(false)
})
@@ -738,7 +738,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
// then falls back to models.json (no whitelist filtering)
it("should fallback to models.json when provider-models cache not found", async () => {
writeModelsCache({
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.2": {} } },
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.4": {} } },
})
const result = await fetchAvailableModels(undefined, {
@@ -748,7 +748,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
expect(result.size).toBe(3)
expect(result.has("opencode/big-pickle")).toBe(true)
expect(result.has("opencode/gpt-5-nano")).toBe(true)
expect(result.has("opencode/gpt-5.2")).toBe(true)
expect(result.has("opencode/gpt-5.4")).toBe(true)
})
// given provider-models cache with whitelist
@@ -907,7 +907,7 @@ describe("fallback model availability", () => {
it("returns null for completely unknown model", () => {
// given
const available = new Set(["openai/gpt-5.2", "anthropic/claude-opus-4-6"])
const available = new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6"])
// when
const result = fuzzyMatchModel("non-existent-model-family", available)
@@ -918,7 +918,7 @@ describe("fallback model availability", () => {
it("returns true when models do not match but provider is connected", () => {
// given
const fallbackChain = [{ providers: ["openai"], model: "gpt-5.2" }]
const fallbackChain = [{ providers: ["openai"], model: "gpt-5.4" }]
const availableModels = new Set(["anthropic/claude-opus-4-6"])
writeConnectedProvidersCache(["openai"])
@@ -932,25 +932,25 @@ describe("fallback model availability", () => {
it("returns first resolved fallback model from chain", () => {
// given
const fallbackChain = [
{ providers: ["openai"], model: "gpt-5.2" },
{ providers: ["openai"], model: "gpt-5.4" },
{ providers: ["anthropic"], model: "claude-opus-4-6" },
]
const availableModels = new Set([
"anthropic/claude-opus-4-6",
"openai/gpt-5.2-preview",
"openai/gpt-5.4-preview",
])
// when
const result = resolveFirstAvailableFallback(fallbackChain, availableModels)
// then
expect(result).toEqual({ provider: "openai", model: "openai/gpt-5.2-preview" })
expect(result).toEqual({ provider: "openai", model: "openai/gpt-5.4-preview" })
})
it("returns null when no fallback model resolves", () => {
// given
const fallbackChain = [
{ providers: ["openai"], model: "gpt-5.2" },
{ providers: ["openai"], model: "gpt-5.4" },
{ providers: ["anthropic"], model: "claude-opus-4-6" },
]
const availableModels = new Set(["google/gemini-3.1-pro"])
+4 -4
View File
@@ -8,7 +8,7 @@ import { normalizeSDKResponse } from "./normalize-sdk-response"
/**
* Fuzzy match a target model name against available models
*
* @param target - The model name or substring to search for (e.g., "gpt-5.2", "claude-opus")
* @param target - The model name or substring to search for (e.g., "gpt-5.4", "claude-opus")
* @param available - Set of available model names in format "provider/model-name"
* @param providers - Optional array of provider names to filter by (e.g., ["openai", "anthropic"])
* @returns The matched model name or null if no match found
@@ -21,8 +21,8 @@ import { normalizeSDKResponse } from "./normalize-sdk-response"
* If providers array is given, only models starting with "provider/" are considered.
*
* @example
* const available = new Set(["openai/gpt-5.2", "openai/gpt-5.3-codex", "anthropic/claude-opus-4-6"])
* fuzzyMatchModel("gpt-5.2", available) // → "openai/gpt-5.2"
* const available = new Set(["openai/gpt-5.4", "openai/gpt-5.3-codex", "anthropic/claude-opus-4-6"])
* fuzzyMatchModel("gpt-5.4", available) // → "openai/gpt-5.4"
* fuzzyMatchModel("claude", available, ["openai"]) // → null (provider filter excludes anthropic)
*/
function normalizeModelName(name: string): string {
@@ -82,7 +82,7 @@ export function fuzzyMatchModel(
// Priority 2: Exact model ID match (part after provider/)
// This ensures "big-pickle" matches "zai-coding-plan/big-pickle" over "zai-coding-plan/glm-5"
// Use filter + shortest to handle multi-provider cases (e.g., openai/gpt-5.2 + opencode/gpt-5.2)
// Use filter + shortest to handle multi-provider cases (e.g., openai/gpt-5.4 + opencode/gpt-5.4)
const exactModelIdMatches = matches.filter((model) => {
const modelId = model.split("/").slice(1).join("/")
return normalizeModelName(modelId) === targetNormalized
+5 -5
View File
@@ -7,19 +7,19 @@ import {
} from "./model-requirements"
describe("AGENT_MODEL_REQUIREMENTS", () => {
test("oracle has valid fallbackChain with gpt-5.2 as primary", () => {
test("oracle has valid fallbackChain with gpt-5.4 as primary", () => {
// given - oracle agent requirement
const oracle = AGENT_MODEL_REQUIREMENTS["oracle"]
// when - accessing oracle requirement
// then - fallbackChain exists with gpt-5.2 as first entry
// then - fallbackChain exists with gpt-5.4 as first entry
expect(oracle).toBeDefined()
expect(oracle.fallbackChain).toBeArray()
expect(oracle.fallbackChain.length).toBeGreaterThan(0)
const primary = oracle.fallbackChain[0]
expect(primary.providers).toContain("openai")
expect(primary.model).toBe("gpt-5.2")
expect(primary.model).toBe("gpt-5.4")
expect(primary.variant).toBe("high")
})
@@ -435,7 +435,7 @@ describe("ModelRequirement type", () => {
const requirement: ModelRequirement = {
fallbackChain: [
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6", variant: "max" },
{ providers: ["openai", "github-copilot"], model: "gpt-5.2", variant: "high" },
{ providers: ["openai", "github-copilot"], model: "gpt-5.4", variant: "high" },
],
}
@@ -444,7 +444,7 @@ describe("ModelRequirement type", () => {
expect(requirement.fallbackChain).toBeArray()
expect(requirement.fallbackChain).toHaveLength(2)
expect(requirement.fallbackChain[0].model).toBe("claude-opus-4-6")
expect(requirement.fallbackChain[1].model).toBe("gpt-5.2")
expect(requirement.fallbackChain[1].model).toBe("gpt-5.4")
})
test("ModelRequirement variant is optional", () => {
+4 -4
View File
@@ -32,7 +32,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
model: "gpt-5.3-codex",
variant: "medium",
},
{ providers: ["github-copilot"], model: "gpt-5.2", variant: "medium" },
{ providers: ["github-copilot"], model: "gpt-5.4", variant: "medium" },
],
requiresProvider: ["openai", "github-copilot", "venice", "opencode"],
},
@@ -40,7 +40,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
fallbackChain: [
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.2",
model: "gpt-5.4",
variant: "high",
},
{
@@ -119,7 +119,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
},
{
providers: ["openai", "github-copilot", "opencode"],
model: "gpt-5.2",
model: "gpt-5.4",
variant: "high",
},
{
@@ -226,7 +226,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
model: "claude-opus-4-6",
variant: "max",
},
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4" },
],
requiresModel: "gemini-3.1-pro",
},
+19 -19
View File
@@ -9,7 +9,7 @@ describe("resolveModel", () => {
// given
const input: ModelResolutionInput = {
userModel: "anthropic/claude-opus-4-6",
inheritedModel: "openai/gpt-5.2",
inheritedModel: "openai/gpt-5.4",
systemDefault: "google/gemini-3.1-pro",
}
@@ -24,7 +24,7 @@ describe("resolveModel", () => {
// given
const input: ModelResolutionInput = {
userModel: undefined,
inheritedModel: "openai/gpt-5.2",
inheritedModel: "openai/gpt-5.4",
systemDefault: "google/gemini-3.1-pro",
}
@@ -32,7 +32,7 @@ describe("resolveModel", () => {
const result = resolveModel(input)
// then
expect(result).toBe("openai/gpt-5.2")
expect(result).toBe("openai/gpt-5.4")
})
test("returns systemDefault when both userModel and inheritedModel are undefined", () => {
@@ -56,7 +56,7 @@ describe("resolveModel", () => {
// given
const input: ModelResolutionInput = {
userModel: "",
inheritedModel: "openai/gpt-5.2",
inheritedModel: "openai/gpt-5.4",
systemDefault: "google/gemini-3.1-pro",
}
@@ -64,7 +64,7 @@ describe("resolveModel", () => {
const result = resolveModel(input)
// then
expect(result).toBe("openai/gpt-5.2")
expect(result).toBe("openai/gpt-5.4")
})
test("treats whitespace-only string as unset, uses fallback", () => {
@@ -88,7 +88,7 @@ describe("resolveModel", () => {
// given
const input: ModelResolutionInput = {
userModel: "anthropic/claude-opus-4-6",
inheritedModel: "openai/gpt-5.2",
inheritedModel: "openai/gpt-5.4",
systemDefault: "google/gemini-3.1-pro",
}
@@ -292,9 +292,9 @@ describe("resolveModelWithFallback", () => {
// given
const input: ExtendedModelResolutionInput = {
fallbackChain: [
{ providers: ["openai", "anthropic", "google"], model: "gpt-5.2" },
{ providers: ["openai", "anthropic", "google"], model: "gpt-5.4" },
],
availableModels: new Set(["openai/gpt-5.2", "anthropic/claude-opus-4-6", "google/gemini-3.1-pro"]),
availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6", "google/gemini-3.1-pro"]),
systemDefaultModel: "google/gemini-3.1-pro",
}
@@ -302,7 +302,7 @@ describe("resolveModelWithFallback", () => {
const result = resolveModelWithFallback(input)
// then
expect(result!.model).toBe("openai/gpt-5.2")
expect(result!.model).toBe("openai/gpt-5.4")
expect(result!.source).toBe("provider-fallback")
})
@@ -476,7 +476,7 @@ describe("resolveModelWithFallback", () => {
fallbackChain: [
{ providers: ["anthropic"], model: "nonexistent-model" },
],
availableModels: new Set(["openai/gpt-5.2", "anthropic/claude-opus-4-6"]),
availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6"]),
systemDefaultModel: "google/gemini-3.1-pro",
}
@@ -592,7 +592,7 @@ describe("resolveModelWithFallback", () => {
test("returns system default when fallbackChain is not provided", () => {
// given
const input: ExtendedModelResolutionInput = {
availableModels: new Set(["openai/gpt-5.2"]),
availableModels: new Set(["openai/gpt-5.4"]),
systemDefaultModel: "google/gemini-3.1-pro",
}
@@ -613,7 +613,7 @@ describe("resolveModelWithFallback", () => {
// when
const result = resolveModelWithFallback({
fallbackChain: [
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4", variant: "high" },
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
],
availableModels,
@@ -632,7 +632,7 @@ describe("resolveModelWithFallback", () => {
// when
const result = resolveModelWithFallback({
fallbackChain: [
{ providers: ["openai", "anthropic"], model: "gpt-5.2" },
{ providers: ["openai", "anthropic"], model: "gpt-5.4" },
{ providers: ["google"], model: "gemini-3.1-pro" },
],
availableModels,
@@ -647,14 +647,14 @@ describe("resolveModelWithFallback", () => {
test("returns first matching entry even if later entries have better matches", () => {
// given
const availableModels = new Set([
"openai/gpt-5.2",
"openai/gpt-5.4",
"anthropic/claude-opus-4-6",
])
// when
const result = resolveModelWithFallback({
fallbackChain: [
{ providers: ["openai"], model: "gpt-5.2" },
{ providers: ["openai"], model: "gpt-5.4" },
{ providers: ["anthropic"], model: "claude-opus-4-6" },
],
availableModels,
@@ -662,7 +662,7 @@ describe("resolveModelWithFallback", () => {
})
// then
expect(result!.model).toBe("openai/gpt-5.2")
expect(result!.model).toBe("openai/gpt-5.4")
expect(result!.source).toBe("provider-fallback")
})
@@ -673,7 +673,7 @@ describe("resolveModelWithFallback", () => {
// when
const result = resolveModelWithFallback({
fallbackChain: [
{ providers: ["openai"], model: "gpt-5.2" },
{ providers: ["openai"], model: "gpt-5.4" },
{ providers: ["anthropic"], model: "claude-opus-4-6" },
{ providers: ["google"], model: "gemini-3.1-pro" },
],
@@ -884,7 +884,7 @@ describe("resolveModelWithFallback", () => {
fallbackChain: [
{ providers: ["anthropic"], model: "nonexistent-model" },
],
availableModels: new Set(["openai/gpt-5.2"]),
availableModels: new Set(["openai/gpt-5.4"]),
systemDefaultModel: undefined,
}
@@ -898,7 +898,7 @@ describe("resolveModelWithFallback", () => {
test("returns undefined when no fallbackChain and systemDefaultModel is undefined", () => {
// given
const input: ExtendedModelResolutionInput = {
availableModels: new Set(["openai/gpt-5.2"]),
availableModels: new Set(["openai/gpt-5.4"]),
systemDefaultModel: undefined,
}
+2 -2
View File
@@ -70,7 +70,7 @@ describe("parseModelSuggestion", () => {
data: {
providerID: "openai",
modelID: "gpt-5",
suggestions: ["gpt-5.2"],
suggestions: ["gpt-5.4"],
},
},
}
@@ -82,7 +82,7 @@ describe("parseModelSuggestion", () => {
expect(result).toEqual({
providerID: "openai",
modelID: "gpt-5",
suggestion: "gpt-5.2",
suggestion: "gpt-5.4",
})
})
@@ -365,7 +365,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => {
data: [
{ info: { id: "msg_001", role: "user", time: { created: 1000 }, agent: "oracle" } },
{
info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "end_turn", agent: "oracle", providerID: "openai", modelID: "gpt-5.2" },
info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "end_turn", agent: "oracle", providerID: "openai", modelID: "gpt-5.4" },
parts: [{ type: "text", text: "Response" }],
},
],
+21 -21
View File
@@ -19,7 +19,7 @@ const TEST_AVAILABLE_MODELS = new Set([
"anthropic/claude-haiku-4-5",
"google/gemini-3.1-pro",
"google/gemini-3-flash",
"openai/gpt-5.2",
"openai/gpt-5.4",
"openai/gpt-5.3-codex",
])
@@ -53,7 +53,7 @@ describe("sisyphus-task", () => {
models: {
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"],
google: ["gemini-3.1-pro", "gemini-3-flash"],
openai: ["gpt-5.2", "gpt-5.3-codex"],
openai: ["gpt-5.4", "gpt-5.3-codex"],
},
connected: ["anthropic", "google", "openai"],
updatedAt: "2026-01-01T00:00:00.000Z",
@@ -824,7 +824,7 @@ describe("sisyphus-task", () => {
const categoryName = "my-custom"
const userCategories = {
"my-custom": {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
temperature: 0.5,
prompt_append: "You are a custom agent",
},
@@ -835,7 +835,7 @@ describe("sisyphus-task", () => {
// then
expect(result).not.toBeNull()
expect(result!.config.model).toBe("openai/gpt-5.2")
expect(result!.config.model).toBe("openai/gpt-5.4")
expect(result!.config.temperature).toBe(0.5)
expect(result!.promptAppend).toBe("You are a custom agent")
})
@@ -948,7 +948,7 @@ describe("sisyphus-task", () => {
manager: mockManager,
client: mockClient,
userCategories: {
ultrabrain: { model: "openai/gpt-5.2", variant: "xhigh" },
ultrabrain: { model: "openai/gpt-5.4", variant: "xhigh" },
},
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
@@ -976,7 +976,7 @@ describe("sisyphus-task", () => {
// then
expect(launchInput.model).toEqual({
providerID: "openai",
modelID: "gpt-5.2",
modelID: "gpt-5.4",
variant: "xhigh",
})
})
@@ -1040,9 +1040,9 @@ describe("sisyphus-task", () => {
// then - variant MUST be "max" from DEFAULT_CATEGORIES
expect(launchInput.model).toEqual({
providerID: "anthropic",
modelID: "claude-opus-4-6",
variant: "max",
providerID: "openai",
modelID: "gpt-5.4",
variant: "high",
})
}, { timeout: 20000 })
@@ -1101,10 +1101,10 @@ describe("sisyphus-task", () => {
// then - variant MUST be "max" from DEFAULT_CATEGORIES (passed as separate field)
expect(promptBody.model).toEqual({
providerID: "anthropic",
modelID: "claude-opus-4-6",
providerID: "openai",
modelID: "gpt-5.4",
})
expect(promptBody.variant).toBe("max")
expect(promptBody.variant).toBe("high")
}, { timeout: 20000 })
})
@@ -1976,7 +1976,7 @@ describe("sisyphus-task", () => {
},
}
// Use ultrabrain which uses gpt-5.2 (non-gemini)
// Use ultrabrain which uses gpt-5.4 (non-gemini)
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
@@ -2185,7 +2185,7 @@ describe("sisyphus-task", () => {
client: mockClient,
userCategories: {
"my-unstable-cat": {
model: "openai/gpt-5.2",
model: "openai/gpt-5.4",
is_unstable_agent: true,
},
},
@@ -2568,7 +2568,7 @@ describe("sisyphus-task", () => {
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
sisyphusJuniorModel: "openai/gpt-5.2",
sisyphusJuniorModel: "openai/gpt-5.4",
userCategories: {
"my-custom": { temperature: 0.5 },
},
@@ -2595,7 +2595,7 @@ describe("sisyphus-task", () => {
// then - sisyphus-junior override model should be used as fallback
expect(launchInput.model.providerID).toBe("openai")
expect(launchInput.model.modelID).toBe("gpt-5.2")
expect(launchInput.model.modelID).toBe("gpt-5.4")
})
})
@@ -3406,7 +3406,7 @@ describe("sisyphus-task", () => {
app: {
agents: async () => ({
data: [
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.2" } },
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.4" } },
],
}),
},
@@ -3473,7 +3473,7 @@ describe("sisyphus-task", () => {
app: {
agents: async () => ({
data: [
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.2" } },
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.4" } },
],
}),
},
@@ -3582,11 +3582,11 @@ describe("sisyphus-task", () => {
)
// then - should resolve via AGENT_MODEL_REQUIREMENTS fallback chain for oracle
// oracle fallback chain: gpt-5.2 (openai) > gemini-3.1-pro (google) > claude-opus-4-6 (anthropic)
// Since openai is in connectedProviders, should resolve to openai/gpt-5.2
// oracle fallback chain: gpt-5.4 (openai) > gemini-3.1-pro (google) > claude-opus-4-6 (anthropic)
// Since openai is in connectedProviders, should resolve to openai/gpt-5.4
expect(promptBody.model).toBeDefined()
expect(promptBody.model.providerID).toBe("openai")
expect(promptBody.model.modelID).toContain("gpt-5.2")
expect(promptBody.model.modelID).toContain("gpt-5.4")
}, { timeout: 20000 })
})