refactor(models): bump claude-opus-4-6 to claude-opus-4-7 across fallback chains, categories, and hooks
Updates the canonical Anthropic Opus model in every fallback chain (sisyphus, oracle, prometheus, metis, momus, visual-engineering, ultrabrain, deep, artistry, unspecified-high), the unspecified-high category default, the think-mode HIGH_VARIANT_MAP, the Claude Code alias map, the claude-thinking legacy alias, the context-limit GA regex, and event.ts fallback strings. Widens supportsCachedAnthropicLimit to accept both claude-*-4-6 and claude-*-4-7 so the 1M context cache still applies across the bump. Regenerates the bundled model-capabilities snapshot from models.dev and the model-fallback snapshot to match the new source output.
This commit is contained in:
@@ -57,7 +57,7 @@ describe("Sisyphus prompt identity", () => {
|
||||
describe("#given a Sisyphus agent created with default model", () => {
|
||||
describe("#when checking the prompt", () => {
|
||||
it("#then contains the agent identity section with override directive", () => {
|
||||
const config = createSisyphusAgent("anthropic/claude-opus-4-6")
|
||||
const config = createSisyphusAgent("anthropic/claude-opus-4-7")
|
||||
|
||||
expect(config.prompt).toContain("<agent-identity>")
|
||||
expect(config.prompt).toContain("Sisyphus")
|
||||
@@ -65,7 +65,7 @@ describe("Sisyphus prompt identity", () => {
|
||||
})
|
||||
|
||||
it("#then identity section appears before the Role section", () => {
|
||||
const config = createSisyphusAgent("anthropic/claude-opus-4-6")
|
||||
const config = createSisyphusAgent("anthropic/claude-opus-4-7")
|
||||
const prompt = config.prompt ?? ""
|
||||
const identityIndex = prompt.indexOf("<agent-identity>")
|
||||
const roleIndex = prompt.indexOf("<Role>")
|
||||
@@ -115,7 +115,7 @@ describe("Agent identity preservation through overrides", () => {
|
||||
describe("#given a Sisyphus agent with prompt_append override", () => {
|
||||
describe("#when merging the override", () => {
|
||||
it("#then identity section is preserved in the merged prompt", () => {
|
||||
const baseConfig = createSisyphusAgent("anthropic/claude-opus-4-6")
|
||||
const baseConfig = createSisyphusAgent("anthropic/claude-opus-4-7")
|
||||
const merged = mergeAgentConfig(baseConfig, { prompt_append: "Extra instructions here" })
|
||||
|
||||
expect(merged.prompt).toContain("<agent-identity>")
|
||||
@@ -129,7 +129,7 @@ describe("Agent identity preservation through overrides", () => {
|
||||
describe("#given a Sisyphus agent with model override only", () => {
|
||||
describe("#when merging the override", () => {
|
||||
it("#then identity section is preserved unchanged", () => {
|
||||
const baseConfig = createSisyphusAgent("anthropic/claude-opus-4-6")
|
||||
const baseConfig = createSisyphusAgent("anthropic/claude-opus-4-7")
|
||||
const merged = mergeAgentConfig(baseConfig, { model: "openai/gpt-5.4" })
|
||||
|
||||
expect(merged.prompt).toContain("<agent-identity>")
|
||||
|
||||
@@ -43,7 +43,7 @@ describe("maybeCreateSisyphusConfig", () => {
|
||||
// given
|
||||
const agentOverrides: AgentOverrides = {
|
||||
sisyphus: {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
permission: {
|
||||
apply_patch: "allow",
|
||||
},
|
||||
@@ -55,8 +55,8 @@ describe("maybeCreateSisyphusConfig", () => {
|
||||
const config = maybeCreateSisyphusConfig({
|
||||
disabledAgents: [],
|
||||
agentOverrides,
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "anthropic/claude-opus-4-6",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "anthropic/claude-opus-4-7",
|
||||
isFirstRunNoCache: false,
|
||||
availableAgents: [],
|
||||
availableSkills: [],
|
||||
@@ -67,7 +67,7 @@ describe("maybeCreateSisyphusConfig", () => {
|
||||
|
||||
// then
|
||||
expect(config).toBeDefined();
|
||||
expect(config?.model).toBe("anthropic/claude-opus-4-6");
|
||||
expect(config?.model).toBe("anthropic/claude-opus-4-7");
|
||||
// Claude models should allow the user override
|
||||
expect(config?.permission).toHaveProperty("apply_patch", "allow");
|
||||
});
|
||||
|
||||
@@ -2,13 +2,13 @@ import { describe, expect, spyOn, test } from "bun:test"
|
||||
import { createBuiltinAgents } from "./builtin-agents"
|
||||
import * as shared from "../shared"
|
||||
|
||||
const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-6"
|
||||
const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-7"
|
||||
|
||||
describe("createBuiltinAgents custom agent visibility", () => {
|
||||
test("#given runtime custom agents #when orchestrator prompts are built #then custom agents are not advertised for automatic delegation", async () => {
|
||||
//#given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
|
||||
new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
try {
|
||||
|
||||
@@ -211,7 +211,7 @@ describe("buildParallelDelegationSection", () => {
|
||||
|
||||
it("#given Claude model #when building #then returns empty", () => {
|
||||
//#given
|
||||
const model = "anthropic/claude-opus-4-6"
|
||||
const model = "anthropic/claude-opus-4-7"
|
||||
const categories = [deepCategory]
|
||||
|
||||
//#when
|
||||
|
||||
@@ -56,7 +56,7 @@ describe("getHephaestusPromptSource", () => {
|
||||
|
||||
test("returns 'gpt' for non-GPT models and undefined", () => {
|
||||
// given
|
||||
const model1 = "anthropic/claude-opus-4-6";
|
||||
const model1 = "anthropic/claude-opus-4-7";
|
||||
const model2 = undefined;
|
||||
|
||||
// when
|
||||
@@ -124,7 +124,7 @@ describe("getHephaestusPrompt", () => {
|
||||
|
||||
test("Claude model returns generic GPT prompt (Hephaestus default)", () => {
|
||||
// given
|
||||
const model = "anthropic/claude-opus-4-6";
|
||||
const model = "anthropic/claude-opus-4-7";
|
||||
|
||||
// when
|
||||
const prompt = getHephaestusPrompt(model);
|
||||
@@ -149,7 +149,7 @@ describe("getHephaestusPrompt", () => {
|
||||
|
||||
test("useTaskSystem=false includes Todo Discipline for Claude models", () => {
|
||||
// given
|
||||
const model = "anthropic/claude-opus-4-6";
|
||||
const model = "anthropic/claude-opus-4-7";
|
||||
|
||||
// when
|
||||
const prompt = getHephaestusPrompt(model, false);
|
||||
@@ -239,7 +239,7 @@ describe("createHephaestusAgent", () => {
|
||||
// given
|
||||
const gpt54Model = "openai/gpt-5.4";
|
||||
const gptGenericModel = "openai/gpt-4o";
|
||||
const claudeModel = "anthropic/claude-opus-4-6";
|
||||
const claudeModel = "anthropic/claude-opus-4-7";
|
||||
|
||||
// when
|
||||
const gpt54Config = createHephaestusAgent(gpt54Model);
|
||||
@@ -322,7 +322,7 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => {
|
||||
// given
|
||||
const agentOverrides: AgentOverrides = {
|
||||
hephaestus: {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
permission: {
|
||||
apply_patch: "allow",
|
||||
},
|
||||
@@ -334,8 +334,8 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => {
|
||||
const config = maybeCreateHephaestusConfig({
|
||||
disabledAgents: [],
|
||||
agentOverrides,
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "anthropic/claude-opus-4-6",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "anthropic/claude-opus-4-7",
|
||||
isFirstRunNoCache: false,
|
||||
availableAgents: [],
|
||||
availableSkills: [],
|
||||
@@ -346,7 +346,7 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => {
|
||||
|
||||
// then
|
||||
expect(config).toBeDefined();
|
||||
expect(config?.model).toBe("anthropic/claude-opus-4-6");
|
||||
expect(config?.model).toBe("anthropic/claude-opus-4-7");
|
||||
expect(config?.permission).toHaveProperty("apply_patch", "allow");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("isGpt5_4Model", () => {
|
||||
});
|
||||
|
||||
test("does not match non-GPT models", () => {
|
||||
expect(isGpt5_4Model("anthropic/claude-opus-4-6")).toBe(false);
|
||||
expect(isGpt5_4Model("anthropic/claude-opus-4-7")).toBe(false);
|
||||
expect(isGpt5_4Model("google/gemini-3.1-pro")).toBe(false);
|
||||
expect(isGpt5_4Model("openai/o1")).toBe(false);
|
||||
});
|
||||
@@ -64,7 +64,7 @@ describe("isGptModel", () => {
|
||||
});
|
||||
|
||||
test("claude models are not gpt", () => {
|
||||
expect(isGptModel("anthropic/claude-opus-4-6")).toBe(false);
|
||||
expect(isGptModel("anthropic/claude-opus-4-7")).toBe(false);
|
||||
expect(isGptModel("anthropic/claude-sonnet-4-6")).toBe(false);
|
||||
expect(isGptModel("litellm/anthropic.claude-opus-4-5")).toBe(false);
|
||||
});
|
||||
@@ -75,7 +75,7 @@ describe("isGptModel", () => {
|
||||
});
|
||||
|
||||
test("opencode provider is not gpt", () => {
|
||||
expect(isGptModel("opencode/claude-opus-4-6")).toBe(false);
|
||||
expect(isGptModel("opencode/claude-opus-4-7")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,7 +95,7 @@ describe("isMiniMaxModel", () => {
|
||||
|
||||
test("does not match non-minimax models", () => {
|
||||
expect(isMiniMaxModel("openai/gpt-5.4")).toBe(false);
|
||||
expect(isMiniMaxModel("anthropic/claude-opus-4-6")).toBe(false);
|
||||
expect(isMiniMaxModel("anthropic/claude-opus-4-7")).toBe(false);
|
||||
expect(isMiniMaxModel("google/gemini-3.1-pro")).toBe(false);
|
||||
expect(isMiniMaxModel("opencode-go/kimi-k2.5")).toBe(false);
|
||||
});
|
||||
@@ -116,7 +116,7 @@ describe("isGlmModel", () => {
|
||||
|
||||
test("#given non-GLM models #then returns false", () => {
|
||||
expect(isGlmModel("openai/gpt-5.4")).toBe(false);
|
||||
expect(isGlmModel("anthropic/claude-opus-4-6")).toBe(false);
|
||||
expect(isGlmModel("anthropic/claude-opus-4-7")).toBe(false);
|
||||
expect(isGlmModel("google/gemini-3.1-pro")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -156,11 +156,11 @@ describe("isGeminiModel", () => {
|
||||
});
|
||||
|
||||
test("#given claude models #then returns false", () => {
|
||||
expect(isGeminiModel("anthropic/claude-opus-4-6")).toBe(false);
|
||||
expect(isGeminiModel("anthropic/claude-opus-4-7")).toBe(false);
|
||||
expect(isGeminiModel("anthropic/claude-sonnet-4-6")).toBe(false);
|
||||
});
|
||||
|
||||
test("#given opencode provider #then returns false", () => {
|
||||
expect(isGeminiModel("opencode/claude-opus-4-6")).toBe(false);
|
||||
expect(isGeminiModel("opencode/claude-opus-4-7")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
+23
-23
@@ -7,7 +7,7 @@ import * as connectedProvidersCache from "../shared/connected-providers-cache"
|
||||
import * as modelAvailability from "../shared/model-availability"
|
||||
import * as shared from "../shared"
|
||||
|
||||
const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-6"
|
||||
const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-7"
|
||||
let createBuiltinAgents: (typeof import("./builtin-agents"))["createBuiltinAgents"]
|
||||
|
||||
async function importFreshBuiltinAgentsModule(): Promise<typeof import("./builtin-agents")> {
|
||||
@@ -32,7 +32,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set([
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"kimi-for-coding/k2p5",
|
||||
"opencode/kimi-k2.5-free",
|
||||
"zai-coding-plan/glm-5",
|
||||
@@ -45,7 +45,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
|
||||
|
||||
// #then
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(agents.sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
|
||||
expect(agents.sisyphus.reasoningEffort).toBeUndefined()
|
||||
} finally {
|
||||
@@ -170,7 +170,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
|
||||
test("Sisyphus is created on first run when no availableModels or cache exist", async () => {
|
||||
// #given
|
||||
const systemDefaultModel = "anthropic/claude-opus-4-6"
|
||||
const systemDefaultModel = "anthropic/claude-opus-4-7"
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
|
||||
|
||||
@@ -180,7 +180,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
|
||||
// #then
|
||||
expect(agents.sisyphus).toBeDefined()
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.6")
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.7")
|
||||
} finally {
|
||||
cacheSpy.mockRestore()
|
||||
fetchSpy.mockRestore()
|
||||
@@ -299,7 +299,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set([
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"kimi-for-coding/k2p5",
|
||||
"opencode/kimi-k2.5-free",
|
||||
"zai-coding-plan/glm-5",
|
||||
@@ -341,7 +341,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.4"])
|
||||
new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const customAgentSummaries = [
|
||||
@@ -377,7 +377,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.4"])
|
||||
new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const customAgentSummaries = [
|
||||
@@ -413,7 +413,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.4"])
|
||||
new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const disabledAgents = ["ReSeArChEr"]
|
||||
@@ -449,7 +449,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("does not advertise duplicate custom agents case-insensitively", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
|
||||
new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const customAgentSummaries = [
|
||||
@@ -481,7 +481,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("does not surface custom agent strings in orchestrator prompts", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
|
||||
new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const customAgentSummaries = [
|
||||
@@ -555,7 +555,7 @@ describe("createBuiltinAgents without systemDefaultModel", () => {
|
||||
])
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set([
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"kimi-for-coding/k2p5",
|
||||
"opencode/kimi-k2.5-free",
|
||||
"zai-coding-plan/glm-5",
|
||||
@@ -569,7 +569,7 @@ describe("createBuiltinAgents without systemDefaultModel", () => {
|
||||
|
||||
// #then
|
||||
expect(agents.sisyphus).toBeDefined()
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-7")
|
||||
} finally {
|
||||
cacheSpy.mockRestore()
|
||||
fetchSpy.mockRestore()
|
||||
@@ -590,7 +590,7 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () =>
|
||||
const providers = options?.connectedProviders ?? []
|
||||
return providers.includes("openai")
|
||||
? new Set(["openai/gpt-5.3-codex"])
|
||||
: new Set(["anthropic/claude-opus-4-6"])
|
||||
: new Set(["anthropic/claude-opus-4-7"])
|
||||
})
|
||||
|
||||
try {
|
||||
@@ -609,7 +609,7 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () =>
|
||||
test("hephaestus is not created when no required provider is connected", async () => {
|
||||
// #given - only anthropic models available, not in hephaestus requiresProvider
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6"])
|
||||
new Set(["anthropic/claude-opus-4-7"])
|
||||
)
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic"])
|
||||
|
||||
@@ -699,10 +699,10 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () =>
|
||||
test("hephaestus is created when explicit config provided even if provider unavailable", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6"])
|
||||
new Set(["anthropic/claude-opus-4-7"])
|
||||
)
|
||||
const overrides = {
|
||||
hephaestus: { model: "anthropic/claude-opus-4-6" },
|
||||
hephaestus: { model: "anthropic/claude-opus-4-7" },
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -781,7 +781,7 @@ describe("Sisyphus and Librarian environment context toggle", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "google/gemini-3-flash"])
|
||||
new Set(["anthropic/claude-opus-4-7", "google/gemini-3-flash"])
|
||||
)
|
||||
})
|
||||
|
||||
@@ -840,7 +840,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.4"])
|
||||
new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
|
||||
)
|
||||
})
|
||||
|
||||
@@ -893,7 +893,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
|
||||
test("sisyphus is created when at least one fallback model is available", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6"])
|
||||
new Set(["anthropic/claude-opus-4-7"])
|
||||
)
|
||||
|
||||
try {
|
||||
@@ -918,7 +918,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
|
||||
|
||||
// #then
|
||||
expect(agents.sisyphus).toBeDefined()
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.6")
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.7")
|
||||
} finally {
|
||||
cacheSpy.mockRestore()
|
||||
fetchSpy.mockRestore()
|
||||
@@ -929,7 +929,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
|
||||
const overrides = {
|
||||
sisyphus: { model: "anthropic/claude-opus-4-6" },
|
||||
sisyphus: { model: "anthropic/claude-opus-4-7" },
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1039,7 +1039,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
|
||||
|
||||
describe("buildAgent with category and skills", () => {
|
||||
const { buildAgent } = require("./agent-builder")
|
||||
const TEST_MODEL = "anthropic/claude-opus-4-6"
|
||||
const TEST_MODEL = "anthropic/claude-opus-4-7"
|
||||
|
||||
beforeEach(() => {
|
||||
clearSkillCache()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,7 @@ Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi > Verce
|
||||
OpenAI Native openai/ models (GPT-5.4 for Oracle)
|
||||
Gemini Native google/ models (Gemini 3.1 Pro, Flash)
|
||||
Copilot github-copilot/ models (fallback)
|
||||
OpenCode Zen opencode/ models (opencode/claude-opus-4-6, etc.)
|
||||
OpenCode Zen opencode/ models (opencode/claude-opus-4-7, etc.)
|
||||
Z.ai zai-coding-plan/glm-5 (visual-engineering fallback)
|
||||
Kimi kimi-for-coding/k2p5 (Sisyphus/Prometheus fallback)
|
||||
Vercel vercel/ models (universal proxy, always last fallback)
|
||||
|
||||
@@ -26,8 +26,8 @@ describe("generateOmoConfig - model fallback system", () => {
|
||||
|
||||
//#then
|
||||
expect([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"github-copilot/claude-opus-4-6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"github-copilot/claude-opus-4-7",
|
||||
]).toContain((result.agents as Record<string, { model: string }>).sisyphus.model)
|
||||
})
|
||||
|
||||
@@ -74,7 +74,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
||||
|
||||
//#then
|
||||
expect((result.agents as Record<string, { model: string }>).librarian.model).toBe("zai-coding-plan/glm-4.7")
|
||||
expect((result.agents as Record<string, { model: string }>).sisyphus.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect((result.agents as Record<string, { model: string }>).sisyphus.model).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("uses native OpenAI models when only ChatGPT available", () => {
|
||||
@@ -131,7 +131,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
||||
}>
|
||||
|
||||
//#then
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(agents.sisyphus.fallback_models).toEqual([
|
||||
{
|
||||
model: "openai/gpt-5.4",
|
||||
@@ -141,7 +141,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
||||
expect(categories.deep.model).toBe("openai/gpt-5.4")
|
||||
expect(categories.deep.fallback_models).toEqual([
|
||||
{
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
])
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("loadAvailableModelsFromCache", () => {
|
||||
join(tempDir, "cache", "opencode", "models.json"),
|
||||
JSON.stringify({
|
||||
openai: { models: { "gpt-5.4": {} } },
|
||||
anthropic: { models: { "claude-opus-4-6": {}, "claude-sonnet-4-6": {} } },
|
||||
anthropic: { models: { "claude-opus-4-7": {}, "claude-sonnet-4-6": {} } },
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ describe("model-resolution check", () => {
|
||||
// then: Should have agent entries
|
||||
const sisyphus = info.agents.find((a) => a.name === "sisyphus")
|
||||
expect(sisyphus).toBeDefined()
|
||||
expect(sisyphus!.requirement.fallbackChain[0]?.model).toBe("claude-opus-4-6")
|
||||
expect(sisyphus!.requirement.fallbackChain[0]?.model).toBe("claude-opus-4-7")
|
||||
expect(sisyphus!.requirement.fallbackChain[0]?.providers).toContain("anthropic")
|
||||
})
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("model-resolution check", () => {
|
||||
// given: User has override for oracle agent
|
||||
const mockConfig = {
|
||||
agents: {
|
||||
oracle: { model: "anthropic/claude-opus-4-6" },
|
||||
oracle: { model: "anthropic/claude-opus-4-7" },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ describe("model-resolution check", () => {
|
||||
// then: Oracle should show the override
|
||||
const oracle = info.agents.find((a) => a.name === "oracle")
|
||||
expect(oracle).toBeDefined()
|
||||
expect(oracle!.userOverride).toBe("anthropic/claude-opus-4-6")
|
||||
expect(oracle!.effectiveResolution).toBe("User override: anthropic/claude-opus-4-6")
|
||||
expect(oracle!.userOverride).toBe("anthropic/claude-opus-4-7")
|
||||
expect(oracle!.effectiveResolution).toBe("User override: anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
it("shows user override for category when configured", async () => {
|
||||
@@ -169,13 +169,13 @@ describe("model-resolution check", () => {
|
||||
|
||||
const info = getModelResolutionInfoWithOverrides({
|
||||
agents: {
|
||||
oracle: { model: "anthropic/claude-opus-4-6-thinking" },
|
||||
oracle: { model: "anthropic/claude-opus-4-7-thinking" },
|
||||
},
|
||||
})
|
||||
|
||||
const oracle = info.agents.find((agent) => agent.name === "oracle")
|
||||
expect(oracle).toBeDefined()
|
||||
expect(oracle!.effectiveModel).toBe("anthropic/claude-opus-4-6-thinking")
|
||||
expect(oracle!.effectiveModel).toBe("anthropic/claude-opus-4-7-thinking")
|
||||
expect(oracle!.capabilityDiagnostics).toMatchObject({
|
||||
resolutionMode: "alias-backed",
|
||||
canonicalization: {
|
||||
|
||||
@@ -381,7 +381,7 @@ describe("generateModelConfig", () => {
|
||||
const result = generateModelConfig(config)
|
||||
|
||||
// #then
|
||||
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("Sisyphus is created when multiple fallback providers are available", () => {
|
||||
@@ -398,7 +398,7 @@ describe("generateModelConfig", () => {
|
||||
const result = generateModelConfig(config)
|
||||
|
||||
// #then
|
||||
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("Sisyphus resolves to gpt-5.4 medium when only OpenAI is available", () => {
|
||||
@@ -668,7 +668,7 @@ describe("generateModelConfig", () => {
|
||||
const result = generateModelConfig(config)
|
||||
|
||||
// #then should prefer native anthropic over gateway
|
||||
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -5,16 +5,16 @@ import { transformModelForProvider as transformSharedModelForProvider } from "..
|
||||
|
||||
describe("transformModelForProvider", () => {
|
||||
describe("github-copilot provider", () => {
|
||||
test("transforms claude-opus-4-6 to claude-opus-4.6", () => {
|
||||
// #given github-copilot provider and claude-opus-4-6 model
|
||||
test("transforms claude-opus-4-7 to claude-opus-4.7", () => {
|
||||
// #given github-copilot provider and claude-opus-4-7 model
|
||||
const provider = "github-copilot"
|
||||
const model = "claude-opus-4-6"
|
||||
const model = "claude-opus-4-7"
|
||||
|
||||
// #when transformModelForProvider is called
|
||||
const result = transformModelForProvider(provider, model)
|
||||
|
||||
// #then should transform to claude-opus-4.6
|
||||
expect(result).toBe("claude-opus-4.6")
|
||||
// #then should transform to claude-opus-4.7
|
||||
expect(result).toBe("claude-opus-4.7")
|
||||
})
|
||||
|
||||
test("transforms claude-sonnet-4-5 to claude-sonnet-4.5", () => {
|
||||
@@ -152,29 +152,29 @@ describe("transformModelForProvider", () => {
|
||||
})
|
||||
|
||||
test("does not transform claude models for google provider", () => {
|
||||
// #given google provider and claude-opus-4-6 model
|
||||
// #given google provider and claude-opus-4-7 model
|
||||
const provider = "google"
|
||||
const model = "claude-opus-4-6"
|
||||
const model = "claude-opus-4-7"
|
||||
|
||||
// #when transformModelForProvider is called
|
||||
const result = transformModelForProvider(provider, model)
|
||||
|
||||
// #then should pass through unchanged (google doesn't use claude)
|
||||
expect(result).toBe("claude-opus-4-6")
|
||||
expect(result).toBe("claude-opus-4-7")
|
||||
})
|
||||
})
|
||||
|
||||
describe("anthropic provider", () => {
|
||||
test("preserves hyphenated claude-opus-4-6 for config output (regression: installer must not write dotted IDs)", () => {
|
||||
// #given anthropic provider and claude-opus-4-6 model
|
||||
test("preserves hyphenated claude-opus-4-7 for config output (regression: installer must not write dotted IDs)", () => {
|
||||
// #given anthropic provider and claude-opus-4-7 model
|
||||
const provider = "anthropic"
|
||||
const model = "claude-opus-4-6"
|
||||
const model = "claude-opus-4-7"
|
||||
|
||||
// #when transformModelForProvider is called
|
||||
const result = transformModelForProvider(provider, model)
|
||||
|
||||
// #then should keep hyphenated form so Anthropic provider resolution succeeds on fresh installs
|
||||
expect(result).toBe("claude-opus-4-6")
|
||||
expect(result).toBe("claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("preserves hyphenated claude-sonnet-4-6 for config output", () => {
|
||||
@@ -204,12 +204,12 @@ describe("transformModelForProvider", () => {
|
||||
|
||||
describe("vercel provider", () => {
|
||||
test("prepends anthropic/ and applies anthropic transform for claude models", () => {
|
||||
// #given vercel provider and claude-opus-4-6 model
|
||||
// #given vercel provider and claude-opus-4-7 model
|
||||
// #when transformModelForProvider is called
|
||||
const result = transformModelForProvider("vercel", "claude-opus-4-6")
|
||||
const result = transformModelForProvider("vercel", "claude-opus-4-7")
|
||||
|
||||
// #then should produce anthropic/claude-opus-4.6
|
||||
expect(result).toBe("anthropic/claude-opus-4.6")
|
||||
// #then should produce anthropic/claude-opus-4.7
|
||||
expect(result).toBe("anthropic/claude-opus-4.7")
|
||||
})
|
||||
|
||||
test("prepends anthropic/ and applies anthropic transform for claude-sonnet", () => {
|
||||
@@ -267,12 +267,12 @@ describe("transformModelForProvider", () => {
|
||||
})
|
||||
|
||||
test("delegates to sub-provider when model already has sub-provider prefix", () => {
|
||||
// #given vercel provider and anthropic/claude-opus-4-6 (already prefixed)
|
||||
// #given vercel provider and anthropic/claude-opus-4-7 (already prefixed)
|
||||
// #when transformModelForProvider is called
|
||||
const result = transformModelForProvider("vercel", "anthropic/claude-opus-4-6")
|
||||
const result = transformModelForProvider("vercel", "anthropic/claude-opus-4-7")
|
||||
|
||||
// #then should apply anthropic transform within the prefix
|
||||
expect(result).toBe("anthropic/claude-opus-4.6")
|
||||
expect(result).toBe("anthropic/claude-opus-4.7")
|
||||
})
|
||||
|
||||
test("prepends minimax/ for minimax models", () => {
|
||||
@@ -340,14 +340,14 @@ describe("transformModelForProvider", () => {
|
||||
|
||||
test("uses a CLI-local transform implementation distinct from the shared runtime transform", () => {
|
||||
// #given the CLI transform (used by the installer) and the shared runtime transform
|
||||
const cliResult = transformModelForProvider("anthropic", "claude-opus-4-6")
|
||||
const sharedResult = transformSharedModelForProvider("anthropic", "claude-opus-4-6")
|
||||
const cliResult = transformModelForProvider("anthropic", "claude-opus-4-7")
|
||||
const sharedResult = transformSharedModelForProvider("anthropic", "claude-opus-4-7")
|
||||
|
||||
// #when both are called with the same anthropic claude input
|
||||
// #then the CLI preserves hyphenated form for config output,
|
||||
// the shared runtime transform converts dash→dot for API calls
|
||||
expect(transformModelForProvider).not.toBe(transformSharedModelForProvider)
|
||||
expect(cliResult).toBe("claude-opus-4-6")
|
||||
expect(sharedResult).toBe("claude-opus-4.6")
|
||||
expect(cliResult).toBe("claude-opus-4-7")
|
||||
expect(sharedResult).toBe("claude-opus-4.7")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,7 +54,7 @@ export function transformModelForProvider(provider: string, model: string): stri
|
||||
}
|
||||
|
||||
if (provider === "anthropic") {
|
||||
// Installer writes hyphenated IDs (claude-opus-4-6) to the config. The
|
||||
// Installer writes hyphenated IDs (claude-opus-4-7) to the config. The
|
||||
// runtime provider-model-id-transform converts dash→dot when calling the
|
||||
// Anthropic API. Keeping the dotted form in the config breaks fresh
|
||||
// installs with ProviderModelNotFoundError because Anthropic's provider
|
||||
|
||||
@@ -49,7 +49,7 @@ describe("refreshModelCapabilities", () => {
|
||||
sourceUrl: "https://override.example/api.json",
|
||||
models: {
|
||||
"gpt-5.4": { id: "gpt-5.4" },
|
||||
"claude-opus-4-6": { id: "claude-opus-4-6" },
|
||||
"claude-opus-4-7": { id: "claude-opus-4-7" },
|
||||
},
|
||||
}))
|
||||
let stdout = ""
|
||||
|
||||
@@ -98,7 +98,7 @@ describe("message.part.delta handling", () => {
|
||||
sessionID: "ses_main",
|
||||
role: "assistant",
|
||||
agent: "Sisyphus - Ultraworker",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
},
|
||||
@@ -113,7 +113,7 @@ describe("message.part.delta handling", () => {
|
||||
//#then
|
||||
const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("")
|
||||
expect(rendered).toContain("\u001b[38;2;0;206;209m")
|
||||
expect(rendered).toContain("claude-opus-4-6 (max)")
|
||||
expect(rendered).toContain("claude-opus-4-7 (max)")
|
||||
expect(rendered).toContain("└─")
|
||||
expect(rendered).toContain("Sisyphus - Ultraworker")
|
||||
stdoutSpy.mockRestore()
|
||||
@@ -128,7 +128,7 @@ describe("message.part.delta handling", () => {
|
||||
{
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" },
|
||||
info: { sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -187,7 +187,7 @@ describe("message.part.delta handling", () => {
|
||||
{
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" },
|
||||
info: { sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -242,7 +242,7 @@ describe("message.part.delta handling", () => {
|
||||
{
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" },
|
||||
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -309,7 +309,7 @@ describe("message.part.delta handling", () => {
|
||||
{
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" },
|
||||
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -353,7 +353,7 @@ describe("message.part.delta handling", () => {
|
||||
{
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6", variant: "max" },
|
||||
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7", variant: "max" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -388,7 +388,7 @@ describe("message.part.delta handling", () => {
|
||||
{
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { id: "msg_user", sessionID: "ses_main", role: "user", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" },
|
||||
info: { id: "msg_user", sessionID: "ses_main", role: "user", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -410,7 +410,7 @@ describe("message.part.delta handling", () => {
|
||||
{
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" },
|
||||
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -619,7 +619,7 @@ describe("message.part.delta handling", () => {
|
||||
{
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { id: "msg_1", sessionID: "ses_main", role: "assistant", agent: "Sisyphus", modelID: "claude-opus-4-6" },
|
||||
info: { id: "msg_1", sessionID: "ses_main", role: "assistant", agent: "Sisyphus", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -634,7 +634,7 @@ describe("message.part.delta handling", () => {
|
||||
{
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { id: "msg_1", sessionID: "ses_main", role: "assistant", agent: "Sisyphus", modelID: "claude-opus-4-6" },
|
||||
info: { id: "msg_1", sessionID: "ses_main", role: "assistant", agent: "Sisyphus", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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.4, etc." },
|
||||
{ value: "yes", label: "Yes", hint: "opencode/claude-opus-4-7, opencode/gpt-5.4, etc." },
|
||||
],
|
||||
initialValue: initial.opencodeZen,
|
||||
})
|
||||
|
||||
@@ -83,7 +83,7 @@ describe("findNearestMessageExcludingCompaction", () => {
|
||||
// given
|
||||
const message = {
|
||||
agent: "sisyphus",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}
|
||||
writeFileSync(join(tempDir, "001.json"), JSON.stringify(message))
|
||||
|
||||
@@ -94,18 +94,18 @@ describe("findNearestMessageExcludingCompaction", () => {
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.agent).toBe("sisyphus")
|
||||
expect(result?.model?.providerID).toBe("anthropic")
|
||||
expect(result?.model?.modelID).toBe("claude-opus-4-6")
|
||||
expect(result?.model?.modelID).toBe("claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("skips compaction agent messages", () => {
|
||||
// given
|
||||
const compactionMessage = {
|
||||
agent: "compaction",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}
|
||||
const validMessage = {
|
||||
agent: "sisyphus",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}
|
||||
writeFileSync(join(tempDir, "002.json"), JSON.stringify(compactionMessage))
|
||||
writeFileSync(join(tempDir, "001.json"), JSON.stringify(validMessage))
|
||||
@@ -125,12 +125,12 @@ describe("findNearestMessageExcludingCompaction", () => {
|
||||
writeFileSync(join(tempDir, "002.json"), JSON.stringify({
|
||||
id: compactionMessageID,
|
||||
agent: "atlas",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}))
|
||||
writeFileSync(join(tempDir, "001.json"), JSON.stringify({
|
||||
id: "msg_001",
|
||||
agent: "sisyphus",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}))
|
||||
mkdirSync(partDir, { recursive: true })
|
||||
writeFileSync(join(partDir, "prt_0001.json"), JSON.stringify({ type: "compaction" }))
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("ConcurrencyManager.getConcurrencyLimit", () => {
|
||||
|
||||
// when
|
||||
const modelLimit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-6")
|
||||
const providerLimit = manager.getConcurrencyLimit("anthropic/claude-opus-4-6")
|
||||
const providerLimit = manager.getConcurrencyLimit("anthropic/claude-opus-4-7")
|
||||
const defaultLimit = manager.getConcurrencyLimit("google/gemini-3.1-pro")
|
||||
|
||||
// then
|
||||
|
||||
@@ -855,7 +855,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
||||
{
|
||||
info: {
|
||||
agent: "sisyphus",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -890,7 +890,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
||||
|
||||
//#then
|
||||
expect(capturedBody?.agent).toBe("sisyphus")
|
||||
expect(capturedBody?.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.6" })
|
||||
expect(capturedBody?.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.7" })
|
||||
|
||||
manager.shutdown()
|
||||
})
|
||||
@@ -913,7 +913,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
||||
}
|
||||
const currentMessage: CurrentMessage = {
|
||||
agent: "sisyphus",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -921,7 +921,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
||||
|
||||
// then - uses currentMessage values, not task.parentModel/parentAgent
|
||||
expect(promptBody.agent).toBe("sisyphus")
|
||||
expect(promptBody.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.6" })
|
||||
expect(promptBody.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.7" })
|
||||
})
|
||||
|
||||
test("should fallback to parentAgent when currentMessage.agent is undefined", async () => {
|
||||
@@ -1155,7 +1155,7 @@ describe("BackgroundManager.notifyParentSession - notifications toggle", () => {
|
||||
agent: "explore",
|
||||
model: {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4.6",
|
||||
modelID: "claude-opus-4.7",
|
||||
variant: "high",
|
||||
},
|
||||
},
|
||||
@@ -1211,7 +1211,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
|
||||
agent: "explore",
|
||||
model: {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4.6",
|
||||
modelID: "claude-opus-4.7",
|
||||
variant: "max",
|
||||
},
|
||||
},
|
||||
@@ -1231,7 +1231,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
|
||||
status: "completed",
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6", variant: "high" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7", variant: "high" },
|
||||
}
|
||||
getPendingByParent(manager).set("session-parent", new Set([task.id]))
|
||||
|
||||
@@ -1272,7 +1272,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
|
||||
status: "completed",
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
|
||||
}
|
||||
getPendingByParent(manager).set("session-parent", new Set([task.id]))
|
||||
|
||||
@@ -1349,7 +1349,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
||||
|
||||
test("should release concurrency and clear key on completion", async () => {
|
||||
// given
|
||||
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||
const concurrencyKey = "anthropic/claude-opus-4.7"
|
||||
const concurrencyManager = getConcurrencyManager(manager)
|
||||
await concurrencyManager.acquire(concurrencyKey)
|
||||
|
||||
@@ -1378,7 +1378,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
||||
|
||||
test("should prevent double completion and double release", async () => {
|
||||
// given
|
||||
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||
const concurrencyKey = "anthropic/claude-opus-4.7"
|
||||
const concurrencyManager = getConcurrencyManager(manager)
|
||||
await concurrencyManager.acquire(concurrencyKey)
|
||||
|
||||
@@ -1508,7 +1508,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
||||
|
||||
test("should release task concurrencyKey when startTask throws after assigning it", async () => {
|
||||
// given
|
||||
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||
const concurrencyKey = "anthropic/claude-opus-4.7"
|
||||
const concurrencyManager = getConcurrencyManager(manager)
|
||||
|
||||
const task = createMockTask({
|
||||
@@ -1524,7 +1524,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
||||
agent: task.agent,
|
||||
parentSessionID: task.parentSessionID,
|
||||
parentMessageID: task.parentMessageID,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
|
||||
}
|
||||
getTaskMap(manager).set(task.id, task)
|
||||
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
|
||||
@@ -1544,7 +1544,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
||||
|
||||
test("should mark task as error when startTask throws after session creation", async () => {
|
||||
//#given - startTask creates session but fails before sending prompt
|
||||
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||
const concurrencyKey = "anthropic/claude-opus-4.7"
|
||||
|
||||
const task = createMockTask({
|
||||
id: "task-zombie-session",
|
||||
@@ -1561,7 +1561,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
||||
agent: task.agent,
|
||||
parentSessionID: task.parentSessionID,
|
||||
parentMessageID: task.parentMessageID,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
|
||||
}
|
||||
getTaskMap(manager).set(task.id, task)
|
||||
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
|
||||
@@ -1585,7 +1585,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
||||
|
||||
test("should release queue slot when queued task is already interrupt", async () => {
|
||||
// given
|
||||
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||
const concurrencyKey = "anthropic/claude-opus-4.7"
|
||||
const concurrencyManager = getConcurrencyManager(manager)
|
||||
|
||||
const task = createMockTask({
|
||||
@@ -1601,7 +1601,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
||||
agent: task.agent,
|
||||
parentSessionID: task.parentSessionID,
|
||||
parentMessageID: task.parentMessageID,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
|
||||
}
|
||||
getTaskMap(manager).set(task.id, task)
|
||||
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
|
||||
@@ -2104,7 +2104,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
|
||||
agent: "test-agent",
|
||||
parentSessionID: "parent-session",
|
||||
parentMessageID: "parent-message",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
|
||||
}
|
||||
const launchInputWithoutModel = {
|
||||
description: "Test task without model",
|
||||
@@ -2124,7 +2124,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
|
||||
expect(taskWithModel.status).toBe("pending")
|
||||
expect(taskWithoutModel.status).toBe("pending")
|
||||
expect(promptBodies).toHaveLength(2)
|
||||
expect(promptBodies[0].model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.6" })
|
||||
expect(promptBodies[0].model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.7" })
|
||||
expect(promptBodies[0].agent).toBe("test-agent")
|
||||
expect(promptBodies[1].agent).toBe("test-agent")
|
||||
expect("model" in promptBodies[1]).toBe(false)
|
||||
@@ -3245,7 +3245,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
|
||||
description: "Task 1",
|
||||
prompt: "Do something",
|
||||
agent: "test-agent",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
|
||||
parentSessionID: "parent-session",
|
||||
parentMessageID: "parent-message",
|
||||
}
|
||||
@@ -4225,7 +4225,7 @@ describe("BackgroundManager.handleEvent - session.deleted cascade", () => {
|
||||
|
||||
describe("BackgroundManager.handleEvent - session.error", () => {
|
||||
const defaultRetryFallbackChain = [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7", variant: "max" },
|
||||
{ providers: ["anthropic"], model: "gpt-5.3-codex", variant: "high" },
|
||||
]
|
||||
|
||||
@@ -4249,7 +4249,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
||||
agent: "sisyphus",
|
||||
status: "running",
|
||||
concurrencyKey: input.concurrencyKey,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.6-thinking" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4.7-thinking" },
|
||||
fallbackChain: input.fallbackChain ?? defaultRetryFallbackChain,
|
||||
attemptCount: 0,
|
||||
})
|
||||
@@ -4394,7 +4394,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
||||
//#given
|
||||
const manager = createBackgroundManager()
|
||||
const concurrencyManager = getConcurrencyManager(manager)
|
||||
const concurrencyKey = "anthropic/claude-opus-4.6-thinking"
|
||||
const concurrencyKey = "anthropic/claude-opus-4.7-thinking"
|
||||
await concurrencyManager.acquire(concurrencyKey)
|
||||
|
||||
stubProcessKey(manager)
|
||||
@@ -4406,7 +4406,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
||||
description: "task that should retry",
|
||||
concurrencyKey,
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7", variant: "max" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-5", variant: "max" },
|
||||
],
|
||||
})
|
||||
@@ -4420,7 +4420,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
||||
name: "UnknownError",
|
||||
data: {
|
||||
message:
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4.6-thinking\"}}",
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4.7-thinking\"}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4431,7 +4431,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
||||
expect(task.attemptCount).toBe(1)
|
||||
expect(task.model).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4.6",
|
||||
modelID: "claude-opus-4.7",
|
||||
variant: "max",
|
||||
})
|
||||
expect(task.concurrencyKey).toBeUndefined()
|
||||
@@ -4469,7 +4469,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
||||
expect(task.attemptCount).toBe(1)
|
||||
expect(task.model).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4.6",
|
||||
modelID: "claude-opus-4.7",
|
||||
variant: "max",
|
||||
})
|
||||
|
||||
@@ -4497,7 +4497,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
||||
name: "UnknownError",
|
||||
data: {
|
||||
message:
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4.6-thinking\"}}",
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4.7-thinking\"}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -4514,7 +4514,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
||||
expect(task.attemptCount).toBe(1)
|
||||
expect(task.model).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4.6",
|
||||
modelID: "claude-opus-4.7",
|
||||
variant: "max",
|
||||
})
|
||||
|
||||
|
||||
@@ -648,7 +648,7 @@ describe("checkAndInterruptStaleTasks", () => {
|
||||
const task = createRunningTask({
|
||||
startedAt: new Date(Date.now() - 15 * 60 * 1000),
|
||||
progress: undefined,
|
||||
concurrencyKey: "anthropic/claude-opus-4-6",
|
||||
concurrencyKey: "anthropic/claude-opus-4-7",
|
||||
})
|
||||
|
||||
//#when
|
||||
@@ -661,7 +661,7 @@ describe("checkAndInterruptStaleTasks", () => {
|
||||
})
|
||||
|
||||
//#then
|
||||
expect(releaseMock).toHaveBeenCalledWith("anthropic/claude-opus-4-6")
|
||||
expect(releaseMock).toHaveBeenCalledWith("anthropic/claude-opus-4-7")
|
||||
expect(task.concurrencyKey).toBeUndefined()
|
||||
})
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ describe("mapClaudeModelToOpenCode", () => {
|
||||
expect(mapClaudeModelToOpenCode("sonnet")).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-6" })
|
||||
})
|
||||
|
||||
it("#when called with opus #then maps to anthropic claude-opus-4-6 object", () => {
|
||||
expect(mapClaudeModelToOpenCode("opus")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
|
||||
it("#when called with opus #then maps to anthropic claude-opus-4-7 object", () => {
|
||||
expect(mapClaudeModelToOpenCode("opus")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
|
||||
})
|
||||
|
||||
it("#when called with haiku #then maps to anthropic claude-haiku-4-5 object", () => {
|
||||
@@ -47,8 +47,8 @@ describe("mapClaudeModelToOpenCode", () => {
|
||||
expect(mapClaudeModelToOpenCode("claude-sonnet-4-5-20250514")).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-5-20250514" })
|
||||
})
|
||||
|
||||
it("#when called with claude-opus-4-6 #then adds anthropic object format", () => {
|
||||
expect(mapClaudeModelToOpenCode("claude-opus-4-6")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
|
||||
it("#when called with claude-opus-4-7 #then adds anthropic object format", () => {
|
||||
expect(mapClaudeModelToOpenCode("claude-opus-4-7")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
|
||||
})
|
||||
|
||||
it("#when called with claude-haiku-4-5-20251001 #then adds anthropic object format", () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ const ANTHROPIC_PREFIX = "anthropic/"
|
||||
|
||||
const CLAUDE_CODE_ALIAS_MAP = new Map<string, string>([
|
||||
["sonnet", `${ANTHROPIC_PREFIX}claude-sonnet-4-6`],
|
||||
["opus", `${ANTHROPIC_PREFIX}claude-opus-4-6`],
|
||||
["opus", `${ANTHROPIC_PREFIX}claude-opus-4-7`],
|
||||
["haiku", `${ANTHROPIC_PREFIX}claude-haiku-4-5`],
|
||||
])
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("readOpencodeConfigAgents", () => {
|
||||
agents: {
|
||||
"my-agent": {
|
||||
description: "Custom agent",
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
mode: "subagent",
|
||||
prompt: "You are a helpful assistant",
|
||||
},
|
||||
|
||||
@@ -203,7 +203,7 @@ describe("TaskToastManager", () => {
|
||||
description: "Task with inherited model",
|
||||
agent: "sisyphus-junior",
|
||||
isBackground: false,
|
||||
modelInfo: { model: "cliproxy/claude-opus-4-6", type: "inherited" as const },
|
||||
modelInfo: { model: "cliproxy/claude-opus-4-7", type: "inherited" as const },
|
||||
}
|
||||
|
||||
// when - addTask is called
|
||||
@@ -213,7 +213,7 @@ describe("TaskToastManager", () => {
|
||||
expect(mockClient.tui.showToast).toHaveBeenCalled()
|
||||
const call = mockClient.tui.showToast.mock.calls[0][0]
|
||||
expect(call.body.message).toContain("[FALLBACK]")
|
||||
expect(call.body.message).toContain("cliproxy/claude-opus-4-6")
|
||||
expect(call.body.message).toContain("cliproxy/claude-opus-4-7")
|
||||
expect(call.body.message).toContain("(inherited from parent)")
|
||||
})
|
||||
|
||||
|
||||
+39007
-37219
File diff suppressed because it is too large
Load Diff
@@ -84,7 +84,7 @@ describe("executeCompact lock management", () => {
|
||||
let pluginConfig: ReturnType<typeof OhMyOpenCodeConfigSchema.parse>
|
||||
const sessionID = "test-session-123"
|
||||
const directory = "/test/dir"
|
||||
const msg = { providerID: "anthropic", modelID: "claude-opus-4-6" }
|
||||
const msg = { providerID: "anthropic", modelID: "claude-opus-4-7" }
|
||||
|
||||
beforeEach(() => {
|
||||
// given: Fresh state for each test
|
||||
@@ -132,7 +132,7 @@ describe("executeCompact lock management", () => {
|
||||
expect(mockClient.session.summarize).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
path: { id: sessionID },
|
||||
body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true },
|
||||
body: { providerID: "anthropic", modelID: "claude-opus-4-7", auto: true },
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -157,7 +157,7 @@ describe("executeCompact lock management", () => {
|
||||
expect(mockClient.session.summarize).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
path: { id: sessionID },
|
||||
body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true },
|
||||
body: { providerID: "anthropic", modelID: "claude-opus-4-7", auto: true },
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -352,7 +352,7 @@ describe("executeCompact lock management", () => {
|
||||
expect(mockClient.session.summarize).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
path: { id: sessionID },
|
||||
body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true },
|
||||
body: { providerID: "anthropic", modelID: "claude-opus-4-7", auto: true },
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ function createMockParams(overrides: {
|
||||
existingOptions?: Record<string, unknown>
|
||||
}): { input: ChatParamsInput; output: ChatParamsOutput } {
|
||||
const providerID = overrides.providerID ?? "anthropic"
|
||||
const modelID = overrides.modelID ?? "claude-opus-4-6"
|
||||
const modelID = overrides.modelID ?? "claude-opus-4-7"
|
||||
const variant = "variant" in overrides ? overrides.variant : "max"
|
||||
const agentName = overrides.agentName ?? "sisyphus"
|
||||
const existingOptions = overrides.existingOptions ?? {}
|
||||
@@ -71,7 +71,7 @@ describe("createAnthropicEffortHook", () => {
|
||||
|
||||
it("injects effort max for dotted opus ids", async () => {
|
||||
const hook = createAnthropicEffortHook()
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4.6" })
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4.7" })
|
||||
|
||||
await hook["chat.params"](input, output)
|
||||
|
||||
@@ -158,7 +158,7 @@ describe("createAnthropicEffortHook", () => {
|
||||
const hook = createAnthropicEffortHook()
|
||||
const { input, output } = createMockParams({
|
||||
providerID: "github-copilot",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
|
||||
// when
|
||||
@@ -256,7 +256,7 @@ describe("createAnthropicEffortHook", () => {
|
||||
// given an Anthropic OAuth session and variant=max on an Opus model
|
||||
writeAuthFile({ anthropic: { type: "oauth" } })
|
||||
const hook = createAnthropicEffortHook()
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4-6" })
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4-7" })
|
||||
|
||||
// when chat.params fires
|
||||
await hook["chat.params"](input, output)
|
||||
@@ -270,7 +270,7 @@ describe("createAnthropicEffortHook", () => {
|
||||
// given an Anthropic OAuth session and a dotted opus id
|
||||
writeAuthFile({ anthropic: { type: "oauth" } })
|
||||
const hook = createAnthropicEffortHook()
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4.6" })
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4.7" })
|
||||
|
||||
// when chat.params fires
|
||||
await hook["chat.params"](input, output)
|
||||
@@ -284,7 +284,7 @@ describe("createAnthropicEffortHook", () => {
|
||||
// given an Anthropic API-key session (not OAuth)
|
||||
writeAuthFile({ anthropic: { type: "api", key: "sk-ant-xxx" } })
|
||||
const hook = createAnthropicEffortHook()
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4-6" })
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4-7" })
|
||||
|
||||
// when chat.params fires
|
||||
await hook["chat.params"](input, output)
|
||||
@@ -298,7 +298,7 @@ describe("createAnthropicEffortHook", () => {
|
||||
// given OAuth entries for unrelated providers only
|
||||
writeAuthFile({ "github-copilot": { type: "oauth" }, opencode: { type: "api", key: "sk-x" } })
|
||||
const hook = createAnthropicEffortHook()
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4-6", providerID: "anthropic" })
|
||||
const { input, output } = createMockParams({ modelID: "claude-opus-4-7", providerID: "anthropic" })
|
||||
|
||||
// when chat.params fires for the anthropic provider
|
||||
await hook["chat.params"](input, output)
|
||||
|
||||
@@ -58,7 +58,7 @@ describe("atlas hook compaction agent filtering", () => {
|
||||
join(messageDir, fileName),
|
||||
JSON.stringify({
|
||||
agent,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ describe("Atlas final-wave approval gate regressions", () => {
|
||||
join(messageDirectory, "msg_test001.json"),
|
||||
JSON.stringify({
|
||||
agent: "atlas",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ describe("Atlas final verification approval gate", () => {
|
||||
join(messageDirectory, "msg_test001.json"),
|
||||
JSON.stringify({
|
||||
agent: "atlas",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ describe("atlas hook", () => {
|
||||
}
|
||||
const messageData = {
|
||||
agent,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}
|
||||
writeFileSync(join(messageDir, "msg_test001.json"), JSON.stringify(messageData))
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const selectFallbackProviderMock = mock((providers: string[], preferredProviderI
|
||||
const transformModelForProviderMock = mock((provider: string, model: string) => {
|
||||
if (provider === "github-copilot") {
|
||||
return model
|
||||
.replace("claude-opus-4-6", "claude-opus-4.6")
|
||||
.replace("claude-opus-4-7", "claude-opus-4.7")
|
||||
.replace("claude-sonnet-4-6", "claude-sonnet-4.6")
|
||||
.replace("claude-sonnet-4-5", "claude-sonnet-4.5")
|
||||
.replace("claude-haiku-4-5", "claude-haiku-4.5")
|
||||
@@ -96,13 +96,13 @@ describe("model fallback hook", () => {
|
||||
"ses_model_fallback_main",
|
||||
"Sisyphus - Ultraworker",
|
||||
"anthropic",
|
||||
"claude-opus-4-6-thinking",
|
||||
"claude-opus-4-7-thinking",
|
||||
)
|
||||
expect(set).toBe(true)
|
||||
|
||||
const output = {
|
||||
message: {
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
|
||||
variant: "max",
|
||||
},
|
||||
parts: [{ type: "text", text: "continue" }],
|
||||
@@ -117,7 +117,7 @@ describe("model fallback hook", () => {
|
||||
//#then
|
||||
expect(output.message["model"]).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -132,12 +132,12 @@ describe("model fallback hook", () => {
|
||||
const sessionID = "ses_model_fallback_main"
|
||||
|
||||
expect(
|
||||
setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-6-thinking"),
|
||||
setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-7-thinking"),
|
||||
).toBe(true)
|
||||
|
||||
const firstOutput = {
|
||||
message: {
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
|
||||
variant: "max",
|
||||
},
|
||||
parts: [{ type: "text", text: "continue" }],
|
||||
@@ -149,17 +149,17 @@ describe("model fallback hook", () => {
|
||||
//#then
|
||||
expect(firstOutput.message["model"]).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
|
||||
//#when - second error re-arms fallback and should advance to next entry
|
||||
expect(
|
||||
setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-6"),
|
||||
setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-7"),
|
||||
).toBe(true)
|
||||
|
||||
const secondOutput = {
|
||||
message: {
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
parts: [{ type: "text", text: "continue" }],
|
||||
}
|
||||
@@ -183,13 +183,13 @@ describe("model fallback hook", () => {
|
||||
sessionID,
|
||||
"Sisyphus - Ultraworker",
|
||||
"anthropic",
|
||||
"claude-opus-4-6-thinking",
|
||||
"claude-opus-4-7-thinking",
|
||||
)
|
||||
const secondSet = setPendingModelFallback(
|
||||
sessionID,
|
||||
"Sisyphus - Ultraworker",
|
||||
"anthropic",
|
||||
"claude-opus-4-6-thinking",
|
||||
"claude-opus-4-7-thinking",
|
||||
)
|
||||
|
||||
//#then
|
||||
@@ -211,7 +211,7 @@ describe("model fallback hook", () => {
|
||||
}
|
||||
|
||||
setSessionFallbackChain(sessionID, [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||
])
|
||||
|
||||
@@ -220,13 +220,13 @@ describe("model fallback hook", () => {
|
||||
sessionID,
|
||||
"Sisyphus - Ultraworker",
|
||||
"anthropic",
|
||||
"claude-opus-4-6",
|
||||
"claude-opus-4-7",
|
||||
),
|
||||
).toBe(true)
|
||||
|
||||
const output = {
|
||||
message: {
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
parts: [{ type: "text", text: "continue" }],
|
||||
}
|
||||
@@ -255,7 +255,7 @@ describe("model fallback hook", () => {
|
||||
}
|
||||
|
||||
setSessionFallbackChain(sessionID, [
|
||||
{ providers: ["quotio"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["quotio"], model: "claude-opus-4-7", variant: "max" },
|
||||
{ providers: ["quotio"], model: "gpt-5.2" },
|
||||
])
|
||||
|
||||
@@ -264,13 +264,13 @@ describe("model fallback hook", () => {
|
||||
sessionID,
|
||||
"Sisyphus - Ultraworker",
|
||||
"quotio",
|
||||
"claude-opus-4-6",
|
||||
"claude-opus-4-7",
|
||||
),
|
||||
).toBe(true)
|
||||
|
||||
const output = {
|
||||
message: {
|
||||
model: { providerID: "quotio", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "quotio", modelID: "claude-opus-4-7" },
|
||||
variant: "max",
|
||||
},
|
||||
parts: [{ type: "text", text: "continue" }],
|
||||
@@ -369,13 +369,13 @@ describe("model fallback hook", () => {
|
||||
"ses_model_fallback_toast",
|
||||
"Sisyphus - Ultraworker",
|
||||
"anthropic",
|
||||
"claude-opus-4-6-thinking",
|
||||
"claude-opus-4-7-thinking",
|
||||
)
|
||||
expect(set).toBe(true)
|
||||
|
||||
const output = {
|
||||
message: {
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
|
||||
variant: "max",
|
||||
},
|
||||
parts: [{ type: "text", text: "continue" }],
|
||||
|
||||
@@ -30,12 +30,12 @@ describe("no-hephaestus-non-gpt hook", () => {
|
||||
await hook["chat.message"]?.({
|
||||
sessionID: "ses_1",
|
||||
agent: HEPHAESTUS_DISPLAY,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}, output1)
|
||||
await hook["chat.message"]?.({
|
||||
sessionID: "ses_1",
|
||||
agent: HEPHAESTUS_DISPLAY,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}, output2)
|
||||
|
||||
// then - toast is shown and agent is switched to sisyphus
|
||||
@@ -66,7 +66,7 @@ describe("no-hephaestus-non-gpt hook", () => {
|
||||
await hook["chat.message"]?.({
|
||||
sessionID: "ses_opt_out",
|
||||
agent: HEPHAESTUS_DISPLAY,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}, output)
|
||||
|
||||
// then - warning toast is shown but agent is not switched
|
||||
@@ -114,7 +114,7 @@ describe("no-hephaestus-non-gpt hook", () => {
|
||||
await hook["chat.message"]?.({
|
||||
sessionID: "ses_3",
|
||||
agent: SISYPHUS_DISPLAY,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}, output)
|
||||
|
||||
// then - no toast
|
||||
@@ -136,7 +136,7 @@ describe("no-hephaestus-non-gpt hook", () => {
|
||||
// when - chat.message runs without input.agent
|
||||
await hook["chat.message"]?.({
|
||||
sessionID: "ses_4",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}, output)
|
||||
|
||||
// then - toast shown via session-agent fallback, switched to sisyphus
|
||||
|
||||
@@ -83,7 +83,7 @@ describe("no-sisyphus-gpt hook", () => {
|
||||
await hook["chat.message"]?.({
|
||||
sessionID: "ses_2",
|
||||
agent: SISYPHUS_DISPLAY,
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
}, output)
|
||||
|
||||
// then - no toast
|
||||
|
||||
@@ -125,10 +125,10 @@ describe("createRuntimeFallbackHook dispose", () => {
|
||||
const fallbackTimeout = setTimeout(() => {}, 60_000)
|
||||
|
||||
capturedDeps?.sessionStates.set("session-1", {
|
||||
originalModel: "anthropic/claude-opus-4-6",
|
||||
originalModel: "anthropic/claude-opus-4-7",
|
||||
currentModel: "openai/gpt-5.4",
|
||||
fallbackIndex: 1,
|
||||
failedModels: new Map([["anthropic/claude-opus-4-6", 1]]),
|
||||
failedModels: new Map([["anthropic/claude-opus-4-7", 1]]),
|
||||
attemptCount: 1,
|
||||
})
|
||||
capturedDeps?.sessionLastAccess.set("session-1", Date.now())
|
||||
|
||||
@@ -7,7 +7,7 @@ describe("runtime-fallback error classifier", () => {
|
||||
//#given
|
||||
const info = {
|
||||
status:
|
||||
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
"All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -21,7 +21,7 @@ describe("runtime-fallback error classifier", () => {
|
||||
//#given
|
||||
const info = {
|
||||
status:
|
||||
"All credentials for model claude-opus-4-6 are cooldown [retrying in 7m 56s attempt #1]",
|
||||
"All credentials for model claude-opus-4-7 are cooldown [retrying in 7m 56s attempt #1]",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -49,7 +49,7 @@ describe("runtime-fallback error classifier", () => {
|
||||
//#given
|
||||
const error = {
|
||||
message:
|
||||
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
"All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -65,8 +65,8 @@ describe("runtime-fallback error classifier", () => {
|
||||
name: "ProviderModelNotFoundError",
|
||||
data: {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
message: "Model not found: anthropic/claude-opus-4-6.",
|
||||
modelID: "claude-opus-4-7",
|
||||
message: "Model not found: anthropic/claude-opus-4-7.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("runtime-fallback fallback-models", () => {
|
||||
const pluginConfig = {
|
||||
categories: {
|
||||
quick: {
|
||||
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-6"],
|
||||
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
|
||||
},
|
||||
},
|
||||
} as any
|
||||
@@ -24,7 +24,7 @@ describe("runtime-fallback fallback-models", () => {
|
||||
const result = getFallbackModelsForSession(sessionID, undefined, pluginConfig)
|
||||
|
||||
//#then
|
||||
expect(result).toEqual(["openai/gpt-5.2", "anthropic/claude-opus-4-6"])
|
||||
expect(result).toEqual(["openai/gpt-5.2", "anthropic/claude-opus-4-7"])
|
||||
})
|
||||
|
||||
test("uses agent-specific fallback_models when agent is resolved", () => {
|
||||
@@ -32,7 +32,7 @@ describe("runtime-fallback fallback-models", () => {
|
||||
const pluginConfig = {
|
||||
agents: {
|
||||
oracle: {
|
||||
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-6"],
|
||||
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
|
||||
},
|
||||
},
|
||||
} as any
|
||||
@@ -41,7 +41,7 @@ describe("runtime-fallback fallback-models", () => {
|
||||
const result = getFallbackModelsForSession("ses_runtime_fallback_agent", "oracle", pluginConfig)
|
||||
|
||||
//#then
|
||||
expect(result).toEqual(["openai/gpt-5.2", "anthropic/claude-opus-4-6"])
|
||||
expect(result).toEqual(["openai/gpt-5.2", "anthropic/claude-opus-4-7"])
|
||||
})
|
||||
|
||||
test("does not fall back to another agent chain when agent cannot be resolved", () => {
|
||||
@@ -52,7 +52,7 @@ describe("runtime-fallback fallback-models", () => {
|
||||
fallback_models: ["quotio/gpt-5.2", "quotio/glm-5", "quotio/kimi-k2.5"],
|
||||
},
|
||||
oracle: {
|
||||
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-6"],
|
||||
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
@@ -51,7 +51,7 @@ describe("createRuntimeFallbackHook dispose retry-key cleanup", () => {
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.created",
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } },
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ describe("createRuntimeFallbackHook dispose retry-key cleanup", () => {
|
||||
status: {
|
||||
type: "retry",
|
||||
attempt: 1,
|
||||
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
message: "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -77,7 +77,7 @@ describe("createRuntimeFallbackHook dispose retry-key cleanup", () => {
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.created",
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } },
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
|
||||
},
|
||||
})
|
||||
await hook.event(retryEvent)
|
||||
|
||||
@@ -329,7 +329,7 @@ describe("runtime-fallback", () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"anthropic/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4.7",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
})
|
||||
@@ -365,14 +365,14 @@ describe("runtime-fallback", () => {
|
||||
type: "session.error",
|
||||
properties: {
|
||||
sessionID,
|
||||
error: { name: "UnknownError", data: { message: "Model not found: anthropic/claude-opus-4.6." } },
|
||||
error: { name: "UnknownError", data: { message: "Model not found: anthropic/claude-opus-4.7." } },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
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.4" })
|
||||
expect(fallbackLogs[1]?.data).toMatchObject({ from: "anthropic/claude-opus-4.7", to: "openai/gpt-5.4" })
|
||||
|
||||
const nonRetryLog = logCalls.find(
|
||||
(c) => c.msg.includes("Error not retryable") && (c.data as { sessionID?: string } | undefined)?.sessionID === sessionID
|
||||
@@ -384,7 +384,7 @@ describe("runtime-fallback", () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"anthropic/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4.7",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
})
|
||||
@@ -421,8 +421,8 @@ describe("runtime-fallback", () => {
|
||||
name: "ProviderModelNotFoundError",
|
||||
data: {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4.6",
|
||||
message: "Model not found: anthropic/claude-opus-4.6.",
|
||||
modelID: "claude-opus-4.7",
|
||||
message: "Model not found: anthropic/claude-opus-4.7.",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -431,7 +431,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.4" })
|
||||
expect(fallbackLogs[1]?.data).toMatchObject({ from: "anthropic/claude-opus-4.7", to: "openai/gpt-5.4" })
|
||||
})
|
||||
|
||||
test("should bootstrap session.error fallback from session category model and preserve variant", async () => {
|
||||
@@ -500,7 +500,7 @@ describe("runtime-fallback", () => {
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.created",
|
||||
properties: { info: { id: sessionID, model: "github-copilot/claude-opus-4.6" } },
|
||||
properties: { info: { id: sessionID, model: "github-copilot/claude-opus-4.7" } },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -511,7 +511,7 @@ describe("runtime-fallback", () => {
|
||||
info: {
|
||||
sessionID,
|
||||
role: "assistant",
|
||||
model: "github-copilot/claude-opus-4.6",
|
||||
model: "github-copilot/claude-opus-4.7",
|
||||
status:
|
||||
"Too Many Requests: quota exceeded [retrying in ~2 weeks attempt #1]",
|
||||
},
|
||||
@@ -524,13 +524,13 @@ 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.4" })
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "github-copilot/claude-opus-4.7", to: "openai/gpt-5.4" })
|
||||
})
|
||||
|
||||
test("should trigger fallback on OpenAI auto-retry signal in message.updated", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-6"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-7"]),
|
||||
})
|
||||
|
||||
const sessionID = "test-session-openai-auto-retry"
|
||||
@@ -562,7 +562,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "openai/gpt-5.3-codex", to: "anthropic/claude-opus-4-6" })
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "openai/gpt-5.3-codex", to: "anthropic/claude-opus-4-7" })
|
||||
})
|
||||
|
||||
test("should trigger fallback on auto-retry signal in assistant text parts", async () => {
|
||||
@@ -577,7 +577,7 @@ describe("runtime-fallback", () => {
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.created",
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } },
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -588,7 +588,7 @@ describe("runtime-fallback", () => {
|
||||
info: {
|
||||
sessionID,
|
||||
role: "assistant",
|
||||
model: "quotio/claude-opus-4-6",
|
||||
model: "quotio/claude-opus-4-7",
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
@@ -605,7 +605,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "quotio/claude-opus-4-6", to: "openai/gpt-5.2" })
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "quotio/claude-opus-4-7", to: "openai/gpt-5.2" })
|
||||
})
|
||||
|
||||
test("should trigger fallback when auto-retry text parts are nested under info.parts", async () => {
|
||||
@@ -620,7 +620,7 @@ describe("runtime-fallback", () => {
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.created",
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } },
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -631,7 +631,7 @@ describe("runtime-fallback", () => {
|
||||
info: {
|
||||
sessionID,
|
||||
role: "assistant",
|
||||
model: "quotio/claude-opus-4-6",
|
||||
model: "quotio/claude-opus-4-7",
|
||||
parts: [
|
||||
{
|
||||
type: "text",
|
||||
@@ -648,7 +648,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "quotio/claude-opus-4-6", to: "openai/gpt-5.2" })
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "quotio/claude-opus-4-7", to: "openai/gpt-5.2" })
|
||||
})
|
||||
|
||||
test("should trigger fallback on session.status auto-retry signal", async () => {
|
||||
@@ -682,7 +682,7 @@ describe("runtime-fallback", () => {
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.created",
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } },
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -695,7 +695,7 @@ describe("runtime-fallback", () => {
|
||||
type: "retry",
|
||||
next: 476,
|
||||
attempt: 1,
|
||||
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
message: "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -706,7 +706,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "quotio/claude-opus-4-6", to: "openai/gpt-5.2" })
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "quotio/claude-opus-4-7", to: "openai/gpt-5.2" })
|
||||
expect(promptCalls.length).toBe(1)
|
||||
})
|
||||
|
||||
@@ -741,7 +741,7 @@ describe("runtime-fallback", () => {
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.created",
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } },
|
||||
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -754,7 +754,7 @@ describe("runtime-fallback", () => {
|
||||
type: "retry",
|
||||
next: 476,
|
||||
attempt: 1,
|
||||
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
message: "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -769,7 +769,7 @@ describe("runtime-fallback", () => {
|
||||
type: "retry",
|
||||
next: 475,
|
||||
attempt: 1,
|
||||
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 55s attempt #1]",
|
||||
message: "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 55s attempt #1]",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -781,7 +781,7 @@ describe("runtime-fallback", () => {
|
||||
test("should NOT trigger fallback on auto-retry signal when timeout_seconds is 0", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 0 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-6"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-7"]),
|
||||
})
|
||||
|
||||
const sessionID = "test-session-auto-retry-timeout-disabled"
|
||||
@@ -1161,8 +1161,8 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
}
|
||||
@@ -1212,7 +1212,7 @@ describe("runtime-fallback", () => {
|
||||
"Google Generative AI API key is missing. Pass it using the 'apiKey' parameter or the GOOGLE_GENERATIVE_AI_API_KEY environment variable.",
|
||||
},
|
||||
},
|
||||
model: "github-copilot/claude-opus-4.6",
|
||||
model: "github-copilot/claude-opus-4.7",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1251,8 +1251,8 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
}
|
||||
@@ -1294,7 +1294,7 @@ describe("runtime-fallback", () => {
|
||||
info: {
|
||||
sessionID,
|
||||
role: "assistant",
|
||||
model: "github-copilot/claude-opus-4.6",
|
||||
model: "github-copilot/claude-opus-4.7",
|
||||
status:
|
||||
"Too Many Requests: quota exceeded [retrying in ~2 weeks attempt #1]",
|
||||
},
|
||||
@@ -1303,8 +1303,8 @@ describe("runtime-fallback", () => {
|
||||
})
|
||||
|
||||
expect(retriedModels.length).toBeGreaterThanOrEqual(2)
|
||||
expect(retriedModels[0]).toBe("github-copilot/claude-opus-4.6")
|
||||
expect(retriedModels[1]).toBe("anthropic/claude-opus-4-6")
|
||||
expect(retriedModels[0]).toBe("github-copilot/claude-opus-4.7")
|
||||
expect(retriedModels[1]).toBe("anthropic/claude-opus-4-7")
|
||||
|
||||
void sessionErrorPromise
|
||||
})
|
||||
@@ -1335,8 +1335,8 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
@@ -1372,8 +1372,8 @@ describe("runtime-fallback", () => {
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
|
||||
expect(retriedModels).toContain("github-copilot/claude-opus-4.6")
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-6")
|
||||
expect(retriedModels).toContain("github-copilot/claude-opus-4.7")
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-7")
|
||||
expect(abortCalls.some((call) => call.path?.id === sessionID)).toBe(true)
|
||||
|
||||
const timeoutLog = logCalls.find((c) => c.msg.includes("Session fallback timeout reached"))
|
||||
@@ -1401,8 +1401,8 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
@@ -1443,15 +1443,15 @@ describe("runtime-fallback", () => {
|
||||
await hook["chat.message"]?.(
|
||||
{
|
||||
sessionID,
|
||||
model: { providerID: "github-copilot", modelID: "claude-opus-4.6" },
|
||||
model: { providerID: "github-copilot", modelID: "claude-opus-4.7" },
|
||||
},
|
||||
output
|
||||
)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
|
||||
expect(retriedModels).toContain("github-copilot/claude-opus-4.6")
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-6")
|
||||
expect(retriedModels).toContain("github-copilot/claude-opus-4.7")
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("should abort in-flight fallback request before advancing on timeout", async () => {
|
||||
@@ -1486,8 +1486,8 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
@@ -1524,8 +1524,8 @@ describe("runtime-fallback", () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
|
||||
expect(abortCalls.some((call) => call.path?.id === sessionID)).toBe(true)
|
||||
expect(retriedModels).toContain("github-copilot/claude-opus-4.6")
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-6")
|
||||
expect(retriedModels).toContain("github-copilot/claude-opus-4.7")
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-7")
|
||||
|
||||
void sessionErrorPromise
|
||||
})
|
||||
@@ -1551,8 +1551,8 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
@@ -1586,7 +1586,7 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(retriedModels).toContain("github-copilot/claude-opus-4.6")
|
||||
expect(retriedModels).toContain("github-copilot/claude-opus-4.7")
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
@@ -1624,9 +1624,9 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"openai/gpt-5.3-codex",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -1659,7 +1659,7 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"])
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
@@ -1695,7 +1695,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"])
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
|
||||
})
|
||||
|
||||
test("should not clear fallback timeout on assistant non-error update with Copilot retry signal", async () => {
|
||||
@@ -1719,9 +1719,9 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"openai/gpt-5.3-codex",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -1754,7 +1754,7 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"])
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
@@ -1796,7 +1796,7 @@ describe("runtime-fallback", () => {
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"openai/gpt-5.3-codex",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -1846,7 +1846,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 60))
|
||||
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-6")
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("should not clear fallback timeout on assistant non-error update without user-visible content", async () => {
|
||||
@@ -1870,9 +1870,9 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"openai/gpt-5.3-codex",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -1905,7 +1905,7 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"])
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
@@ -1914,7 +1914,7 @@ describe("runtime-fallback", () => {
|
||||
info: {
|
||||
sessionID,
|
||||
role: "assistant",
|
||||
model: "github-copilot/claude-opus-4.6",
|
||||
model: "github-copilot/claude-opus-4.7",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1946,9 +1946,9 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"openai/gpt-5.3-codex",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -1981,7 +1981,7 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"])
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
@@ -2022,9 +2022,9 @@ describe("runtime-fallback", () => {
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"openai/gpt-5.3-codex",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -2057,7 +2057,7 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"])
|
||||
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
@@ -2145,7 +2145,7 @@ describe("runtime-fallback", () => {
|
||||
}),
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-6"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-7"]),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2176,7 +2176,7 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-6")
|
||||
expect(retriedModels).toContain("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("does NOT trigger fallback for normal type:error-free messages", async () => {
|
||||
@@ -2452,7 +2452,7 @@ describe("runtime-fallback", () => {
|
||||
}),
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithAgentFallback("prometheus", ["github-copilot/claude-opus-4.6"]),
|
||||
pluginConfig: createMockPluginConfigWithAgentFallback("prometheus", ["github-copilot/claude-opus-4.7"]),
|
||||
},
|
||||
)
|
||||
const sessionID = "test-preserve-agent-on-retry"
|
||||
@@ -2462,7 +2462,7 @@ describe("runtime-fallback", () => {
|
||||
type: "session.error",
|
||||
properties: {
|
||||
sessionID,
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
error: { statusCode: 503, message: "Service unavailable" },
|
||||
agent: "prometheus",
|
||||
},
|
||||
@@ -2472,7 +2472,7 @@ describe("runtime-fallback", () => {
|
||||
expect(promptCalls.length).toBe(1)
|
||||
const callBody = promptCalls[0]?.body as Record<string, unknown>
|
||||
expect(callBody?.agent).toBe("prometheus")
|
||||
expect(callBody?.model).toEqual({ providerID: "github-copilot", modelID: "claude-opus-4.6" })
|
||||
expect(callBody?.model).toEqual({ providerID: "github-copilot", modelID: "claude-opus-4.7" })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ describe("runtime-fallback provider matrix quota tests", () => {
|
||||
//#given
|
||||
const error = {
|
||||
name: "AI_APICallError",
|
||||
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in ~2 weeks]",
|
||||
message: "All credentials for model claude-opus-4-7 are cooling down [retrying in ~2 weeks]",
|
||||
provider: "anthropic",
|
||||
}
|
||||
|
||||
|
||||
@@ -74,12 +74,12 @@ describe("createSessionStatusHandler", () => {
|
||||
const deps = createDeps()
|
||||
const abortCalls: string[] = []
|
||||
const retryCalls: Array<{ sessionID: string; model: string; source: string }> = []
|
||||
const state = createFallbackState("anthropic/claude-opus-4-6")
|
||||
const state = createFallbackState("anthropic/claude-opus-4-7")
|
||||
state.currentModel = "openai/gpt-5.4"
|
||||
state.fallbackIndex = 0
|
||||
state.attemptCount = 1
|
||||
state.pendingFallbackModel = "openai/gpt-5.4"
|
||||
state.failedModels.set("anthropic/claude-opus-4-6", Date.now())
|
||||
state.failedModels.set("anthropic/claude-opus-4-7", Date.now())
|
||||
deps.sessionStates.set(sessionID, state)
|
||||
|
||||
const handler = createSessionStatusHandler(deps, createHelpers(abortCalls, retryCalls), deps.sessionStatusRetryKeys)
|
||||
|
||||
@@ -49,7 +49,7 @@ describe("createThinkModeHook", () => {
|
||||
const input = createHookInput({
|
||||
sessionID,
|
||||
providerID: "github-copilot",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
const output = createHookOutput("Please think deeply about this")
|
||||
|
||||
|
||||
@@ -23,26 +23,26 @@ describe("think-mode switcher", () => {
|
||||
describe("getHighVariant with dots vs hyphens", () => {
|
||||
it("should handle dots in Claude version numbers", () => {
|
||||
// given a Claude model ID with dot format
|
||||
const variant = getHighVariant("claude-opus-4.6")
|
||||
const variant = getHighVariant("claude-opus-4.7")
|
||||
|
||||
// then should return high variant with hyphen format
|
||||
expect(variant).toBe("claude-opus-4-6-high")
|
||||
expect(variant).toBe("claude-opus-4-7-high")
|
||||
})
|
||||
|
||||
it("should handle hyphens in Claude version numbers", () => {
|
||||
// given a Claude model ID with hyphen format
|
||||
const variant = getHighVariant("claude-opus-4-6")
|
||||
const variant = getHighVariant("claude-opus-4-7")
|
||||
|
||||
// then should return high variant
|
||||
expect(variant).toBe("claude-opus-4-6-high")
|
||||
expect(variant).toBe("claude-opus-4-7-high")
|
||||
})
|
||||
|
||||
it("should handle claude-opus-4-6 high variant", () => {
|
||||
it("should handle claude-opus-4-7 high variant", () => {
|
||||
// given a Claude Opus 4.6 model ID
|
||||
const variant = getHighVariant("claude-opus-4-6")
|
||||
const variant = getHighVariant("claude-opus-4-7")
|
||||
|
||||
// then should return high variant
|
||||
expect(variant).toBe("claude-opus-4-6-high")
|
||||
expect(variant).toBe("claude-opus-4-7-high")
|
||||
})
|
||||
|
||||
it("should handle dots in GPT version numbers", () => {
|
||||
@@ -73,7 +73,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("claude-opus-4-7-high")).toBeNull()
|
||||
expect(getHighVariant("gpt-5-4-high")).toBeNull()
|
||||
expect(getHighVariant("gemini-3-1-pro-high")).toBeNull()
|
||||
})
|
||||
@@ -89,7 +89,7 @@ describe("think-mode switcher", () => {
|
||||
describe("isAlreadyHighVariant", () => {
|
||||
it("should detect -high suffix", () => {
|
||||
// given model IDs with -high suffix
|
||||
expect(isAlreadyHighVariant("claude-opus-4-6-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("claude-opus-4-7-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("gpt-5-4-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("gemini-3.1-pro-high")).toBe(true)
|
||||
})
|
||||
@@ -101,8 +101,8 @@ describe("think-mode switcher", () => {
|
||||
|
||||
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("claude-opus-4-7")).toBe(false)
|
||||
expect(isAlreadyHighVariant("claude-opus-4.7")).toBe(false)
|
||||
expect(isAlreadyHighVariant("gpt-5.4")).toBe(false)
|
||||
expect(isAlreadyHighVariant("gemini-3.1-pro")).toBe(false)
|
||||
})
|
||||
@@ -133,10 +133,10 @@ describe("think-mode switcher", () => {
|
||||
|
||||
it("should handle prefixes with dots in version numbers", () => {
|
||||
// given a model ID with prefix and dots
|
||||
const variant = getHighVariant("vertex_ai/claude-opus-4.6")
|
||||
const variant = getHighVariant("vertex_ai/claude-opus-4.7")
|
||||
|
||||
// then should normalize dots and preserve prefix
|
||||
expect(variant).toBe("vertex_ai/claude-opus-4-6-high")
|
||||
expect(variant).toBe("vertex_ai/claude-opus-4-7-high")
|
||||
})
|
||||
|
||||
it("should handle multiple different prefixes", () => {
|
||||
@@ -167,7 +167,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("vertex_ai/claude-opus-4-7-high")).toBeNull()
|
||||
expect(getHighVariant("openai/gpt-5-4-high")).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -175,14 +175,14 @@ describe("think-mode switcher", () => {
|
||||
describe("isAlreadyHighVariant with prefixes", () => {
|
||||
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("vertex_ai/claude-opus-4-7-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("vertex_ai/claude-opus-4-7")).toBe(false)
|
||||
expect(isAlreadyHighVariant("openai/gpt-5-4")).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ function extractModelPrefix(modelID: string): { prefix: string; base: string } {
|
||||
const HIGH_VARIANT_MAP: Record<string, string> = {
|
||||
// Claude
|
||||
"claude-sonnet-4-6": "claude-sonnet-4-6-high",
|
||||
"claude-opus-4-6": "claude-opus-4-6-high",
|
||||
"claude-opus-4-7": "claude-opus-4-7-high",
|
||||
// Gemini
|
||||
"gemini-3-1-pro": "gemini-3-1-pro-high",
|
||||
"gemini-3-1-pro-low": "gemini-3-1-pro-high",
|
||||
|
||||
@@ -13,7 +13,7 @@ describe("experimental.session.compacting", () => {
|
||||
|
||||
//#then
|
||||
expect(hookIndex).toBeGreaterThanOrEqual(0)
|
||||
expect(content.includes('modelID: "claude-opus-4-6"')).toBe(false)
|
||||
expect(content.includes('modelID: "claude-opus-4-7"')).toBe(false)
|
||||
expect(hookSlice.includes("output.context.push")).toBe(true)
|
||||
expect(hookSlice.includes("providerID:")).toBe(false)
|
||||
expect(hookSlice.includes("modelID:")).toBe(false)
|
||||
|
||||
@@ -81,7 +81,7 @@ describe("applyAgentConfig .agents skills", () => {
|
||||
|
||||
// when
|
||||
await applyAgentConfig({
|
||||
config: { model: "anthropic/claude-opus-4-6", agent: {} },
|
||||
config: { model: "anthropic/claude-opus-4-7", agent: {} },
|
||||
pluginConfig: createPluginConfig(),
|
||||
ctx: { directory },
|
||||
pluginComponents: createPluginComponents(),
|
||||
@@ -111,7 +111,7 @@ describe("applyAgentConfig .agents skills", () => {
|
||||
|
||||
// when
|
||||
await applyAgentConfig({
|
||||
config: { model: "anthropic/claude-opus-4-6", agent: {} },
|
||||
config: { model: "anthropic/claude-opus-4-7", agent: {} },
|
||||
pluginConfig: createPluginConfig(),
|
||||
ctx: { directory: "/tmp/project" },
|
||||
pluginComponents: createPluginComponents(),
|
||||
|
||||
@@ -31,7 +31,7 @@ function createPluginComponents(): PluginComponents {
|
||||
|
||||
function createBaseConfig(): Record<string, unknown> {
|
||||
return {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ beforeEach(async () => {
|
||||
spyOn(mcpModule, "createBuiltinMcps" as any).mockReturnValue({})
|
||||
|
||||
spyOn(shared, "log" as any).mockImplementation(() => {})
|
||||
spyOn(shared, "fetchAvailableModels" as any).mockResolvedValue(new Set(["anthropic/claude-opus-4-6"]))
|
||||
spyOn(shared, "fetchAvailableModels" as any).mockResolvedValue(new Set(["anthropic/claude-opus-4-7"]))
|
||||
spyOn(shared, "readConnectedProvidersCache" as any).mockReturnValue(null)
|
||||
|
||||
spyOn(configDir, "getOpenCodeConfigPaths" as any).mockReturnValue({
|
||||
@@ -98,7 +98,7 @@ beforeEach(async () => {
|
||||
|
||||
spyOn(permissionCompat, "migrateAgentConfig" as any).mockImplementation((config: Record<string, unknown>) => config)
|
||||
|
||||
spyOn(modelResolver, "resolveModelWithFallback" as any).mockReturnValue({ model: "anthropic/claude-opus-4-6" })
|
||||
spyOn(modelResolver, "resolveModelWithFallback" as any).mockReturnValue({ model: "anthropic/claude-opus-4-7" })
|
||||
;({ createConfigHandler } = await importFreshConfigHandlerModule())
|
||||
})
|
||||
|
||||
@@ -204,7 +204,7 @@ describe("MCP env allowlist initialization", () => {
|
||||
mcp_env_allowlist: ["CUSTOM_API_KEY", "CUSTOM_AUTH_TOKEN"],
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -246,7 +246,7 @@ describe("Plan agent demote behavior", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -292,7 +292,7 @@ describe("Plan agent demote behavior", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -336,7 +336,7 @@ describe("Plan agent demote behavior", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -385,7 +385,7 @@ describe("Plan agent demote behavior", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {
|
||||
plan: {
|
||||
name: "plan",
|
||||
@@ -422,7 +422,7 @@ describe("Plan agent demote behavior", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {
|
||||
plan: {
|
||||
name: "plan",
|
||||
@@ -459,7 +459,7 @@ describe("Plan agent demote behavior", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -495,7 +495,7 @@ describe("Agent permission defaults", () => {
|
||||
})
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -523,7 +523,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
|
||||
// given
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
default_agent: " hephaestus ",
|
||||
agent: {},
|
||||
}
|
||||
@@ -547,7 +547,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
|
||||
// given
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
default_agent: "HePhAeStUs",
|
||||
agent: {},
|
||||
}
|
||||
@@ -571,7 +571,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
|
||||
// #given
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
default_agent: "hephaestus",
|
||||
agent: {},
|
||||
}
|
||||
@@ -596,7 +596,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const displayName = getAgentListDisplayName("hephaestus")
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
default_agent: displayName,
|
||||
agent: {},
|
||||
}
|
||||
@@ -620,7 +620,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
|
||||
// #given
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -643,7 +643,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
|
||||
// given
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
default_agent: "hephaestus",
|
||||
agent: {},
|
||||
}
|
||||
@@ -667,7 +667,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
|
||||
// given
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
default_agent: " ",
|
||||
agent: {},
|
||||
}
|
||||
@@ -691,7 +691,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
|
||||
// given
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
default_agent: " Custom Agent ",
|
||||
agent: {},
|
||||
}
|
||||
@@ -719,7 +719,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
default_agent: " HePhAeStUs ",
|
||||
agent: {},
|
||||
}
|
||||
@@ -861,7 +861,7 @@ describe("Prometheus direct override priority over category", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -902,7 +902,7 @@ describe("Prometheus direct override priority over category", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -944,7 +944,7 @@ describe("Prometheus direct override priority over category", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -980,7 +980,7 @@ describe("Prometheus direct override priority over category", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1007,9 +1007,9 @@ describe("Prometheus direct override priority over category", () => {
|
||||
|
||||
describe("Plan agent model inheritance from prometheus", () => {
|
||||
test("plan agent inherits all model-related settings from resolved prometheus config", async () => {
|
||||
//#given - prometheus resolves to claude-opus-4-6 with model settings
|
||||
//#given - prometheus resolves to claude-opus-4-7 with model settings
|
||||
spyOn(prometheusAgentConfigBuilder, "buildPrometheusAgentConfig").mockResolvedValue({
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
variant: "max",
|
||||
mode: "primary",
|
||||
prompt: "prometheus prompt",
|
||||
@@ -1021,7 +1021,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {
|
||||
plan: {
|
||||
name: "plan",
|
||||
@@ -1047,7 +1047,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
const agents = config.agent as Record<string, { mode?: string; model?: string; variant?: string; prompt?: string }>
|
||||
expect(agents.plan).toBeDefined()
|
||||
expect(agents.plan.mode).toBe("subagent")
|
||||
expect(agents.plan.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(agents.plan.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(agents.plan.variant).toBe("max")
|
||||
expect(agents.plan.prompt).toBeUndefined()
|
||||
})
|
||||
@@ -1078,7 +1078,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1110,7 +1110,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.4
|
||||
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
provenance: "provider-fallback",
|
||||
variant: "max",
|
||||
})
|
||||
@@ -1128,7 +1128,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1153,7 +1153,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
test("plan agent does NOT inherit prompt, description, or color from prometheus", async () => {
|
||||
//#given
|
||||
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
provenance: "provider-fallback",
|
||||
variant: "max",
|
||||
})
|
||||
@@ -1164,7 +1164,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1181,7 +1181,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
|
||||
//#then - plan has model settings but NOT prompt/description/color
|
||||
const agents = config.agent as Record<string, Record<string, unknown>>
|
||||
expect(agents.plan.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(agents.plan.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(agents.plan.prompt).toBeUndefined()
|
||||
expect(agents.plan.description).toBeUndefined()
|
||||
expect(agents.plan.color).toBeUndefined()
|
||||
@@ -1200,7 +1200,7 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", (
|
||||
},
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const mockClient = {
|
||||
@@ -1233,7 +1233,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => {
|
||||
spyOn(pluginLoader, "loadAllPluginComponents" as any).mockRejectedValue(new Error("crash"))
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule()
|
||||
@@ -1263,7 +1263,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => {
|
||||
experimental: { plugin_load_timeout_ms: 100 },
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule()
|
||||
@@ -1289,7 +1289,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => {
|
||||
spyOn(pluginLoader, "loadAllPluginComponents" as any).mockRejectedValue(new Error("crash"))
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule()
|
||||
@@ -1326,7 +1326,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => {
|
||||
})
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule()
|
||||
@@ -1370,7 +1370,7 @@ describe("command agent routing coherence", () => {
|
||||
})
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1420,7 +1420,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => {
|
||||
experimental: { task_system: true },
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1458,7 +1458,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => {
|
||||
experimental: { task_system: false },
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1497,7 +1497,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => {
|
||||
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1538,7 +1538,7 @@ describe("disable_omo_env pass-through", () => {
|
||||
experimental: { disable_omo_env: true },
|
||||
})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1575,7 +1575,7 @@ describe("disable_omo_env pass-through", () => {
|
||||
|
||||
const pluginConfig = createPluginConfig({})
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1621,7 +1621,7 @@ describe("Agent merge priority — project-local overrides global", () => {
|
||||
|
||||
const pluginConfig: OhMyOpenCodeConfig = {}
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1661,7 +1661,7 @@ describe("Agent merge priority — project-local overrides global", () => {
|
||||
|
||||
const pluginConfig: OhMyOpenCodeConfig = {}
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1701,7 +1701,7 @@ describe("Agent merge priority — project-local overrides global", () => {
|
||||
|
||||
const pluginConfig: OhMyOpenCodeConfig = {}
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
@@ -1749,7 +1749,7 @@ describe("Agent merge priority — project-local overrides global", () => {
|
||||
|
||||
const pluginConfig: OhMyOpenCodeConfig = {}
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("buildPlanDemoteConfig", () => {
|
||||
//#given
|
||||
const prometheusConfig = {
|
||||
name: "prometheus",
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
variant: "max",
|
||||
mode: "primary",
|
||||
prompt: "You are Prometheus...",
|
||||
@@ -39,7 +39,7 @@ describe("buildPlanDemoteConfig", () => {
|
||||
|
||||
//#then - picks model settings, NOT prompt/permission/description/color/name/mode
|
||||
expect(result.mode).toBe("subagent")
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(result.variant).toBe("max")
|
||||
expect(result.temperature).toBe(0.1)
|
||||
expect(result.top_p).toBe(0.95)
|
||||
@@ -58,7 +58,7 @@ describe("buildPlanDemoteConfig", () => {
|
||||
test("plan override takes priority over prometheus for all model settings", () => {
|
||||
//#given
|
||||
const prometheusConfig = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
variant: "max",
|
||||
temperature: 0.1,
|
||||
reasoningEffort: "high",
|
||||
@@ -83,7 +83,7 @@ describe("buildPlanDemoteConfig", () => {
|
||||
test("falls back to prometheus when plan override has partial settings", () => {
|
||||
//#given
|
||||
const prometheusConfig = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
variant: "max",
|
||||
temperature: 0.1,
|
||||
reasoningEffort: "high",
|
||||
@@ -105,14 +105,14 @@ describe("buildPlanDemoteConfig", () => {
|
||||
test("skips undefined values from both sources", () => {
|
||||
//#given
|
||||
const prometheusConfig = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
}
|
||||
|
||||
//#when
|
||||
const result = buildPlanDemoteConfig(prometheusConfig, undefined)
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({ mode: "subagent", hidden: true, model: "anthropic/claude-opus-4-6" })
|
||||
expect(result).toEqual({ mode: "subagent", hidden: true, model: "anthropic/claude-opus-4-7" })
|
||||
expect(Object.keys(result)).toEqual(["mode", "hidden", "model"])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("buildPrometheusAgentConfig", () => {
|
||||
(category) => ({ model: `${category}/default-model` } as CategoryConfig)
|
||||
);
|
||||
resolveModelPipelineSpy = spyOn(shared, "resolveModelPipeline").mockReturnValue({
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
provenance: "provider-fallback",
|
||||
});
|
||||
;({ buildPrometheusAgentConfig } = await importFreshPrometheusAgentConfigBuilderModule())
|
||||
@@ -42,7 +42,7 @@ describe("buildPrometheusAgentConfig", () => {
|
||||
describe("#when currentModel is NOT in Prometheus fallback chain", () => {
|
||||
test("falls through to fallback chain instead of using currentModel as override", async () => {
|
||||
// given - currentModel is a model NOT in Prometheus fallback chain
|
||||
// Prometheus chain: claude-opus-4-6, gpt-5.4, glm-5, gemini-3.1-pro
|
||||
// Prometheus chain: claude-opus-4-7, gpt-5.4, glm-5, gemini-3.1-pro
|
||||
const currentModel = "some-provider/gpt-5.3-codex";
|
||||
|
||||
// when
|
||||
@@ -65,14 +65,14 @@ describe("buildPrometheusAgentConfig", () => {
|
||||
systemDefaultModel: undefined,
|
||||
}),
|
||||
});
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-6");
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-7");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#when currentModel IS in Prometheus fallback chain", () => {
|
||||
test("preserves currentModel as uiSelectedModel for claude-opus-4-6", async () => {
|
||||
test("preserves currentModel as uiSelectedModel for claude-opus-4-7", async () => {
|
||||
// given - currentModel matches a Prometheus fallback chain entry
|
||||
const currentModel = "anthropic/claude-opus-4-6";
|
||||
const currentModel = "anthropic/claude-opus-4-7";
|
||||
|
||||
// when - should not throw and should produce a valid config
|
||||
const result = await buildPrometheusAgentConfig({
|
||||
@@ -128,7 +128,7 @@ describe("buildPrometheusAgentConfig", () => {
|
||||
describe("#given explicit Prometheus model configured via plugin override", () => {
|
||||
test("explicit config wins over currentModel and fallback chain", async () => {
|
||||
// given
|
||||
const currentModel = "anthropic/claude-opus-4-6";
|
||||
const currentModel = "anthropic/claude-opus-4-7";
|
||||
const explicitModel = "custom-provider/custom-model";
|
||||
|
||||
// when
|
||||
@@ -163,7 +163,7 @@ describe("buildPrometheusAgentConfig", () => {
|
||||
describe("#given category with model configured", () => {
|
||||
test("category model wins when no explicit override", async () => {
|
||||
// given
|
||||
const currentModel = "anthropic/claude-opus-4-6";
|
||||
const currentModel = "anthropic/claude-opus-4-7";
|
||||
const categoryModel = "category-provider/category-model";
|
||||
|
||||
resolveCategoryConfigSpy.mockReturnValue({
|
||||
@@ -264,7 +264,7 @@ describe("buildPrometheusAgentConfig", () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-6");
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-7");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -709,7 +709,7 @@ describe("createChatMessageHandler - TUI variant passthrough", () => {
|
||||
shouldOverride: false,
|
||||
pluginConfig: {
|
||||
agents: {
|
||||
sisyphus: { model: "anthropic/claude-opus-4-6" },
|
||||
sisyphus: { model: "anthropic/claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -733,7 +733,7 @@ describe("createChatMessageHandler - TUI variant passthrough", () => {
|
||||
shouldOverride: false,
|
||||
pluginConfig: {
|
||||
agents: {
|
||||
prometheus: { model: "anthropic/claude-opus-4-6" },
|
||||
prometheus: { model: "anthropic/claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -753,7 +753,7 @@ describe("createChatMessageHandler - TUI variant passthrough", () => {
|
||||
test("respects a mid-conversation model switch instead of reusing the previous stored model", async () => {
|
||||
//#given
|
||||
setMainSession("test-session")
|
||||
setSessionModel("test-session", { providerID: "anthropic", modelID: "claude-opus-4-6" })
|
||||
setSessionModel("test-session", { providerID: "anthropic", modelID: "claude-opus-4-7" })
|
||||
const args = createMockHandlerArgs({ shouldOverride: false })
|
||||
const handler = createChatMessageHandler(args)
|
||||
const nextModel = { providerID: "openai", modelID: "gpt-5.4" }
|
||||
|
||||
@@ -48,7 +48,7 @@ describe("createChatParamsHandler", () => {
|
||||
const input = {
|
||||
sessionID: "ses_chat_params",
|
||||
agent: { name: "sisyphus" },
|
||||
model: { providerID: "opencode", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "opencode", modelID: "claude-opus-4-7" },
|
||||
provider: { id: "opencode" },
|
||||
message: {},
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ describe("createEventHandler compaction agent filtering", () => {
|
||||
role: "user",
|
||||
agent: "compaction",
|
||||
time: { created: Date.now() },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -85,12 +85,12 @@ describe("createEventHandler - model fallback", () => {
|
||||
name: "APIError",
|
||||
data: {
|
||||
message:
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}",
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
|
||||
isRetryable: true,
|
||||
},
|
||||
},
|
||||
parentID: "msg_user_1",
|
||||
modelID: "claude-opus-4-6-thinking",
|
||||
modelID: "claude-opus-4-7-thinking",
|
||||
providerID: "anthropic",
|
||||
mode: "Sisyphus - Ultraworker",
|
||||
agent: "Sisyphus - Ultraworker",
|
||||
@@ -125,7 +125,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
data: {
|
||||
error: {
|
||||
message:
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}",
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -182,7 +182,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
role: "user",
|
||||
time: { created: 1 },
|
||||
content: [],
|
||||
modelID: "claude-opus-4-6-thinking",
|
||||
modelID: "claude-opus-4-7-thinking",
|
||||
providerID: "anthropic",
|
||||
agent: "Sisyphus - Ultraworker",
|
||||
path: { cwd: "/tmp", root: "/tmp" },
|
||||
@@ -201,7 +201,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
type: "retry",
|
||||
attempt: 1,
|
||||
message:
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}",
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
|
||||
next: 1234,
|
||||
},
|
||||
},
|
||||
@@ -213,7 +213,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
{
|
||||
sessionID,
|
||||
agent: "sisyphus",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
|
||||
},
|
||||
output,
|
||||
)
|
||||
@@ -244,7 +244,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
id: "msg_user_status_dedup",
|
||||
sessionID,
|
||||
role: "user",
|
||||
modelID: "claude-opus-4-6-thinking",
|
||||
modelID: "claude-opus-4-7-thinking",
|
||||
providerID: "anthropic",
|
||||
agent: "Sisyphus - Ultraworker",
|
||||
},
|
||||
@@ -262,7 +262,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
type: "retry",
|
||||
attempt: 1,
|
||||
message:
|
||||
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
"All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
next: 300,
|
||||
},
|
||||
},
|
||||
@@ -277,7 +277,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
type: "retry",
|
||||
attempt: 1,
|
||||
message:
|
||||
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~4 days attempt #1]",
|
||||
"All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~4 days attempt #1]",
|
||||
next: 299,
|
||||
},
|
||||
},
|
||||
@@ -312,7 +312,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
id: "msg_user_status_runtime_enabled",
|
||||
sessionID,
|
||||
role: "user",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
providerID: "quotio",
|
||||
agent: "Sisyphus - Ultraworker",
|
||||
},
|
||||
@@ -330,7 +330,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
type: "retry",
|
||||
attempt: 1,
|
||||
message:
|
||||
"All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
"All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
next: 476,
|
||||
},
|
||||
},
|
||||
@@ -393,7 +393,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
role: "user",
|
||||
time: { created: 1 },
|
||||
content: [],
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
providerID: "quotio",
|
||||
agent: "Sisyphus - Ultraworker",
|
||||
path: { cwd: "/tmp", root: "/tmp" },
|
||||
@@ -412,7 +412,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
type: "retry",
|
||||
attempt: 1,
|
||||
message:
|
||||
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
"All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
next: 300,
|
||||
},
|
||||
},
|
||||
@@ -424,7 +424,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
{
|
||||
sessionID,
|
||||
agent: "sisyphus",
|
||||
model: { providerID: "quotio", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "quotio", modelID: "claude-opus-4-7" },
|
||||
},
|
||||
output,
|
||||
)
|
||||
@@ -520,13 +520,13 @@ describe("createEventHandler - model fallback", () => {
|
||||
properties: {
|
||||
sessionID,
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6-thinking",
|
||||
modelID: "claude-opus-4-7-thinking",
|
||||
error: {
|
||||
name: "UnknownError",
|
||||
data: {
|
||||
error: {
|
||||
message:
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}",
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -539,7 +539,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
{
|
||||
sessionID,
|
||||
agent: "sisyphus",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
|
||||
},
|
||||
output,
|
||||
)
|
||||
@@ -549,7 +549,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
//#when - first retry cycle
|
||||
const first = await triggerRetryCycle()
|
||||
|
||||
//#then - first fallback entry applied (no-op skip: claude-opus-4-6 matches current model after normalization)
|
||||
//#then - first fallback entry applied (no-op skip: claude-opus-4-7 matches current model after normalization)
|
||||
expect(first.message["model"]).toMatchObject({
|
||||
providerID: "opencode-go",
|
||||
modelID: "kimi-k2.5",
|
||||
@@ -590,12 +590,12 @@ describe("createEventHandler - model fallback", () => {
|
||||
name: "APIError",
|
||||
data: {
|
||||
message:
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}",
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
|
||||
isRetryable: true,
|
||||
},
|
||||
},
|
||||
parentID: "msg_user_disabled_1",
|
||||
modelID: "claude-opus-4-6-thinking",
|
||||
modelID: "claude-opus-4-7-thinking",
|
||||
providerID: "anthropic",
|
||||
agent: "Sisyphus - Ultraworker",
|
||||
path: { cwd: "/tmp", root: "/tmp" },
|
||||
@@ -617,7 +617,7 @@ describe("createEventHandler - model fallback", () => {
|
||||
data: {
|
||||
error: {
|
||||
message:
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}",
|
||||
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -826,7 +826,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
||||
const retryStatus = {
|
||||
type: "retry",
|
||||
attempt: 1,
|
||||
message: "All credentials for model claude-opus-4-6-thinking are cooling down [retrying in 7m 56s attempt #1]",
|
||||
message: "All credentials for model claude-opus-4-7-thinking are cooling down [retrying in 7m 56s attempt #1]",
|
||||
next: 476,
|
||||
} as const
|
||||
|
||||
@@ -838,7 +838,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
||||
id: "msg_user_retry_rearm",
|
||||
sessionID,
|
||||
role: "user",
|
||||
modelID: "claude-opus-4-6-thinking",
|
||||
modelID: "claude-opus-4-7-thinking",
|
||||
providerID: "anthropic",
|
||||
agent: "Sisyphus - Ultraworker",
|
||||
},
|
||||
@@ -862,7 +862,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
||||
{
|
||||
sessionID,
|
||||
agent: "sisyphus",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
|
||||
},
|
||||
firstOutput,
|
||||
)
|
||||
|
||||
+3
-3
@@ -515,7 +515,7 @@ export function createEventHandler(args: {
|
||||
sessionID,
|
||||
info?.providerID as string | undefined,
|
||||
);
|
||||
const rawModel = (info?.modelID as string | undefined) ?? "claude-opus-4-6";
|
||||
const rawModel = (info?.modelID as string | undefined) ?? "claude-opus-4-7";
|
||||
const currentModel = normalizeFallbackModelID(rawModel);
|
||||
applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig);
|
||||
|
||||
@@ -578,7 +578,7 @@ export function createEventHandler(args: {
|
||||
const parsed = extractProviderModelFromErrorMessage(retryMessage);
|
||||
const lastKnown = lastKnownModelBySession.get(sessionID);
|
||||
const currentProvider = resolveFallbackProviderID(sessionID, parsed.providerID);
|
||||
let currentModel = parsed.modelID ?? lastKnown?.modelID ?? "claude-opus-4-6";
|
||||
let currentModel = parsed.modelID ?? lastKnown?.modelID ?? "claude-opus-4-7";
|
||||
currentModel = normalizeFallbackModelID(currentModel);
|
||||
applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig);
|
||||
|
||||
@@ -664,7 +664,7 @@ export function createEventHandler(args: {
|
||||
sessionID,
|
||||
(props?.providerID as string | undefined) || parsed.providerID,
|
||||
);
|
||||
let currentModel = (props?.modelID as string) || parsed.modelID || "claude-opus-4-6";
|
||||
let currentModel = (props?.modelID as string) || parsed.modelID || "claude-opus-4-7";
|
||||
currentModel = normalizeFallbackModelID(currentModel);
|
||||
applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig);
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ function createChatMessageHandlerHooks(
|
||||
|
||||
const PRIMARY_MODEL = {
|
||||
providerID: PROVIDER_ID,
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
}
|
||||
|
||||
const PRIMARY_MODEL_STRING = `${PRIMARY_MODEL.providerID}/${PRIMARY_MODEL.modelID}`
|
||||
@@ -313,7 +313,7 @@ async function triggerSessionStatusRetry(
|
||||
type: "retry",
|
||||
attempt: 1,
|
||||
message:
|
||||
"All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
"All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
|
||||
next: 476,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -112,13 +112,13 @@ describe("scheduleDeferredModelOverride", () => {
|
||||
//#when
|
||||
scheduleDeferredModelOverride(
|
||||
"msg_001",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
)
|
||||
await flushMicrotasks(5)
|
||||
|
||||
//#then
|
||||
const model = readMessageModel("msg_001")
|
||||
expect(model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
|
||||
expect(model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
|
||||
})
|
||||
|
||||
test("should update variant and thinking fields when variant provided", async () => {
|
||||
@@ -128,7 +128,7 @@ describe("scheduleDeferredModelOverride", () => {
|
||||
//#when
|
||||
scheduleDeferredModelOverride(
|
||||
"msg_002",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
"max",
|
||||
)
|
||||
await flushMicrotasks(5)
|
||||
@@ -144,7 +144,7 @@ describe("scheduleDeferredModelOverride", () => {
|
||||
//#when
|
||||
scheduleDeferredModelOverride(
|
||||
"msg_nonexistent",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
)
|
||||
await flushWithTimeout()
|
||||
|
||||
@@ -162,13 +162,13 @@ describe("scheduleDeferredModelOverride", () => {
|
||||
//#when
|
||||
scheduleDeferredModelOverride(
|
||||
"msg_003",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
)
|
||||
await flushMicrotasks(5)
|
||||
|
||||
//#then
|
||||
const model = readMessageModel("msg_003")
|
||||
expect(model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
|
||||
expect(model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
|
||||
expect(readMessageField("msg_003", "variant")).toBeNull()
|
||||
expect(readMessageField("msg_003", "thinking")).toBeNull()
|
||||
})
|
||||
@@ -180,7 +180,7 @@ describe("scheduleDeferredModelOverride", () => {
|
||||
//#when
|
||||
scheduleDeferredModelOverride(
|
||||
"msg_004",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
)
|
||||
await flushMicrotasks(5)
|
||||
|
||||
@@ -200,7 +200,7 @@ describe("scheduleDeferredModelOverride", () => {
|
||||
//#when
|
||||
scheduleDeferredModelOverride(
|
||||
"msg_corrupt",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
)
|
||||
await flushMicrotasks(5)
|
||||
|
||||
|
||||
@@ -79,19 +79,19 @@ describe("resolveUltraworkOverride", () => {
|
||||
|
||||
test("should resolve override when ultrawork keyword detected", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
|
||||
const output = createOutput("ultrawork do something")
|
||||
|
||||
//#when
|
||||
const result = resolveUltraworkOverride(config, "sisyphus", output)
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" })
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" })
|
||||
})
|
||||
|
||||
test("should return null when no keyword detected", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
|
||||
const output = createOutput("just do something normal")
|
||||
|
||||
//#when
|
||||
@@ -103,7 +103,7 @@ describe("resolveUltraworkOverride", () => {
|
||||
|
||||
test("should return null when agent name is undefined", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
|
||||
const output = createOutput("ultrawork do something")
|
||||
|
||||
//#when
|
||||
@@ -115,14 +115,14 @@ describe("resolveUltraworkOverride", () => {
|
||||
|
||||
test("should use message.agent when input agent is undefined", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
|
||||
const output = createOutput("ultrawork do something", "sisyphus")
|
||||
|
||||
//#when
|
||||
const result = resolveUltraworkOverride(config, undefined, output)
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: undefined })
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: undefined })
|
||||
})
|
||||
|
||||
test("should return null when agents config is missing", () => {
|
||||
@@ -189,19 +189,19 @@ describe("resolveUltraworkOverride", () => {
|
||||
|
||||
test("should resolve display name to config key", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
|
||||
const output = createOutput("ulw do something")
|
||||
|
||||
//#when
|
||||
const result = resolveUltraworkOverride(config, "Sisyphus - Ultraworker", output)
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" })
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" })
|
||||
})
|
||||
|
||||
test("should handle multiple text parts by joining them", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [
|
||||
@@ -215,12 +215,12 @@ describe("resolveUltraworkOverride", () => {
|
||||
const result = resolveUltraworkOverride(config, "sisyphus", output)
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: undefined })
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: undefined })
|
||||
})
|
||||
|
||||
test("should use session agent when input and message agents are undefined", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
|
||||
const output = createOutput("ultrawork do something")
|
||||
const getSessionAgentSpy = spyOn(sessionStateModule, "getSessionAgent")
|
||||
getSessionAgentSpy.mockReturnValue("sisyphus")
|
||||
@@ -230,7 +230,7 @@ describe("resolveUltraworkOverride", () => {
|
||||
|
||||
//#then
|
||||
expect(getSessionAgentSpy).toHaveBeenCalledWith("ses_test")
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" })
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" })
|
||||
|
||||
getSessionAgentSpy.mockRestore()
|
||||
})
|
||||
@@ -287,7 +287,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
|
||||
test("should schedule deferred DB override without variant when SDK unavailable", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
|
||||
const output = createOutput("ultrawork do something", { messageId: "msg_123" })
|
||||
const tui = createMockTui()
|
||||
|
||||
@@ -297,7 +297,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
//#then - variant should NOT be applied without SDK validation
|
||||
expect(dbOverrideSpy).toHaveBeenCalledWith(
|
||||
"msg_123",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
@@ -305,7 +305,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
test("should NOT override variant when SDK unavailable even if config specifies variant", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
variant: "extended",
|
||||
})
|
||||
const output = createOutput("ultrawork do something", { messageId: "msg_123" })
|
||||
@@ -319,7 +319,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
//#then - existing variant preserved, not overridden to "extended"
|
||||
expect(dbOverrideSpy).toHaveBeenCalledWith(
|
||||
"msg_123",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
undefined,
|
||||
)
|
||||
expect(output.message["variant"]).toBe("max")
|
||||
@@ -329,7 +329,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
test("should NOT mutate output.message.model when message ID present", () => {
|
||||
//#given
|
||||
const sonnetModel = { providerID: "anthropic", modelID: "claude-sonnet-4-6" }
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
|
||||
const output = createOutput("ultrawork do something", {
|
||||
existingModel: sonnetModel,
|
||||
messageId: "msg_123",
|
||||
@@ -345,7 +345,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
|
||||
test("should fall back to direct model mutation without variant when no message ID and no SDK", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
|
||||
const output = createOutput("ultrawork do something")
|
||||
const tui = createMockTui()
|
||||
|
||||
@@ -353,7 +353,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
applyUltraworkModelOverrideOnMessage(config, "sisyphus", output, tui)
|
||||
|
||||
//#then - model is set but variant is NOT applied without SDK validation
|
||||
expect(output.message.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
|
||||
expect(output.message.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
|
||||
expect(output.message["variant"]).toBeUndefined()
|
||||
expect(dbOverrideSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -375,7 +375,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
|
||||
test("should not apply override when no keyword detected", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
|
||||
const output = createOutput("just do something normal", { messageId: "msg_123" })
|
||||
const tui = createMockTui()
|
||||
|
||||
@@ -388,7 +388,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
|
||||
test("should log the model transition with deferred DB tag", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
|
||||
const existingModel = { providerID: "anthropic", modelID: "claude-sonnet-4-6" }
|
||||
const output = createOutput("ultrawork do something", {
|
||||
existingModel,
|
||||
@@ -408,7 +408,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
|
||||
test("should call showToast on override", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
|
||||
const output = createOutput("ultrawork do something", { messageId: "msg_123" })
|
||||
let toastCalled = false
|
||||
const tui = {
|
||||
@@ -426,7 +426,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
|
||||
test("should resolve display name to config key with deferred path", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
|
||||
const output = createOutput("ulw do something", { messageId: "msg_123" })
|
||||
const tui = createMockTui()
|
||||
|
||||
@@ -436,16 +436,16 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
//#then
|
||||
expect(dbOverrideSpy).toHaveBeenCalledWith(
|
||||
"msg_123",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
test("should skip override trigger when current model already matches ultrawork model", () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
|
||||
const output = createOutput("ultrawork do something", {
|
||||
existingModel: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
existingModel: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
messageId: "msg_123",
|
||||
})
|
||||
let toastCalled = false
|
||||
@@ -465,13 +465,13 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
|
||||
test("should apply validated variant when SDK confirms model supports it", async () => {
|
||||
//#given
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" })
|
||||
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
|
||||
const output = createOutput("ultrawork do something", { messageId: "msg_123" })
|
||||
const tui = createMockTui()
|
||||
const mockClient = {
|
||||
provider: {
|
||||
list: async () => ({
|
||||
data: { all: [{ id: "anthropic", models: { "claude-opus-4-6": { variants: { max: {} } } } }] },
|
||||
data: { all: [{ id: "anthropic", models: { "claude-opus-4-7": { variants: { max: {} } } } }] },
|
||||
}),
|
||||
},
|
||||
}
|
||||
@@ -482,7 +482,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
|
||||
//#then - SDK confirmed max exists, so variant is applied
|
||||
expect(dbOverrideSpy).toHaveBeenCalledWith(
|
||||
"msg_123",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
"max",
|
||||
)
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("resolveValidUltraworkVariant", () => {
|
||||
// given
|
||||
const client = createClient({
|
||||
anthropic: {
|
||||
"claude-opus-4-6": {
|
||||
"claude-opus-4-7": {
|
||||
variants: {
|
||||
max: {},
|
||||
high: {},
|
||||
@@ -35,7 +35,7 @@ describe("resolveValidUltraworkVariant", () => {
|
||||
// when
|
||||
const result = await resolveValidUltraworkVariant(
|
||||
client,
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
"max",
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("resolveValidUltraworkVariant", () => {
|
||||
// given
|
||||
const client = createClient({
|
||||
anthropic: {
|
||||
"claude-opus-4-6": {
|
||||
"claude-opus-4-7": {
|
||||
variants: {
|
||||
high: {},
|
||||
},
|
||||
@@ -58,7 +58,7 @@ describe("resolveValidUltraworkVariant", () => {
|
||||
// when
|
||||
const result = await resolveValidUltraworkVariant(
|
||||
client,
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
"max",
|
||||
)
|
||||
|
||||
@@ -87,7 +87,7 @@ describe("applyUltraworkModelOverrideOnMessage variant guard", () => {
|
||||
// given
|
||||
const client = createClient({
|
||||
anthropic: {
|
||||
"claude-opus-4-6": {
|
||||
"claude-opus-4-7": {
|
||||
variants: {
|
||||
high: {},
|
||||
},
|
||||
@@ -100,7 +100,7 @@ describe("applyUltraworkModelOverrideOnMessage variant guard", () => {
|
||||
agents: {
|
||||
sisyphus: {
|
||||
ultrawork: {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
},
|
||||
@@ -130,7 +130,7 @@ describe("applyUltraworkModelOverrideOnMessage variant guard", () => {
|
||||
expect(output.message["thinking"]).toBeUndefined()
|
||||
expect(dbOverrideSpy).toHaveBeenCalledWith(
|
||||
"msg_123",
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
undefined,
|
||||
)
|
||||
dbOverrideSpy.mockRestore()
|
||||
|
||||
@@ -8,9 +8,9 @@ describe("Agent Config Integration", () => {
|
||||
test("migrates old format agent keys to lowercase", () => {
|
||||
// given - config with old format keys
|
||||
const oldConfig = {
|
||||
Sisyphus: { model: "anthropic/claude-opus-4-6" },
|
||||
Atlas: { model: "anthropic/claude-opus-4-6" },
|
||||
"Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-6" },
|
||||
Sisyphus: { model: "anthropic/claude-opus-4-7" },
|
||||
Atlas: { model: "anthropic/claude-opus-4-7" },
|
||||
"Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-7" },
|
||||
"Metis - Plan Consultant": { model: "anthropic/claude-sonnet-4-6" },
|
||||
"Momus - Plan Critic": { model: "anthropic/claude-sonnet-4-6" },
|
||||
}
|
||||
@@ -33,9 +33,9 @@ describe("Agent Config Integration", () => {
|
||||
expect(result.migrated).not.toHaveProperty("Momus - Plan Critic")
|
||||
|
||||
// then - values are preserved
|
||||
expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-6" })
|
||||
expect(result.migrated.atlas).toEqual({ model: "anthropic/claude-opus-4-6" })
|
||||
expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-6" })
|
||||
expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-7" })
|
||||
expect(result.migrated.atlas).toEqual({ model: "anthropic/claude-opus-4-7" })
|
||||
expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-7" })
|
||||
|
||||
// then - changed flag is true
|
||||
expect(result.changed).toBe(true)
|
||||
@@ -44,7 +44,7 @@ describe("Agent Config Integration", () => {
|
||||
test("preserves already lowercase keys", () => {
|
||||
// given - config with lowercase keys
|
||||
const config = {
|
||||
sisyphus: { model: "anthropic/claude-opus-4-6" },
|
||||
sisyphus: { model: "anthropic/claude-opus-4-7" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
librarian: { model: "opencode/big-pickle" },
|
||||
}
|
||||
@@ -62,9 +62,9 @@ describe("Agent Config Integration", () => {
|
||||
test("handles mixed case config", () => {
|
||||
// given - config with mixed old and new format
|
||||
const mixedConfig = {
|
||||
Sisyphus: { model: "anthropic/claude-opus-4-6" },
|
||||
Sisyphus: { model: "anthropic/claude-opus-4-7" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
"Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-6" },
|
||||
"Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-7" },
|
||||
librarian: { model: "opencode/big-pickle" },
|
||||
}
|
||||
|
||||
@@ -173,8 +173,8 @@ describe("Agent Config Integration", () => {
|
||||
test("old config migrates and displays correctly", () => {
|
||||
// given - old format config
|
||||
const oldConfig = {
|
||||
Sisyphus: { model: "anthropic/claude-opus-4-6", temperature: 0.1 },
|
||||
"Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-6" },
|
||||
Sisyphus: { model: "anthropic/claude-opus-4-7", temperature: 0.1 },
|
||||
"Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-7" },
|
||||
}
|
||||
|
||||
// when - config is migrated
|
||||
@@ -193,15 +193,15 @@ describe("Agent Config Integration", () => {
|
||||
expect(prometheusDisplay).toBe("Prometheus - Plan Builder")
|
||||
|
||||
// then - config values are preserved
|
||||
expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-6", temperature: 0.1 })
|
||||
expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-6" })
|
||||
expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-7", temperature: 0.1 })
|
||||
expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-7" })
|
||||
})
|
||||
|
||||
test("new config works without migration", () => {
|
||||
// given - new format config (already lowercase)
|
||||
const newConfig = {
|
||||
sisyphus: { model: "anthropic/claude-opus-4-6" },
|
||||
atlas: { model: "anthropic/claude-opus-4-6" },
|
||||
sisyphus: { model: "anthropic/claude-opus-4-7" },
|
||||
atlas: { model: "anthropic/claude-opus-4-7" },
|
||||
}
|
||||
|
||||
// when - migration is applied (should be no-op)
|
||||
|
||||
@@ -84,14 +84,14 @@ describe("applyAgentVariant", () => {
|
||||
|
||||
describe("resolveVariantForModel", () => {
|
||||
test("returns agent override variant when configured", () => {
|
||||
// given - use a model in sisyphus chain (claude-opus-4-6 has default variant "max")
|
||||
// given - use a model in sisyphus chain (claude-opus-4-7 has default variant "max")
|
||||
// to verify override takes precedence over fallback chain
|
||||
const config = {
|
||||
agents: {
|
||||
sisyphus: { variant: "high" },
|
||||
},
|
||||
} as OhMyOpenCodeConfig
|
||||
const model = { providerID: "anthropic", modelID: "claude-opus-4-6" }
|
||||
const model = { providerID: "anthropic", modelID: "claude-opus-4-7" }
|
||||
|
||||
// when
|
||||
const variant = resolveVariantForModel(config, "sisyphus", model)
|
||||
@@ -103,7 +103,7 @@ describe("resolveVariantForModel", () => {
|
||||
test("returns correct variant for anthropic provider", () => {
|
||||
// given
|
||||
const config = {} as OhMyOpenCodeConfig
|
||||
const model = { providerID: "anthropic", modelID: "claude-opus-4-6" }
|
||||
const model = { providerID: "anthropic", modelID: "claude-opus-4-7" }
|
||||
|
||||
// when
|
||||
const variant = resolveVariantForModel(config, "sisyphus", model)
|
||||
@@ -151,7 +151,7 @@ describe("resolveVariantForModel", () => {
|
||||
test("returns undefined for unknown agent", () => {
|
||||
// given
|
||||
const config = {} as OhMyOpenCodeConfig
|
||||
const model = { providerID: "anthropic", modelID: "claude-opus-4-6" }
|
||||
const model = { providerID: "anthropic", modelID: "claude-opus-4-7" }
|
||||
|
||||
// when
|
||||
const variant = resolveVariantForModel(config, "nonexistent-agent", model)
|
||||
@@ -203,7 +203,7 @@ describe("resolveVariantForModel", () => {
|
||||
test("returns correct variant for oracle agent with anthropic", () => {
|
||||
// given
|
||||
const config = {} as OhMyOpenCodeConfig
|
||||
const model = { providerID: "anthropic", modelID: "claude-opus-4-6" }
|
||||
const model = { providerID: "anthropic", modelID: "claude-opus-4-7" }
|
||||
|
||||
// when
|
||||
const variant = resolveVariantForModel(config, "oracle", model)
|
||||
|
||||
@@ -61,7 +61,7 @@ describe("updateConnectedProvidersCache", () => {
|
||||
name: "Anthropic",
|
||||
env: [],
|
||||
models: {
|
||||
"claude-opus-4-6": { id: "claude-opus-4-6", name: "Claude Opus 4.6" },
|
||||
"claude-opus-4-7": { id: "claude-opus-4-7", name: "Claude Opus 4.6" },
|
||||
"claude-sonnet-4-6": { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
|
||||
},
|
||||
},
|
||||
@@ -84,7 +84,7 @@ describe("updateConnectedProvidersCache", () => {
|
||||
{ id: "gpt-5.4", name: "GPT-5.4" },
|
||||
],
|
||||
anthropic: [
|
||||
{ id: "claude-opus-4-6", name: "Claude Opus 4.6" },
|
||||
{ id: "claude-opus-4-7", name: "Claude Opus 4.6" },
|
||||
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
|
||||
],
|
||||
})
|
||||
|
||||
@@ -28,15 +28,15 @@ describe("resolveActualContextLimit", () => {
|
||||
resetContextLimitEnv()
|
||||
})
|
||||
|
||||
it("returns cached limit for Anthropic 4.6 models when 1M mode is disabled (GA support)", () => {
|
||||
it("returns cached limit for Anthropic 4.7 models when 1M mode is disabled (GA support)", () => {
|
||||
// given
|
||||
delete process.env[ANTHROPIC_CONTEXT_ENV_KEY]
|
||||
delete process.env[VERTEX_CONTEXT_ENV_KEY]
|
||||
const modelContextLimitsCache = new Map<string, number>()
|
||||
modelContextLimitsCache.set("anthropic/claude-opus-4-6", 1_000_000)
|
||||
modelContextLimitsCache.set("anthropic/claude-opus-4-7", 1_000_000)
|
||||
|
||||
// when
|
||||
const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4-6", {
|
||||
const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4-7", {
|
||||
anthropicContext1MEnabled: false,
|
||||
modelContextLimitsCache,
|
||||
})
|
||||
@@ -107,15 +107,15 @@ describe("resolveActualContextLimit", () => {
|
||||
expect(actualLimit).toBe(200000)
|
||||
})
|
||||
|
||||
it("supports Anthropic 4.6 dot-version model IDs without explicit 1M mode", () => {
|
||||
it("supports Anthropic 4.7 dot-version model IDs without explicit 1M mode", () => {
|
||||
// given
|
||||
delete process.env[ANTHROPIC_CONTEXT_ENV_KEY]
|
||||
delete process.env[VERTEX_CONTEXT_ENV_KEY]
|
||||
const modelContextLimitsCache = new Map<string, number>()
|
||||
modelContextLimitsCache.set("anthropic/claude-opus-4.6", 1_000_000)
|
||||
modelContextLimitsCache.set("anthropic/claude-opus-4.7", 1_000_000)
|
||||
|
||||
// when
|
||||
const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4.6", {
|
||||
const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4.7", {
|
||||
anthropicContext1MEnabled: false,
|
||||
modelContextLimitsCache,
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ function getAnthropicActualLimit(modelCacheState?: ContextLimitModelCacheState):
|
||||
}
|
||||
|
||||
function supportsCachedAnthropicLimit(modelID: string): boolean {
|
||||
return /^claude-(opus|sonnet)-4(?:-|\.)6(?:-high)?$/.test(modelID)
|
||||
return /^claude-(opus|sonnet)-4(?:-|\.)(?:6|7)(?:-high)?$/.test(modelID)
|
||||
}
|
||||
|
||||
export function resolveActualContextLimit(
|
||||
|
||||
@@ -71,7 +71,7 @@ describe("mergeCategories", () => {
|
||||
it("user overrides merge with defaults", () => {
|
||||
//#given
|
||||
const userCategories = {
|
||||
"ultrabrain": { model: "anthropic/claude-opus-4-6" },
|
||||
"ultrabrain": { model: "anthropic/claude-opus-4-7" },
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -79,6 +79,6 @@ describe("mergeCategories", () => {
|
||||
|
||||
//#then
|
||||
expect(result["ultrabrain"]).toBeDefined()
|
||||
expect(result["ultrabrain"].model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result["ultrabrain"].model).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -65,7 +65,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.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { id: "anthropic", models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
anthropic: { id: "anthropic", models: { "claude-opus-4-7": { id: "claude-opus-4-7" } } },
|
||||
google: { id: "google", models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
||||
})
|
||||
|
||||
@@ -76,7 +76,7 @@ describe("fetchAvailableModels", () => {
|
||||
expect(result).toBeInstanceOf(Set)
|
||||
expect(result.size).toBe(3)
|
||||
expect(result.has("openai/gpt-5.4")).toBe(true)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("anthropic/claude-opus-4-7")).toBe(true)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(true)
|
||||
})
|
||||
|
||||
@@ -145,7 +145,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.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { id: "anthropic", models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
anthropic: { id: "anthropic", models: { "claude-opus-4-7": { id: "claude-opus-4-7" } } },
|
||||
})
|
||||
|
||||
const result1 = await fetchAvailableModels(undefined, { connectedProviders: ["openai"] })
|
||||
@@ -192,7 +192,7 @@ describe("fuzzyMatchModel", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.4",
|
||||
"openai/gpt-5.3-codex",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt-5.4", available)
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
@@ -239,25 +239,25 @@ describe("fuzzyMatchModel", () => {
|
||||
// given available models with claude variants
|
||||
// when searching for claude-opus
|
||||
// then return matching claude-opus model
|
||||
it("should match claude-opus to claude-opus-4-6", () => {
|
||||
it("should match claude-opus to claude-opus-4-7", () => {
|
||||
const available = new Set([
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"anthropic/claude-sonnet-4-6",
|
||||
])
|
||||
const result = fuzzyMatchModel("claude-opus", available)
|
||||
expect(result).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
// given github-copilot serves claude versions with dot notation
|
||||
// when fallback chain uses hyphen notation in requested model
|
||||
// then normalize both forms and match github-copilot model
|
||||
it("should match github-copilot claude-opus-4-6 to claude-opus-4.6", () => {
|
||||
it("should match github-copilot claude-opus-4-7 to claude-opus-4.7", () => {
|
||||
const available = new Set([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"github-copilot/claude-opus-4.7",
|
||||
"opencode/big-pickle",
|
||||
])
|
||||
const result = fuzzyMatchModel("claude-opus-4-6", available, ["github-copilot"])
|
||||
expect(result).toBe("github-copilot/claude-opus-4.6")
|
||||
const result = fuzzyMatchModel("claude-opus-4-7", available, ["github-copilot"])
|
||||
expect(result).toBe("github-copilot/claude-opus-4.7")
|
||||
})
|
||||
|
||||
// given claude models can evolve to newer version numbers
|
||||
@@ -275,7 +275,7 @@ describe("fuzzyMatchModel", () => {
|
||||
it("should filter by provider when providers array is given", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"google/gemini-3",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt", available, ["openai"])
|
||||
@@ -288,7 +288,7 @@ describe("fuzzyMatchModel", () => {
|
||||
it("should return null when provider filter excludes all matches", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
])
|
||||
const result = fuzzyMatchModel("claude", available, ["openai"])
|
||||
expect(result).toBeNull()
|
||||
@@ -300,7 +300,7 @@ describe("fuzzyMatchModel", () => {
|
||||
it("should return null when no match found", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
])
|
||||
const result = fuzzyMatchModel("gemini", available)
|
||||
expect(result).toBeNull()
|
||||
@@ -312,7 +312,7 @@ describe("fuzzyMatchModel", () => {
|
||||
it("should match case-insensitively", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
])
|
||||
const result = fuzzyMatchModel("GPT-5.4", available)
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
@@ -323,11 +323,11 @@ describe("fuzzyMatchModel", () => {
|
||||
// then return exact match first
|
||||
it("should prioritize exact match over longer variants", () => {
|
||||
const available = new Set([
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-6-extended",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"anthropic/claude-opus-4-7-extended",
|
||||
])
|
||||
const result = fuzzyMatchModel("claude-opus-4-6", available)
|
||||
expect(result).toBe("anthropic/claude-opus-4-6")
|
||||
const result = fuzzyMatchModel("claude-opus-4-7", available)
|
||||
expect(result).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
// given available models with similar model IDs (e.g., glm-5 and big-pickle)
|
||||
@@ -372,7 +372,7 @@ describe("fuzzyMatchModel", () => {
|
||||
it("should search all specified providers", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"google/gemini-3",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt", available, ["openai", "google"])
|
||||
@@ -520,7 +520,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
it("should filter models by connected providers", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
anthropic: { models: { "claude-opus-4-7": { id: "claude-opus-4-7" } } },
|
||||
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
||||
})
|
||||
|
||||
@@ -529,7 +529,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("anthropic/claude-opus-4-7")).toBe(true)
|
||||
expect(result.has("openai/gpt-5.4")).toBe(false)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(false)
|
||||
})
|
||||
@@ -540,7 +540,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
it("should filter models by multiple connected providers", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
anthropic: { models: { "claude-opus-4-7": { id: "claude-opus-4-7" } } },
|
||||
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
||||
})
|
||||
|
||||
@@ -549,7 +549,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("anthropic/claude-opus-4-7")).toBe(true)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(true)
|
||||
expect(result.has("openai/gpt-5.4")).toBe(false)
|
||||
})
|
||||
@@ -560,7 +560,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
it("should return empty set when connectedProviders is empty", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
anthropic: { models: { "claude-opus-4-7": { id: "claude-opus-4-7" } } },
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels(undefined, {
|
||||
@@ -576,7 +576,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
it("should return empty set when connectedProviders not specified", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
anthropic: { models: { "claude-opus-4-7": { id: "claude-opus-4-7" } } },
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels()
|
||||
@@ -605,7 +605,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
it("should return models from providers that exist in both cache and connected list", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
anthropic: { models: { "claude-opus-4-7": { id: "claude-opus-4-7" } } },
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels(undefined, {
|
||||
@@ -613,7 +613,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("anthropic/claude-opus-4-7")).toBe(true)
|
||||
})
|
||||
|
||||
// given filtered fetch
|
||||
@@ -622,7 +622,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
it("should not cache filtered results", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
anthropic: { models: { "claude-opus-4-7": { id: "claude-opus-4-7" } } },
|
||||
})
|
||||
|
||||
// First call with anthropic
|
||||
@@ -706,13 +706,13 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
writeProviderModelsCache({
|
||||
models: {
|
||||
opencode: ["big-pickle", "gpt-5-nano"],
|
||||
anthropic: ["claude-opus-4-6"]
|
||||
anthropic: ["claude-opus-4-7"]
|
||||
},
|
||||
connected: ["opencode", "anthropic"]
|
||||
})
|
||||
writeModelsCache({
|
||||
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.4": {} } },
|
||||
anthropic: { models: { "claude-opus-4-6": {}, "claude-sonnet-4-6": {} } }
|
||||
anthropic: { models: { "claude-opus-4-7": {}, "claude-sonnet-4-6": {} } }
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels(undefined, {
|
||||
@@ -722,7 +722,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("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("anthropic/claude-opus-4-7")).toBe(true)
|
||||
expect(result.has("opencode/gpt-5.4")).toBe(false)
|
||||
expect(result.has("anthropic/claude-sonnet-4-6")).toBe(false)
|
||||
})
|
||||
@@ -773,7 +773,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
writeProviderModelsCache({
|
||||
models: {
|
||||
opencode: ["big-pickle"],
|
||||
anthropic: ["claude-opus-4-6"],
|
||||
anthropic: ["claude-opus-4-7"],
|
||||
google: ["gemini-3.1-pro"]
|
||||
},
|
||||
connected: ["opencode", "anthropic", "google"]
|
||||
@@ -785,7 +785,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
|
||||
expect(result.size).toBe(1)
|
||||
expect(result.has("opencode/big-pickle")).toBe(true)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(false)
|
||||
expect(result.has("anthropic/claude-opus-4-7")).toBe(false)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(false)
|
||||
})
|
||||
|
||||
@@ -812,7 +812,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
it("should handle mixed string[] and object[] formats across providers", async () => {
|
||||
writeProviderModelsCache({
|
||||
models: {
|
||||
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6"],
|
||||
anthropic: ["claude-opus-4-7", "claude-sonnet-4-6"],
|
||||
ollama: [
|
||||
{ id: "ministral-3:14b-32k-agent", provider: "ollama" },
|
||||
{ id: "qwen3-coder:32k-agent", provider: "ollama" }
|
||||
@@ -826,7 +826,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
})
|
||||
|
||||
expect(result.size).toBe(4)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("anthropic/claude-opus-4-7")).toBe(true)
|
||||
expect(result.has("anthropic/claude-sonnet-4-6")).toBe(true)
|
||||
expect(result.has("ollama/ministral-3:14b-32k-agent")).toBe(true)
|
||||
expect(result.has("ollama/qwen3-coder:32k-agent")).toBe(true)
|
||||
@@ -859,7 +859,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
describe("isModelAvailable", () => {
|
||||
it("returns true when model exists via fuzzy match", () => {
|
||||
// given
|
||||
const available = new Set(["openai/gpt-5.3-codex", "anthropic/claude-opus-4-6"])
|
||||
const available = new Set(["openai/gpt-5.3-codex", "anthropic/claude-opus-4-7"])
|
||||
|
||||
// when
|
||||
const result = isModelAvailable("gpt-5.3-codex", available)
|
||||
@@ -870,7 +870,7 @@ describe("isModelAvailable", () => {
|
||||
|
||||
it("returns false when model not found", () => {
|
||||
// given
|
||||
const available = new Set(["anthropic/claude-opus-4-6"])
|
||||
const available = new Set(["anthropic/claude-opus-4-7"])
|
||||
|
||||
// when
|
||||
const result = isModelAvailable("gpt-5.3-codex", available)
|
||||
@@ -924,7 +924,7 @@ describe("fallback model availability", () => {
|
||||
|
||||
it("returns null for completely unknown model", () => {
|
||||
// given
|
||||
const available = new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6"])
|
||||
const available = new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-7"])
|
||||
|
||||
// when
|
||||
const result = fuzzyMatchModel("non-existent-model-family", available)
|
||||
@@ -936,7 +936,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.4" }]
|
||||
const availableModels = new Set(["anthropic/claude-opus-4-6"])
|
||||
const availableModels = new Set(["anthropic/claude-opus-4-7"])
|
||||
writeConnectedProvidersCache(["openai"])
|
||||
|
||||
// when
|
||||
@@ -950,10 +950,10 @@ describe("fallback model availability", () => {
|
||||
// given
|
||||
const fallbackChain = [
|
||||
{ providers: ["openai"], model: "gpt-5.4" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
]
|
||||
const availableModels = new Set([
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"openai/gpt-5.4-preview",
|
||||
])
|
||||
|
||||
@@ -968,7 +968,7 @@ describe("fallback model availability", () => {
|
||||
// given
|
||||
const fallbackChain = [
|
||||
{ providers: ["openai"], model: "gpt-5.4" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
]
|
||||
const availableModels = new Set(["google/gemini-3.1-pro"])
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ 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.4", "openai/gpt-5.3-codex", "anthropic/claude-opus-4-6"])
|
||||
* const available = new Set(["openai/gpt-5.4", "openai/gpt-5.3-codex", "anthropic/claude-opus-4-7"])
|
||||
* fuzzyMatchModel("gpt-5.4", available) // → "openai/gpt-5.4"
|
||||
* fuzzyMatchModel("claude", available, ["openai"]) // → null (provider filter excludes anthropic)
|
||||
*/
|
||||
|
||||
@@ -16,8 +16,8 @@ describe("getModelCapabilities", () => {
|
||||
generatedAt: "2026-03-25T00:00:00.000Z",
|
||||
sourceUrl: "https://models.dev/api.json",
|
||||
models: {
|
||||
"claude-opus-4-6": {
|
||||
id: "claude-opus-4-6",
|
||||
"claude-opus-4-7": {
|
||||
id: "claude-opus-4-7",
|
||||
family: "claude-opus",
|
||||
reasoning: true,
|
||||
temperature: true,
|
||||
@@ -66,7 +66,7 @@ describe("getModelCapabilities", () => {
|
||||
findProviderModelMetadataSpy = spyOn(connectedProvidersCache, "findProviderModelMetadata").mockReturnValue(undefined)
|
||||
const result = getModelCapabilities({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
runtimeModel: {
|
||||
variants: {
|
||||
low: {},
|
||||
@@ -78,7 +78,7 @@ describe("getModelCapabilities", () => {
|
||||
})
|
||||
|
||||
expect(result).toMatchObject({
|
||||
canonicalModelID: "claude-opus-4-6",
|
||||
canonicalModelID: "claude-opus-4-7",
|
||||
family: "claude-opus",
|
||||
variants: ["low", "medium", "high"],
|
||||
supportsThinking: true,
|
||||
@@ -173,12 +173,12 @@ describe("getModelCapabilities", () => {
|
||||
findProviderModelMetadataSpy = spyOn(connectedProvidersCache, "findProviderModelMetadata").mockReturnValue(undefined)
|
||||
const result = getModelCapabilities({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6-thinking",
|
||||
modelID: "claude-opus-4-7-thinking",
|
||||
bundledSnapshot,
|
||||
})
|
||||
|
||||
expect(result).toMatchObject({
|
||||
canonicalModelID: "claude-opus-4-6",
|
||||
canonicalModelID: "claude-opus-4-7",
|
||||
family: "claude-opus",
|
||||
supportsThinking: true,
|
||||
supportsTemperature: true,
|
||||
@@ -247,13 +247,13 @@ describe("getModelCapabilities", () => {
|
||||
test("canonicalizes provider-prefixed Claude thinking aliases to bare snapshot IDs", () => {
|
||||
const result = getModelCapabilities({
|
||||
providerID: "anthropic",
|
||||
modelID: "anthropic/claude-opus-4-6-thinking",
|
||||
modelID: "anthropic/claude-opus-4-7-thinking",
|
||||
bundledSnapshot,
|
||||
})
|
||||
|
||||
expect(result).toMatchObject({
|
||||
requestedModelID: "anthropic/claude-opus-4-6-thinking",
|
||||
canonicalModelID: "claude-opus-4-6",
|
||||
requestedModelID: "anthropic/claude-opus-4-7-thinking",
|
||||
canonicalModelID: "claude-opus-4-7",
|
||||
family: "claude-opus",
|
||||
supportsThinking: true,
|
||||
supportsTemperature: true,
|
||||
|
||||
@@ -67,22 +67,22 @@ describe("model-capability-aliases", () => {
|
||||
})
|
||||
|
||||
test("normalizes provider-prefixed Claude thinking aliases through a pattern rule", () => {
|
||||
const result = resolveModelIDAlias("anthropic/claude-opus-4-6-thinking")
|
||||
const result = resolveModelIDAlias("anthropic/claude-opus-4-7-thinking")
|
||||
|
||||
expect(result).toEqual({
|
||||
requestedModelID: "anthropic/claude-opus-4-6-thinking",
|
||||
canonicalModelID: "claude-opus-4-6",
|
||||
requestedModelID: "anthropic/claude-opus-4-7-thinking",
|
||||
canonicalModelID: "claude-opus-4-7",
|
||||
source: "pattern-alias",
|
||||
ruleID: "claude-thinking-legacy-alias",
|
||||
})
|
||||
})
|
||||
|
||||
test("does not pattern-match nearby canonical Claude IDs incorrectly", () => {
|
||||
const result = resolveModelIDAlias("claude-opus-4-6-think")
|
||||
const result = resolveModelIDAlias("claude-opus-4-7-think")
|
||||
|
||||
expect(result).toEqual({
|
||||
requestedModelID: "claude-opus-4-6-think",
|
||||
canonicalModelID: "claude-opus-4-6-think",
|
||||
requestedModelID: "claude-opus-4-7-think",
|
||||
canonicalModelID: "claude-opus-4-7-think",
|
||||
source: "canonical",
|
||||
})
|
||||
})
|
||||
@@ -98,11 +98,11 @@ describe("model-capability-aliases", () => {
|
||||
})
|
||||
|
||||
test("normalizes legacy Claude thinking aliases through a pattern rule", () => {
|
||||
const result = resolveModelIDAlias("claude-opus-4-6-thinking")
|
||||
const result = resolveModelIDAlias("claude-opus-4-7-thinking")
|
||||
|
||||
expect(result).toEqual({
|
||||
requestedModelID: "claude-opus-4-6-thinking",
|
||||
canonicalModelID: "claude-opus-4-6",
|
||||
requestedModelID: "claude-opus-4-7-thinking",
|
||||
canonicalModelID: "claude-opus-4-7",
|
||||
source: "pattern-alias",
|
||||
ruleID: "claude-thinking-legacy-alias",
|
||||
})
|
||||
|
||||
@@ -42,8 +42,8 @@ const PATTERN_ALIAS_RULES: ReadonlyArray<PatternAliasRule> = [
|
||||
{
|
||||
ruleID: "claude-thinking-legacy-alias",
|
||||
description: "Normalizes the legacy Claude Opus 4.6 thinking suffix to the canonical snapshot ID.",
|
||||
match: (normalizedModelID) => /^claude-opus-4-6-thinking$/.test(normalizedModelID),
|
||||
canonicalize: () => "claude-opus-4-6",
|
||||
match: (normalizedModelID) => /^claude-opus-4-7-thinking$/.test(normalizedModelID),
|
||||
canonicalize: () => "claude-opus-4-7",
|
||||
},
|
||||
{
|
||||
ruleID: "gemini-3.1-pro-tier-alias",
|
||||
|
||||
@@ -19,7 +19,7 @@ describe("model-capability-guardrails", () => {
|
||||
|
||||
expect(modelIDs).toEqual([...modelIDs].sort())
|
||||
expect(new Set(modelIDs).size).toBe(modelIDs.length)
|
||||
expect(modelIDs).toContain("claude-opus-4-6")
|
||||
expect(modelIDs).toContain("claude-opus-4-7")
|
||||
expect(modelIDs).toContain("gpt-5.4")
|
||||
expect(modelIDs).toContain("kimi-k2.5")
|
||||
})
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("model-error-classifier", () => {
|
||||
//#given
|
||||
const error = {
|
||||
message:
|
||||
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
"All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
|
||||
}
|
||||
|
||||
//#when
|
||||
|
||||
@@ -9,8 +9,8 @@ describe("normalizeModelFormat", () => {
|
||||
})
|
||||
|
||||
it("handles provider with multiple slashes", () => {
|
||||
const result = normalizeModelFormat("anthropic/claude-opus-4-6/max")
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6/max" })
|
||||
const result = normalizeModelFormat("anthropic/claude-opus-4-7/max")
|
||||
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7/max" })
|
||||
})
|
||||
|
||||
it("returns undefined for malformed string without separator", () => {
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
|
||||
expect(primary.variant).toBe("high")
|
||||
})
|
||||
|
||||
test("sisyphus has claude-opus-4-6 as primary with k2p5, kimi-k2.5, gpt-5.4 medium fallbacks", () => {
|
||||
test("sisyphus has claude-opus-4-7 as primary with k2p5, kimi-k2.5, gpt-5.4 medium fallbacks", () => {
|
||||
// #given - sisyphus agent requirement
|
||||
const sisyphus = AGENT_MODEL_REQUIREMENTS["sisyphus"]
|
||||
|
||||
@@ -36,7 +36,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
|
||||
|
||||
const primary = sisyphus.fallbackChain[0]
|
||||
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
|
||||
expect(primary.model).toBe("claude-opus-4-6")
|
||||
expect(primary.model).toBe("claude-opus-4-7")
|
||||
expect(primary.variant).toBe("max")
|
||||
|
||||
const second = sisyphus.fallbackChain[1]
|
||||
@@ -148,34 +148,34 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
|
||||
expect(last.model).toBe("gpt-5-nano")
|
||||
})
|
||||
|
||||
test("prometheus has claude-opus-4-6 as primary", () => {
|
||||
test("prometheus has claude-opus-4-7 as primary", () => {
|
||||
// #given - prometheus agent requirement
|
||||
const prometheus = AGENT_MODEL_REQUIREMENTS["prometheus"]
|
||||
|
||||
// #when - accessing Prometheus requirement
|
||||
// #then - claude-opus-4-6 is first
|
||||
// #then - claude-opus-4-7 is first
|
||||
expect(prometheus).toBeDefined()
|
||||
expect(prometheus.fallbackChain).toBeArray()
|
||||
expect(prometheus.fallbackChain.length).toBeGreaterThan(1)
|
||||
|
||||
const primary = prometheus.fallbackChain[0]
|
||||
expect(primary.model).toBe("claude-opus-4-6")
|
||||
expect(primary.model).toBe("claude-opus-4-7")
|
||||
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
|
||||
expect(primary.variant).toBe("max")
|
||||
})
|
||||
|
||||
test("metis has claude-opus-4-6 as primary", () => {
|
||||
test("metis has claude-opus-4-7 as primary", () => {
|
||||
// #given - metis agent requirement
|
||||
const metis = AGENT_MODEL_REQUIREMENTS["metis"]
|
||||
|
||||
// #when - accessing Metis requirement
|
||||
// #then - claude-opus-4-6 is first
|
||||
// #then - claude-opus-4-7 is first
|
||||
expect(metis).toBeDefined()
|
||||
expect(metis.fallbackChain).toBeArray()
|
||||
expect(metis.fallbackChain.length).toBeGreaterThan(1)
|
||||
|
||||
const primary = metis.fallbackChain[0]
|
||||
expect(primary.model).toBe("claude-opus-4-6")
|
||||
expect(primary.model).toBe("claude-opus-4-7")
|
||||
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
|
||||
expect(primary.variant).toBe("max")
|
||||
|
||||
@@ -356,7 +356,7 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
|
||||
expect(second.model).toBe("glm-5")
|
||||
|
||||
const third = visualEngineering.fallbackChain[2]
|
||||
expect(third.model).toBe("claude-opus-4-6")
|
||||
expect(third.model).toBe("claude-opus-4-7")
|
||||
expect(third.variant).toBe("max")
|
||||
|
||||
const fourth = visualEngineering.fallbackChain[3]
|
||||
@@ -402,18 +402,18 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
|
||||
expect(primary.providers[0]).toBe("anthropic")
|
||||
})
|
||||
|
||||
test("unspecified-high has claude-opus-4-6 as primary and gpt-5.4 as secondary", () => {
|
||||
test("unspecified-high has claude-opus-4-7 as primary and gpt-5.4 as secondary", () => {
|
||||
// #given - unspecified-high category requirement
|
||||
const unspecifiedHigh = CATEGORY_MODEL_REQUIREMENTS["unspecified-high"]
|
||||
|
||||
// #when - accessing unspecified-high requirement
|
||||
// #then - claude-opus-4-6 is first and gpt-5.4 is second
|
||||
// #then - claude-opus-4-7 is first and gpt-5.4 is second
|
||||
expect(unspecifiedHigh).toBeDefined()
|
||||
expect(unspecifiedHigh.fallbackChain).toBeArray()
|
||||
expect(unspecifiedHigh.fallbackChain.length).toBeGreaterThan(1)
|
||||
|
||||
const primary = unspecifiedHigh.fallbackChain[0]
|
||||
expect(primary.model).toBe("claude-opus-4-6")
|
||||
expect(primary.model).toBe("claude-opus-4-7")
|
||||
expect(primary.variant).toBe("max")
|
||||
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
|
||||
|
||||
@@ -505,14 +505,14 @@ describe("FallbackEntry type", () => {
|
||||
// given - a valid FallbackEntry object
|
||||
const entry: FallbackEntry = {
|
||||
providers: ["anthropic", "github-copilot", "opencode"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "high",
|
||||
}
|
||||
|
||||
// when - accessing properties
|
||||
// then - all properties are accessible
|
||||
expect(entry.providers).toEqual(["anthropic", "github-copilot", "opencode"])
|
||||
expect(entry.model).toBe("claude-opus-4-6")
|
||||
expect(entry.model).toBe("claude-opus-4-7")
|
||||
expect(entry.variant).toBe("high")
|
||||
})
|
||||
|
||||
@@ -534,7 +534,7 @@ describe("ModelRequirement type", () => {
|
||||
// given - a valid ModelRequirement object
|
||||
const requirement: ModelRequirement = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-7", variant: "max" },
|
||||
{ providers: ["openai", "github-copilot"], model: "gpt-5.4", variant: "high" },
|
||||
],
|
||||
}
|
||||
@@ -543,7 +543,7 @@ describe("ModelRequirement type", () => {
|
||||
// then - fallbackChain is accessible with correct structure
|
||||
expect(requirement.fallbackChain).toBeArray()
|
||||
expect(requirement.fallbackChain).toHaveLength(2)
|
||||
expect(requirement.fallbackChain[0].model).toBe("claude-opus-4-6")
|
||||
expect(requirement.fallbackChain[0].model).toBe("claude-opus-4-7")
|
||||
expect(requirement.fallbackChain[1].model).toBe("gpt-5.4")
|
||||
})
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
fallbackChain: [
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{ providers: ["opencode-go", "vercel"], model: "kimi-k2.5" },
|
||||
@@ -69,7 +69,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
},
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{ providers: ["opencode-go", "vercel"], model: "glm-5" },
|
||||
@@ -104,7 +104,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
fallbackChain: [
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{
|
||||
@@ -123,7 +123,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
fallbackChain: [
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{
|
||||
@@ -144,7 +144,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
},
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{
|
||||
@@ -193,7 +193,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
{ providers: ["zai-coding-plan", "opencode", "vercel"], model: "glm-5" },
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{ providers: ["opencode-go", "vercel"], model: "glm-5" },
|
||||
@@ -214,7 +214,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
},
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{ providers: ["opencode-go", "vercel"], model: "glm-5" },
|
||||
@@ -229,7 +229,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
},
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{
|
||||
@@ -248,7 +248,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
},
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{ providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4" },
|
||||
@@ -296,7 +296,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
fallbackChain: [
|
||||
{
|
||||
providers: ["anthropic", "github-copilot", "opencode", "vercel"],
|
||||
model: "claude-opus-4-6",
|
||||
model: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("resolveModel", () => {
|
||||
test("returns userModel when all three are set", () => {
|
||||
// given
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
inheritedModel: "openai/gpt-5.4",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
@@ -21,7 +21,7 @@ describe("resolveModel", () => {
|
||||
const result = resolveModel(input)
|
||||
|
||||
// then
|
||||
expect(result).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("returns inheritedModel when userModel is undefined", () => {
|
||||
@@ -91,7 +91,7 @@ describe("resolveModel", () => {
|
||||
test("same input returns same output (referential transparency)", () => {
|
||||
// given
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
inheritedModel: "openai/gpt-5.4",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
@@ -122,11 +122,11 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
uiSelectedModel: "opencode/big-pickle",
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6", "github-copilot/claude-opus-4-6-preview"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7", "github-copilot/claude-opus-4-7-preview"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -143,8 +143,8 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
uiSelectedModel: "opencode/big-pickle",
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -160,8 +160,8 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
uiSelectedModel: " ",
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -169,16 +169,16 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(logSpy).toHaveBeenCalledWith("Model resolved via config override", { model: "anthropic/claude-opus-4-6" })
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(logSpy).toHaveBeenCalledWith("Model resolved via config override", { model: "anthropic/claude-opus-4-7" })
|
||||
})
|
||||
|
||||
test("empty string uiSelectedModel falls through to config override", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
uiSelectedModel: "",
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -194,11 +194,11 @@ describe("resolveModelWithFallback", () => {
|
||||
test("returns userModel with override source when userModel is provided", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6", "github-copilot/claude-opus-4-6-preview"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7", "github-copilot/claude-opus-4-7-preview"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -206,9 +206,9 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(result!.source).toBe("override")
|
||||
expect(logSpy).toHaveBeenCalledWith("Model resolved via config override", { model: "anthropic/claude-opus-4-6" })
|
||||
expect(logSpy).toHaveBeenCalledWith("Model resolved via config override", { model: "anthropic/claude-opus-4-7" })
|
||||
})
|
||||
|
||||
test("override takes priority even if model not in availableModels", () => {
|
||||
@@ -216,9 +216,9 @@ describe("resolveModelWithFallback", () => {
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: "custom/my-model",
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -235,9 +235,9 @@ describe("resolveModelWithFallback", () => {
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: " ",
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -253,9 +253,9 @@ describe("resolveModelWithFallback", () => {
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: "",
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -272,9 +272,9 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(["github-copilot/claude-opus-4-6-preview", "opencode/claude-opus-4-7"]),
|
||||
availableModels: new Set(["github-copilot/claude-opus-4-7-preview", "opencode/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -282,12 +282,12 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("github-copilot/claude-opus-4-6-preview")
|
||||
expect(result!.model).toBe("github-copilot/claude-opus-4-7-preview")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
expect(logSpy).toHaveBeenCalledWith("Model resolved via fallback chain (availability confirmed)", {
|
||||
provider: "github-copilot",
|
||||
model: "claude-opus-4-6",
|
||||
match: "github-copilot/claude-opus-4-6-preview",
|
||||
model: "claude-opus-4-7",
|
||||
match: "github-copilot/claude-opus-4-7-preview",
|
||||
variant: undefined,
|
||||
})
|
||||
})
|
||||
@@ -298,7 +298,7 @@ describe("resolveModelWithFallback", () => {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "anthropic", "google"], model: "gpt-5.4" },
|
||||
],
|
||||
availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6", "google/gemini-3.1-pro"]),
|
||||
availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-7", "google/gemini-3.1-pro"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ describe("resolveModelWithFallback", () => {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6", "github-copilot/claude-opus-4-6-preview"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7", "github-copilot/claude-opus-4-7-preview"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -342,14 +342,14 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
test("skips fallback chain when not provided", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -364,7 +364,7 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ describe("resolveModelWithFallback", () => {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "CLAUDE-OPUS" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
@@ -480,7 +480,7 @@ describe("resolveModelWithFallback", () => {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "nonexistent-model" },
|
||||
],
|
||||
availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: undefined, // no system default configured
|
||||
@@ -517,7 +517,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai", "google"])
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "openai"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic", "openai"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
@@ -527,7 +527,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - should use connected provider (openai) from fallback chain
|
||||
expect(result!.model).toBe("openai/claude-opus-4-6")
|
||||
expect(result!.model).toBe("openai/claude-opus-4-7")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
cacheSpy.mockRestore()
|
||||
})
|
||||
@@ -561,14 +561,14 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["openai", "opencode"], model: "claude-haiku-4-5" },
|
||||
],
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "anthropic/claude-opus-4-6-20251101",
|
||||
systemDefaultModel: "anthropic/claude-opus-4-7-20251101",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - no provider in fallback is connected, fall through to system default
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6-20251101")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7-20251101")
|
||||
expect(result!.source).toBe("system-default")
|
||||
cacheSpy.mockRestore()
|
||||
})
|
||||
@@ -578,7 +578,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
@@ -612,20 +612,20 @@ describe("resolveModelWithFallback", () => {
|
||||
describe("Multi-entry fallbackChain", () => {
|
||||
test("resolves to claude-opus when OpenAI unavailable but Anthropic available (oracle scenario)", () => {
|
||||
// given
|
||||
const availableModels = new Set(["anthropic/claude-opus-4-6"])
|
||||
const availableModels = new Set(["anthropic/claude-opus-4-7"])
|
||||
|
||||
// when
|
||||
const result = resolveModelWithFallback({
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4", variant: "high" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-7", variant: "max" },
|
||||
],
|
||||
availableModels,
|
||||
systemDefaultModel: "system/default",
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
@@ -652,14 +652,14 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const availableModels = new Set([
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
])
|
||||
|
||||
// when
|
||||
const result = resolveModelWithFallback({
|
||||
fallbackChain: [
|
||||
{ providers: ["openai"], model: "gpt-5.4" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels,
|
||||
systemDefaultModel: "system/default",
|
||||
@@ -678,7 +678,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback({
|
||||
fallbackChain: [
|
||||
{ providers: ["openai"], model: "gpt-5.4" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels,
|
||||
@@ -695,7 +695,7 @@ describe("resolveModelWithFallback", () => {
|
||||
test("result has correct ModelResolutionResult shape", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
@@ -718,7 +718,7 @@ describe("resolveModelWithFallback", () => {
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
||||
}
|
||||
|
||||
@@ -754,9 +754,9 @@ describe("resolveModelWithFallback", () => {
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
categoryDefaultModel: "google/gemini-3.1-pro",
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "system/default",
|
||||
}
|
||||
|
||||
@@ -764,19 +764,19 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - should fall through to fallbackChain
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
test("userModel takes priority over categoryDefaultModel", () => {
|
||||
// given - both userModel and categoryDefaultModel provided
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
categoryDefaultModel: "google/gemini-3.1-pro",
|
||||
fallbackChain: [
|
||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: "system/default",
|
||||
}
|
||||
|
||||
@@ -784,7 +784,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - userModel wins
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(result!.source).toBe("override")
|
||||
})
|
||||
|
||||
@@ -916,7 +916,7 @@ describe("resolveModelWithFallback", () => {
|
||||
test("still returns override when userModel provided even if systemDefaultModel undefined", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
userModel: "anthropic/claude-opus-4-7",
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: undefined,
|
||||
}
|
||||
@@ -926,7 +926,7 @@ describe("resolveModelWithFallback", () => {
|
||||
|
||||
// then
|
||||
expect(result).toBeDefined()
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(result!.source).toBe("override")
|
||||
})
|
||||
|
||||
@@ -934,9 +934,9 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["anthropic/claude-opus-4-7"]),
|
||||
systemDefaultModel: undefined,
|
||||
}
|
||||
|
||||
@@ -945,7 +945,7 @@ describe("resolveModelWithFallback", () => {
|
||||
|
||||
// then
|
||||
expect(result).toBeDefined()
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ describe("resolveCompatibleModelSettings", () => {
|
||||
test("keeps supported Claude Opus variant unchanged", () => {
|
||||
const result = resolveCompatibleModelSettings({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
desired: { variant: "max" },
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("resolveCompatibleModelSettings", () => {
|
||||
test("uses model metadata first for variant support", () => {
|
||||
const result = resolveCompatibleModelSettings({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
desired: { variant: "max" },
|
||||
capabilities: { variants: ["low", "medium", "high"] },
|
||||
})
|
||||
@@ -42,7 +42,7 @@ describe("resolveCompatibleModelSettings", () => {
|
||||
test("prefers metadata over family heuristics even when family would allow a higher level", () => {
|
||||
const result = resolveCompatibleModelSettings({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
desired: { variant: "max" },
|
||||
capabilities: { variants: ["low", "medium"] },
|
||||
})
|
||||
@@ -514,7 +514,7 @@ describe("resolveCompatibleModelSettings", () => {
|
||||
test("no-op when desired settings are empty", () => {
|
||||
const result = resolveCompatibleModelSettings({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
desired: {},
|
||||
})
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ function applyGatewayTransforms(model: string): string {
|
||||
}
|
||||
|
||||
export function transformModelForProvider(provider: string, model: string): string {
|
||||
// Vercel AI Gateway expects <sub-provider>/<model> (e.g. anthropic/claude-opus-4.6).
|
||||
// Canonical names in model-requirements.ts may be bare (claude-opus-4-6) or
|
||||
// already prefixed (anthropic/claude-opus-4-6). Both need gateway-specific transforms.
|
||||
// Vercel AI Gateway expects <sub-provider>/<model> (e.g. anthropic/claude-opus-4.7).
|
||||
// Canonical names in model-requirements.ts may be bare (claude-opus-4-7) or
|
||||
// already prefixed (anthropic/claude-opus-4-7). Both need gateway-specific transforms.
|
||||
if (provider === "vercel") {
|
||||
// Already prefixed — transform only the model part
|
||||
const slashIndex = model.indexOf("/")
|
||||
|
||||
@@ -47,7 +47,7 @@ export const ANTHROPIC_CATEGORIES: BuiltinCategoryDefinition[] = [
|
||||
},
|
||||
{
|
||||
name: "unspecified-high",
|
||||
config: { model: "anthropic/claude-opus-4-6", variant: "max" },
|
||||
config: { model: "anthropic/claude-opus-4-7", variant: "max" },
|
||||
description: "Tasks that don't fit other categories, high effort required",
|
||||
promptAppend: UNSPECIFIED_HIGH_CATEGORY_PROMPT_APPEND,
|
||||
},
|
||||
|
||||
@@ -99,7 +99,7 @@ describe("resolveCategoryExecution", () => {
|
||||
const executorCtx = createMockExecutorContext()
|
||||
executorCtx.userCategories = {
|
||||
deep: {
|
||||
model: "quotio/claude-opus-4-6",
|
||||
model: "quotio/claude-opus-4-7",
|
||||
fallback_models: ["quotio/kimi-k2.5", "openai/gpt-5.2(high)"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -272,11 +272,11 @@ describe("executeSyncTask - cleanup on error paths", () => {
|
||||
|
||||
const initialModel = {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
}
|
||||
const fallbackChain = [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7", variant: "max" },
|
||||
{ providers: ["opencode-go"], model: "kimi-k2.5" },
|
||||
]
|
||||
|
||||
@@ -289,7 +289,7 @@ describe("executeSyncTask - cleanup on error paths", () => {
|
||||
expect(result).toContain("Task completed")
|
||||
expect(result).toContain("Model: opencode-go/kimi-k2.5")
|
||||
expect(attemptedModels).toEqual([
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" },
|
||||
{ providerID: "opencode-go", modelID: "kimi-k2.5", variant: undefined },
|
||||
])
|
||||
})
|
||||
@@ -339,11 +339,11 @@ describe("executeSyncTask - cleanup on error paths", () => {
|
||||
|
||||
const initialModel = {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
}
|
||||
const fallbackChain = [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-7", variant: "max" },
|
||||
{ providers: ["opencode-go"], model: "kimi-k2.5" },
|
||||
{ providers: ["openai"], model: "gpt-5.4", variant: "medium" },
|
||||
]
|
||||
@@ -356,7 +356,7 @@ describe("executeSyncTask - cleanup on error paths", () => {
|
||||
//#then
|
||||
expect(result).toBe("Final failure")
|
||||
expect(attemptedModels).toEqual([
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" },
|
||||
{ providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" },
|
||||
{ providerID: "opencode-go", modelID: "kimi-k2.5", variant: undefined },
|
||||
{ providerID: "openai", modelID: "gpt-5.4", variant: "medium" },
|
||||
])
|
||||
|
||||
@@ -28,7 +28,7 @@ const SYSTEM_DEFAULT_MODEL = "anthropic/claude-sonnet-4-6"
|
||||
|
||||
const TEST_CONNECTED_PROVIDERS = ["anthropic", "google", "openai"]
|
||||
const TEST_AVAILABLE_MODELS = new Set([
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"anthropic/claude-sonnet-4-6",
|
||||
"anthropic/claude-haiku-4-5",
|
||||
"google/gemini-3.1-pro",
|
||||
@@ -66,7 +66,7 @@ describe("sisyphus-task", () => {
|
||||
cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic", "google", "openai"])
|
||||
providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue({
|
||||
models: {
|
||||
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
||||
anthropic: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
||||
google: ["gemini-3.1-pro", "gemini-3-flash"],
|
||||
openai: ["gpt-5.4", "gpt-5.3-codex"],
|
||||
},
|
||||
@@ -112,13 +112,13 @@ describe("sisyphus-task", () => {
|
||||
expect(category.variant).toBe("medium")
|
||||
})
|
||||
|
||||
test("unspecified-high category uses claude-opus-4-6 max as primary", () => {
|
||||
test("unspecified-high category uses claude-opus-4-7 max as primary", () => {
|
||||
// given
|
||||
const category = DEFAULT_CATEGORIES["unspecified-high"]
|
||||
|
||||
// when / #then
|
||||
expect(category).toBeDefined()
|
||||
expect(category.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(category.model).toBe("anthropic/claude-opus-4-7")
|
||||
expect(category.variant).toBe("max")
|
||||
})
|
||||
})
|
||||
@@ -757,7 +757,7 @@ describe("sisyphus-task", () => {
|
||||
test("blocks requiresModel when availability is known and missing the required model", () => {
|
||||
// given - artistry has requiresModel: gemini-3.1-pro
|
||||
const categoryName = "artistry"
|
||||
const availableModels = new Set<string>(["anthropic/claude-opus-4-6"])
|
||||
const availableModels = new Set<string>(["anthropic/claude-opus-4-7"])
|
||||
|
||||
// when
|
||||
const result = resolveCategoryConfig(categoryName, {
|
||||
@@ -787,9 +787,9 @@ describe("sisyphus-task", () => {
|
||||
test("bypasses requiresModel when explicit user config provided", () => {
|
||||
// #given
|
||||
const categoryName = "deep"
|
||||
const availableModels = new Set<string>(["anthropic/claude-opus-4-6"])
|
||||
const availableModels = new Set<string>(["anthropic/claude-opus-4-7"])
|
||||
const userCategories = {
|
||||
deep: { model: "anthropic/claude-opus-4-6" },
|
||||
deep: { model: "anthropic/claude-opus-4-7" },
|
||||
}
|
||||
|
||||
// #when
|
||||
@@ -801,7 +801,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// #then
|
||||
expect(result).not.toBeNull()
|
||||
expect(result!.config.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.config.model).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("bypasses requiresModel when explicit user config provided even with empty availability", () => {
|
||||
@@ -809,7 +809,7 @@ describe("sisyphus-task", () => {
|
||||
const categoryName = "deep"
|
||||
const availableModels = new Set<string>()
|
||||
const userCategories = {
|
||||
deep: { model: "anthropic/claude-opus-4-6" },
|
||||
deep: { model: "anthropic/claude-opus-4-7" },
|
||||
}
|
||||
|
||||
// #when
|
||||
@@ -821,7 +821,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// #then
|
||||
expect(result).not.toBeNull()
|
||||
expect(result!.config.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.config.model).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("returns default model from DEFAULT_CATEGORIES for builtin category", () => {
|
||||
@@ -841,7 +841,7 @@ describe("sisyphus-task", () => {
|
||||
// given
|
||||
const categoryName = "visual-engineering"
|
||||
const userCategories = {
|
||||
"visual-engineering": { model: "anthropic/claude-opus-4-6" },
|
||||
"visual-engineering": { model: "anthropic/claude-opus-4-7" },
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -849,7 +849,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// then
|
||||
expect(result).not.toBeNull()
|
||||
expect(result!.config.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(result!.config.model).toBe("anthropic/claude-opus-4-7")
|
||||
})
|
||||
|
||||
test("user prompt_append is appended to default", () => {
|
||||
@@ -913,7 +913,7 @@ describe("sisyphus-task", () => {
|
||||
test("category built-in model takes precedence over inheritedModel", () => {
|
||||
// given - builtin category with its own model, parent model also provided
|
||||
const categoryName = "visual-engineering"
|
||||
const inheritedModel = "cliproxy/claude-opus-4-6"
|
||||
const inheritedModel = "cliproxy/claude-opus-4-7"
|
||||
|
||||
// when
|
||||
const result = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
@@ -927,7 +927,7 @@ describe("sisyphus-task", () => {
|
||||
// given - custom category with no model defined
|
||||
const categoryName = "my-custom-no-model"
|
||||
const userCategories = { "my-custom-no-model": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
|
||||
const inheritedModel = "cliproxy/claude-opus-4-6"
|
||||
const inheritedModel = "cliproxy/claude-opus-4-7"
|
||||
|
||||
// when
|
||||
const result = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
@@ -943,7 +943,7 @@ describe("sisyphus-task", () => {
|
||||
const userCategories = {
|
||||
"visual-engineering": { model: "my-provider/my-model" },
|
||||
}
|
||||
const inheritedModel = "cliproxy/claude-opus-4-6"
|
||||
const inheritedModel = "cliproxy/claude-opus-4-7"
|
||||
|
||||
// when
|
||||
const result = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
@@ -1054,7 +1054,7 @@ describe("sisyphus-task", () => {
|
||||
const mockClient = {
|
||||
app: { agents: async () => ({ data: [] }) },
|
||||
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||
model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-6" }] },
|
||||
model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-7" }] },
|
||||
session: {
|
||||
create: async () => ({ data: { id: "test-session" } }),
|
||||
prompt: async () => ({ data: {} }),
|
||||
@@ -1078,7 +1078,7 @@ describe("sisyphus-task", () => {
|
||||
abort: new AbortController().signal,
|
||||
}
|
||||
|
||||
// when - unspecified-high uses claude-opus-4-6 max in DEFAULT_CATEGORIES
|
||||
// when - unspecified-high uses claude-opus-4-7 max in DEFAULT_CATEGORIES
|
||||
await tool.execute(
|
||||
{
|
||||
description: "Test unspecified-high default variant",
|
||||
@@ -1090,10 +1090,10 @@ describe("sisyphus-task", () => {
|
||||
toolContext
|
||||
)
|
||||
|
||||
// then - claude-opus-4-6 should be passed with max variant
|
||||
// then - claude-opus-4-7 should be passed with max variant
|
||||
expect(launchInput.model).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
variant: "max",
|
||||
})
|
||||
}, { timeout: 20000 })
|
||||
@@ -1113,7 +1113,7 @@ describe("sisyphus-task", () => {
|
||||
const mockClient = {
|
||||
app: { agents: async () => ({ data: [] }) },
|
||||
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||
model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-6" }] },
|
||||
model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-7" }] },
|
||||
session: {
|
||||
get: async () => ({ data: { directory: "/project" } }),
|
||||
create: async () => ({ data: { id: "ses_sync_default_variant" } }),
|
||||
@@ -1139,7 +1139,7 @@ describe("sisyphus-task", () => {
|
||||
abort: new AbortController().signal,
|
||||
}
|
||||
|
||||
// when - unspecified-high uses claude-opus-4-6 max in DEFAULT_CATEGORIES
|
||||
// when - unspecified-high uses claude-opus-4-7 max in DEFAULT_CATEGORIES
|
||||
await tool.execute(
|
||||
{
|
||||
description: "Test unspecified-high sync variant",
|
||||
@@ -1151,10 +1151,10 @@ describe("sisyphus-task", () => {
|
||||
toolContext
|
||||
)
|
||||
|
||||
// then - claude-opus-4-6 should be passed with max variant
|
||||
// then - claude-opus-4-7 should be passed with max variant
|
||||
expect(promptBody.model).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
expect(promptBody.variant).toBe("max")
|
||||
}, { timeout: 20000 })
|
||||
@@ -1550,7 +1550,7 @@ describe("sisyphus-task", () => {
|
||||
let promptCalled = false
|
||||
const mockManager = { launch: async () => ({}) }
|
||||
const mockClient = {
|
||||
app: { agents: async () => ({ data: [{ name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-6" } }] }) },
|
||||
app: { agents: async () => ({ data: [{ name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-7" } }] }) },
|
||||
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||
session: {
|
||||
get: async () => ({ data: { directory: "/project" } }),
|
||||
@@ -1835,7 +1835,7 @@ describe("sisyphus-task", () => {
|
||||
id: "msg_001",
|
||||
role: "user",
|
||||
agent: "sisyphus-junior",
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||
model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
|
||||
variant: "max",
|
||||
time: { created: baseTime },
|
||||
},
|
||||
@@ -1917,7 +1917,7 @@ describe("sisyphus-task", () => {
|
||||
const callArgs = promptMock.mock.calls[0][0]
|
||||
expect(callArgs.body.variant).toBe("max")
|
||||
expect(callArgs.body.agent).toBe("sisyphus-junior")
|
||||
expect(callArgs.body.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
|
||||
expect(callArgs.body.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
|
||||
}, { timeout: 10000 })
|
||||
|
||||
test("task_id with background=true should return immediately without waiting", async () => {
|
||||
@@ -2551,7 +2551,7 @@ describe("sisyphus-task", () => {
|
||||
// Override provider cache to include kimi-for-coding provider
|
||||
providerModelsSpy.mockReturnValue({
|
||||
models: {
|
||||
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
||||
anthropic: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
||||
google: ["gemini-3.1-pro", "gemini-3-flash"],
|
||||
openai: ["gpt-5.4", "gpt-5.3-codex"],
|
||||
"kimi-for-coding": ["k2p5"],
|
||||
@@ -2798,7 +2798,7 @@ describe("sisyphus-task", () => {
|
||||
manager: mockManager,
|
||||
client: mockClient,
|
||||
userCategories: {
|
||||
"fallback-test": { model: "anthropic/claude-opus-4-6" },
|
||||
"fallback-test": { model: "anthropic/claude-opus-4-7" },
|
||||
},
|
||||
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||
availableModelsOverride: createTestAvailableModels(),
|
||||
@@ -3465,7 +3465,7 @@ describe("sisyphus-task", () => {
|
||||
test("category built-in model takes precedence over inheritedModel for builtin category", () => {
|
||||
// given - builtin ultrabrain category with its own model, inherited model also provided
|
||||
const categoryName = "ultrabrain"
|
||||
const inheritedModel = "cliproxy/claude-opus-4-6"
|
||||
const inheritedModel = "cliproxy/claude-opus-4-7"
|
||||
|
||||
// when
|
||||
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
@@ -3480,7 +3480,7 @@ describe("sisyphus-task", () => {
|
||||
// given
|
||||
const categoryName = "ultrabrain"
|
||||
const userCategories = { "ultrabrain": { model: "my-provider/custom-model" } }
|
||||
const inheritedModel = "cliproxy/claude-opus-4-6"
|
||||
const inheritedModel = "cliproxy/claude-opus-4-7"
|
||||
|
||||
// when
|
||||
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
@@ -3497,7 +3497,7 @@ describe("sisyphus-task", () => {
|
||||
// given - This test verifies the fix for PR #770 bug
|
||||
// The bug was: checking `if (inheritedModel)` instead of `if (actualModel === inheritedModel)`
|
||||
const categoryName = "ultrabrain"
|
||||
const inheritedModel = "cliproxy/claude-opus-4-6"
|
||||
const inheritedModel = "cliproxy/claude-opus-4-7"
|
||||
const userCategories = { "ultrabrain": { model: "user/model" } }
|
||||
|
||||
// when - user model wins
|
||||
@@ -3525,7 +3525,7 @@ describe("sisyphus-task", () => {
|
||||
// given a builtin category with its own model, and an inherited model from parent
|
||||
// The CORRECT chain: userConfig?.model ?? categoryBuiltIn ?? systemDefaultModel
|
||||
const categoryName = "ultrabrain"
|
||||
const inheritedModel = "anthropic/claude-opus-4-6"
|
||||
const inheritedModel = "anthropic/claude-opus-4-7"
|
||||
|
||||
// when category has a built-in model (gpt-5.4 for ultrabrain)
|
||||
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
@@ -3556,7 +3556,7 @@ describe("sisyphus-task", () => {
|
||||
// given userConfig.model is explicitly set
|
||||
const categoryName = "ultrabrain"
|
||||
const userCategories = { "ultrabrain": { model: "custom/user-model" } }
|
||||
const inheritedModel = "anthropic/claude-opus-4-6"
|
||||
const inheritedModel = "anthropic/claude-opus-4-7"
|
||||
const systemDefaultModel = "anthropic/claude-sonnet-4-6"
|
||||
|
||||
// when resolveCategoryConfig is called with all sources
|
||||
@@ -3575,7 +3575,7 @@ describe("sisyphus-task", () => {
|
||||
// given userConfig.model is empty string "" for a custom category (no built-in model)
|
||||
const categoryName = "custom-empty-model"
|
||||
const userCategories = { "custom-empty-model": { model: "", temperature: 0.3 } }
|
||||
const inheritedModel = "anthropic/claude-opus-4-6"
|
||||
const inheritedModel = "anthropic/claude-opus-4-7"
|
||||
|
||||
// when resolveCategoryConfig is called
|
||||
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
@@ -3590,7 +3590,7 @@ describe("sisyphus-task", () => {
|
||||
const categoryName = "visual-engineering"
|
||||
// Using type assertion since we're testing fallback behavior for categories without model
|
||||
const userCategories = { "visual-engineering": { temperature: 0.2 } } as unknown as Record<string, CategoryConfig>
|
||||
const inheritedModel = "anthropic/claude-opus-4-6"
|
||||
const inheritedModel = "anthropic/claude-opus-4-7"
|
||||
|
||||
// when resolveCategoryConfig is called
|
||||
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
@@ -3810,7 +3810,7 @@ describe("sisyphus-task", () => {
|
||||
app: {
|
||||
agents: async () => ({
|
||||
data: [
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-6" } },
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-7" } },
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -3854,7 +3854,7 @@ describe("sisyphus-task", () => {
|
||||
// then - matched agent's model should be passed to session.prompt
|
||||
expect(promptBody.model).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
}, { timeout: 20000 })
|
||||
|
||||
@@ -3956,7 +3956,7 @@ describe("sisyphus-task", () => {
|
||||
manager: mockManager,
|
||||
client: mockClient,
|
||||
agentOverrides: {
|
||||
oracle: { model: "anthropic/claude-opus-4-6" },
|
||||
oracle: { model: "anthropic/claude-opus-4-7" },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3982,7 +3982,7 @@ describe("sisyphus-task", () => {
|
||||
// then - user-configured model should take priority over matchedAgent.model
|
||||
expect(promptBody.model).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
}, { timeout: 20000 })
|
||||
|
||||
@@ -4023,7 +4023,7 @@ describe("sisyphus-task", () => {
|
||||
manager: mockManager,
|
||||
client: mockClient,
|
||||
agentOverrides: {
|
||||
oracle: { model: "anthropic/claude-opus-4-6", variant: "max" },
|
||||
oracle: { model: "anthropic/claude-opus-4-7", variant: "max" },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4111,7 +4111,7 @@ describe("sisyphus-task", () => {
|
||||
)
|
||||
|
||||
// then - should resolve via AGENT_MODEL_REQUIREMENTS fallback chain for oracle
|
||||
// oracle fallback chain: gpt-5.4 (openai) > gemini-3.1-pro (google) > claude-opus-4-6 (anthropic)
|
||||
// oracle fallback chain: gpt-5.4 (openai) > gemini-3.1-pro (google) > claude-opus-4-7 (anthropic)
|
||||
// Since openai is in connectedProviders, should resolve to openai/gpt-5.4
|
||||
expect(promptBody.model).toBeDefined()
|
||||
expect(promptBody.model.providerID).toBe("openai")
|
||||
|
||||
Reference in New Issue
Block a user