Browse Source

Enabled reasoning in Roo provider (#8874)

Matt Rubens 4 months ago
parent
commit
b72d55ef45
28 changed files with 1223 additions and 9 deletions
  1. 1 0
      packages/types/src/model.ts
  2. 150 0
      src/api/providers/__tests__/roo.spec.ts
  3. 477 0
      src/api/providers/fetchers/__tests__/roo.spec.ts
  4. 8 0
      src/api/providers/fetchers/roo.ts
  5. 68 3
      src/api/providers/roo.ts
  6. 131 0
      src/api/transform/__tests__/reasoning.spec.ts
  7. 35 0
      src/api/transform/reasoning.ts
  8. 16 6
      webview-ui/src/components/settings/ApiOptions.tsx
  9. 107 0
      webview-ui/src/components/settings/SimpleThinkingBudget.tsx
  10. 212 0
      webview-ui/src/components/settings/__tests__/SimpleThinkingBudget.spec.tsx
  11. 1 0
      webview-ui/src/i18n/locales/ca/settings.json
  12. 1 0
      webview-ui/src/i18n/locales/de/settings.json
  13. 1 0
      webview-ui/src/i18n/locales/en/settings.json
  14. 1 0
      webview-ui/src/i18n/locales/es/settings.json
  15. 1 0
      webview-ui/src/i18n/locales/fr/settings.json
  16. 1 0
      webview-ui/src/i18n/locales/hi/settings.json
  17. 1 0
      webview-ui/src/i18n/locales/id/settings.json
  18. 1 0
      webview-ui/src/i18n/locales/it/settings.json
  19. 1 0
      webview-ui/src/i18n/locales/ja/settings.json
  20. 1 0
      webview-ui/src/i18n/locales/ko/settings.json
  21. 1 0
      webview-ui/src/i18n/locales/nl/settings.json
  22. 1 0
      webview-ui/src/i18n/locales/pl/settings.json
  23. 1 0
      webview-ui/src/i18n/locales/pt-BR/settings.json
  24. 1 0
      webview-ui/src/i18n/locales/ru/settings.json
  25. 1 0
      webview-ui/src/i18n/locales/tr/settings.json
  26. 1 0
      webview-ui/src/i18n/locales/vi/settings.json
  27. 1 0
      webview-ui/src/i18n/locales/zh-CN/settings.json
  28. 1 0
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 1 - 0
packages/types/src/model.ts

@@ -65,6 +65,7 @@ export const modelInfoSchema = z.object({
 	supportsTemperature: z.boolean().optional(),
 	requiredReasoningBudget: z.boolean().optional(),
 	supportsReasoningEffort: z.boolean().optional(),
+	requiredReasoningEffort: z.boolean().optional(),
 	supportedParameters: z.array(modelParametersSchema).optional(),
 	inputPrice: z.number().optional(),
 	outputPrice: z.number().optional(),

+ 150 - 0
src/api/providers/__tests__/roo.spec.ts

@@ -86,6 +86,28 @@ vitest.mock("../../../i18n", () => ({
 	}),
 }))
 
+// Mock model cache
+vitest.mock("../../providers/fetchers/modelCache", () => ({
+	getModels: vitest.fn(),
+	flushModels: vitest.fn(),
+	getModelsFromCache: vitest.fn((provider: string) => {
+		if (provider === "roo") {
+			return {
+				"xai/grok-code-fast-1": {
+					maxTokens: 16_384,
+					contextWindow: 262_144,
+					supportsImages: false,
+					supportsReasoningEffort: true, // Enable reasoning for tests
+					supportsPromptCache: true,
+					inputPrice: 0,
+					outputPrice: 0,
+				},
+			}
+		}
+		return {}
+	}),
+}))
+
 // Import after mocks are set up
 import { RooHandler } from "../roo"
 import { CloudService } from "@roo-code/cloud"
@@ -446,4 +468,132 @@ describe("RooHandler", () => {
 			expect(handler).toBeInstanceOf(RooHandler)
 		})
 	})
+
+	describe("reasoning effort support", () => {
+		it("should include reasoning with enabled: false when not enabled", async () => {
+			handler = new RooHandler(mockOptions)
+			const stream = handler.createMessage(systemPrompt, messages)
+			for await (const _chunk of stream) {
+				// Consume stream
+			}
+
+			expect(mockCreate).toHaveBeenCalledWith(
+				expect.objectContaining({
+					model: mockOptions.apiModelId,
+					messages: expect.any(Array),
+					stream: true,
+					stream_options: { include_usage: true },
+					reasoning: { enabled: false },
+				}),
+				undefined,
+			)
+		})
+
+		it("should include reasoning with enabled: false when explicitly disabled", async () => {
+			handler = new RooHandler({
+				...mockOptions,
+				enableReasoningEffort: false,
+			})
+			const stream = handler.createMessage(systemPrompt, messages)
+			for await (const _chunk of stream) {
+				// Consume stream
+			}
+
+			expect(mockCreate).toHaveBeenCalledWith(
+				expect.objectContaining({
+					reasoning: { enabled: false },
+				}),
+				undefined,
+			)
+		})
+
+		it("should include reasoning with enabled: true and effort: low", async () => {
+			handler = new RooHandler({
+				...mockOptions,
+				reasoningEffort: "low",
+			})
+			const stream = handler.createMessage(systemPrompt, messages)
+			for await (const _chunk of stream) {
+				// Consume stream
+			}
+
+			expect(mockCreate).toHaveBeenCalledWith(
+				expect.objectContaining({
+					reasoning: { enabled: true, effort: "low" },
+				}),
+				undefined,
+			)
+		})
+
+		it("should include reasoning with enabled: true and effort: medium", async () => {
+			handler = new RooHandler({
+				...mockOptions,
+				reasoningEffort: "medium",
+			})
+			const stream = handler.createMessage(systemPrompt, messages)
+			for await (const _chunk of stream) {
+				// Consume stream
+			}
+
+			expect(mockCreate).toHaveBeenCalledWith(
+				expect.objectContaining({
+					reasoning: { enabled: true, effort: "medium" },
+				}),
+				undefined,
+			)
+		})
+
+		it("should include reasoning with enabled: true and effort: high", async () => {
+			handler = new RooHandler({
+				...mockOptions,
+				reasoningEffort: "high",
+			})
+			const stream = handler.createMessage(systemPrompt, messages)
+			for await (const _chunk of stream) {
+				// Consume stream
+			}
+
+			expect(mockCreate).toHaveBeenCalledWith(
+				expect.objectContaining({
+					reasoning: { enabled: true, effort: "high" },
+				}),
+				undefined,
+			)
+		})
+
+		it("should not include reasoning for minimal (treated as none)", async () => {
+			handler = new RooHandler({
+				...mockOptions,
+				reasoningEffort: "minimal",
+			})
+			const stream = handler.createMessage(systemPrompt, messages)
+			for await (const _chunk of stream) {
+				// Consume stream
+			}
+
+			// minimal should result in no reasoning parameter
+			const callArgs = mockCreate.mock.calls[0][0]
+			expect(callArgs.reasoning).toBeUndefined()
+		})
+
+		it("should handle enableReasoningEffort: false overriding reasoningEffort setting", async () => {
+			handler = new RooHandler({
+				...mockOptions,
+				enableReasoningEffort: false,
+				reasoningEffort: "high",
+			})
+			const stream = handler.createMessage(systemPrompt, messages)
+			for await (const _chunk of stream) {
+				// Consume stream
+			}
+
+			// When explicitly disabled, should send enabled: false regardless of effort setting
+			expect(mockCreate).toHaveBeenCalledWith(
+				expect.objectContaining({
+					reasoning: { enabled: false },
+				}),
+				undefined,
+			)
+		})
+	})
 })

+ 477 - 0
src/api/providers/fetchers/__tests__/roo.spec.ts

@@ -0,0 +1,477 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
+import { getRooModels } from "../roo"
+
+// Mock fetch globally
+const mockFetch = vi.fn()
+global.fetch = mockFetch as any
+
+describe("getRooModels", () => {
+	const baseUrl = "https://api.roocode.com/proxy"
+	const apiKey = "test-api-key"
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+		vi.useFakeTimers()
+	})
+
+	afterEach(() => {
+		vi.useRealTimers()
+	})
+
+	it("should fetch and parse models successfully", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "xai/grok-code-fast-1",
+					object: "model",
+					created: 1234567890,
+					owned_by: "xai",
+					name: "Grok Code Fast 1",
+					description: "Fast coding model",
+					context_window: 262144,
+					max_tokens: 16384,
+					type: "language",
+					tags: ["vision", "reasoning"],
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+						input_cache_read: "0.00005",
+						input_cache_write: "0.0001",
+					},
+					deprecated: false,
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl, apiKey)
+
+		expect(mockFetch).toHaveBeenCalledWith(
+			"https://api.roocode.com/proxy/v1/models",
+			expect.objectContaining({
+				headers: expect.objectContaining({
+					"Content-Type": "application/json",
+					Authorization: `Bearer ${apiKey}`,
+				}),
+			}),
+		)
+
+		expect(models).toEqual({
+			"xai/grok-code-fast-1": {
+				maxTokens: 16384,
+				contextWindow: 262144,
+				supportsImages: true,
+				supportsReasoningEffort: true,
+				requiredReasoningEffort: false,
+				supportsPromptCache: true,
+				inputPrice: 100, // 0.0001 * 1_000_000
+				outputPrice: 200, // 0.0002 * 1_000_000
+				cacheWritesPrice: 100, // 0.0001 * 1_000_000
+				cacheReadsPrice: 50, // 0.00005 * 1_000_000
+				description: "Fast coding model",
+				deprecated: false,
+				isFree: false,
+			},
+		})
+	})
+
+	it("should handle reasoning-required tag", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "test/reasoning-required-model",
+					object: "model",
+					created: 1234567890,
+					owned_by: "test",
+					name: "Reasoning Required Model",
+					description: "Model that requires reasoning",
+					context_window: 128000,
+					max_tokens: 8192,
+					type: "language",
+					tags: ["reasoning", "reasoning-required"],
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+					},
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl, apiKey)
+
+		expect(models["test/reasoning-required-model"]).toEqual({
+			maxTokens: 8192,
+			contextWindow: 128000,
+			supportsImages: false,
+			supportsReasoningEffort: true,
+			requiredReasoningEffort: true,
+			supportsPromptCache: false,
+			inputPrice: 100, // 0.0001 * 1_000_000
+			outputPrice: 200, // 0.0002 * 1_000_000
+			cacheWritesPrice: undefined,
+			cacheReadsPrice: undefined,
+			description: "Model that requires reasoning",
+			deprecated: false,
+			isFree: false,
+		})
+	})
+
+	it("should handle models without required_reasoning_effort field", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "test/normal-model",
+					object: "model",
+					created: 1234567890,
+					owned_by: "test",
+					name: "Normal Model",
+					description: "Normal model without reasoning",
+					context_window: 128000,
+					max_tokens: 8192,
+					type: "language",
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+					},
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl, apiKey)
+
+		expect(models["test/normal-model"]).toEqual({
+			maxTokens: 8192,
+			contextWindow: 128000,
+			supportsImages: false,
+			supportsReasoningEffort: false,
+			requiredReasoningEffort: false,
+			supportsPromptCache: false,
+			inputPrice: 100, // 0.0001 * 1_000_000
+			outputPrice: 200, // 0.0002 * 1_000_000
+			cacheWritesPrice: undefined,
+			cacheReadsPrice: undefined,
+			description: "Normal model without reasoning",
+			deprecated: false,
+			isFree: false,
+		})
+	})
+
+	it("should work without API key", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "test/public-model",
+					object: "model",
+					created: 1234567890,
+					owned_by: "test",
+					name: "Public Model",
+					description: "Public model",
+					context_window: 128000,
+					max_tokens: 8192,
+					type: "language",
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+					},
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl)
+
+		expect(mockFetch).toHaveBeenCalledWith(
+			"https://api.roocode.com/proxy/v1/models",
+			expect.objectContaining({
+				headers: expect.not.objectContaining({
+					Authorization: expect.anything(),
+				}),
+			}),
+		)
+
+		expect(models["test/public-model"]).toBeDefined()
+	})
+
+	it("should handle HTTP errors", async () => {
+		mockFetch.mockResolvedValueOnce({
+			ok: false,
+			status: 401,
+			statusText: "Unauthorized",
+		})
+
+		await expect(getRooModels(baseUrl, apiKey)).rejects.toThrow(
+			"Failed to fetch Roo Code Cloud models: HTTP 401: Unauthorized",
+		)
+	})
+
+	it("should handle timeout", async () => {
+		const abortError = new Error("AbortError")
+		abortError.name = "AbortError"
+
+		mockFetch.mockRejectedValueOnce(abortError)
+
+		await expect(getRooModels(baseUrl, apiKey)).rejects.toThrow(
+			"Failed to fetch Roo Code Cloud models: Request timed out",
+		)
+	})
+
+	it("should handle invalid response format", async () => {
+		const invalidResponse = {
+			invalid: "data",
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => invalidResponse,
+		})
+
+		await expect(getRooModels(baseUrl, apiKey)).rejects.toThrow(
+			"Failed to fetch Roo Code Cloud models: Unexpected response format",
+		)
+	})
+
+	it("should normalize base URL correctly", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		await getRooModels("https://api.roocode.com/proxy/v1", apiKey)
+
+		expect(mockFetch).toHaveBeenCalledWith("https://api.roocode.com/proxy/v1/models", expect.any(Object))
+	})
+
+	it("should handle deprecated models", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "test/deprecated-model",
+					object: "model",
+					created: 1234567890,
+					owned_by: "test",
+					name: "Deprecated Model",
+					description: "Old model",
+					context_window: 128000,
+					max_tokens: 8192,
+					type: "language",
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+					},
+					deprecated: true,
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl, apiKey)
+
+		expect(models["test/deprecated-model"].deprecated).toBe(true)
+	})
+
+	it("should detect vision support from tags", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "test/vision-model",
+					object: "model",
+					created: 1234567890,
+					owned_by: "test",
+					name: "Vision Model",
+					description: "Model with vision",
+					context_window: 128000,
+					max_tokens: 8192,
+					type: "language",
+					tags: ["vision"],
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+					},
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl, apiKey)
+
+		expect(models["test/vision-model"].supportsImages).toBe(true)
+	})
+
+	it("should detect reasoning support from tags", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "test/reasoning-model",
+					object: "model",
+					created: 1234567890,
+					owned_by: "test",
+					name: "Reasoning Model",
+					description: "Model with reasoning",
+					context_window: 128000,
+					max_tokens: 8192,
+					type: "language",
+					tags: ["reasoning"],
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+					},
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl, apiKey)
+
+		expect(models["test/reasoning-model"].supportsReasoningEffort).toBe(true)
+	})
+
+	it("should handle models with cache pricing", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "test/cache-model",
+					object: "model",
+					created: 1234567890,
+					owned_by: "test",
+					name: "Cache Model",
+					description: "Model with cache",
+					context_window: 128000,
+					max_tokens: 8192,
+					type: "language",
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+						input_cache_read: "0.00005",
+						input_cache_write: "0.0001",
+					},
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl, apiKey)
+
+		expect(models["test/cache-model"].supportsPromptCache).toBe(true)
+		expect(models["test/cache-model"].cacheReadsPrice).toBe(50) // 0.00005 * 1_000_000
+		expect(models["test/cache-model"].cacheWritesPrice).toBe(100) // 0.0001 * 1_000_000
+	})
+
+	it("should skip models without ID", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "",
+					object: "model",
+					created: 1234567890,
+					owned_by: "test",
+					name: "Invalid Model",
+					description: "Model without ID",
+					context_window: 128000,
+					max_tokens: 8192,
+					type: "language",
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+					},
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl, apiKey)
+
+		expect(Object.keys(models)).toHaveLength(0)
+	})
+
+	it("should use model name as description fallback", async () => {
+		const mockResponse = {
+			object: "list",
+			data: [
+				{
+					id: "test/no-description",
+					object: "model",
+					created: 1234567890,
+					owned_by: "test",
+					name: "Model Name",
+					description: "",
+					context_window: 128000,
+					max_tokens: 8192,
+					type: "language",
+					pricing: {
+						input: "0.0001",
+						output: "0.0002",
+					},
+				},
+			],
+		}
+
+		mockFetch.mockResolvedValueOnce({
+			ok: true,
+			json: async () => mockResponse,
+		})
+
+		const models = await getRooModels(baseUrl, apiKey)
+
+		expect(models["test/no-description"].description).toBe("Model Name")
+	})
+
+	it("should handle network errors", async () => {
+		mockFetch.mockRejectedValueOnce(new TypeError("Network error"))
+
+		await expect(getRooModels(baseUrl, apiKey)).rejects.toThrow(
+			"Failed to fetch Roo Code Cloud models: No response from server",
+		)
+	})
+})

+ 8 - 0
src/api/providers/fetchers/roo.ts

@@ -71,6 +71,12 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
 				// Determine if the model supports images based on tags
 				const supportsImages = tags.includes("vision")
 
+				// Determine if the model supports reasoning effort based on tags
+				const supportsReasoningEffort = tags.includes("reasoning")
+
+				// Determine if the model requires reasoning effort based on tags
+				const requiredReasoningEffort = tags.includes("reasoning-required")
+
 				// Parse pricing (API returns strings, convert to numbers)
 				const inputPrice = parseApiPrice(pricing.input)
 				const outputPrice = parseApiPrice(pricing.output)
@@ -81,6 +87,8 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
 					maxTokens,
 					contextWindow,
 					supportsImages,
+					supportsReasoningEffort,
+					requiredReasoningEffort,
 					supportsPromptCache: Boolean(cacheReadPrice !== undefined),
 					inputPrice,
 					outputPrice,

+ 68 - 3
src/api/providers/roo.ts

@@ -6,11 +6,16 @@ import { CloudService } from "@roo-code/cloud"
 
 import type { ApiHandlerOptions, ModelRecord } from "../../shared/api"
 import { ApiStream } from "../transform/stream"
+import { getModelParams } from "../transform/model-params"
+import { convertToOpenAiMessages } from "../transform/openai-format"
+import type { RooReasoningParams } from "../transform/reasoning"
+import { getRooReasoning } from "../transform/reasoning"
 
 import type { ApiHandlerCreateMessageMetadata } from "../index"
 import { DEFAULT_HEADERS } from "./constants"
 import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
 import { getModels, flushModels, getModelsFromCache } from "../providers/fetchers/modelCache"
+import { handleOpenAIError } from "./utils/openai-error-handler"
 
 // Extend OpenAI's CompletionUsage to include Roo specific fields
 interface RooUsage extends OpenAI.CompletionUsage {
@@ -18,6 +23,11 @@ interface RooUsage extends OpenAI.CompletionUsage {
 	cost?: number
 }
 
+// Add custom interface for Roo params to support reasoning
+type RooChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParamsStreaming & {
+	reasoning?: RooReasoningParams
+}
+
 export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
 	private authStateListener?: (state: { state: AuthState }) => void
 	private fetcherBaseURL: string
@@ -78,6 +88,51 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
 		}
 	}
 
+	protected override createStream(
+		systemPrompt: string,
+		messages: Anthropic.Messages.MessageParam[],
+		metadata?: ApiHandlerCreateMessageMetadata,
+		requestOptions?: OpenAI.RequestOptions,
+	) {
+		const { id: model, info } = this.getModel()
+
+		// Get model parameters including reasoning
+		const params = getModelParams({
+			format: "openai",
+			modelId: model,
+			model: info,
+			settings: this.options,
+			defaultTemperature: this.defaultTemperature,
+		})
+
+		// Get Roo-specific reasoning parameters
+		const reasoning = getRooReasoning({
+			model: info,
+			reasoningBudget: params.reasoningBudget,
+			reasoningEffort: params.reasoningEffort,
+			settings: this.options,
+		})
+
+		const max_tokens = params.maxTokens ?? undefined
+		const temperature = params.temperature ?? this.defaultTemperature
+
+		const rooParams: RooChatCompletionParams = {
+			model,
+			max_tokens,
+			temperature,
+			messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
+			stream: true,
+			stream_options: { include_usage: true },
+			...(reasoning && { reasoning }),
+		}
+
+		try {
+			return this.client.chat.completions.create(rooParams, requestOptions)
+		} catch (error) {
+			throw handleOpenAIError(error, this.providerName)
+		}
+	}
+
 	override async *createMessage(
 		systemPrompt: string,
 		messages: Anthropic.Messages.MessageParam[],
@@ -96,19 +151,28 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
 			const delta = chunk.choices[0]?.delta
 
 			if (delta) {
-				if (delta.content) {
+				// Check for reasoning content (similar to OpenRouter)
+				if ("reasoning" in delta && delta.reasoning && typeof delta.reasoning === "string") {
 					yield {
-						type: "text",
-						text: delta.content,
+						type: "reasoning",
+						text: delta.reasoning,
 					}
 				}
 
+				// Also check for reasoning_content for backward compatibility
 				if ("reasoning_content" in delta && typeof delta.reasoning_content === "string") {
 					yield {
 						type: "reasoning",
 						text: delta.reasoning_content,
 					}
 				}
+
+				if (delta.content) {
+					yield {
+						type: "text",
+						text: delta.content,
+					}
+				}
 			}
 
 			if (chunk.usage) {
@@ -163,6 +227,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
 				maxTokens: 16_384,
 				contextWindow: 262_144,
 				supportsImages: false,
+				supportsReasoningEffort: false,
 				supportsPromptCache: true,
 				inputPrice: 0,
 				outputPrice: 0,

+ 131 - 0
src/api/transform/__tests__/reasoning.spec.ts

@@ -6,10 +6,12 @@ import {
 	getOpenRouterReasoning,
 	getAnthropicReasoning,
 	getOpenAiReasoning,
+	getRooReasoning,
 	GetModelReasoningOptions,
 	OpenRouterReasoningParams,
 	AnthropicReasoningParams,
 	OpenAiReasoningParams,
+	RooReasoningParams,
 } from "../reasoning"
 
 describe("reasoning.ts", () => {
@@ -761,4 +763,133 @@ describe("reasoning.ts", () => {
 			}
 		})
 	})
+
+	describe("getRooReasoning", () => {
+		it("should return undefined when model does not support reasoning effort", () => {
+			const options = { ...baseOptions }
+			const result = getRooReasoning(options)
+			expect(result).toBeUndefined()
+		})
+
+		it("should return enabled: false when enableReasoningEffort is explicitly false", () => {
+			const modelWithSupported: ModelInfo = {
+				...baseModel,
+				supportsReasoningEffort: true,
+			}
+
+			const settingsWithDisabled: ProviderSettings = {
+				enableReasoningEffort: false,
+			}
+
+			const options = {
+				...baseOptions,
+				model: modelWithSupported,
+				settings: settingsWithDisabled,
+			}
+
+			const result = getRooReasoning(options)
+			expect(result).toEqual({ enabled: false })
+		})
+
+		it("should return enabled: true with effort when reasoningEffort is provided", () => {
+			const modelWithSupported: ModelInfo = {
+				...baseModel,
+				supportsReasoningEffort: true,
+			}
+
+			const settingsWithEffort: ProviderSettings = {
+				reasoningEffort: "high",
+			}
+
+			const options = {
+				...baseOptions,
+				model: modelWithSupported,
+				settings: settingsWithEffort,
+				reasoningEffort: "high" as const,
+			}
+
+			const result = getRooReasoning(options)
+			expect(result).toEqual({ enabled: true, effort: "high" })
+		})
+
+		it("should return enabled: false when reasoningEffort is undefined (None selected)", () => {
+			const modelWithSupported: ModelInfo = {
+				...baseModel,
+				supportsReasoningEffort: true,
+			}
+
+			const options = {
+				...baseOptions,
+				model: modelWithSupported,
+				settings: {},
+				reasoningEffort: undefined,
+			}
+
+			const result = getRooReasoning(options)
+			expect(result).toEqual({ enabled: false })
+		})
+
+		it("should not return reasoning params for minimal effort", () => {
+			const modelWithSupported: ModelInfo = {
+				...baseModel,
+				supportsReasoningEffort: true,
+			}
+
+			const settingsWithMinimal: ProviderSettings = {
+				reasoningEffort: "minimal",
+			}
+
+			const options = {
+				...baseOptions,
+				model: modelWithSupported,
+				settings: settingsWithMinimal,
+				reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
+			}
+
+			const result = getRooReasoning(options)
+			expect(result).toBeUndefined()
+		})
+
+		it("should handle all valid reasoning effort values", () => {
+			const efforts: Array<"low" | "medium" | "high"> = ["low", "medium", "high"]
+
+			efforts.forEach((effort) => {
+				const modelWithSupported: ModelInfo = {
+					...baseModel,
+					supportsReasoningEffort: true,
+				}
+
+				const settingsWithEffort: ProviderSettings = {
+					reasoningEffort: effort,
+				}
+
+				const options = {
+					...baseOptions,
+					model: modelWithSupported,
+					settings: settingsWithEffort,
+					reasoningEffort: effort,
+				}
+
+				const result = getRooReasoning(options)
+				expect(result).toEqual({ enabled: true, effort })
+			})
+		})
+
+		it("should return enabled: false when model supports reasoning but no effort is provided", () => {
+			const modelWithSupported: ModelInfo = {
+				...baseModel,
+				supportsReasoningEffort: true,
+			}
+
+			const options = {
+				...baseOptions,
+				model: modelWithSupported,
+				settings: {},
+				reasoningEffort: undefined,
+			}
+
+			const result = getRooReasoning(options)
+			expect(result).toEqual({ enabled: false })
+		})
+	})
 })

+ 35 - 0
src/api/transform/reasoning.ts

@@ -12,6 +12,11 @@ export type OpenRouterReasoningParams = {
 	exclude?: boolean
 }
 
+export type RooReasoningParams = {
+	enabled?: boolean
+	effort?: ReasoningEffortWithMinimal
+}
+
 export type AnthropicReasoningParams = BetaThinkingConfigParam
 
 export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] }
@@ -39,6 +44,36 @@ export const getOpenRouterReasoning = ({
 				: undefined
 			: undefined
 
+export const getRooReasoning = ({
+	model,
+	reasoningEffort,
+	settings,
+}: GetModelReasoningOptions): RooReasoningParams | undefined => {
+	// Check if model supports reasoning effort
+	if (!model.supportsReasoningEffort) {
+		return undefined
+	}
+
+	// If enableReasoningEffort is explicitly false, return enabled: false
+	if (settings.enableReasoningEffort === false) {
+		return { enabled: false }
+	}
+
+	// If reasoning effort is provided, return it with enabled: true
+	if (reasoningEffort && reasoningEffort !== "minimal") {
+		return { enabled: true, effort: reasoningEffort }
+	}
+
+	// If reasoningEffort is explicitly undefined (None selected), disable reasoning
+	// This ensures we explicitly tell the backend not to use reasoning
+	if (reasoningEffort === undefined) {
+		return { enabled: false }
+	}
+
+	// Default: no reasoning parameter (reasoning not enabled)
+	return undefined
+}
+
 export const getAnthropicReasoning = ({
 	model,
 	reasoningBudget,

+ 16 - 6
webview-ui/src/components/settings/ApiOptions.tsx

@@ -103,6 +103,7 @@ import { inputEventTransform, noTransform } from "./transforms"
 import { ModelInfoView } from "./ModelInfoView"
 import { ApiErrorMessage } from "./ApiErrorMessage"
 import { ThinkingBudget } from "./ThinkingBudget"
+import { SimpleThinkingBudget } from "./SimpleThinkingBudget"
 import { Verbosity } from "./Verbosity"
 import { DiffSettingsControl } from "./DiffSettingsControl"
 import { TodoListSettingsControl } from "./TodoListSettingsControl"
@@ -747,12 +748,21 @@ const ApiOptions = ({
 				</>
 			)}
 
-			<ThinkingBudget
-				key={`${selectedProvider}-${selectedModelId}`}
-				apiConfiguration={apiConfiguration}
-				setApiConfigurationField={setApiConfigurationField}
-				modelInfo={selectedModelInfo}
-			/>
+			{selectedProvider === "roo" ? (
+				<SimpleThinkingBudget
+					key={`${selectedProvider}-${selectedModelId}`}
+					apiConfiguration={apiConfiguration}
+					setApiConfigurationField={setApiConfigurationField}
+					modelInfo={selectedModelInfo}
+				/>
+			) : (
+				<ThinkingBudget
+					key={`${selectedProvider}-${selectedModelId}`}
+					apiConfiguration={apiConfiguration}
+					setApiConfigurationField={setApiConfigurationField}
+					modelInfo={selectedModelInfo}
+				/>
+			)}
 
 			{/* Gate Verbosity UI by capability flag */}
 			{selectedModelInfo?.supportsVerbosity && (

+ 107 - 0
webview-ui/src/components/settings/SimpleThinkingBudget.tsx

@@ -0,0 +1,107 @@
+import { useEffect } from "react"
+
+import { type ProviderSettings, type ModelInfo, type ReasoningEffort, reasoningEfforts } from "@roo-code/types"
+
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui"
+
+interface SimpleThinkingBudgetProps {
+	apiConfiguration: ProviderSettings
+	setApiConfigurationField: <K extends keyof ProviderSettings>(
+		field: K,
+		value: ProviderSettings[K],
+		isUserAction?: boolean,
+	) => void
+	modelInfo?: ModelInfo
+}
+
+// Extended type to include "none" option
+type ReasoningEffortWithNone = ReasoningEffort | "none"
+
+export const SimpleThinkingBudget = ({
+	apiConfiguration,
+	setApiConfigurationField,
+	modelInfo,
+}: SimpleThinkingBudgetProps) => {
+	const { t } = useAppTranslation()
+
+	// Check model capabilities
+	const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort
+	const isReasoningEffortRequired = !!modelInfo && modelInfo.requiredReasoningEffort
+
+	// Build available reasoning efforts list
+	// Include "none" option unless reasoning effort is required
+	const baseEfforts = [...reasoningEfforts] as ReasoningEffort[]
+	const availableReasoningEfforts: ReadonlyArray<ReasoningEffortWithNone> = isReasoningEffortRequired
+		? baseEfforts
+		: (["none", ...baseEfforts] as ReasoningEffortWithNone[])
+
+	// Default reasoning effort - use model's default if available, otherwise "medium"
+	const modelDefaultReasoningEffort = modelInfo?.reasoningEffort as ReasoningEffort | undefined
+	const defaultReasoningEffort: ReasoningEffortWithNone = isReasoningEffortRequired
+		? modelDefaultReasoningEffort || "medium"
+		: "none"
+
+	// Current reasoning effort - treat undefined/null as "none"
+	const currentReasoningEffort: ReasoningEffortWithNone =
+		(apiConfiguration.reasoningEffort as ReasoningEffort | undefined) || defaultReasoningEffort
+
+	// Set default reasoning effort when model supports it and no value is set
+	useEffect(() => {
+		if (isReasoningEffortSupported && !apiConfiguration.reasoningEffort) {
+			// Only set a default if reasoning is required, otherwise leave as undefined (which maps to "none")
+			if (isReasoningEffortRequired && defaultReasoningEffort !== "none") {
+				setApiConfigurationField("reasoningEffort", defaultReasoningEffort as ReasoningEffort, false)
+			}
+		}
+	}, [
+		isReasoningEffortSupported,
+		isReasoningEffortRequired,
+		apiConfiguration.reasoningEffort,
+		defaultReasoningEffort,
+		setApiConfigurationField,
+	])
+
+	if (!modelInfo || !isReasoningEffortSupported) {
+		return null
+	}
+
+	return (
+		<div className="flex flex-col gap-1" data-testid="simple-reasoning-effort">
+			<div className="flex justify-between items-center">
+				<label className="block font-medium mb-1">{t("settings:providers.reasoningEffort.label")}</label>
+			</div>
+			<Select
+				value={currentReasoningEffort}
+				onValueChange={(value: ReasoningEffortWithNone) => {
+					// If "none" is selected, clear the reasoningEffort field
+					if (value === "none") {
+						setApiConfigurationField("reasoningEffort", undefined)
+					} else {
+						setApiConfigurationField("reasoningEffort", value as ReasoningEffort)
+					}
+				}}>
+				<SelectTrigger className="w-full">
+					<SelectValue
+						placeholder={
+							currentReasoningEffort
+								? currentReasoningEffort === "none"
+									? t("settings:providers.reasoningEffort.none")
+									: t(`settings:providers.reasoningEffort.${currentReasoningEffort}`)
+								: t("settings:common.select")
+						}
+					/>
+				</SelectTrigger>
+				<SelectContent>
+					{availableReasoningEfforts.map((value) => (
+						<SelectItem key={value} value={value}>
+							{value === "none"
+								? t("settings:providers.reasoningEffort.none")
+								: t(`settings:providers.reasoningEffort.${value}`)}
+						</SelectItem>
+					))}
+				</SelectContent>
+			</Select>
+		</div>
+	)
+}

+ 212 - 0
webview-ui/src/components/settings/__tests__/SimpleThinkingBudget.spec.tsx

@@ -0,0 +1,212 @@
+import { describe, it, expect, vi, beforeEach } from "vitest"
+import { render, screen } from "@testing-library/react"
+import { SimpleThinkingBudget } from "../SimpleThinkingBudget"
+import type { ProviderSettings, ModelInfo } from "@roo-code/types"
+
+// Mock the translation hook
+vi.mock("@src/i18n/TranslationContext", () => ({
+	useAppTranslation: () => ({
+		t: (key: string) => {
+			const translations: Record<string, string> = {
+				"settings:providers.reasoningEffort.label": "Model Reasoning Effort",
+				"settings:providers.reasoningEffort.none": "None",
+				"settings:providers.reasoningEffort.low": "Low",
+				"settings:providers.reasoningEffort.medium": "Medium",
+				"settings:providers.reasoningEffort.high": "High",
+				"settings:common.select": "Select",
+			}
+			return translations[key] || key
+		},
+	}),
+}))
+
+// Mock the useSelectedModel hook
+vi.mock("@src/components/ui/hooks/useSelectedModel", () => ({
+	useSelectedModel: () => ({ id: "test-model" }),
+}))
+
+describe("SimpleThinkingBudget", () => {
+	const mockSetApiConfigurationField = vi.fn()
+
+	const baseApiConfiguration: ProviderSettings = {
+		apiProvider: "roo",
+	}
+
+	const modelWithReasoningEffort: ModelInfo = {
+		maxTokens: 8192,
+		contextWindow: 128000,
+		supportsImages: false,
+		supportsPromptCache: true,
+		supportsReasoningEffort: true,
+		inputPrice: 0,
+		outputPrice: 0,
+	}
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+	})
+
+	it("should not render when model does not support reasoning effort", () => {
+		const modelWithoutReasoningEffort: ModelInfo = {
+			...modelWithReasoningEffort,
+			supportsReasoningEffort: false,
+		}
+
+		const { container } = render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithoutReasoningEffort}
+			/>,
+		)
+
+		expect(container.firstChild).toBeNull()
+	})
+
+	it("should not render when modelInfo is undefined", () => {
+		const { container } = render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={undefined}
+			/>,
+		)
+
+		expect(container.firstChild).toBeNull()
+	})
+
+	it("should render with None option when reasoning effort is not required", () => {
+		render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithReasoningEffort}
+			/>,
+		)
+
+		expect(screen.getByTestId("simple-reasoning-effort")).toBeInTheDocument()
+		expect(screen.getByText("Model Reasoning Effort")).toBeInTheDocument()
+	})
+
+	it("should not render None option when reasoning effort is required", () => {
+		const modelWithRequiredReasoningEffort: ModelInfo = {
+			...modelWithReasoningEffort,
+			requiredReasoningEffort: true,
+		}
+
+		render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithRequiredReasoningEffort}
+			/>,
+		)
+
+		expect(screen.getByTestId("simple-reasoning-effort")).toBeInTheDocument()
+	})
+
+	it("should set default reasoning effort when required and no value is set", () => {
+		const modelWithRequiredReasoningEffort: ModelInfo = {
+			...modelWithReasoningEffort,
+			requiredReasoningEffort: true,
+			reasoningEffort: "high",
+		}
+
+		render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithRequiredReasoningEffort}
+			/>,
+		)
+
+		// Should set default reasoning effort
+		expect(mockSetApiConfigurationField).toHaveBeenCalledWith("reasoningEffort", "high", false)
+	})
+
+	it("should not set default reasoning effort when not required", () => {
+		render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithReasoningEffort}
+			/>,
+		)
+
+		// Should not set any default value
+		expect(mockSetApiConfigurationField).not.toHaveBeenCalled()
+	})
+
+	it("should include None option in available efforts when not required", () => {
+		render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithReasoningEffort}
+			/>,
+		)
+
+		// Component should render with the select
+		expect(screen.getByRole("combobox")).toBeInTheDocument()
+	})
+
+	it("should exclude None option when reasoning effort is required", () => {
+		const modelWithRequiredReasoningEffort: ModelInfo = {
+			...modelWithReasoningEffort,
+			requiredReasoningEffort: true,
+		}
+
+		render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithRequiredReasoningEffort}
+			/>,
+		)
+
+		// Component should render with the select
+		expect(screen.getByRole("combobox")).toBeInTheDocument()
+	})
+
+	it("should display current reasoning effort value", () => {
+		render(
+			<SimpleThinkingBudget
+				apiConfiguration={{ ...baseApiConfiguration, reasoningEffort: "low" }}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithReasoningEffort}
+			/>,
+		)
+
+		expect(screen.getByText("Low")).toBeInTheDocument()
+	})
+
+	it("should display None when no reasoning effort is set", () => {
+		render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithReasoningEffort}
+			/>,
+		)
+
+		expect(screen.getByText("None")).toBeInTheDocument()
+	})
+
+	it("should use model default reasoning effort when required and available", () => {
+		const modelWithDefaultEffort: ModelInfo = {
+			...modelWithReasoningEffort,
+			requiredReasoningEffort: true,
+			reasoningEffort: "medium",
+		}
+
+		render(
+			<SimpleThinkingBudget
+				apiConfiguration={baseApiConfiguration}
+				setApiConfigurationField={mockSetApiConfigurationField}
+				modelInfo={modelWithDefaultEffort}
+			/>,
+		)
+
+		expect(mockSetApiConfigurationField).toHaveBeenCalledWith("reasoningEffort", "medium", false)
+	})
+})

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Esforç de raonament del model",
+			"none": "Cap",
 			"minimal": "Mínim (el més ràpid)",
 			"high": "Alt",
 			"medium": "Mitjà",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Modell-Denkaufwand",
+			"none": "Keine",
 			"minimal": "Minimal (schnellste)",
 			"high": "Hoch",
 			"medium": "Mittel",

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

@@ -465,6 +465,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Model Reasoning Effort",
+			"none": "None",
 			"minimal": "Minimal (Fastest)",
 			"low": "Low",
 			"medium": "Medium",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Esfuerzo de razonamiento del modelo",
+			"none": "Ninguno",
 			"minimal": "Mínimo (el más rápido)",
 			"high": "Alto",
 			"medium": "Medio",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Effort de raisonnement du modèle",
+			"none": "Aucun",
 			"minimal": "Minimal (le plus rapide)",
 			"high": "Élevé",
 			"medium": "Moyen",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "मॉडल तर्क प्रयास",
+			"none": "कोई नहीं",
 			"minimal": "न्यूनतम (सबसे तेज़)",
 			"high": "उच्च",
 			"medium": "मध्यम",

+ 1 - 0
webview-ui/src/i18n/locales/id/settings.json

@@ -464,6 +464,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Upaya Reasoning Model",
+			"none": "Tidak Ada",
 			"minimal": "Minimal (Tercepat)",
 			"high": "Tinggi",
 			"medium": "Sedang",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Sforzo di ragionamento del modello",
+			"none": "Nessuno",
 			"minimal": "Minimo (più veloce)",
 			"high": "Alto",
 			"medium": "Medio",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "モデル推論の労力",
+			"none": "なし",
 			"minimal": "最小 (最速)",
 			"high": "高",
 			"medium": "中",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "모델 추론 노력",
+			"none": "없음",
 			"minimal": "최소 (가장 빠름)",
 			"high": "높음",
 			"medium": "중간",

+ 1 - 0
webview-ui/src/i18n/locales/nl/settings.json

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Model redeneervermogen",
+			"none": "Geen",
 			"minimal": "Minimaal (Snelst)",
 			"high": "Hoog",
 			"medium": "Middel",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Wysiłek rozumowania modelu",
+			"none": "Brak",
 			"minimal": "Minimalny (najszybszy)",
 			"high": "Wysoki",
 			"medium": "Średni",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Esforço de raciocínio do modelo",
+			"none": "Nenhum",
 			"minimal": "Mínimo (mais rápido)",
 			"high": "Alto",
 			"medium": "Médio",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Усилия по рассуждению модели",
+			"none": "Нет",
 			"minimal": "Минимальный (самый быстрый)",
 			"high": "Высокие",
 			"medium": "Средние",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Model Akıl Yürütme Çabası",
+			"none": "Yok",
 			"minimal": "Minimal (en hızlı)",
 			"high": "Yüksek",
 			"medium": "Orta",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "Nỗ lực suy luận của mô hình",
+			"none": "Không",
 			"minimal": "Tối thiểu (nhanh nhất)",
 			"high": "Cao",
 			"medium": "Trung bình",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "模型推理强度",
+			"none": "无",
 			"minimal": "最小 (最快)",
 			"high": "高",
 			"medium": "中",

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

@@ -460,6 +460,7 @@
 		},
 		"reasoningEffort": {
 			"label": "模型推理強度",
+			"none": "無",
 			"minimal": "最小 (最快)",
 			"high": "高",
 			"medium": "中",