Merge pull request #2606 from RaviTharuma/fix/clamp-variant-on-non-opus-fallback

fix: clamp unsupported max variant for non-Opus Claude models
This commit is contained in:
YeonGyu-Kim
2026-03-25 11:06:31 +09:00
committed by GitHub
4 changed files with 98 additions and 17 deletions
+36 -10
View File
@@ -1,6 +1,6 @@
import { log, normalizeModelID } from "../../shared"
const OPUS_4_6_PATTERN = /claude-opus-4[-.]6/i
const OPUS_PATTERN = /claude-opus/i
function isClaudeProvider(providerID: string, modelID: string): boolean {
if (["anthropic", "google-vertex-anthropic", "opencode"].includes(providerID)) return true
@@ -8,9 +8,9 @@ function isClaudeProvider(providerID: string, modelID: string): boolean {
return false
}
function isOpus46(modelID: string): boolean {
function isOpusModel(modelID: string): boolean {
const normalized = normalizeModelID(modelID)
return OPUS_4_6_PATTERN.test(normalized)
return OPUS_PATTERN.test(normalized)
}
interface ChatParamsInput {
@@ -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 = isOpusModel(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,
})
}
},
}
}
+20 -4
View File
@@ -116,6 +116,21 @@ describe("createAnthropicEffortHook", () => {
//#then should normalize and inject effort
expect(output.options.effort).toBe("max")
})
it("should preserve max for other opus model IDs such as opus-4-5", async () => {
//#given another opus model id that is not 4.6
const hook = createAnthropicEffortHook()
const { input, output } = createMockParams({
modelID: "claude-opus-4-5",
})
//#when chat.params hook is called
await hook["chat.params"](input, output)
//#then max should still be treated as valid for opus family
expect(output.options.effort).toBe("max")
expect(input.message.variant).toBe("max")
})
})
describe("conditions NOT met - should skip", () => {
@@ -143,8 +158,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 +168,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 () => {