Add minimal reasoning support to OpenRouter (#6998)

Co-authored-by: Roo Code <roomote@roocode.com>
Co-authored-by: daniel-lxs <ricciodaniel98@gmail.com>
This commit is contained in:
Matt Rubens 2025-08-12 11:46:31 -04:00 committed by GitHub
parent 01e417e0e5
commit cee7c9894c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 89 additions and 20 deletions

View file

@ -1,6 +1,6 @@
// npx vitest run src/api/transform/__tests__/reasoning.spec.ts
import type { ModelInfo, ProviderSettings } from "@roo-code/types"
import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@roo-code/types"
import {
getOpenRouterReasoning,
@ -154,24 +154,81 @@ describe("reasoning.ts", () => {
const result = getOpenRouterReasoning(optionsWithoutEffort)
expect(result).toEqual({ effort: undefined })
// When reasoningEffort is undefined, the function should return undefined
expect(result).toBeUndefined()
})
it("should handle all reasoning effort values", () => {
const efforts: Array<"low" | "medium" | "high"> = ["low", "medium", "high"]
it("should handle all reasoning effort values including minimal", () => {
const efforts: Array<ReasoningEffortWithMinimal> = ["minimal", "low", "medium", "high"]
efforts.forEach((effort) => {
const modelWithEffort: ModelInfo = {
...baseModel,
supportsReasoningEffort: true,
}
const settingsWithEffort: ProviderSettings = {
reasoningEffort: effort,
}
const options = { ...baseOptions, model: modelWithEffort, reasoningEffort: effort }
const options = {
...baseOptions,
model: modelWithEffort,
settings: settingsWithEffort,
reasoningEffort: effort,
}
const result = getOpenRouterReasoning(options)
// All effort values including "minimal" should be passed through
expect(result).toEqual({ effort })
})
})
it("should handle minimal reasoning effort specifically", () => {
const modelWithSupported: ModelInfo = {
...baseModel,
supportsReasoningEffort: true,
}
const settingsWithEffort: ProviderSettings = {
reasoningEffort: "minimal",
}
const options = {
...baseOptions,
model: modelWithSupported,
settings: settingsWithEffort,
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
}
const result = getOpenRouterReasoning(options)
// "minimal" should be passed through to OpenRouter
expect(result).toEqual({ effort: "minimal" })
})
it("should handle minimal reasoning effort from settings", () => {
const modelWithSupported: ModelInfo = {
...baseModel,
supportsReasoningEffort: true,
}
const settingsWithMinimal: ProviderSettings = {
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
}
const options = {
...baseOptions,
model: modelWithSupported,
settings: settingsWithMinimal,
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
}
const result = getOpenRouterReasoning(options)
// "minimal" should be passed through to OpenRouter
expect(result).toEqual({ effort: "minimal" })
})
it("should handle zero reasoningBudget", () => {
const modelWithRequired: ModelInfo = {
...baseModel,

View file

@ -6,10 +6,8 @@ import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@r
import { shouldUseReasoningBudget, shouldUseReasoningEffort } from "../../shared/api"
type ReasoningEffort = "low" | "medium" | "high"
export type OpenRouterReasoningParams = {
effort?: ReasoningEffort
effort?: ReasoningEffortWithMinimal
max_tokens?: number
exclude?: boolean
}
@ -36,7 +34,7 @@ export const getOpenRouterReasoning = ({
shouldUseReasoningBudget({ model, settings })
? { max_tokens: reasoningBudget }
: shouldUseReasoningEffort({ model, settings })
? reasoningEffort !== "minimal"
? reasoningEffort
? { effort: reasoningEffort }
: undefined
: undefined

View file

@ -24,6 +24,17 @@ interface ThinkingBudgetProps {
modelInfo?: ModelInfo
}
// Helper function to determine if minimal option should be shown
const shouldShowMinimalOption = (
provider: string | undefined,
modelId: string | undefined,
supportsEffort: boolean | undefined,
): boolean => {
const isGpt5Model = provider === "openai-native" && modelId?.startsWith("gpt-5")
const isOpenRouterWithEffort = provider === "openrouter" && supportsEffort === true
return !!(isGpt5Model || isOpenRouterWithEffort)
}
export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, modelInfo }: ThinkingBudgetProps) => {
const { t } = useAppTranslation()
const { id: selectedModelId } = useSelectedModel(apiConfiguration)
@ -32,14 +43,21 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
const isGemini25Pro = selectedModelId && selectedModelId.includes("gemini-2.5-pro")
const minThinkingTokens = isGemini25Pro ? GEMINI_25_PRO_MIN_THINKING_TOKENS : 1024
// Check if this is a GPT-5 model to show "minimal" option
// Only show minimal for OpenAI Native provider GPT-5 models
const isOpenAiNativeProvider = apiConfiguration.apiProvider === "openai-native"
const isGpt5Model = isOpenAiNativeProvider && selectedModelId && selectedModelId.startsWith("gpt-5")
// Add "minimal" option for GPT-5 models
// Spread to convert readonly tuple into a mutable array, then expose as readonly for safety
// Check model capabilities
const isReasoningBudgetSupported = !!modelInfo && modelInfo.supportsReasoningBudget
const isReasoningBudgetRequired = !!modelInfo && modelInfo.requiredReasoningBudget
const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort
// Determine if minimal option should be shown
const showMinimalOption = shouldShowMinimalOption(
apiConfiguration.apiProvider,
selectedModelId,
isReasoningEffortSupported,
)
// Build available reasoning efforts list
const baseEfforts = [...reasoningEfforts] as ReasoningEffortWithMinimal[]
const availableReasoningEfforts: ReadonlyArray<ReasoningEffortWithMinimal> = isGpt5Model
const availableReasoningEfforts: ReadonlyArray<ReasoningEffortWithMinimal> = showMinimalOption
? (["minimal", ...baseEfforts] as ReasoningEffortWithMinimal[])
: baseEfforts
@ -50,10 +68,6 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
const currentReasoningEffort: ReasoningEffortWithMinimal =
(apiConfiguration.reasoningEffort as ReasoningEffortWithMinimal | undefined) || defaultReasoningEffort
const isReasoningBudgetSupported = !!modelInfo && modelInfo.supportsReasoningBudget
const isReasoningBudgetRequired = !!modelInfo && modelInfo.requiredReasoningBudget
const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort
// Set default reasoning effort when model supports it and no value is set
useEffect(() => {
if (isReasoningEffortSupported && !apiConfiguration.reasoningEffort && defaultReasoningEffort) {