fix: clamp variant "max" to "high" for non-Opus Claude models on fallback
When an agent configured with variant: "max" falls back from Opus to Sonnet (or Haiku), the "max" variant was passed through unchanged. OpenCode sends this as level: "max" to the Anthropic API, which rejects it with: level "max" not supported, valid levels: low, medium, high The anthropic-effort hook previously only handled Opus (inject effort=max) and skipped all other Claude models. Now it actively clamps "max" → "high" for non-Opus Claude models and mutates message.variant so OpenCode doesn't pass the unsupported level to the API.
This commit is contained in:
@@ -28,6 +28,20 @@ interface ChatParamsOutput {
|
||||
options: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid thinking budget levels per model tier.
|
||||
* Opus supports "max"; all other Claude models cap at "high".
|
||||
*/
|
||||
const MAX_VARIANT_BY_TIER: Record<string, string> = {
|
||||
opus: "max",
|
||||
default: "high",
|
||||
}
|
||||
|
||||
function clampVariant(variant: string, isOpus: boolean): string {
|
||||
if (variant !== "max") return variant
|
||||
return isOpus ? MAX_VARIANT_BY_TIER.opus : MAX_VARIANT_BY_TIER.default
|
||||
}
|
||||
|
||||
export function createAnthropicEffortHook() {
|
||||
return {
|
||||
"chat.params": async (
|
||||
@@ -38,15 +52,27 @@ export function createAnthropicEffortHook() {
|
||||
if (!model?.modelID || !model?.providerID) return
|
||||
if (message.variant !== "max") return
|
||||
if (!isClaudeProvider(model.providerID, model.modelID)) return
|
||||
if (!isOpus46(model.modelID)) return
|
||||
if (output.options.effort !== undefined) return
|
||||
|
||||
output.options.effort = "max"
|
||||
log("anthropic-effort: injected effort=max", {
|
||||
sessionID: input.sessionID,
|
||||
provider: model.providerID,
|
||||
model: model.modelID,
|
||||
})
|
||||
const opus = isOpus46(model.modelID)
|
||||
const clamped = clampVariant(message.variant, opus)
|
||||
output.options.effort = clamped
|
||||
|
||||
if (!opus) {
|
||||
// Override the variant so OpenCode doesn't pass "max" to the API
|
||||
;(message as { variant?: string }).variant = clamped
|
||||
log("anthropic-effort: clamped variant max→high for non-Opus model", {
|
||||
sessionID: input.sessionID,
|
||||
provider: model.providerID,
|
||||
model: model.modelID,
|
||||
})
|
||||
} else {
|
||||
log("anthropic-effort: injected effort=max", {
|
||||
sessionID: input.sessionID,
|
||||
provider: model.providerID,
|
||||
model: model.modelID,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +143,8 @@ describe("createAnthropicEffortHook", () => {
|
||||
expect(output.options.effort).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should NOT inject effort for non-opus model", async () => {
|
||||
//#given claude-sonnet-4-6 (not opus)
|
||||
it("should clamp effort to high for non-opus claude model with variant max", async () => {
|
||||
//#given claude-sonnet-4-6 (not opus) with variant max
|
||||
const hook = createAnthropicEffortHook()
|
||||
const { input, output } = createMockParams({
|
||||
modelID: "claude-sonnet-4-6",
|
||||
@@ -153,8 +153,9 @@ describe("createAnthropicEffortHook", () => {
|
||||
//#when chat.params hook is called
|
||||
await hook["chat.params"](input, output)
|
||||
|
||||
//#then effort should NOT be injected
|
||||
expect(output.options.effort).toBeUndefined()
|
||||
//#then effort should be clamped to high (not max)
|
||||
expect(output.options.effort).toBe("high")
|
||||
expect(input.message.variant).toBe("high")
|
||||
})
|
||||
|
||||
it("should NOT inject effort for non-anthropic provider with non-claude model", async () => {
|
||||
|
||||
Reference in New Issue
Block a user