diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts index 73b542dbc7..ccbbb870df 100644 --- a/src/api/providers/__tests__/openai.spec.ts +++ b/src/api/providers/__tests__/openai.spec.ts @@ -4,7 +4,7 @@ import { OpenAiHandler, getOpenAiModels } from "../openai" import { ApiHandlerOptions } from "../../../shared/api" import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" -import { openAiModelInfoSaneDefaults } from "@roo-code/types" +import { openAiModelInfoSaneDefaults, DEEP_SEEK_DEFAULT_TEMPERATURE } from "@roo-code/types" import { Package } from "../../../shared/package" import axios from "axios" @@ -391,6 +391,53 @@ describe("OpenAiHandler", () => { expect(callArgs.reasoning_effort).toBeUndefined() }) + it("should omit temperature when the model sets supportsTemperature to false", async () => { + const noTempOptions: ApiHandlerOptions = { + ...mockOptions, + openAiCustomModelInfo: { + contextWindow: 128_000, + supportsPromptCache: false, + supportsTemperature: false, + }, + } + const noTempHandler = new OpenAiHandler(noTempOptions) + const stream = noTempHandler.createMessage(systemPrompt, messages) + for await (const _chunk of stream) { + } + expect(mockCreate).toHaveBeenCalled() + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs).not.toHaveProperty("temperature") + }) + + it("should include temperature by default when supportsTemperature is not set", async () => { + const stream = handler.createMessage(systemPrompt, messages) + for await (const _chunk of stream) { + } + expect(mockCreate).toHaveBeenCalled() + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs).toHaveProperty("temperature") + }) + + it("should use the configured modelTemperature when supportsTemperature is not false", async () => { + const customTempHandler = new OpenAiHandler({ ...mockOptions, modelTemperature: 0.5 }) + const stream = customTempHandler.createMessage(systemPrompt, messages) + for await (const _chunk of stream) { + } + expect(mockCreate).toHaveBeenCalled() + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs.temperature).toBe(0.5) + }) + + it("should default to DEEP_SEEK_DEFAULT_TEMPERATURE for deepseek-reasoner models", async () => { + const deepseekHandler = new OpenAiHandler({ ...mockOptions, openAiModelId: "deepseek-reasoner" }) + const stream = deepseekHandler.createMessage(systemPrompt, messages) + for await (const _chunk of stream) { + } + expect(mockCreate).toHaveBeenCalled() + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs.temperature).toBe(DEEP_SEEK_DEFAULT_TEMPERATURE) + }) + it("should include max_tokens when includeMaxTokens is true", async () => { const optionsWithMaxTokens: ApiHandlerOptions = { ...mockOptions, diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 9eafca9636..7ea33196f9 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -154,7 +154,13 @@ 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), + // Some OpenAI-Compatible models (e.g. claude-opus-4-7) reject `temperature` as + // deprecated/unsupported. Honor the model's `supportsTemperature` flag and omit it + // when explicitly set to false (undefined still sends temperature, preserving behavior). + ...(modelInfo.supportsTemperature !== false && { + temperature: + this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0), + }), messages: convertedMessages, stream: true as const, ...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),