Roo-Code/src/api/transform/reasoning.ts
Hannes Rudolph 55e9c880d0
fix: gemini maxOutputTokens and reasoning config (#9375)
* fix: gemini maxOutputTokens and reasoning config

* test: tighten gemini reasoning typings
2025-11-18 19:52:50 -05:00

139 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta"
import OpenAI from "openai"
import type { GenerateContentConfig } from "@google/genai"
import type { ModelInfo, ProviderSettings, ReasoningEffortExtended } from "@roo-code/types"
import { shouldUseReasoningBudget, shouldUseReasoningEffort } from "../../shared/api"
export type OpenRouterReasoningParams = {
effort?: ReasoningEffortExtended
max_tokens?: number
exclude?: boolean
}
export type RooReasoningParams = {
enabled?: boolean
effort?: ReasoningEffortExtended
}
export type AnthropicReasoningParams = BetaThinkingConfigParam
export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] }
export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"] & {
thinkingLevel?: "low" | "high"
}
export type GetModelReasoningOptions = {
model: ModelInfo
reasoningBudget: number | undefined
reasoningEffort: ReasoningEffortExtended | "disable" | undefined
settings: ProviderSettings
}
export const getOpenRouterReasoning = ({
model,
reasoningBudget,
reasoningEffort,
settings,
}: GetModelReasoningOptions): OpenRouterReasoningParams | undefined =>
shouldUseReasoningBudget({ model, settings })
? { max_tokens: reasoningBudget }
: shouldUseReasoningEffort({ model, settings })
? reasoningEffort && reasoningEffort !== "disable"
? { effort: reasoningEffort as ReasoningEffortExtended }
: undefined
: undefined
export const getRooReasoning = ({
model,
reasoningEffort,
settings,
}: GetModelReasoningOptions): RooReasoningParams | undefined => {
// Check if model supports reasoning effort
if (!model.supportsReasoningEffort) return undefined
// Explicit off switch from settings: always send disabled for back-compat and to
// prevent automatic reasoning when the toggle is turned off.
if (settings.enableReasoningEffort === false) {
return { enabled: false }
}
// For Roo models that support reasoning effort, absence of a selection should be
// treated as an explicit "off" signal so that the backend does not auto-enable
// reasoning. This aligns with the default behavior in tests.
if (!reasoningEffort) {
return { enabled: false }
}
// "disable" is a legacy sentinel that means "omit the reasoning field entirely"
// and let the server decide any defaults.
if (reasoningEffort === "disable") {
return undefined
}
// For Roo, "minimal" is treated as "none" for effort-based reasoning we omit
// the reasoning field entirely instead of sending an explicit effort.
if (reasoningEffort === "minimal") {
return undefined
}
// When an effort is provided (e.g. "low" | "medium" | "high" | "none"), enable
// with the selected effort.
return { enabled: true, effort: reasoningEffort as ReasoningEffortExtended }
}
export const getAnthropicReasoning = ({
model,
reasoningBudget,
settings,
}: GetModelReasoningOptions): AnthropicReasoningParams | undefined =>
shouldUseReasoningBudget({ model, settings }) ? { type: "enabled", budget_tokens: reasoningBudget! } : undefined
export const getOpenAiReasoning = ({
model,
reasoningEffort,
settings,
}: GetModelReasoningOptions): OpenAiReasoningParams | undefined => {
if (!shouldUseReasoningEffort({ model, settings })) return undefined
if (reasoningEffort === "disable" || !reasoningEffort) return undefined
// Include "none" | "minimal" | "low" | "medium" | "high" literally
return {
reasoning_effort: reasoningEffort as OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"],
}
}
export const getGeminiReasoning = ({
model,
reasoningBudget,
reasoningEffort,
settings,
}: GetModelReasoningOptions): GeminiReasoningParams | undefined => {
// Budget-based (2.5) models: use thinkingBudget, not thinkingLevel.
if (shouldUseReasoningBudget({ model, settings })) {
return { thinkingBudget: reasoningBudget!, includeThoughts: true }
}
// For effort-based Gemini models, rely directly on the selected effort value.
// We intentionally ignore enableReasoningEffort here so that explicitly chosen
// efforts in the UI (e.g. "High" for gemini-3-pro-preview) always translate
// into a thinkingConfig, regardless of legacy boolean flags.
const selectedEffort = (settings.reasoningEffort ?? model.reasoningEffort) as
| ReasoningEffortExtended
| "disable"
| undefined
// Respect “off” / unset semantics from the effort selector itself.
if (!selectedEffort || selectedEffort === "disable") {
return undefined
}
// Effort-based models on Google GenAI currently support only explicit low/high levels.
if (selectedEffort !== "low" && selectedEffort !== "high") {
return undefined
}
return { thinkingLevel: selectedEffort, includeThoughts: true }
}