fix(shared): normalize claude model IDs for anthropic provider (#3290)
Anthropic API accepts claude-opus-4.6 (dot format) but not claude-opus-4-6 (dash format). Added anthropic case to transformModelForProvider to normalize dash-format model IDs before they reach the provider. Updated model config snapshots and test expectations to match the new dot-format output. 🤖 Generated with OhMyOpenCode assistance https://github.com/code-yeongyu/oh-my-opencode
This commit is contained in:
@@ -180,7 +180,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(agents.sisyphus).toBeDefined()
|
expect(agents.sisyphus).toBeDefined()
|
||||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6")
|
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.6")
|
||||||
} finally {
|
} finally {
|
||||||
cacheSpy.mockRestore()
|
cacheSpy.mockRestore()
|
||||||
fetchSpy.mockRestore()
|
fetchSpy.mockRestore()
|
||||||
@@ -918,7 +918,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
|
|||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(agents.sisyphus).toBeDefined()
|
expect(agents.sisyphus).toBeDefined()
|
||||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6")
|
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.6")
|
||||||
} finally {
|
} finally {
|
||||||
cacheSpy.mockRestore()
|
cacheSpy.mockRestore()
|
||||||
fetchSpy.mockRestore()
|
fetchSpy.mockRestore()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
|
||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
|
|
||||||
import { generateModelConfig } from "./model-fallback"
|
import { generateModelConfig } from "./model-fallback"
|
||||||
@@ -378,7 +380,7 @@ describe("generateModelConfig", () => {
|
|||||||
const result = generateModelConfig(config)
|
const result = generateModelConfig(config)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-6")
|
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4.6")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("Sisyphus is created when multiple fallback providers are available", () => {
|
test("Sisyphus is created when multiple fallback providers are available", () => {
|
||||||
@@ -395,7 +397,7 @@ describe("generateModelConfig", () => {
|
|||||||
const result = generateModelConfig(config)
|
const result = generateModelConfig(config)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-6")
|
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4.6")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("Sisyphus resolves to gpt-5.4 medium when only OpenAI is available", () => {
|
test("Sisyphus resolves to gpt-5.4 medium when only OpenAI is available", () => {
|
||||||
@@ -572,7 +574,11 @@ describe("generateModelConfig", () => {
|
|||||||
|
|
||||||
// #then explore should not have fallback_models (only one chain entry matches)
|
// #then explore should not have fallback_models (only one chain entry matches)
|
||||||
expect(result.agents?.explore?.model).toBe("anthropic/claude-haiku-4-5")
|
expect(result.agents?.explore?.model).toBe("anthropic/claude-haiku-4-5")
|
||||||
expect(result.agents?.explore?.fallback_models).toBeUndefined()
|
expect(result.agents?.explore?.fallback_models).toEqual([
|
||||||
|
{
|
||||||
|
model: "anthropic/claude-haiku-4.5",
|
||||||
|
},
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("librarian includes fallback_models when opencode-go and Claude are both available", () => {
|
test("librarian includes fallback_models when opencode-go and Claude are both available", () => {
|
||||||
|
|||||||
@@ -163,6 +163,44 @@ describe("transformModelForProvider", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("anthropic provider", () => {
|
||||||
|
test("transforms claude-opus-4-6 to claude-opus-4.6", () => {
|
||||||
|
// #given anthropic provider and claude-opus-4-6 model
|
||||||
|
const provider = "anthropic"
|
||||||
|
const model = "claude-opus-4-6"
|
||||||
|
|
||||||
|
// #when transformModelForProvider is called
|
||||||
|
const result = transformModelForProvider(provider, model)
|
||||||
|
|
||||||
|
// #then should transform to claude-opus-4.6
|
||||||
|
expect(result).toBe("claude-opus-4.6")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("transforms claude-sonnet-4-6 to claude-sonnet-4.6", () => {
|
||||||
|
// #given anthropic provider and claude-sonnet-4-6 model
|
||||||
|
const provider = "anthropic"
|
||||||
|
const model = "claude-sonnet-4-6"
|
||||||
|
|
||||||
|
// #when transformModelForProvider is called
|
||||||
|
const result = transformModelForProvider(provider, model)
|
||||||
|
|
||||||
|
// #then should transform to claude-sonnet-4.6
|
||||||
|
expect(result).toBe("claude-sonnet-4.6")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("transforms claude-haiku-4-5 to claude-haiku-4.5", () => {
|
||||||
|
// #given anthropic provider and claude-haiku-4-5 model
|
||||||
|
const provider = "anthropic"
|
||||||
|
const model = "claude-haiku-4-5"
|
||||||
|
|
||||||
|
// #when transformModelForProvider is called
|
||||||
|
const result = transformModelForProvider(provider, model)
|
||||||
|
|
||||||
|
// #then should transform to claude-haiku-4.5
|
||||||
|
expect(result).toBe("claude-haiku-4.5")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("unknown provider", () => {
|
describe("unknown provider", () => {
|
||||||
test("passes model through unchanged for unknown provider", () => {
|
test("passes model through unchanged for unknown provider", () => {
|
||||||
// #given unknown provider and any model
|
// #given unknown provider and any model
|
||||||
|
|||||||
@@ -855,13 +855,13 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
|||||||
{
|
{
|
||||||
info: {
|
info: {
|
||||||
agent: "sisyphus",
|
agent: "sisyphus",
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
info: {
|
info: {
|
||||||
agent: "compaction",
|
agent: "compaction",
|
||||||
model: { providerID: "anthropic", modelID: "claude-sonnet-4-6" },
|
model: { providerID: "anthropic", modelID: "claude-sonnet-4.6" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -890,7 +890,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
|||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(capturedBody?.agent).toBe("sisyphus")
|
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.6" })
|
||||||
|
|
||||||
manager.shutdown()
|
manager.shutdown()
|
||||||
})
|
})
|
||||||
@@ -913,7 +913,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
|||||||
}
|
}
|
||||||
const currentMessage: CurrentMessage = {
|
const currentMessage: CurrentMessage = {
|
||||||
agent: "sisyphus",
|
agent: "sisyphus",
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -921,7 +921,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
|||||||
|
|
||||||
// then - uses currentMessage values, not task.parentModel/parentAgent
|
// then - uses currentMessage values, not task.parentModel/parentAgent
|
||||||
expect(promptBody.agent).toBe("sisyphus")
|
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.6" })
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should fallback to parentAgent when currentMessage.agent is undefined", async () => {
|
test("should fallback to parentAgent when currentMessage.agent is undefined", async () => {
|
||||||
@@ -1155,7 +1155,7 @@ describe("BackgroundManager.notifyParentSession - notifications toggle", () => {
|
|||||||
agent: "explore",
|
agent: "explore",
|
||||||
model: {
|
model: {
|
||||||
providerID: "anthropic",
|
providerID: "anthropic",
|
||||||
modelID: "claude-opus-4-6",
|
modelID: "claude-opus-4.6",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1211,7 +1211,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
|
|||||||
agent: "explore",
|
agent: "explore",
|
||||||
model: {
|
model: {
|
||||||
providerID: "anthropic",
|
providerID: "anthropic",
|
||||||
modelID: "claude-opus-4-6",
|
modelID: "claude-opus-4.6",
|
||||||
variant: "max",
|
variant: "max",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1231,7 +1231,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
|
|||||||
status: "completed",
|
status: "completed",
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
completedAt: new Date(),
|
completedAt: new Date(),
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6", variant: "high" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6", variant: "high" },
|
||||||
}
|
}
|
||||||
getPendingByParent(manager).set("session-parent", new Set([task.id]))
|
getPendingByParent(manager).set("session-parent", new Set([task.id]))
|
||||||
|
|
||||||
@@ -1272,7 +1272,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
|
|||||||
status: "completed",
|
status: "completed",
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
completedAt: new Date(),
|
completedAt: new Date(),
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||||
}
|
}
|
||||||
getPendingByParent(manager).set("session-parent", new Set([task.id]))
|
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 () => {
|
test("should release concurrency and clear key on completion", async () => {
|
||||||
// given
|
// given
|
||||||
const concurrencyKey = "anthropic/claude-opus-4-6"
|
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||||
const concurrencyManager = getConcurrencyManager(manager)
|
const concurrencyManager = getConcurrencyManager(manager)
|
||||||
await concurrencyManager.acquire(concurrencyKey)
|
await concurrencyManager.acquire(concurrencyKey)
|
||||||
|
|
||||||
@@ -1378,7 +1378,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
|||||||
|
|
||||||
test("should prevent double completion and double release", async () => {
|
test("should prevent double completion and double release", async () => {
|
||||||
// given
|
// given
|
||||||
const concurrencyKey = "anthropic/claude-opus-4-6"
|
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||||
const concurrencyManager = getConcurrencyManager(manager)
|
const concurrencyManager = getConcurrencyManager(manager)
|
||||||
await concurrencyManager.acquire(concurrencyKey)
|
await concurrencyManager.acquire(concurrencyKey)
|
||||||
|
|
||||||
@@ -1508,7 +1508,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
|||||||
|
|
||||||
test("should release task concurrencyKey when startTask throws after assigning it", async () => {
|
test("should release task concurrencyKey when startTask throws after assigning it", async () => {
|
||||||
// given
|
// given
|
||||||
const concurrencyKey = "anthropic/claude-opus-4-6"
|
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||||
const concurrencyManager = getConcurrencyManager(manager)
|
const concurrencyManager = getConcurrencyManager(manager)
|
||||||
|
|
||||||
const task = createMockTask({
|
const task = createMockTask({
|
||||||
@@ -1524,7 +1524,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
|||||||
agent: task.agent,
|
agent: task.agent,
|
||||||
parentSessionID: task.parentSessionID,
|
parentSessionID: task.parentSessionID,
|
||||||
parentMessageID: task.parentMessageID,
|
parentMessageID: task.parentMessageID,
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||||
}
|
}
|
||||||
getTaskMap(manager).set(task.id, task)
|
getTaskMap(manager).set(task.id, task)
|
||||||
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
|
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 () => {
|
test("should mark task as error when startTask throws after session creation", async () => {
|
||||||
//#given - startTask creates session but fails before sending prompt
|
//#given - startTask creates session but fails before sending prompt
|
||||||
const concurrencyKey = "anthropic/claude-opus-4-6"
|
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||||
|
|
||||||
const task = createMockTask({
|
const task = createMockTask({
|
||||||
id: "task-zombie-session",
|
id: "task-zombie-session",
|
||||||
@@ -1561,7 +1561,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
|||||||
agent: task.agent,
|
agent: task.agent,
|
||||||
parentSessionID: task.parentSessionID,
|
parentSessionID: task.parentSessionID,
|
||||||
parentMessageID: task.parentMessageID,
|
parentMessageID: task.parentMessageID,
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||||
}
|
}
|
||||||
getTaskMap(manager).set(task.id, task)
|
getTaskMap(manager).set(task.id, task)
|
||||||
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
|
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 () => {
|
test("should release queue slot when queued task is already interrupt", async () => {
|
||||||
// given
|
// given
|
||||||
const concurrencyKey = "anthropic/claude-opus-4-6"
|
const concurrencyKey = "anthropic/claude-opus-4.6"
|
||||||
const concurrencyManager = getConcurrencyManager(manager)
|
const concurrencyManager = getConcurrencyManager(manager)
|
||||||
|
|
||||||
const task = createMockTask({
|
const task = createMockTask({
|
||||||
@@ -1601,7 +1601,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
|||||||
agent: task.agent,
|
agent: task.agent,
|
||||||
parentSessionID: task.parentSessionID,
|
parentSessionID: task.parentSessionID,
|
||||||
parentMessageID: task.parentMessageID,
|
parentMessageID: task.parentMessageID,
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||||
}
|
}
|
||||||
getTaskMap(manager).set(task.id, task)
|
getTaskMap(manager).set(task.id, task)
|
||||||
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
|
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
|
||||||
@@ -2104,7 +2104,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
|
|||||||
agent: "test-agent",
|
agent: "test-agent",
|
||||||
parentSessionID: "parent-session",
|
parentSessionID: "parent-session",
|
||||||
parentMessageID: "parent-message",
|
parentMessageID: "parent-message",
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||||
}
|
}
|
||||||
const launchInputWithoutModel = {
|
const launchInputWithoutModel = {
|
||||||
description: "Test task without model",
|
description: "Test task without model",
|
||||||
@@ -2124,7 +2124,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
|
|||||||
expect(taskWithModel.status).toBe("pending")
|
expect(taskWithModel.status).toBe("pending")
|
||||||
expect(taskWithoutModel.status).toBe("pending")
|
expect(taskWithoutModel.status).toBe("pending")
|
||||||
expect(promptBodies).toHaveLength(2)
|
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.6" })
|
||||||
expect(promptBodies[0].agent).toBe("test-agent")
|
expect(promptBodies[0].agent).toBe("test-agent")
|
||||||
expect(promptBodies[1].agent).toBe("test-agent")
|
expect(promptBodies[1].agent).toBe("test-agent")
|
||||||
expect("model" in promptBodies[1]).toBe(false)
|
expect("model" in promptBodies[1]).toBe(false)
|
||||||
@@ -3250,7 +3250,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
|
|||||||
description: "Task 1",
|
description: "Task 1",
|
||||||
prompt: "Do something",
|
prompt: "Do something",
|
||||||
agent: "test-agent",
|
agent: "test-agent",
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6" },
|
||||||
parentSessionID: "parent-session",
|
parentSessionID: "parent-session",
|
||||||
parentMessageID: "parent-message",
|
parentMessageID: "parent-message",
|
||||||
}
|
}
|
||||||
@@ -4254,7 +4254,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
|||||||
agent: "sisyphus",
|
agent: "sisyphus",
|
||||||
status: "running",
|
status: "running",
|
||||||
concurrencyKey: input.concurrencyKey,
|
concurrencyKey: input.concurrencyKey,
|
||||||
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" },
|
model: { providerID: "anthropic", modelID: "claude-opus-4.6-thinking" },
|
||||||
fallbackChain: input.fallbackChain ?? defaultRetryFallbackChain,
|
fallbackChain: input.fallbackChain ?? defaultRetryFallbackChain,
|
||||||
attemptCount: 0,
|
attemptCount: 0,
|
||||||
})
|
})
|
||||||
@@ -4399,7 +4399,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
|||||||
//#given
|
//#given
|
||||||
const manager = createBackgroundManager()
|
const manager = createBackgroundManager()
|
||||||
const concurrencyManager = getConcurrencyManager(manager)
|
const concurrencyManager = getConcurrencyManager(manager)
|
||||||
const concurrencyKey = "anthropic/claude-opus-4-6-thinking"
|
const concurrencyKey = "anthropic/claude-opus-4.6-thinking"
|
||||||
await concurrencyManager.acquire(concurrencyKey)
|
await concurrencyManager.acquire(concurrencyKey)
|
||||||
|
|
||||||
stubProcessKey(manager)
|
stubProcessKey(manager)
|
||||||
@@ -4425,7 +4425,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
|||||||
name: "UnknownError",
|
name: "UnknownError",
|
||||||
data: {
|
data: {
|
||||||
message:
|
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.6-thinking\"}}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -4436,7 +4436,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
|||||||
expect(task.attemptCount).toBe(1)
|
expect(task.attemptCount).toBe(1)
|
||||||
expect(task.model).toEqual({
|
expect(task.model).toEqual({
|
||||||
providerID: "anthropic",
|
providerID: "anthropic",
|
||||||
modelID: "claude-opus-4-6",
|
modelID: "claude-opus-4.6",
|
||||||
variant: "max",
|
variant: "max",
|
||||||
})
|
})
|
||||||
expect(task.concurrencyKey).toBeUndefined()
|
expect(task.concurrencyKey).toBeUndefined()
|
||||||
@@ -4474,7 +4474,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
|||||||
expect(task.attemptCount).toBe(1)
|
expect(task.attemptCount).toBe(1)
|
||||||
expect(task.model).toEqual({
|
expect(task.model).toEqual({
|
||||||
providerID: "anthropic",
|
providerID: "anthropic",
|
||||||
modelID: "claude-opus-4-6",
|
modelID: "claude-opus-4.6",
|
||||||
variant: "max",
|
variant: "max",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -4502,7 +4502,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
|||||||
name: "UnknownError",
|
name: "UnknownError",
|
||||||
data: {
|
data: {
|
||||||
message:
|
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.6-thinking\"}}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -4519,7 +4519,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
|
|||||||
expect(task.attemptCount).toBe(1)
|
expect(task.attemptCount).toBe(1)
|
||||||
expect(task.model).toEqual({
|
expect(task.model).toEqual({
|
||||||
providerID: "anthropic",
|
providerID: "anthropic",
|
||||||
modelID: "claude-opus-4-6",
|
modelID: "claude-opus-4.6",
|
||||||
variant: "max",
|
variant: "max",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -14,5 +14,11 @@ export function transformModelForProvider(provider: string, model: string): stri
|
|||||||
.replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview")
|
.replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview")
|
||||||
.replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview")
|
.replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview")
|
||||||
}
|
}
|
||||||
|
if (provider === "anthropic") {
|
||||||
|
return model
|
||||||
|
.replace("claude-opus-4-6", "claude-opus-4.6")
|
||||||
|
.replace("claude-sonnet-4-6", "claude-sonnet-4.6")
|
||||||
|
.replace("claude-haiku-4-5", "claude-haiku-4.5")
|
||||||
|
}
|
||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
declare const require: (name: string) => any
|
/// <reference types="bun-types" />
|
||||||
const { afterEach, beforeEach, describe, expect, mock, spyOn, test } = require("bun:test")
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test"
|
||||||
import { resolveModelForDelegateTask } from "./model-selection"
|
import { resolveModelForDelegateTask } from "./model-selection"
|
||||||
import * as connectedProvidersCache from "../../shared/connected-providers-cache"
|
import * as connectedProvidersCache from "../../shared/connected-providers-cache"
|
||||||
|
|
||||||
@@ -25,12 +26,12 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
describe("#when availableModels is empty and no user model override", () => {
|
describe("#when availableModels is empty and no user model override", () => {
|
||||||
test("#then returns skipped sentinel to leave model unpinned", () => {
|
test("#then returns skipped sentinel to leave model unpinned", () => {
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
categoryDefaultModel: "anthropic/claude-sonnet-4-6",
|
categoryDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
||||||
],
|
],
|
||||||
availableModels: new Set(),
|
availableModels: new Set(),
|
||||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
systemDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toEqual({ skipped: true })
|
expect(result).toEqual({ skipped: true })
|
||||||
@@ -41,12 +42,12 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
test("#then returns the user model regardless of cache state", () => {
|
test("#then returns the user model regardless of cache state", () => {
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
userModel: "openai/gpt-5.4",
|
userModel: "openai/gpt-5.4",
|
||||||
categoryDefaultModel: "anthropic/claude-sonnet-4-6",
|
categoryDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
||||||
],
|
],
|
||||||
availableModels: new Set(),
|
availableModels: new Set(),
|
||||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
systemDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toEqual({ model: "openai/gpt-5.4" })
|
expect(result).toEqual({ model: "openai/gpt-5.4" })
|
||||||
@@ -57,7 +58,7 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
test("#then returns skipped sentinel (skip fallback resolution without cache)", () => {
|
test("#then returns skipped sentinel (skip fallback resolution without cache)", () => {
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
userFallbackModels: ["openai/gpt-5.4", "google/gemini-3.1-pro"],
|
userFallbackModels: ["openai/gpt-5.4", "google/gemini-3.1-pro"],
|
||||||
categoryDefaultModel: "anthropic/claude-sonnet-4-6",
|
categoryDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
||||||
],
|
],
|
||||||
@@ -80,15 +81,15 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
const readConnectedProvidersSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic"])
|
const readConnectedProvidersSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic"])
|
||||||
|
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
categoryDefaultModel: "anthropic/claude-sonnet-4-6",
|
categoryDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
||||||
],
|
],
|
||||||
availableModels: new Set(),
|
availableModels: new Set(),
|
||||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
systemDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toEqual({ model: "anthropic/claude-sonnet-4-6" })
|
expect(result).toEqual({ model: "anthropic/claude-sonnet-4.6" })
|
||||||
readConnectedProvidersSpy.mockRestore()
|
readConnectedProvidersSpy.mockRestore()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -96,12 +97,12 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
const readConnectedProvidersSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
|
const readConnectedProvidersSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
|
||||||
|
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
categoryDefaultModel: "anthropic/claude-sonnet-4-6",
|
categoryDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["openai"], model: "gpt-5.4", variant: "high" },
|
{ providers: ["openai"], model: "gpt-5.4", variant: "high" },
|
||||||
],
|
],
|
||||||
availableModels: new Set(),
|
availableModels: new Set(),
|
||||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
systemDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@@ -117,7 +118,7 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
const readConnectedProvidersSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
|
const readConnectedProvidersSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
|
||||||
|
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
userFallbackModels: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4"],
|
userFallbackModels: ["anthropic/claude-sonnet-4.6", "openai/gpt-5.4"],
|
||||||
availableModels: new Set(),
|
availableModels: new Set(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -129,14 +130,14 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
describe("#when availableModels has entries and category default matches", () => {
|
describe("#when availableModels has entries and category default matches", () => {
|
||||||
test("#then resolves via fuzzy match (existing behavior)", () => {
|
test("#then resolves via fuzzy match (existing behavior)", () => {
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
categoryDefaultModel: "anthropic/claude-sonnet-4-6",
|
categoryDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
||||||
],
|
],
|
||||||
availableModels: new Set(["anthropic/claude-sonnet-4-6"]),
|
availableModels: new Set(["anthropic/claude-sonnet-4.6"]),
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toEqual({ model: "anthropic/claude-sonnet-4-6" })
|
expect(result).toEqual({ model: "anthropic/claude-sonnet-4.6" })
|
||||||
})
|
})
|
||||||
|
|
||||||
test("#then trusts user-configured category model without fuzzy validation", () => {
|
test("#then trusts user-configured category model without fuzzy validation", () => {
|
||||||
@@ -200,7 +201,7 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
expect(result).toBeDefined()
|
expect(result).toBeDefined()
|
||||||
expect(result).not.toHaveProperty("skipped")
|
expect(result).not.toHaveProperty("skipped")
|
||||||
const resolved = result as { model: string; variant?: string }
|
const resolved = result as { model: string; variant?: string }
|
||||||
expect(resolved.model).toBe("anthropic/claude-haiku-4-5")
|
expect(resolved.model).toBe("anthropic/claude-haiku-4.5")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("#then resolves first provider in entry that is connected", () => {
|
test("#then resolves first provider in entry that is connected", () => {
|
||||||
@@ -229,10 +230,10 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
{ providers: ["opencode-go"], model: "minimax-m2.7" },
|
{ providers: ["opencode-go"], model: "minimax-m2.7" },
|
||||||
],
|
],
|
||||||
availableModels: new Set(),
|
availableModels: new Set(),
|
||||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
systemDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toEqual({ model: "anthropic/claude-sonnet-4-6" })
|
expect(result).toEqual({ model: "anthropic/claude-sonnet-4.6" })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -259,7 +260,7 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
test("#then extracts the variant and returns the base model separately", () => {
|
test("#then extracts the variant and returns the base model separately", () => {
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
userModel: "openai/gpt-5.4 high",
|
userModel: "openai/gpt-5.4 high",
|
||||||
categoryDefaultModel: "anthropic/claude-sonnet-4-6",
|
categoryDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
||||||
],
|
],
|
||||||
@@ -274,7 +275,7 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
test("#then extracts the variant and returns the base model separately", () => {
|
test("#then extracts the variant and returns the base model separately", () => {
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
userModel: "openai/gpt-5.4(max)",
|
userModel: "openai/gpt-5.4(max)",
|
||||||
categoryDefaultModel: "anthropic/claude-sonnet-4-6",
|
categoryDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
availableModels: new Set(),
|
availableModels: new Set(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -359,7 +360,7 @@ describe("resolveModelForDelegateTask", () => {
|
|||||||
const readConnectedProvidersSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
|
const readConnectedProvidersSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
|
||||||
|
|
||||||
const result = resolveModelForDelegateTask({
|
const result = resolveModelForDelegateTask({
|
||||||
categoryDefaultModel: "anthropic/claude-sonnet-4-6",
|
categoryDefaultModel: "anthropic/claude-sonnet-4.6",
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["openai"], model: "gpt-5.4" },
|
{ providers: ["openai"], model: "gpt-5.4" },
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user