diff --git a/src/browser/components/ThinkingSlider.stories.tsx b/src/browser/components/ThinkingSlider.stories.tsx index 0b324cc7a2..b25b7c7867 100644 --- a/src/browser/components/ThinkingSlider.stories.tsx +++ b/src/browser/components/ThinkingSlider.stories.tsx @@ -41,17 +41,33 @@ export const DifferentModels: Story = { render: () => (
-
Claude Sonnet 4.5
+
+ Claude Sonnet 4.5 (4 levels) +
-
Claude Opus 4.1
+
+ Claude Opus 4.5 (3 levels: low/medium/high) +
+ +
+ +
+
Claude Opus 4.1 (4 levels)
-
GPT-5 Codex
+
+ Gemini 3 (2 levels: low/high) +
+ +
+ +
+
GPT-5 Codex (4 levels)
@@ -59,6 +75,14 @@ export const DifferentModels: Story = { }; export const InteractiveDemo: Story = { + // Use unique workspaceId to isolate state from other stories + decorators: [ + (Story) => ( + + + + ), + ], render: () => (
@@ -92,6 +116,23 @@ export const InteractiveDemo: Story = { }, }; +export const Opus45ThreeLevels: Story = { + args: { modelString: "anthropic:claude-opus-4-5" }, + render: (args) => ( +
+
+ Claude Opus 4.5 uses the effort parameter (low/medium/high only, no “off”): +
+ +
+ • Low: Conservative token usage +
Medium: Balanced usage (default) +
High: Best results, more tokens +
+
+ ), +}; + export const LockedThinking: Story = { args: { modelString: "openai:gpt-5-pro" }, render: (args) => ( diff --git a/src/browser/utils/thinking/policy.test.ts b/src/browser/utils/thinking/policy.test.ts index ac99b3b7d6..974fca0c63 100644 --- a/src/browser/utils/thinking/policy.test.ts +++ b/src/browser/utils/thinking/policy.test.ts @@ -33,6 +33,23 @@ describe("getThinkingPolicyForModel", () => { ]); }); + test("returns low/medium/high for Opus 4.5", () => { + expect(getThinkingPolicyForModel("anthropic:claude-opus-4-5")).toEqual([ + "low", + "medium", + "high", + ]); + expect(getThinkingPolicyForModel("anthropic:claude-opus-4-5-20251101")).toEqual([ + "low", + "medium", + "high", + ]); + }); + + test("returns low/high for Gemini 3", () => { + expect(getThinkingPolicyForModel("google:gemini-3-pro-preview")).toEqual(["low", "high"]); + }); + test("returns all levels for other providers", () => { expect(getThinkingPolicyForModel("anthropic:claude-opus-4")).toEqual([ "off", @@ -46,7 +63,6 @@ describe("getThinkingPolicyForModel", () => { "medium", "high", ]); - expect(getThinkingPolicyForModel("google:gemini-3-pro-preview")).toEqual(["low", "high"]); }); }); @@ -78,6 +94,22 @@ describe("enforceThinkingPolicy", () => { expect(enforceThinkingPolicy("openai:gpt-5-pro", "low")).toBe("high"); }); }); + + describe("Opus 4.5 (no off option)", () => { + test("allows low/medium/high levels", () => { + expect(enforceThinkingPolicy("anthropic:claude-opus-4-5", "low")).toBe("low"); + expect(enforceThinkingPolicy("anthropic:claude-opus-4-5", "medium")).toBe("medium"); + expect(enforceThinkingPolicy("anthropic:claude-opus-4-5", "high")).toBe("high"); + }); + + test("falls back to high when off is requested", () => { + expect(enforceThinkingPolicy("anthropic:claude-opus-4-5", "off")).toBe("high"); + }); + + test("falls back to high when off is requested (versioned model)", () => { + expect(enforceThinkingPolicy("anthropic:claude-opus-4-5-20251101", "off")).toBe("high"); + }); + }); }); // Note: Tests for invalid levels removed - TypeScript type system prevents invalid diff --git a/src/browser/utils/thinking/policy.ts b/src/browser/utils/thinking/policy.ts index e8157b3725..41c2fad4fd 100644 --- a/src/browser/utils/thinking/policy.ts +++ b/src/browser/utils/thinking/policy.ts @@ -25,6 +25,8 @@ export type ThinkingPolicy = readonly ThinkingLevel[]; * * Rules: * - openai:gpt-5-pro → ["high"] (only supported level) + * - anthropic:claude-opus-4-5 → ["low", "medium", "high"] (effort parameter only) + * - gemini-3 → ["low", "high"] (thinking level only) * - default → ["off", "low", "medium", "high"] (all levels selectable) * * Tolerates version suffixes (e.g., gpt-5-pro-2025-10-06). @@ -37,6 +39,12 @@ export function getThinkingPolicyForModel(modelString: string): ThinkingPolicy { return ["high"]; } + // Claude Opus 4.5 only supports effort parameter: low, medium, high (no "off") + // Match "anthropic:" followed by "claude-opus-4-5" with optional version suffix + if (modelString.includes("opus-4-5")) { + return ["low", "medium", "high"]; + } + // Gemini 3 Pro only supports "low" and "high" reasoning levels if (modelString.includes("gemini-3")) { return ["low", "high"]; @@ -51,8 +59,8 @@ export function getThinkingPolicyForModel(modelString: string): ThinkingPolicy { * * Fallback strategy: * 1. If requested level is allowed, use it - * 2. If "medium" is allowed, use it (reasonable default) - * 3. Otherwise use first allowed level + * 2. For Opus 4.5: prefer "high" (best experience for reasoning model) + * 3. Otherwise: prefer "medium" if allowed, else use first allowed level */ export function enforceThinkingPolicy( modelString: string, @@ -64,6 +72,11 @@ export function enforceThinkingPolicy( return requested; } + // Special case: Opus 4.5 defaults to "high" for best experience + if (modelString.includes("opus-4-5") && allowed.includes("high")) { + return "high"; + } + // Fallback: prefer "medium" if allowed, else use first allowed level return allowed.includes("medium") ? "medium" : allowed[0]; }