From 46ea320821a85c0e96f199f13d87c5b04ef59b12 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 21 Apr 2026 15:25:56 +0000 Subject: [PATCH] fix: respect supportsTemperature flag in OpenAI-compatible providers 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 --- .../base-openai-compatible-provider.ts | 5 +- src/api/providers/openai-compatible.ts | 6 +- src/api/providers/openai.ts | 5 +- .../transform/__tests__/model-params.spec.ts | 78 +++++++++++++++++++ src/api/transform/model-params.ts | 14 ++-- .../settings/providers/OpenAICompatible.tsx | 26 +++++++ webview-ui/src/i18n/locales/en/settings.json | 4 + 7 files changed, 125 insertions(+), 13 deletions(-) diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index fc3d769ae2..0ac7c5cb81 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -84,7 +84,10 @@ export abstract class BaseOpenAiCompatibleProvider format: "openai", }) ?? undefined - const temperature = this.options.modelTemperature ?? info.defaultTemperature ?? this.defaultTemperature + const temperature = + info.supportsTemperature === false + ? undefined + : (this.options.modelTemperature ?? info.defaultTemperature ?? this.defaultTemperature) const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model, diff --git a/src/api/providers/openai-compatible.ts b/src/api/providers/openai-compatible.ts index d129e72452..5b701e0cd9 100644 --- a/src/api/providers/openai-compatible.ts +++ b/src/api/providers/openai-compatible.ts @@ -166,11 +166,12 @@ export abstract class OpenAICompatibleHandler extends BaseProvider implements Si const aiSdkTools = convertToolsForAiSdk(openAiTools) as ToolSet | undefined // Build the request options + const supportsTemp = this.config.modelInfo.supportsTemperature !== false const requestOptions: Parameters[0] = { model: languageModel, system: systemPrompt, messages: aiSdkMessages, - temperature: model.temperature ?? this.config.temperature ?? 0, + temperature: supportsTemp ? (model.temperature ?? this.config.temperature ?? 0) : undefined, maxOutputTokens: this.getMaxOutputTokens(), tools: aiSdkTools, toolChoice: this.mapToolChoice(metadata?.tool_choice), @@ -200,11 +201,12 @@ export abstract class OpenAICompatibleHandler extends BaseProvider implements Si async completePrompt(prompt: string): Promise { const languageModel = this.getLanguageModel() + const supportsTemp = this.config.modelInfo.supportsTemperature !== false const { text } = await generateText({ model: languageModel, prompt, maxOutputTokens: this.getMaxOutputTokens(), - temperature: this.config.temperature ?? 0, + temperature: supportsTemp ? (this.config.temperature ?? 0) : undefined, }) return text diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 33b29abcaf..a007f30d0c 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -154,7 +154,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, - temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0), + temperature: + modelInfo.supportsTemperature === false + ? undefined + : (this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0)), messages: convertedMessages, stream: true as const, ...(isGrokXAI ? {} : { stream_options: { include_usage: true } }), diff --git a/src/api/transform/__tests__/model-params.spec.ts b/src/api/transform/__tests__/model-params.spec.ts index a50f1291be..3bb18c5e18 100644 --- a/src/api/transform/__tests__/model-params.spec.ts +++ b/src/api/transform/__tests__/model-params.spec.ts @@ -994,4 +994,82 @@ describe("getModelParams", () => { expect(result.reasoningBudget).toBe(8192) // Default thinking tokens }) }) + describe("supportsTemperature", () => { + it("should set temperature to undefined for openai format when supportsTemperature is false", () => { + const model: ModelInfo = { + ...baseModel, + supportsTemperature: false, + } + + const result = getModelParams({ + ...openaiParams, + settings: { modelTemperature: 0.5 }, + model, + }) + + expect(result.temperature).toBeUndefined() + }) + + it("should keep temperature for openai format when supportsTemperature is true", () => { + const model: ModelInfo = { + ...baseModel, + supportsTemperature: true, + } + + const result = getModelParams({ + ...openaiParams, + settings: { modelTemperature: 0.5 }, + model, + }) + + expect(result.temperature).toBe(0.5) + }) + + it("should keep temperature for openai format when supportsTemperature is undefined", () => { + const result = getModelParams({ + ...openaiParams, + settings: { modelTemperature: 0.5 }, + model: baseModel, + }) + + expect(result.temperature).toBe(0.5) + }) + + it("should set temperature to undefined for openrouter format when supportsTemperature is false", () => { + const model: ModelInfo = { + ...baseModel, + supportsTemperature: false, + } + + const result = getModelParams({ + ...openrouterParams, + settings: { modelTemperature: 0.5 }, + model, + }) + + expect(result.temperature).toBeUndefined() + }) + + it("should still remove temperature for o1 model IDs in openai format", () => { + const result = getModelParams({ + ...openaiParams, + modelId: "o1-preview", + settings: {}, + model: baseModel, + }) + + expect(result.temperature).toBeUndefined() + }) + + it("should still remove temperature for o3-mini model IDs in openai format", () => { + const result = getModelParams({ + ...openaiParams, + modelId: "o3-mini", + settings: {}, + model: baseModel, + }) + + expect(result.temperature).toBeUndefined() + }) + }) }) diff --git a/src/api/transform/model-params.ts b/src/api/transform/model-params.ts index ac04bce37d..a90009351f 100644 --- a/src/api/transform/model-params.ts +++ b/src/api/transform/model-params.ts @@ -153,9 +153,9 @@ export function getModelParams({ reasoning: getAnthropicReasoning({ model, reasoningBudget, reasoningEffort, settings }), } } else if (format === "openai") { - // Special case for o1 and o3-mini, which don't support temperature. - // TODO: Add a `supportsTemperature` field to the model info. - if (modelId.startsWith("o1") || modelId.startsWith("o3-mini")) { + // 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 } @@ -172,12 +172,8 @@ export function getModelParams({ reasoning: getGeminiReasoning({ model, reasoningBudget, reasoningEffort, settings }), } } else { - // Special case for o1-pro, which doesn't support temperature. - // Note that OpenRouter's `supported_parameters` field includes - // `temperature`, which is probably a bug. - // TODO: Add a `supportsTemperature` field to the model info and populate - // it appropriately in the OpenRouter fetcher. - if (modelId === "openai/o1-pro") { + // Omit temperature for models that don't support it. + if (model.supportsTemperature === false || modelId === "openai/o1-pro") { params.temperature = undefined } diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx index 0524932c5f..ad93f77143 100644 --- a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx +++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx @@ -412,6 +412,32 @@ export const OpenAICompatible = ({ +
+
+ { + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + supportsTemperature: checked, + } + })}> + + {t("settings:providers.customModel.temperatureSupport.label")} + + + + + +
+
+ {t("settings:providers.customModel.temperatureSupport.description")} +
+
+