diff --git a/packages/core/src/variant.ts b/packages/core/src/variant.ts index f5792727ae2..f0b3f1a1c1a 100644 --- a/packages/core/src/variant.ts +++ b/packages/core/src/variant.ts @@ -54,7 +54,8 @@ const modelID = (model: Model.Info) => model.modelID ?? model.id function claudeInfo(model: Model.Info) { const id = modelID(model) - const familyFirst = /(?:claude-)?(opus|sonnet|haiku|fable|mythos)-(\d+)(?:[.-](\d+))?/i.exec(id) + // Versions are at most two digits so snapshot dates such as `-20260901` are not read as versions. + const familyFirst = /(?:claude-)?(opus|sonnet|haiku|fable|mythos)-(\d{1,2})(?:[.-](\d{1,2}))?(?!\d)/i.exec(id) const versionFirst = /claude-(\d+)(?:[.-](\d+))?-(opus|sonnet|haiku|fable|mythos)/i.exec(id) const family = (familyFirst?.[1] ?? versionFirst?.[3])?.toLowerCase() const major = Number(familyFirst?.[2] ?? versionFirst?.[1]) @@ -64,7 +65,12 @@ function claudeInfo(model: Model.Info) { major, minor, manual: (major === 3 && minor === 7) || (major === 4 && minor < 6), - always: family === "fable" || family === "mythos" || id.toLowerCase().includes("mythos-preview"), + // Opus 5.5 and later reject disabled thinking. + always: + family === "fable" || + family === "mythos" || + id.toLowerCase().includes("mythos-preview") || + (family === "opus" && (major > 5 || (major === 5 && minor >= 5))), } } @@ -390,6 +396,7 @@ const bedrockConverse: Protocol = (model, support) => { return fields({ reasoningConfig: { type: "enabled", maxReasoningEffort: effort } }) }) case "toggle": + if (claude && claudeInfo(model).always) return [] return claude ? toggle(fields({ thinking: { type: "disabled" } }), fields({ thinking: ADAPTIVE_THINKING })) : toggle(fields({ reasoningConfig: { type: "disabled" } }), fields({ reasoningConfig: { type: "enabled" } })) @@ -449,6 +456,7 @@ const bedrockAISDK: Protocol = (model, support) => { : { reasoningConfig: { type: "enabled", maxReasoningEffort: effort } }, })) case "toggle": + if (claude && claudeInfo(model).always) return [] return claude ? toggle( { settings: { additionalModelRequestFields: { thinking: { type: "disabled" } } } }, @@ -507,6 +515,7 @@ const sapAICore: Protocol = (model, support) => { sap({ additionalModelRequestFields: { thinking: { type: "disabled" } } }), sap({ additionalModelRequestFields: { thinking: { type: "enabled" } } }), ) + if (id.includes("anthropic") && claudeInfo(model).always) return [] if (id.includes("anthropic")) return toggle( sap({ additionalModelRequestFields: { thinking: { type: "disabled" } } }), diff --git a/packages/core/test/variant.test.ts b/packages/core/test/variant.test.ts index 1366db778d4..57ae608683f 100644 --- a/packages/core/test/variant.test.ts +++ b/packages/core/test/variant.test.ts @@ -104,6 +104,30 @@ test("recognizes Claude version spellings and future models", () => { ]) }) +test("keeps thinking on for Claude Opus 5.5 and later", () => { + const supports: Variant.Support[] = [{ type: "toggle" }, { type: "effort", values: ["low", "high"] }] + const adaptive = ["low", "high"].map((effort) => ({ + id: effort, + settings: { effort, thinking: { type: "adaptive", display: "summarized" } }, + })) + for (const input of [ + model("@opencode/ai/providers/cloudflare-ai-gateway", "anthropic/claude-opus-5.5"), + model("@opencode/ai/providers/anthropic", "claude-opus-5-5"), + model("@opencode/ai/providers/anthropic", "claude-opus-6"), + ]) + expect(resolve(input, supports)).toEqual(adaptive) + + expect( + resolve(model("@opencode/ai/providers/amazon-bedrock", "us.anthropic.claude-opus-5-5-v1:0"), [{ type: "toggle" }]), + ).toEqual([]) + + for (const id of ["claude-opus-5", "claude-opus-5-20260901"]) + expect(resolve(model("@opencode/ai/providers/anthropic", id), supports)).toEqual([ + { id: "none", settings: { thinking: { type: "disabled" } } }, + ...adaptive, + ]) +}) + test("spells Cloudflare AI Gateway variants for their upstream routes", () => { const pkg = "@opencode/ai/providers/cloudflare-ai-gateway" expect(resolve(model(pkg, "openai/gpt-5.4"), [{ type: "effort", values: ["low", "xhigh"] }])).toEqual([