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 AnthropicOutputConfig = { effort: "low" | "medium" | "high" | "max" } export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] } // Valid Gemini thinking levels for effort-based reasoning const GEMINI_THINKING_LEVELS = ["minimal", "low", "medium", "high"] as const export type GeminiThinkingLevel = (typeof GEMINI_THINKING_LEVELS)[number] export function isGeminiThinkingLevel(value: unknown): value is GeminiThinkingLevel { return typeof value === "string" && GEMINI_THINKING_LEVELS.includes(value as GeminiThinkingLevel) } export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"] & { thinkingLevel?: GeminiThinkingLevel } 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 } if (model.requiredReasoningEffort) { // Honor the provided effort if it's valid, otherwise let the model choose. if (reasoningEffort && reasoningEffort !== "disable" && reasoningEffort !== "minimal") { return { enabled: true, effort: reasoningEffort } } else { return { enabled: true } } } // 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 => { // Adaptive thinking: Claude determines dynamically when and how much to use extended thinking. // Supported on claude-sonnet-4-6 and claude-opus-4-6 only. if (settings?.useAdaptiveThinking && model.supportsAdaptiveThinking) { return { type: "adaptive" as any } as any } // Manual mode: fixed budget_tokens return shouldUseReasoningBudget({ model, settings }) ? { type: "enabled", budget_tokens: reasoningBudget! } : undefined } /** * Returns the `output_config` top-level parameter for Anthropic API calls when adaptive thinking * is enabled with an effort level. This is a SEPARATE top-level parameter from `thinking`. * See: https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#adaptive-thinking */ export const getAnthropicOutputConfig = ({ model, settings, }: Pick): AnthropicOutputConfig | undefined => { if (!settings?.useAdaptiveThinking || !model.supportsAdaptiveThinking) { return undefined } const effort = settings.adaptiveThinkingEffort if (!effort) { return undefined } // "max" effort is only supported on claude-opus-4-6; fall back to "high" otherwise if (effort === "max" && !model.supportsAdaptiveThinkingMaxEffort) { return { effort: "high" } } return { effort } } 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 } // Validate that the selected effort is supported by this specific model. // e.g. gemini-3-pro-preview only supports ["low", "high"] — sending // "medium" (carried over from a different model's settings) causes errors. const effortToUse = Array.isArray(model.supportsReasoningEffort) && isGeminiThinkingLevel(selectedEffort) && !model.supportsReasoningEffort.includes(selectedEffort) ? model.reasoningEffort : selectedEffort // Effort-based models on Google GenAI support minimal/low/medium/high levels. if (!effortToUse || !isGeminiThinkingLevel(effortToUse)) { return undefined } return { thinkingLevel: effortToUse, includeThoughts: true } }