瀏覽代碼

feat: Add Reasoning Effort setting for OpenAI Compatible provider (#2906)

Ryan Pfister 8 月之前
父節點
當前提交
2075f26f18

+ 5 - 0
.changeset/odd-ligers-press.md

@@ -0,0 +1,5 @@
+---
+"roo-cline": minor
+---
+
+Add Reasoning Effort setting for OpenAI Compatible provider

+ 11 - 0
package-lock.json

@@ -77,6 +77,7 @@
 				"@types/jest": "^29.5.14",
 				"@types/mocha": "^10.0.10",
 				"@types/node": "20.x",
+				"@types/node-cache": "^4.1.3",
 				"@types/node-ipc": "^9.2.3",
 				"@types/string-similarity": "^4.0.2",
 				"@typescript-eslint/eslint-plugin": "^7.14.1",
@@ -9006,6 +9007,16 @@
 				"undici-types": "~6.19.2"
 			}
 		},
+		"node_modules/@types/node-cache": {
+			"version": "4.1.3",
+			"resolved": "https://registry.npmjs.org/@types/node-cache/-/node-cache-4.1.3.tgz",
+			"integrity": "sha512-3hsqnv3H1zkOhjygJaJUYmgz5+FcPO3vejBX7cE9/cnuINOJYrzkfOnUCvpwGe9kMZANIHJA7J5pOdeyv52OEw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@types/node": "*"
+			}
+		},
 		"node_modules/@types/node-fetch": {
 			"version": "2.6.12",
 			"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",

+ 1 - 0
package.json

@@ -469,6 +469,7 @@
 		"@types/jest": "^29.5.14",
 		"@types/mocha": "^10.0.10",
 		"@types/node": "20.x",
+		"@types/node-cache": "^4.1.3",
 		"@types/node-ipc": "^9.2.3",
 		"@types/string-similarity": "^4.0.2",
 		"@typescript-eslint/eslint-plugin": "^7.14.1",

+ 35 - 0
src/api/providers/__tests__/openai.test.ts

@@ -1,3 +1,5 @@
+// npx jest src/api/providers/__tests__/openai.test.ts
+
 import { OpenAiHandler } from "../openai"
 import { ApiHandlerOptions } from "../../../shared/api"
 import { Anthropic } from "@anthropic-ai/sdk"
@@ -155,6 +157,39 @@ describe("OpenAiHandler", () => {
 			expect(textChunks).toHaveLength(1)
 			expect(textChunks[0].text).toBe("Test response")
 		})
+		it("should include reasoning_effort when reasoning effort is enabled", async () => {
+			const reasoningOptions: ApiHandlerOptions = {
+				...mockOptions,
+				enableReasoningEffort: true,
+				openAiCustomModelInfo: { contextWindow: 128_000, supportsPromptCache: false, reasoningEffort: "high" },
+			}
+			const reasoningHandler = new OpenAiHandler(reasoningOptions)
+			const stream = reasoningHandler.createMessage(systemPrompt, messages)
+			// Consume the stream to trigger the API call
+			for await (const _chunk of stream) {
+			}
+			// Assert the mockCreate was called with reasoning_effort
+			expect(mockCreate).toHaveBeenCalled()
+			const callArgs = mockCreate.mock.calls[0][0]
+			expect(callArgs.reasoning_effort).toBe("high")
+		})
+
+		it("should not include reasoning_effort when reasoning effort is disabled", async () => {
+			const noReasoningOptions: ApiHandlerOptions = {
+				...mockOptions,
+				enableReasoningEffort: false,
+				openAiCustomModelInfo: { contextWindow: 128_000, supportsPromptCache: false },
+			}
+			const noReasoningHandler = new OpenAiHandler(noReasoningOptions)
+			const stream = noReasoningHandler.createMessage(systemPrompt, messages)
+			// Consume the stream to trigger the API call
+			for await (const _chunk of stream) {
+			}
+			// Assert the mockCreate was called without reasoning_effort
+			expect(mockCreate).toHaveBeenCalled()
+			const callArgs = mockCreate.mock.calls[0][0]
+			expect(callArgs.reasoning_effort).toBeUndefined()
+		})
 	})
 
 	describe("error handling", () => {

+ 1 - 0
src/exports/roo-code.d.ts

@@ -87,6 +87,7 @@ type ProviderSettings = {
 	openAiUseAzure?: boolean | undefined
 	azureApiVersion?: string | undefined
 	openAiStreamingEnabled?: boolean | undefined
+	enableReasoningEffort?: boolean | undefined
 	ollamaModelId?: string | undefined
 	ollamaBaseUrl?: string | undefined
 	vsCodeLmModelSelector?:

+ 1 - 0
src/exports/types.ts

@@ -88,6 +88,7 @@ type ProviderSettings = {
 	openAiUseAzure?: boolean | undefined
 	azureApiVersion?: string | undefined
 	openAiStreamingEnabled?: boolean | undefined
+	enableReasoningEffort?: boolean | undefined
 	ollamaModelId?: string | undefined
 	ollamaBaseUrl?: string | undefined
 	vsCodeLmModelSelector?:

+ 2 - 0
src/schemas/index.ts

@@ -355,6 +355,7 @@ export const providerSettingsSchema = z.object({
 	openAiUseAzure: z.boolean().optional(),
 	azureApiVersion: z.string().optional(),
 	openAiStreamingEnabled: z.boolean().optional(),
+	enableReasoningEffort: z.boolean().optional(),
 	// Ollama
 	ollamaModelId: z.string().optional(),
 	ollamaBaseUrl: z.string().optional(),
@@ -453,6 +454,7 @@ const providerSettingsRecord: ProviderSettingsRecord = {
 	openAiUseAzure: undefined,
 	azureApiVersion: undefined,
 	openAiStreamingEnabled: undefined,
+	enableReasoningEffort: undefined,
 	// Ollama
 	ollamaModelId: undefined,
 	ollamaBaseUrl: undefined,

+ 2 - 0
src/utils/__tests__/enhance-prompt.test.ts

@@ -13,6 +13,7 @@ describe("enhancePrompt", () => {
 		apiProvider: "openai",
 		openAiApiKey: "test-key",
 		openAiBaseUrl: "https://api.openai.com/v1",
+		enableReasoningEffort: false,
 	}
 
 	beforeEach(() => {
@@ -97,6 +98,7 @@ describe("enhancePrompt", () => {
 			apiProvider: "openrouter",
 			openRouterApiKey: "test-key",
 			openRouterModelId: "test-model",
+			enableReasoningEffort: false,
 		}
 
 		// Mock successful enhancement

+ 42 - 7
webview-ui/src/components/settings/ApiOptions.tsx

@@ -1,13 +1,12 @@
 import React, { memo, useCallback, useEffect, useMemo, useState } from "react"
-import { useAppTranslation } from "@/i18n/TranslationContext"
-import { Trans } from "react-i18next"
-import { getRequestyAuthUrl, getOpenRouterAuthUrl, getGlamaAuthUrl } from "@src/oauth/urls"
 import { useDebounce, useEvent } from "react-use"
+import { Trans } from "react-i18next"
 import { LanguageModelChatSelector } from "vscode"
 import { Checkbox } from "vscrui"
 import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
 import { ExternalLinkIcon } from "@radix-ui/react-icons"
 
+import { ReasoningEffort as ReasoningEffortType } from "@roo/schemas"
 import {
 	ApiConfiguration,
 	ModelInfo,
@@ -21,21 +20,22 @@ import {
 	ApiProvider,
 } from "@roo/shared/api"
 import { ExtensionMessage } from "@roo/shared/ExtensionMessage"
-import { AWS_REGIONS } from "@roo/shared/aws_regions"
 
 import { vscode } from "@src/utils/vscode"
 import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@src/utils/validate"
-import { useRouterModels } from "@/components/ui/hooks/useRouterModels"
-import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel"
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { useRouterModels } from "@src/components/ui/hooks/useRouterModels"
+import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
 import {
 	useOpenRouterModelProviders,
 	OPENROUTER_DEFAULT_PROVIDER_NAME,
 } from "@src/components/ui/hooks/useOpenRouterModelProviders"
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button } from "@src/components/ui"
+import { getRequestyAuthUrl, getOpenRouterAuthUrl, getGlamaAuthUrl } from "@src/oauth/urls"
 
 import { VSCodeButtonLink } from "../common/VSCodeButtonLink"
 
-import { MODELS_BY_PROVIDER, PROVIDERS, VERTEX_REGIONS, REASONING_MODELS } from "./constants"
+import { MODELS_BY_PROVIDER, PROVIDERS, VERTEX_REGIONS, REASONING_MODELS, AWS_REGIONS } from "./constants"
 import { ModelInfoView } from "./ModelInfoView"
 import { ModelPicker } from "./ModelPicker"
 import { ApiErrorMessage } from "./ApiErrorMessage"
@@ -851,6 +851,41 @@ const ApiOptions = ({
 						)}
 					</div>
 
+					<div className="flex flex-col gap-1">
+						<Checkbox
+							checked={apiConfiguration.enableReasoningEffort ?? false}
+							onChange={(checked: boolean) => {
+								setApiConfigurationField("enableReasoningEffort", checked)
+
+								if (!checked) {
+									const { reasoningEffort: _, ...openAiCustomModelInfo } =
+										apiConfiguration.openAiCustomModelInfo || openAiModelInfoSaneDefaults
+
+									setApiConfigurationField("openAiCustomModelInfo", openAiCustomModelInfo)
+								}
+							}}>
+							{t("settings:providers.setReasoningLevel")}
+						</Checkbox>
+						{!!apiConfiguration.enableReasoningEffort && (
+							<ReasoningEffort
+								apiConfiguration={{
+									...apiConfiguration,
+									reasoningEffort: apiConfiguration.openAiCustomModelInfo?.reasoningEffort,
+								}}
+								setApiConfigurationField={(field, value) => {
+									if (field === "reasoningEffort") {
+										const openAiCustomModelInfo =
+											apiConfiguration.openAiCustomModelInfo || openAiModelInfoSaneDefaults
+
+										setApiConfigurationField("openAiCustomModelInfo", {
+											...openAiCustomModelInfo,
+											reasoningEffort: value as ReasoningEffortType,
+										})
+									}
+								}}
+							/>
+						)}
+					</div>
 					<div className="flex flex-col gap-3">
 						<div className="text-sm text-vscode-descriptionForeground whitespace-pre-line">
 							{t("settings:providers.customModel.capabilities")}

+ 184 - 5
webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx

@@ -1,11 +1,12 @@
 // npx jest src/components/settings/__tests__/ApiOptions.test.ts
 
-import { render, screen } from "@testing-library/react"
+import { render, screen, fireEvent } from "@testing-library/react"
 import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
 
 import { ApiConfiguration } from "@roo/shared/api"
 
 import { ExtensionStateContextProvider } from "@/context/ExtensionStateContext"
+import { openAiModelInfoSaneDefaults } from "@roo/shared/api"
 
 import ApiOptions, { ApiOptionsProps } from "../ApiOptions"
 
@@ -26,8 +27,13 @@ jest.mock("@vscode/webview-ui-toolkit/react", () => ({
 // Mock other components
 jest.mock("vscrui", () => ({
 	Checkbox: ({ children, checked, onChange }: any) => (
-		<label>
-			<input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} />
+		<label data-testid={`checkbox-${children?.toString().replace(/\s+/g, "-").toLowerCase()}`}>
+			<input
+				type="checkbox"
+				checked={checked}
+				onChange={(e) => onChange(e.target.checked)}
+				data-testid={`checkbox-input-${children?.toString().replace(/\s+/g, "-").toLowerCase()}`}
+			/>
 			{children}
 		</label>
 	),
@@ -51,11 +57,32 @@ jest.mock("@/components/ui", () => ({
 		</option>
 	),
 	SelectSeparator: ({ children }: any) => <div className="select-separator-mock">{children}</div>,
-	Button: ({ children, onClick }: any) => (
-		<button onClick={onClick} className="button-mock">
+	Button: ({ children, onClick, _variant, role, className }: any) => (
+		<button onClick={onClick} className={`button-mock ${className || ""}`} role={role}>
 			{children}
 		</button>
 	),
+	// Add missing components used by ModelPicker
+	Command: ({ children }: any) => <div className="command-mock">{children}</div>,
+	CommandEmpty: ({ children }: any) => <div className="command-empty-mock">{children}</div>,
+	CommandGroup: ({ children }: any) => <div className="command-group-mock">{children}</div>,
+	CommandInput: ({ value, onValueChange, placeholder, className, _ref }: any) => (
+		<input
+			value={value}
+			onChange={(e) => onValueChange && onValueChange(e.target.value)}
+			placeholder={placeholder}
+			className={className}
+		/>
+	),
+	CommandItem: ({ children, value, onSelect }: any) => (
+		<div className="command-item-mock" onClick={() => onSelect && onSelect(value)}>
+			{children}
+		</div>
+	),
+	CommandList: ({ children }: any) => <div className="command-list-mock">{children}</div>,
+	Popover: ({ children, _open, _onOpenChange }: any) => <div className="popover-mock">{children}</div>,
+	PopoverContent: ({ children, _className }: any) => <div className="popover-content-mock">{children}</div>,
+	PopoverTrigger: ({ children, _asChild }: any) => <div className="popover-trigger-mock">{children}</div>,
 	Slider: ({ value, onChange }: any) => (
 		<div data-testid="slider">
 			<input type="range" value={value || 0} onChange={(e) => onChange(parseFloat(e.target.value))} />
@@ -136,6 +163,21 @@ jest.mock("@src/components/ui/hooks/useSelectedModel", () => ({
 	}),
 }))
 
+jest.mock("../ReasoningEffort", () => ({
+	ReasoningEffort: ({ apiConfiguration, setApiConfigurationField, value }: any) => (
+		<div data-testid="reasoning-effort-select">
+			<select
+				value={value ?? apiConfiguration.openAiCustomModelInfo?.reasoningEffort}
+				onChange={(e) => setApiConfigurationField("reasoningEffort", e.target.value)}>
+				<option value="auto">Auto</option>
+				<option value="low">Low</option>
+				<option value="medium">Medium</option>
+				<option value="high">High</option>
+			</select>
+		</div>
+	),
+}))
+
 const renderApiOptions = (props: Partial<ApiOptionsProps> = {}) => {
 	const queryClient = new QueryClient()
 
@@ -215,4 +257,141 @@ describe("ApiOptions", () => {
 		// since we have separate tests for that component. We just need to verify that
 		// it's included in the ApiOptions component when appropriate.
 	})
+
+	describe("OpenAI provider tests", () => {
+		it("removes reasoningEffort from openAiCustomModelInfo when unchecked", () => {
+			const mockSetApiConfigurationField = jest.fn()
+			const initialConfig = {
+				apiProvider: "openai" as const,
+				enableReasoningEffort: true,
+				openAiCustomModelInfo: {
+					...openAiModelInfoSaneDefaults, // Start with defaults
+					reasoningEffort: "low" as const, // Set an initial value
+				},
+				// Add other necessary default fields for openai provider if needed
+			}
+
+			renderApiOptions({
+				apiConfiguration: initialConfig,
+				setApiConfigurationField: mockSetApiConfigurationField,
+			})
+
+			// Find the checkbox by its test ID instead of label text
+			// This is more reliable than using the label text which might be affected by translations
+			const checkbox =
+				screen.getByTestId("checkbox-input-settings:providers.setreasoninglevel") ||
+				screen.getByTestId("checkbox-input-set-reasoning-level")
+
+			// Simulate unchecking the checkbox
+			fireEvent.click(checkbox)
+
+			// 1. Check if enableReasoningEffort was set to false
+			expect(mockSetApiConfigurationField).toHaveBeenCalledWith("enableReasoningEffort", false)
+
+			// 2. Check if openAiCustomModelInfo was updated
+			const updateCall = mockSetApiConfigurationField.mock.calls.find(
+				(call) => call[0] === "openAiCustomModelInfo",
+			)
+			expect(updateCall).toBeDefined()
+
+			// 3. Check if reasoningEffort property is absent in the updated info
+			const updatedInfo = updateCall[1]
+			expect(updatedInfo).not.toHaveProperty("reasoningEffort")
+
+			// Optional: Check if other properties were preserved (example)
+			expect(updatedInfo).toHaveProperty("contextWindow", openAiModelInfoSaneDefaults.contextWindow)
+		})
+
+		it("does not render ReasoningEffort component when initially disabled", () => {
+			const mockSetApiConfigurationField = jest.fn()
+			const initialConfig = {
+				apiProvider: "openai" as const,
+				enableReasoningEffort: false, // Initially disabled
+				openAiCustomModelInfo: {
+					...openAiModelInfoSaneDefaults,
+				},
+			}
+
+			renderApiOptions({
+				apiConfiguration: initialConfig,
+				setApiConfigurationField: mockSetApiConfigurationField,
+			})
+
+			// Check that the ReasoningEffort select component is not rendered
+			expect(screen.queryByTestId("reasoning-effort-select")).not.toBeInTheDocument()
+			// Or, if the mock is simpler:
+			// expect(screen.queryByRole("combobox", { name: /reasoning effort/i })).not.toBeInTheDocument();
+		})
+
+		it("renders ReasoningEffort component and sets flag when checkbox is checked", () => {
+			const mockSetApiConfigurationField = jest.fn()
+			const initialConfig = {
+				apiProvider: "openai" as const,
+				enableReasoningEffort: false, // Initially disabled
+				openAiCustomModelInfo: {
+					...openAiModelInfoSaneDefaults,
+				},
+			}
+
+			renderApiOptions({
+				apiConfiguration: initialConfig,
+				setApiConfigurationField: mockSetApiConfigurationField,
+			})
+
+			const checkbox = screen.getByTestId("checkbox-input-settings:providers.setreasoninglevel")
+
+			// Simulate checking the checkbox
+			fireEvent.click(checkbox)
+
+			// 1. Check if enableReasoningEffort was set to true
+			expect(mockSetApiConfigurationField).toHaveBeenCalledWith("enableReasoningEffort", true)
+
+			// We can't directly test the rendering of the ReasoningEffort component after the state change
+			// without a more complex setup involving state management mocks or re-rendering.
+			// However, we've tested the state update call.
+		})
+
+		it("updates reasoningEffort in openAiCustomModelInfo when select value changes", () => {
+			const mockSetApiConfigurationField = jest.fn()
+			const initialConfig = {
+				apiProvider: "openai" as const,
+				enableReasoningEffort: true, // Initially enabled
+				openAiCustomModelInfo: {
+					...openAiModelInfoSaneDefaults,
+					reasoningEffort: "low" as const,
+				},
+			}
+
+			renderApiOptions({
+				apiConfiguration: initialConfig,
+				setApiConfigurationField: mockSetApiConfigurationField,
+			})
+
+			// Find the reasoning effort select among all comboboxes by its current value
+			const allSelects = screen.getAllByRole("combobox") as HTMLSelectElement[]
+			const reasoningSelect = allSelects.find(
+				(el) => el.value === initialConfig.openAiCustomModelInfo.reasoningEffort,
+			)
+			expect(reasoningSelect).toBeDefined()
+
+			// Simulate changing the reasoning effort to 'high'
+			fireEvent.change(reasoningSelect!, { target: { value: "high" } })
+
+			// Check if setApiConfigurationField was called correctly for openAiCustomModelInfo
+			expect(mockSetApiConfigurationField).toHaveBeenCalledWith(
+				"openAiCustomModelInfo",
+				expect.objectContaining({
+					reasoningEffort: "high",
+				}),
+			)
+
+			// Check that other properties were preserved
+			expect(mockSetApiConfigurationField).toHaveBeenCalledWith(
+				"openAiCustomModelInfo",
+				expect.objectContaining({
+					contextWindow: openAiModelInfoSaneDefaults.contextWindow,
+				}),
+			)
+		})
+	})
 })

+ 2 - 0
webview-ui/src/components/settings/constants.ts

@@ -13,6 +13,8 @@ import {
 
 export { REASONING_MODELS, PROMPT_CACHING_MODELS } from "@roo/shared/api"
 
+export { AWS_REGIONS } from "@roo/shared/aws_regions"
+
 export const MODELS_BY_PROVIDER: Partial<Record<ApiProvider, Record<string, ModelInfo>>> = {
 	anthropic: anthropicModels,
 	bedrock: bedrockModels,

+ 2 - 1
webview-ui/src/i18n/locales/ca/settings.json

@@ -233,7 +233,8 @@
 			"high": "Alt",
 			"medium": "Mitjà",
 			"low": "Baix"
-		}
+		},
+		"setReasoningLevel": "Activa l'esforç de raonament"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/de/settings.json

@@ -233,7 +233,8 @@
 			"high": "Hoch",
 			"medium": "Mittel",
 			"low": "Niedrig"
-		}
+		},
+		"setReasoningLevel": "Denkaufwand aktivieren"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/en/settings.json

@@ -233,7 +233,8 @@
 			"high": "High",
 			"medium": "Medium",
 			"low": "Low"
-		}
+		},
+		"setReasoningLevel": "Enable Reasoning Effort"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/es/settings.json

@@ -233,7 +233,8 @@
 			"high": "Alto",
 			"medium": "Medio",
 			"low": "Bajo"
-		}
+		},
+		"setReasoningLevel": "Habilitar esfuerzo de razonamiento"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/fr/settings.json

@@ -233,7 +233,8 @@
 			"high": "Élevé",
 			"medium": "Moyen",
 			"low": "Faible"
-		}
+		},
+		"setReasoningLevel": "Activer l'effort de raisonnement"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/hi/settings.json

@@ -233,7 +233,8 @@
 			"high": "उच्च",
 			"medium": "मध्यम",
 			"low": "निम्न"
-		}
+		},
+		"setReasoningLevel": "तर्क प्रयास सक्षम करें"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/it/settings.json

@@ -233,7 +233,8 @@
 			"high": "Alto",
 			"medium": "Medio",
 			"low": "Basso"
-		}
+		},
+		"setReasoningLevel": "Abilita sforzo di ragionamento"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/ja/settings.json

@@ -233,7 +233,8 @@
 			"high": "高",
 			"medium": "中",
 			"low": "低"
-		}
+		},
+		"setReasoningLevel": "推論労力を有効にする"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/ko/settings.json

@@ -233,7 +233,8 @@
 			"high": "높음",
 			"medium": "중간",
 			"low": "낮음"
-		}
+		},
+		"setReasoningLevel": "추론 노력 활성화"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/pl/settings.json

@@ -233,7 +233,8 @@
 			"high": "Wysoki",
 			"medium": "Średni",
 			"low": "Niski"
-		}
+		},
+		"setReasoningLevel": "Włącz wysiłek rozumowania"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/pt-BR/settings.json

@@ -233,7 +233,8 @@
 			"high": "Alto",
 			"medium": "Médio",
 			"low": "Baixo"
-		}
+		},
+		"setReasoningLevel": "Habilitar esforço de raciocínio"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/ru/settings.json

@@ -233,7 +233,8 @@
 			"high": "Высокие",
 			"medium": "Средние",
 			"low": "Низкие"
-		}
+		},
+		"setReasoningLevel": "Включить усилие рассуждения"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/tr/settings.json

@@ -233,7 +233,8 @@
 			"high": "Yüksek",
 			"medium": "Orta",
 			"low": "Düşük"
-		}
+		},
+		"setReasoningLevel": "Akıl Yürütme Çabasını Etkinleştir"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/vi/settings.json

@@ -233,7 +233,8 @@
 			"high": "Cao",
 			"medium": "Trung bình",
 			"low": "Thấp"
-		}
+		},
+		"setReasoningLevel": "Kích hoạt nỗ lực suy luận"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/zh-CN/settings.json

@@ -233,7 +233,8 @@
 			"high": "高",
 			"medium": "中",
 			"low": "低"
-		}
+		},
+		"setReasoningLevel": "启用推理工作量"
 	},
 	"browser": {
 		"enable": {

+ 2 - 1
webview-ui/src/i18n/locales/zh-TW/settings.json

@@ -233,7 +233,8 @@
 			"high": "高",
 			"medium": "中",
 			"low": "低"
-		}
+		},
+		"setReasoningLevel": "啟用推理工作量"
 	},
 	"browser": {
 		"enable": {