Skip to content

Commit cee7c98

Browse files
mrubensroomote-agentdaniel-lxs
authored
Add minimal reasoning support to OpenRouter (#6998)
Co-authored-by: Roo Code <[email protected]> Co-authored-by: daniel-lxs <[email protected]>
1 parent 01e417e commit cee7c98

File tree

3 files changed

+89
-20
lines changed

3 files changed

+89
-20
lines changed

src/api/transform/__tests__/reasoning.spec.ts

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// npx vitest run src/api/transform/__tests__/reasoning.spec.ts
22

3-
import type { ModelInfo, ProviderSettings } from "@roo-code/types"
3+
import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@roo-code/types"
44

55
import {
66
getOpenRouterReasoning,
@@ -154,24 +154,81 @@ describe("reasoning.ts", () => {
154154

155155
const result = getOpenRouterReasoning(optionsWithoutEffort)
156156

157-
expect(result).toEqual({ effort: undefined })
157+
// When reasoningEffort is undefined, the function should return undefined
158+
expect(result).toBeUndefined()
158159
})
159160

160-
it("should handle all reasoning effort values", () => {
161-
const efforts: Array<"low" | "medium" | "high"> = ["low", "medium", "high"]
161+
it("should handle all reasoning effort values including minimal", () => {
162+
const efforts: Array<ReasoningEffortWithMinimal> = ["minimal", "low", "medium", "high"]
162163

163164
efforts.forEach((effort) => {
164165
const modelWithEffort: ModelInfo = {
165166
...baseModel,
167+
supportsReasoningEffort: true,
168+
}
169+
170+
const settingsWithEffort: ProviderSettings = {
166171
reasoningEffort: effort,
167172
}
168173

169-
const options = { ...baseOptions, model: modelWithEffort, reasoningEffort: effort }
174+
const options = {
175+
...baseOptions,
176+
model: modelWithEffort,
177+
settings: settingsWithEffort,
178+
reasoningEffort: effort,
179+
}
170180
const result = getOpenRouterReasoning(options)
181+
// All effort values including "minimal" should be passed through
171182
expect(result).toEqual({ effort })
172183
})
173184
})
174185

186+
it("should handle minimal reasoning effort specifically", () => {
187+
const modelWithSupported: ModelInfo = {
188+
...baseModel,
189+
supportsReasoningEffort: true,
190+
}
191+
192+
const settingsWithEffort: ProviderSettings = {
193+
reasoningEffort: "minimal",
194+
}
195+
196+
const options = {
197+
...baseOptions,
198+
model: modelWithSupported,
199+
settings: settingsWithEffort,
200+
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
201+
}
202+
203+
const result = getOpenRouterReasoning(options)
204+
205+
// "minimal" should be passed through to OpenRouter
206+
expect(result).toEqual({ effort: "minimal" })
207+
})
208+
209+
it("should handle minimal reasoning effort from settings", () => {
210+
const modelWithSupported: ModelInfo = {
211+
...baseModel,
212+
supportsReasoningEffort: true,
213+
}
214+
215+
const settingsWithMinimal: ProviderSettings = {
216+
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
217+
}
218+
219+
const options = {
220+
...baseOptions,
221+
model: modelWithSupported,
222+
settings: settingsWithMinimal,
223+
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
224+
}
225+
226+
const result = getOpenRouterReasoning(options)
227+
228+
// "minimal" should be passed through to OpenRouter
229+
expect(result).toEqual({ effort: "minimal" })
230+
})
231+
175232
it("should handle zero reasoningBudget", () => {
176233
const modelWithRequired: ModelInfo = {
177234
...baseModel,

src/api/transform/reasoning.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@r
66

77
import { shouldUseReasoningBudget, shouldUseReasoningEffort } from "../../shared/api"
88

9-
type ReasoningEffort = "low" | "medium" | "high"
10-
119
export type OpenRouterReasoningParams = {
12-
effort?: ReasoningEffort
10+
effort?: ReasoningEffortWithMinimal
1311
max_tokens?: number
1412
exclude?: boolean
1513
}
@@ -36,7 +34,7 @@ export const getOpenRouterReasoning = ({
3634
shouldUseReasoningBudget({ model, settings })
3735
? { max_tokens: reasoningBudget }
3836
: shouldUseReasoningEffort({ model, settings })
39-
? reasoningEffort !== "minimal"
37+
? reasoningEffort
4038
? { effort: reasoningEffort }
4139
: undefined
4240
: undefined

webview-ui/src/components/settings/ThinkingBudget.tsx

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ interface ThinkingBudgetProps {
2424
modelInfo?: ModelInfo
2525
}
2626

27+
// Helper function to determine if minimal option should be shown
28+
const shouldShowMinimalOption = (
29+
provider: string | undefined,
30+
modelId: string | undefined,
31+
supportsEffort: boolean | undefined,
32+
): boolean => {
33+
const isGpt5Model = provider === "openai-native" && modelId?.startsWith("gpt-5")
34+
const isOpenRouterWithEffort = provider === "openrouter" && supportsEffort === true
35+
return !!(isGpt5Model || isOpenRouterWithEffort)
36+
}
37+
2738
export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, modelInfo }: ThinkingBudgetProps) => {
2839
const { t } = useAppTranslation()
2940
const { id: selectedModelId } = useSelectedModel(apiConfiguration)
@@ -32,14 +43,21 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
3243
const isGemini25Pro = selectedModelId && selectedModelId.includes("gemini-2.5-pro")
3344
const minThinkingTokens = isGemini25Pro ? GEMINI_25_PRO_MIN_THINKING_TOKENS : 1024
3445

35-
// Check if this is a GPT-5 model to show "minimal" option
36-
// Only show minimal for OpenAI Native provider GPT-5 models
37-
const isOpenAiNativeProvider = apiConfiguration.apiProvider === "openai-native"
38-
const isGpt5Model = isOpenAiNativeProvider && selectedModelId && selectedModelId.startsWith("gpt-5")
39-
// Add "minimal" option for GPT-5 models
40-
// Spread to convert readonly tuple into a mutable array, then expose as readonly for safety
46+
// Check model capabilities
47+
const isReasoningBudgetSupported = !!modelInfo && modelInfo.supportsReasoningBudget
48+
const isReasoningBudgetRequired = !!modelInfo && modelInfo.requiredReasoningBudget
49+
const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort
50+
51+
// Determine if minimal option should be shown
52+
const showMinimalOption = shouldShowMinimalOption(
53+
apiConfiguration.apiProvider,
54+
selectedModelId,
55+
isReasoningEffortSupported,
56+
)
57+
58+
// Build available reasoning efforts list
4159
const baseEfforts = [...reasoningEfforts] as ReasoningEffortWithMinimal[]
42-
const availableReasoningEfforts: ReadonlyArray<ReasoningEffortWithMinimal> = isGpt5Model
60+
const availableReasoningEfforts: ReadonlyArray<ReasoningEffortWithMinimal> = showMinimalOption
4361
? (["minimal", ...baseEfforts] as ReasoningEffortWithMinimal[])
4462
: baseEfforts
4563

@@ -50,10 +68,6 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
5068
const currentReasoningEffort: ReasoningEffortWithMinimal =
5169
(apiConfiguration.reasoningEffort as ReasoningEffortWithMinimal | undefined) || defaultReasoningEffort
5270

53-
const isReasoningBudgetSupported = !!modelInfo && modelInfo.supportsReasoningBudget
54-
const isReasoningBudgetRequired = !!modelInfo && modelInfo.requiredReasoningBudget
55-
const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort
56-
5771
// Set default reasoning effort when model supports it and no value is set
5872
useEffect(() => {
5973
if (isReasoningEffortSupported && !apiConfiguration.reasoningEffort && defaultReasoningEffort) {

0 commit comments

Comments
 (0)