mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-14 04:08:28 +00:00
When models like Claude Opus 4.7 are proxied through OpenAI-compatible gateways, sending the temperature parameter causes a 400 error because the model has deprecated it. This change: - Uses the existing supportsTemperature ModelInfo field in model-params.ts to omit temperature when supportsTemperature === false - Adds the same guard in OpenAiHandler, BaseOpenAiCompatibleProvider, and OpenAICompatibleHandler - Adds a "Supports Temperature" checkbox in the OpenAI Compatible settings UI so users can toggle it off for models that need it - Adds test coverage for the new behavior Closes #12162
186 lines
6 KiB
TypeScript
186 lines
6 KiB
TypeScript
import {
|
|
type ModelInfo,
|
|
type ProviderSettings,
|
|
type VerbosityLevel,
|
|
type ReasoningEffortExtended,
|
|
ANTHROPIC_DEFAULT_MAX_TOKENS,
|
|
} from "@roo-code/types"
|
|
|
|
import {
|
|
DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS,
|
|
DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS,
|
|
GEMINI_25_PRO_MIN_THINKING_TOKENS,
|
|
shouldUseReasoningBudget,
|
|
shouldUseReasoningEffort,
|
|
getModelMaxOutputTokens,
|
|
} from "../../shared/api"
|
|
|
|
import {
|
|
type AnthropicReasoningParams,
|
|
type OpenAiReasoningParams,
|
|
type GeminiReasoningParams,
|
|
type OpenRouterReasoningParams,
|
|
getAnthropicReasoning,
|
|
getOpenAiReasoning,
|
|
getGeminiReasoning,
|
|
getOpenRouterReasoning,
|
|
} from "./reasoning"
|
|
|
|
type Format = "anthropic" | "openai" | "gemini" | "openrouter"
|
|
|
|
type GetModelParamsOptions<T extends Format> = {
|
|
format: T
|
|
modelId: string
|
|
model: ModelInfo
|
|
settings: ProviderSettings
|
|
defaultTemperature: number
|
|
}
|
|
|
|
type BaseModelParams = {
|
|
maxTokens: number | undefined
|
|
temperature: number | undefined
|
|
reasoningEffort: ReasoningEffortExtended | undefined
|
|
reasoningBudget: number | undefined
|
|
verbosity: VerbosityLevel | undefined
|
|
tools?: boolean
|
|
}
|
|
|
|
type AnthropicModelParams = {
|
|
format: "anthropic"
|
|
reasoning: AnthropicReasoningParams | undefined
|
|
} & BaseModelParams
|
|
|
|
type OpenAiModelParams = {
|
|
format: "openai"
|
|
reasoning: OpenAiReasoningParams | undefined
|
|
} & BaseModelParams
|
|
|
|
type GeminiModelParams = {
|
|
format: "gemini"
|
|
reasoning: GeminiReasoningParams | undefined
|
|
} & BaseModelParams
|
|
|
|
type OpenRouterModelParams = {
|
|
format: "openrouter"
|
|
reasoning: OpenRouterReasoningParams | undefined
|
|
} & BaseModelParams
|
|
|
|
export type ModelParams = AnthropicModelParams | OpenAiModelParams | GeminiModelParams | OpenRouterModelParams
|
|
|
|
// Function overloads for specific return types
|
|
export function getModelParams(options: GetModelParamsOptions<"anthropic">): AnthropicModelParams
|
|
export function getModelParams(options: GetModelParamsOptions<"openai">): OpenAiModelParams
|
|
export function getModelParams(options: GetModelParamsOptions<"gemini">): GeminiModelParams
|
|
export function getModelParams(options: GetModelParamsOptions<"openrouter">): OpenRouterModelParams
|
|
export function getModelParams({
|
|
format,
|
|
modelId,
|
|
model,
|
|
settings,
|
|
defaultTemperature,
|
|
}: GetModelParamsOptions<Format>): ModelParams {
|
|
const {
|
|
modelMaxTokens: customMaxTokens,
|
|
modelMaxThinkingTokens: customMaxThinkingTokens,
|
|
modelTemperature: customTemperature,
|
|
reasoningEffort: customReasoningEffort,
|
|
verbosity: customVerbosity,
|
|
} = settings
|
|
|
|
// Use the centralized logic for computing maxTokens
|
|
const maxTokens = getModelMaxOutputTokens({
|
|
modelId,
|
|
model,
|
|
settings,
|
|
format,
|
|
})
|
|
|
|
let temperature = customTemperature ?? model.defaultTemperature ?? defaultTemperature
|
|
let reasoningBudget: ModelParams["reasoningBudget"] = undefined
|
|
let reasoningEffort: ModelParams["reasoningEffort"] = undefined
|
|
let verbosity: VerbosityLevel | undefined = customVerbosity
|
|
|
|
if (shouldUseReasoningBudget({ model, settings })) {
|
|
// Check if this is a Gemini 2.5 Pro model
|
|
const isGemini25Pro = modelId.includes("gemini-2.5-pro")
|
|
|
|
// If `customMaxThinkingTokens` is not specified use the default.
|
|
// For Gemini 2.5 Pro, default to 128 instead of 8192
|
|
const defaultThinkingTokens = isGemini25Pro
|
|
? GEMINI_25_PRO_MIN_THINKING_TOKENS
|
|
: DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS
|
|
reasoningBudget = customMaxThinkingTokens ?? defaultThinkingTokens
|
|
|
|
// Reasoning cannot exceed 80% of the `maxTokens` value.
|
|
// maxTokens should always be defined for reasoning budget models, but add a guard just in case
|
|
if (maxTokens && reasoningBudget > Math.floor(maxTokens * 0.8)) {
|
|
reasoningBudget = Math.floor(maxTokens * 0.8)
|
|
}
|
|
|
|
// Reasoning cannot be less than minimum tokens.
|
|
// For Gemini 2.5 Pro models, the minimum is 128 tokens
|
|
// For other models, the minimum is 1024 tokens
|
|
const minThinkingTokens = isGemini25Pro ? GEMINI_25_PRO_MIN_THINKING_TOKENS : 1024
|
|
if (reasoningBudget < minThinkingTokens) {
|
|
reasoningBudget = minThinkingTokens
|
|
}
|
|
|
|
// Let's assume that "Hybrid" reasoning models require a temperature of
|
|
// 1.0 since Anthropic does.
|
|
temperature = 1.0
|
|
} else if (shouldUseReasoningEffort({ model, settings })) {
|
|
// "Traditional" reasoning models use the `reasoningEffort` parameter.
|
|
// Only fallback to model default if user hasn't explicitly set a value.
|
|
// If customReasoningEffort is "disable", don't fallback to model default.
|
|
const effort =
|
|
customReasoningEffort !== undefined
|
|
? customReasoningEffort
|
|
: (model.reasoningEffort as ReasoningEffortExtended | "disable" | undefined)
|
|
// Capability and settings checks are handled by shouldUseReasoningEffort.
|
|
// Here we simply propagate the resolved effort into the params, while
|
|
// still treating "disable" as an omission.
|
|
if (effort && effort !== "disable") {
|
|
reasoningEffort = effort as ReasoningEffortExtended
|
|
}
|
|
}
|
|
|
|
const params: BaseModelParams = { maxTokens, temperature, reasoningEffort, reasoningBudget, verbosity }
|
|
|
|
if (format === "anthropic") {
|
|
return {
|
|
format,
|
|
...params,
|
|
reasoning: getAnthropicReasoning({ model, reasoningBudget, reasoningEffort, settings }),
|
|
}
|
|
} else if (format === "openai") {
|
|
// Omit temperature for models that don't support it (e.g. o1, o3-mini,
|
|
// claude-opus-4-7 proxied via OpenAI-compatible gateways).
|
|
if (model.supportsTemperature === false || modelId.startsWith("o1") || modelId.startsWith("o3-mini")) {
|
|
params.temperature = undefined
|
|
}
|
|
|
|
return {
|
|
format,
|
|
...params,
|
|
reasoning: getOpenAiReasoning({ model, reasoningBudget, reasoningEffort, settings }),
|
|
// Whether tools are included is determined by whether the caller provided tool definitions.
|
|
}
|
|
} else if (format === "gemini") {
|
|
return {
|
|
format,
|
|
...params,
|
|
reasoning: getGeminiReasoning({ model, reasoningBudget, reasoningEffort, settings }),
|
|
}
|
|
} else {
|
|
// Omit temperature for models that don't support it.
|
|
if (model.supportsTemperature === false || modelId === "openai/o1-pro") {
|
|
params.temperature = undefined
|
|
}
|
|
|
|
return {
|
|
format,
|
|
...params,
|
|
reasoning: getOpenRouterReasoning({ model, reasoningBudget, reasoningEffort, settings }),
|
|
}
|
|
}
|
|
}
|