Skip to content

Commit dacbd6c

Browse files
committed
Specify reasoning effort for OpenRouter reasoning models
1 parent 255a158 commit dacbd6c

File tree

8 files changed

+80
-9
lines changed

8 files changed

+80
-9
lines changed

src/api/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,25 @@ export function getModelParams({
8888
model,
8989
defaultMaxTokens,
9090
defaultTemperature = 0,
91+
defaultReasoningEffort,
9192
}: {
9293
options: ApiHandlerOptions
9394
model: ModelInfo
9495
defaultMaxTokens?: number
9596
defaultTemperature?: number
97+
defaultReasoningEffort?: "low" | "medium" | "high"
9698
}) {
9799
const {
98100
modelMaxTokens: customMaxTokens,
99101
modelMaxThinkingTokens: customMaxThinkingTokens,
100102
modelTemperature: customTemperature,
103+
reasoningEffort: customReasoningEffort,
101104
} = options
102105

103106
let maxTokens = model.maxTokens ?? defaultMaxTokens
104107
let thinking: BetaThinkingConfigParam | undefined = undefined
105108
let temperature = customTemperature ?? defaultTemperature
109+
const reasoningEffort = customReasoningEffort ?? defaultReasoningEffort
106110

107111
if (model.thinking) {
108112
// Only honor `customMaxTokens` for thinking models.
@@ -118,5 +122,5 @@ export function getModelParams({
118122
temperature = 1.0
119123
}
120124

121-
return { maxTokens, thinking, temperature }
125+
return { maxTokens, thinking, temperature, reasoningEffort }
122126
}

src/api/providers/openrouter.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { Anthropic } from "@anthropic-ai/sdk"
22
import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta"
3-
import axios, { AxiosRequestConfig } from "axios"
3+
import axios from "axios"
44
import OpenAI from "openai"
5-
import delay from "delay"
65

76
import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../shared/api"
87
import { parseApiPrice } from "../../utils/cost"
@@ -22,6 +21,12 @@ type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & {
2221
transforms?: string[]
2322
include_reasoning?: boolean
2423
thinking?: BetaThinkingConfigParam
24+
// https://openrouter.ai/docs/use-cases/reasoning-tokens
25+
reasoning?: {
26+
effort?: "high" | "medium" | "low"
27+
max_tokens?: number
28+
exclude?: boolean
29+
}
2530
}
2631

2732
export class OpenRouterHandler extends BaseProvider implements SingleCompletionHandler {
@@ -42,7 +47,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
4247
systemPrompt: string,
4348
messages: Anthropic.Messages.MessageParam[],
4449
): AsyncGenerator<ApiStreamChunk> {
45-
let { id: modelId, maxTokens, thinking, temperature, topP } = this.getModel()
50+
let { id: modelId, maxTokens, thinking, temperature, topP, reasoningEffort } = this.getModel()
4651

4752
// Convert Anthropic messages to OpenAI format.
4853
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
@@ -70,13 +75,16 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
7075
},
7176
],
7277
}
78+
7379
// Add cache_control to the last two user messages
7480
// (note: this works because we only ever add one user message at a time, but if we added multiple we'd need to mark the user message before the last assistant message)
7581
const lastTwoUserMessages = openAiMessages.filter((msg) => msg.role === "user").slice(-2)
82+
7683
lastTwoUserMessages.forEach((msg) => {
7784
if (typeof msg.content === "string") {
7885
msg.content = [{ type: "text", text: msg.content }]
7986
}
87+
8088
if (Array.isArray(msg.content)) {
8189
// NOTE: this is fine since env details will always be added at the end. but if it weren't there, and the user added a image_url type message, it would pop a text part before it and then move it after to the end.
8290
let lastTextPart = msg.content.filter((part) => part.type === "text").pop()
@@ -113,6 +121,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
113121
}),
114122
// This way, the transforms field will only be included in the parameters when openRouterUseMiddleOutTransform is true.
115123
...((this.options.openRouterUseMiddleOutTransform ?? true) && { transforms: ["middle-out"] }),
124+
...(reasoningEffort && { reasoning: { effort: reasoningEffort } }),
116125
}
117126

118127
const stream = await this.client.chat.completions.create(completionParams)

src/exports/roo-code.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,11 @@ type ProviderSettings = {
175175
cachableFields?: string[] | undefined
176176
} | null)
177177
| undefined
178-
modelTemperature?: (number | null) | undefined
179178
modelMaxTokens?: number | undefined
180179
modelMaxThinkingTokens?: number | undefined
181180
includeMaxTokens?: boolean | undefined
181+
modelTemperature?: (number | null) | undefined
182+
reasoningEffort?: ("low" | "medium" | "high") | undefined
182183
rateLimitSeconds?: number | undefined
183184
fakeAi?: unknown | undefined
184185
}

src/exports/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,11 @@ type ProviderSettings = {
176176
cachableFields?: string[] | undefined
177177
} | null)
178178
| undefined
179-
modelTemperature?: (number | null) | undefined
180179
modelMaxTokens?: number | undefined
181180
modelMaxThinkingTokens?: number | undefined
182181
includeMaxTokens?: boolean | undefined
182+
modelTemperature?: (number | null) | undefined
183+
reasoningEffort?: ("low" | "medium" | "high") | undefined
183184
rateLimitSeconds?: number | undefined
184185
fakeAi?: unknown | undefined
185186
}

src/schemas/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,11 +383,12 @@ export const providerSettingsSchema = z.object({
383383
requestyModelId: z.string().optional(),
384384
requestyModelInfo: modelInfoSchema.nullish(),
385385
// Claude 3.7 Sonnet Thinking
386-
modelTemperature: z.number().nullish(),
387386
modelMaxTokens: z.number().optional(),
388387
modelMaxThinkingTokens: z.number().optional(),
389388
// Generic
390389
includeMaxTokens: z.boolean().optional(),
390+
modelTemperature: z.number().nullish(),
391+
reasoningEffort: z.enum(["low", "medium", "high"]).optional(),
391392
rateLimitSeconds: z.number().optional(),
392393
// Fake AI
393394
fakeAi: z.unknown().optional(),
@@ -470,11 +471,12 @@ const providerSettingsRecord: ProviderSettingsRecord = {
470471
requestyModelId: undefined,
471472
requestyModelInfo: undefined,
472473
// Claude 3.7 Sonnet Thinking
473-
modelTemperature: undefined,
474474
modelMaxTokens: undefined,
475475
modelMaxThinkingTokens: undefined,
476476
// Generic
477477
includeMaxTokens: undefined,
478+
modelTemperature: undefined,
479+
reasoningEffort: undefined,
478480
rateLimitSeconds: undefined,
479481
// Fake AI
480482
fakeAi: undefined,

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
OPENROUTER_DEFAULT_PROVIDER_NAME,
4747
} from "@/components/ui/hooks/useOpenRouterModelProviders"
4848
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator, Button } from "@/components/ui"
49-
import { MODELS_BY_PROVIDER, PROVIDERS, VERTEX_REGIONS } from "./constants"
49+
import { MODELS_BY_PROVIDER, PROVIDERS, VERTEX_REGIONS, REASONING_MODELS } from "./constants"
5050
import { AWS_REGIONS } from "../../../../src/shared/aws_regions"
5151
import { VSCodeButtonLink } from "../common/VSCodeButtonLink"
5252
import { ModelInfoView } from "./ModelInfoView"
@@ -58,6 +58,7 @@ import { ThinkingBudget } from "./ThinkingBudget"
5858
import { R1FormatSetting } from "./R1FormatSetting"
5959
import { OpenRouterBalanceDisplay } from "./OpenRouterBalanceDisplay"
6060
import { RequestyBalanceDisplay } from "./RequestyBalanceDisplay"
61+
import { ReasoningEffort } from "./ReasoningEffort"
6162

6263
interface ApiOptionsProps {
6364
uriScheme: string | undefined
@@ -1519,6 +1520,10 @@ const ApiOptions = ({
15191520
</div>
15201521
)}
15211522

1523+
{selectedProvider === "openrouter" && REASONING_MODELS.has(selectedModelId) && (
1524+
<ReasoningEffort setApiConfigurationField={setApiConfigurationField} modelInfo={selectedModelInfo} />
1525+
)}
1526+
15221527
{selectedProvider === "glama" && (
15231528
<ModelPicker
15241529
apiConfiguration={apiConfiguration}
@@ -1646,12 +1651,14 @@ const ApiOptions = ({
16461651
})()}
16471652
</>
16481653
)}
1654+
16491655
<ModelInfoView
16501656
selectedModelId={selectedModelId}
16511657
modelInfo={selectedModelInfo}
16521658
isDescriptionExpanded={isDescriptionExpanded}
16531659
setIsDescriptionExpanded={setIsDescriptionExpanded}
16541660
/>
1661+
16551662
<ThinkingBudget
16561663
key={`${selectedProvider}-${selectedModelId}`}
16571664
apiConfiguration={apiConfiguration}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useAppTranslation } from "@/i18n/TranslationContext"
2+
3+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator } from "@/components/ui"
4+
5+
import { ApiConfiguration, ModelInfo } from "../../../../src/shared/api"
6+
7+
export const EFFORTS = ["high", "medium", "low"] as const
8+
9+
interface ReasoningEffortProps {
10+
setApiConfigurationField: <K extends keyof ApiConfiguration>(field: K, value: ApiConfiguration[K]) => void
11+
modelInfo: ModelInfo
12+
}
13+
14+
export const ReasoningEffort = ({ setApiConfigurationField, modelInfo }: ReasoningEffortProps) => {
15+
const { t } = useAppTranslation()
16+
17+
return (
18+
<div className="flex flex-col gap-1">
19+
<div className="flex justify-between items-center">
20+
<label className="block font-medium mb-1">Model Reasoning Effort</label>
21+
</div>
22+
<Select
23+
value={modelInfo.reasoningEffort}
24+
onValueChange={(value) =>
25+
setApiConfigurationField("openRouterModelInfo", {
26+
...modelInfo,
27+
reasoningEffort: value as "high" | "medium" | "low",
28+
})
29+
}>
30+
<SelectTrigger className="w-full">
31+
<SelectValue placeholder={t("settings:common.select")} />
32+
</SelectTrigger>
33+
<SelectContent>
34+
<SelectItem value="openrouter">OpenRouter</SelectItem>
35+
<SelectSeparator />
36+
{EFFORTS.map((value) => (
37+
<SelectItem key={value} value={value}>
38+
{value}
39+
</SelectItem>
40+
))}
41+
</SelectContent>
42+
</Select>
43+
</div>
44+
)
45+
}

webview-ui/src/components/settings/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@ export const VERTEX_REGIONS = [
4646
{ value: "europe-west4", label: "europe-west4" },
4747
{ value: "asia-southeast1", label: "asia-southeast1" },
4848
]
49+
50+
export const REASONING_MODELS = new Set(["x-ai/grok-3-mini-beta"])

0 commit comments

Comments
 (0)