diff --git a/.changeset/odd-ligers-press.md b/.changeset/odd-ligers-press.md
new file mode 100644
index 0000000000..c9942c972d
--- /dev/null
+++ b/.changeset/odd-ligers-press.md
@@ -0,0 +1,5 @@
+---
+"roo-cline": minor
+---
+
+Add Reasoning Effort setting for OpenAI Compatible provider
diff --git a/package-lock.json b/package-lock.json
index 3aedb880bf..c66b610cf7 100644
--- a/package-lock.json
+++ b/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",
diff --git a/package.json b/package.json
index 4862683683..a78fec7cb7 100644
--- a/package.json
+++ b/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",
diff --git a/src/api/providers/__tests__/openai.test.ts b/src/api/providers/__tests__/openai.test.ts
index 17da968ae6..493c1e549f 100644
--- a/src/api/providers/__tests__/openai.test.ts
+++ b/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", () => {
diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts
index f2f7da5609..2c552b1702 100644
--- a/src/exports/roo-code.d.ts
+++ b/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?:
diff --git a/src/exports/types.ts b/src/exports/types.ts
index 9582a21279..1072469509 100644
--- a/src/exports/types.ts
+++ b/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?:
diff --git a/src/schemas/index.ts b/src/schemas/index.ts
index 6874783f48..a3ec338131 100644
--- a/src/schemas/index.ts
+++ b/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,
diff --git a/src/utils/__tests__/enhance-prompt.test.ts b/src/utils/__tests__/enhance-prompt.test.ts
index d3cca04c38..adda8860eb 100644
--- a/src/utils/__tests__/enhance-prompt.test.ts
+++ b/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
diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx
index 86e9defc5f..917947ac05 100644
--- a/webview-ui/src/components/settings/ApiOptions.tsx
+++ b/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 = ({
)}
+
+ {
+ setApiConfigurationField("enableReasoningEffort", checked)
+
+ if (!checked) {
+ const { reasoningEffort: _, ...openAiCustomModelInfo } =
+ apiConfiguration.openAiCustomModelInfo || openAiModelInfoSaneDefaults
+
+ setApiConfigurationField("openAiCustomModelInfo", openAiCustomModelInfo)
+ }
+ }}>
+ {t("settings:providers.setReasoningLevel")}
+
+ {!!apiConfiguration.enableReasoningEffort && (
+ {
+ if (field === "reasoningEffort") {
+ const openAiCustomModelInfo =
+ apiConfiguration.openAiCustomModelInfo || openAiModelInfoSaneDefaults
+
+ setApiConfigurationField("openAiCustomModelInfo", {
+ ...openAiCustomModelInfo,
+ reasoningEffort: value as ReasoningEffortType,
+ })
+ }
+ }}
+ />
+ )}
+
{t("settings:providers.customModel.capabilities")}
diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx
index d93b39f229..115c623a27 100644
--- a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx
+++ b/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) => (
-