Преглед на файлове

fix: respect explicit supportsReasoningEffort array values (#9970)

Hannes Rudolph преди 3 седмици
родител
ревизия
df5fdefea3
променени са 2 файла, в които са добавени 99 реда и са изтрити 4 реда
  1. 9 4
      webview-ui/src/components/settings/ThinkingBudget.tsx
  2. 90 0
      webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx

+ 9 - 4
webview-ui/src/components/settings/ThinkingBudget.tsx

@@ -87,11 +87,16 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
 
 	// "disable" turns off reasoning entirely; "none" is a valid reasoning level.
 	// Both display as "None" in the UI but behave differently.
-	// Add "disable" option if reasoning effort is not required.
+	// Add "disable" option only when:
+	// 1. requiredReasoningEffort is not true, AND
+	// 2. supportsReasoningEffort is boolean true (not an explicit array)
+	// When the model provides an explicit array, respect those exact values.
 	type ReasoningEffortOption = ReasoningEffortWithMinimal | "none" | "disable"
-	const availableOptions: ReadonlyArray<ReasoningEffortOption> = modelInfo?.requiredReasoningEffort
-		? (baseAvailableOptions as ReadonlyArray<ReasoningEffortOption>)
-		: (["disable", ...baseAvailableOptions] as ReasoningEffortOption[])
+	const shouldAutoAddDisable =
+		!modelInfo?.requiredReasoningEffort && supports === true && !baseAvailableOptions.includes("disable" as any)
+	const availableOptions: ReadonlyArray<ReasoningEffortOption> = shouldAutoAddDisable
+		? (["disable", ...baseAvailableOptions] as ReasoningEffortOption[])
+		: (baseAvailableOptions as ReadonlyArray<ReasoningEffortOption>)
 
 	// Default reasoning effort - use model's default if available
 	// GPT-5 models have "medium" as their default in the model configuration

+ 90 - 0
webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx

@@ -18,6 +18,19 @@ vi.mock("@/components/ui", () => ({
 			onChange={(e) => onValueChange([parseInt(e.target.value)])}
 		/>
 	),
+	Select: ({ children, value, onValueChange: _onValueChange }: any) => (
+		<div data-testid="select" data-value={value}>
+			{children}
+		</div>
+	),
+	SelectTrigger: ({ children }: any) => <button data-testid="select-trigger">{children}</button>,
+	SelectValue: ({ placeholder }: any) => <span data-testid="select-value">{placeholder}</span>,
+	SelectContent: ({ children }: any) => <div data-testid="select-content">{children}</div>,
+	SelectItem: ({ children, value }: any) => (
+		<div data-testid={`select-item-${value}`} data-value={value}>
+			{children}
+		</div>
+	),
 }))
 
 vi.mock("@/components/ui/hooks/useSelectedModel", () => ({
@@ -215,4 +228,81 @@ describe("ThinkingBudget", () => {
 
 		expect(setApiConfigurationField).toHaveBeenCalledWith("modelMaxTokens", 12000)
 	})
+
+	describe("reasoning effort dropdown", () => {
+		const reasoningEffortModelInfo: ModelInfo = {
+			supportsReasoningEffort: true,
+			contextWindow: 200000,
+			supportsPromptCache: true,
+		}
+
+		it("should show 'disable' option when supportsReasoningEffort is boolean true", () => {
+			render(<ThinkingBudget {...defaultProps} modelInfo={reasoningEffortModelInfo} />)
+
+			expect(screen.getByTestId("reasoning-effort")).toBeInTheDocument()
+			// "disable" should be shown when supportsReasoningEffort is true (boolean)
+			expect(screen.getByTestId("select-item-disable")).toBeInTheDocument()
+			expect(screen.getByTestId("select-item-low")).toBeInTheDocument()
+			expect(screen.getByTestId("select-item-medium")).toBeInTheDocument()
+			expect(screen.getByTestId("select-item-high")).toBeInTheDocument()
+		})
+
+		it("should NOT show 'disable' option when supportsReasoningEffort is an explicit array without disable", () => {
+			render(
+				<ThinkingBudget
+					{...defaultProps}
+					modelInfo={{
+						...reasoningEffortModelInfo,
+						supportsReasoningEffort: ["low", "high"],
+					}}
+				/>,
+			)
+
+			expect(screen.getByTestId("reasoning-effort")).toBeInTheDocument()
+			// "disable" should NOT be shown when model explicitly specifies only ["low", "high"]
+			expect(screen.queryByTestId("select-item-disable")).not.toBeInTheDocument()
+			expect(screen.getByTestId("select-item-low")).toBeInTheDocument()
+			expect(screen.queryByTestId("select-item-medium")).not.toBeInTheDocument()
+			expect(screen.getByTestId("select-item-high")).toBeInTheDocument()
+		})
+
+		it("should show 'disable' option when supportsReasoningEffort array explicitly includes disable", () => {
+			render(
+				<ThinkingBudget
+					{...defaultProps}
+					modelInfo={{
+						...reasoningEffortModelInfo,
+						supportsReasoningEffort: ["disable", "low", "high"],
+					}}
+				/>,
+			)
+
+			expect(screen.getByTestId("reasoning-effort")).toBeInTheDocument()
+			// "disable" should be shown when model explicitly includes it in the array
+			expect(screen.getByTestId("select-item-disable")).toBeInTheDocument()
+			expect(screen.getByTestId("select-item-low")).toBeInTheDocument()
+			expect(screen.queryByTestId("select-item-medium")).not.toBeInTheDocument()
+			expect(screen.getByTestId("select-item-high")).toBeInTheDocument()
+		})
+
+		it("should show 'none' option when supportsReasoningEffort array includes none", () => {
+			render(
+				<ThinkingBudget
+					{...defaultProps}
+					modelInfo={{
+						...reasoningEffortModelInfo,
+						supportsReasoningEffort: ["none", "low", "medium", "high"],
+					}}
+				/>,
+			)
+
+			expect(screen.getByTestId("reasoning-effort")).toBeInTheDocument()
+			// Only values from the explicit array should be shown
+			expect(screen.queryByTestId("select-item-disable")).not.toBeInTheDocument()
+			expect(screen.getByTestId("select-item-none")).toBeInTheDocument()
+			expect(screen.getByTestId("select-item-low")).toBeInTheDocument()
+			expect(screen.getByTestId("select-item-medium")).toBeInTheDocument()
+			expect(screen.getByTestId("select-item-high")).toBeInTheDocument()
+		})
+	})
 })