diff --git a/packages/types/src/model.ts b/packages/types/src/model.ts index 49a13651e26..d2f88c0d8ae 100644 --- a/packages/types/src/model.ts +++ b/packages/types/src/model.ts @@ -18,6 +18,22 @@ export const reasoningEffortWithMinimalSchema = z.union([reasoningEffortsSchema, export type ReasoningEffortWithMinimal = z.infer +/** + * Extended Reasoning Effort (includes "none" and "minimal") + * Note: "disable" is a UI/control value, not a value sent as effort + */ +export const reasoningEffortsExtended = ["none", "minimal", "low", "medium", "high"] as const + +export const reasoningEffortExtendedSchema = z.enum(reasoningEffortsExtended) + +export type ReasoningEffortExtended = z.infer + +/** + * Reasoning Effort user setting (includes "disable") + */ +export const reasoningEffortSettingValues = ["disable", "none", "minimal", "low", "medium", "high"] as const +export const reasoningEffortSettingSchema = z.enum(reasoningEffortSettingValues) + /** * Verbosity */ @@ -67,7 +83,9 @@ export const modelInfoSchema = z.object({ supportsTemperature: z.boolean().optional(), defaultTemperature: z.number().optional(), requiredReasoningBudget: z.boolean().optional(), - supportsReasoningEffort: z.boolean().optional(), + supportsReasoningEffort: z + .union([z.boolean(), z.array(z.enum(["disable", "none", "minimal", "low", "medium", "high"]))]) + .optional(), requiredReasoningEffort: z.boolean().optional(), preserveReasoning: z.boolean().optional(), supportedParameters: z.array(modelParametersSchema).optional(), @@ -76,7 +94,8 @@ export const modelInfoSchema = z.object({ cacheWritesPrice: z.number().optional(), cacheReadsPrice: z.number().optional(), description: z.string().optional(), - reasoningEffort: reasoningEffortsSchema.optional(), + // Default effort value for models that support reasoning effort + reasoningEffort: reasoningEffortExtendedSchema.optional(), minTokensPerCachePoint: z.number().optional(), maxCachePoints: z.number().optional(), cachableFields: z.array(z.string()).optional(), diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 23e0a548d11..e2bb861256a 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -1,6 +1,6 @@ import { z } from "zod" -import { modelInfoSchema, reasoningEffortWithMinimalSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js" +import { modelInfoSchema, reasoningEffortSettingSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js" import { codebaseIndexProviderSchema } from "./codebase-index.js" import { anthropicModels, @@ -176,7 +176,7 @@ const baseProviderSettingsSchema = z.object({ // Model reasoning. enableReasoningEffort: z.boolean().optional(), - reasoningEffort: reasoningEffortWithMinimalSchema.optional(), + reasoningEffort: reasoningEffortSettingSchema.optional(), modelMaxTokens: z.number().optional(), modelMaxThinkingTokens: z.number().optional(), diff --git a/packages/types/src/providers/openai.ts b/packages/types/src/providers/openai.ts index 89bc2e63ab4..77cfaccaae7 100644 --- a/packages/types/src/providers/openai.ts +++ b/packages/types/src/providers/openai.ts @@ -3,85 +3,128 @@ import type { ModelInfo } from "../model.js" // https://openai.com/api/pricing/ export type OpenAiNativeModelId = keyof typeof openAiNativeModels -export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5-2025-08-07" +export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5.1" export const openAiNativeModels = { - "gpt-5-chat-latest": { + "gpt-5.1": { maxTokens: 128000, contextWindow: 400000, supportsImages: true, supportsPromptCache: true, - supportsReasoningEffort: false, + supportsReasoningEffort: ["none", "low", "medium", "high"], + reasoningEffort: "medium", inputPrice: 1.25, outputPrice: 10.0, - cacheReadsPrice: 0.13, - description: "GPT-5 Chat Latest: Optimized for conversational AI and non-reasoning tasks", + cacheReadsPrice: 0.125, + supportsVerbosity: true, + supportsTemperature: false, + tiers: [ + { name: "flex", contextWindow: 400000, inputPrice: 0.625, outputPrice: 5.0, cacheReadsPrice: 0.0625 }, + { name: "priority", contextWindow: 400000, inputPrice: 2.5, outputPrice: 20.0, cacheReadsPrice: 0.25 }, + ], + description: "GPT-5.1: The best model for coding and agentic tasks across domains", }, - "gpt-5-2025-08-07": { + "gpt-5.1-codex": { maxTokens: 128000, contextWindow: 400000, supportsImages: true, supportsPromptCache: true, - supportsReasoningEffort: true, + supportsReasoningEffort: ["low", "medium", "high"], reasoningEffort: "medium", inputPrice: 1.25, outputPrice: 10.0, - cacheReadsPrice: 0.13, - description: "GPT-5: The best model for coding and agentic tasks across domains", - // supportsVerbosity is a new capability; ensure ModelInfo includes it + cacheReadsPrice: 0.125, + supportsTemperature: false, + tiers: [{ name: "priority", contextWindow: 400000, inputPrice: 2.5, outputPrice: 20.0, cacheReadsPrice: 0.25 }], + description: "GPT-5.1 Codex: A version of GPT-5.1 optimized for agentic coding in Codex", + }, + "gpt-5.1-codex-mini": { + maxTokens: 128000, + contextWindow: 400000, + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["low", "medium", "high"], + reasoningEffort: "medium", + inputPrice: 0.25, + outputPrice: 2.0, + cacheReadsPrice: 0.025, + supportsTemperature: false, + description: "GPT-5.1 Codex mini: A version of GPT-5.1 optimized for agentic coding in Codex", + }, + "gpt-5": { + maxTokens: 128000, + contextWindow: 400000, + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high"], + reasoningEffort: "medium", + inputPrice: 1.25, + outputPrice: 10.0, + cacheReadsPrice: 0.125, supportsVerbosity: true, supportsTemperature: false, tiers: [ { name: "flex", contextWindow: 400000, inputPrice: 0.625, outputPrice: 5.0, cacheReadsPrice: 0.0625 }, { name: "priority", contextWindow: 400000, inputPrice: 2.5, outputPrice: 20.0, cacheReadsPrice: 0.25 }, ], + description: "GPT-5: The best model for coding and agentic tasks across domains", }, - "gpt-5-mini-2025-08-07": { + "gpt-5-mini": { maxTokens: 128000, contextWindow: 400000, supportsImages: true, supportsPromptCache: true, - supportsReasoningEffort: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high"], reasoningEffort: "medium", inputPrice: 0.25, outputPrice: 2.0, - cacheReadsPrice: 0.03, - description: "GPT-5 Mini: A faster, more cost-efficient version of GPT-5 for well-defined tasks", + cacheReadsPrice: 0.025, supportsVerbosity: true, supportsTemperature: false, tiers: [ { name: "flex", contextWindow: 400000, inputPrice: 0.125, outputPrice: 1.0, cacheReadsPrice: 0.0125 }, { name: "priority", contextWindow: 400000, inputPrice: 0.45, outputPrice: 3.6, cacheReadsPrice: 0.045 }, ], + description: "GPT-5 Mini: A faster, more cost-efficient version of GPT-5 for well-defined tasks", }, - "gpt-5-nano-2025-08-07": { + "gpt-5-codex": { maxTokens: 128000, contextWindow: 400000, supportsImages: true, supportsPromptCache: true, - supportsReasoningEffort: true, + supportsReasoningEffort: ["low", "medium", "high"], + reasoningEffort: "medium", + inputPrice: 1.25, + outputPrice: 10.0, + cacheReadsPrice: 0.125, + supportsTemperature: false, + tiers: [{ name: "priority", contextWindow: 400000, inputPrice: 2.5, outputPrice: 20.0, cacheReadsPrice: 0.25 }], + description: "GPT-5-Codex: A version of GPT-5 optimized for agentic coding in Codex", + }, + "gpt-5-nano": { + maxTokens: 128000, + contextWindow: 400000, + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high"], reasoningEffort: "medium", inputPrice: 0.05, outputPrice: 0.4, - cacheReadsPrice: 0.01, - description: "GPT-5 Nano: Fastest, most cost-efficient version of GPT-5", + cacheReadsPrice: 0.005, supportsVerbosity: true, supportsTemperature: false, tiers: [{ name: "flex", contextWindow: 400000, inputPrice: 0.025, outputPrice: 0.2, cacheReadsPrice: 0.0025 }], + description: "GPT-5 Nano: Fastest, most cost-efficient version of GPT-5", }, - "gpt-5-codex": { + "gpt-5-chat-latest": { maxTokens: 128000, contextWindow: 400000, supportsImages: true, supportsPromptCache: true, - supportsReasoningEffort: true, - reasoningEffort: "medium", inputPrice: 1.25, outputPrice: 10.0, - cacheReadsPrice: 0.13, - description: "GPT-5-Codex: A version of GPT-5 optimized for agentic coding in Codex", - supportsVerbosity: true, - supportsTemperature: false, + cacheReadsPrice: 0.125, + description: "GPT-5 Chat: Optimized for conversational AI and non-reasoning tasks", }, "gpt-4.1": { maxTokens: 32_768, @@ -130,7 +173,7 @@ export const openAiNativeModels = { inputPrice: 2.0, outputPrice: 8.0, cacheReadsPrice: 0.5, - supportsReasoningEffort: true, + supportsReasoningEffort: ["low", "medium", "high"], reasoningEffort: "medium", supportsTemperature: false, tiers: [ @@ -168,7 +211,7 @@ export const openAiNativeModels = { inputPrice: 1.1, outputPrice: 4.4, cacheReadsPrice: 0.275, - supportsReasoningEffort: true, + supportsReasoningEffort: ["low", "medium", "high"], reasoningEffort: "medium", supportsTemperature: false, tiers: [ @@ -206,7 +249,7 @@ export const openAiNativeModels = { inputPrice: 1.1, outputPrice: 4.4, cacheReadsPrice: 0.55, - supportsReasoningEffort: true, + supportsReasoningEffort: ["low", "medium", "high"], reasoningEffort: "medium", supportsTemperature: false, }, @@ -295,11 +338,63 @@ export const openAiNativeModels = { supportsPromptCache: false, inputPrice: 1.5, outputPrice: 6, - cacheReadsPrice: 0, + cacheReadsPrice: 0.375, supportsTemperature: false, description: "Codex Mini: Cloud-based software engineering agent powered by codex-1, a version of o3 optimized for coding tasks. Trained with reinforcement learning to generate human-style code, adhere to instructions, and iteratively run tests.", }, + // Dated clones (snapshots) preserved for backward compatibility + "gpt-5-2025-08-07": { + maxTokens: 128000, + contextWindow: 400000, + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high"], + reasoningEffort: "medium", + inputPrice: 1.25, + outputPrice: 10.0, + cacheReadsPrice: 0.125, + supportsVerbosity: true, + supportsTemperature: false, + tiers: [ + { name: "flex", contextWindow: 400000, inputPrice: 0.625, outputPrice: 5.0, cacheReadsPrice: 0.0625 }, + { name: "priority", contextWindow: 400000, inputPrice: 2.5, outputPrice: 20.0, cacheReadsPrice: 0.25 }, + ], + description: "GPT-5: The best model for coding and agentic tasks across domains", + }, + "gpt-5-mini-2025-08-07": { + maxTokens: 128000, + contextWindow: 400000, + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high"], + reasoningEffort: "medium", + inputPrice: 0.25, + outputPrice: 2.0, + cacheReadsPrice: 0.025, + supportsVerbosity: true, + supportsTemperature: false, + tiers: [ + { name: "flex", contextWindow: 400000, inputPrice: 0.125, outputPrice: 1.0, cacheReadsPrice: 0.0125 }, + { name: "priority", contextWindow: 400000, inputPrice: 0.45, outputPrice: 3.6, cacheReadsPrice: 0.045 }, + ], + description: "GPT-5 Mini: A faster, more cost-efficient version of GPT-5 for well-defined tasks", + }, + "gpt-5-nano-2025-08-07": { + maxTokens: 128000, + contextWindow: 400000, + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high"], + reasoningEffort: "medium", + inputPrice: 0.05, + outputPrice: 0.4, + cacheReadsPrice: 0.005, + supportsVerbosity: true, + supportsTemperature: false, + tiers: [{ name: "flex", contextWindow: 400000, inputPrice: 0.025, outputPrice: 0.2, cacheReadsPrice: 0.0025 }], + description: "GPT-5 Nano: Fastest, most cost-efficient version of GPT-5", + }, } as const satisfies Record export const openAiModelInfoSaneDefaults: ModelInfo = { @@ -316,6 +411,5 @@ export const openAiModelInfoSaneDefaults: ModelInfo = { export const azureOpenAiDefaultApiVersion = "2024-08-01-preview" export const OPENAI_NATIVE_DEFAULT_TEMPERATURE = 0 -export const GPT5_DEFAULT_TEMPERATURE = 1.0 export const OPENAI_AZURE_AI_INFERENCE_PATH = "/models/chat/completions" diff --git a/src/api/providers/__tests__/openai-native.spec.ts b/src/api/providers/__tests__/openai-native.spec.ts index 405d275951f..c68c138bc40 100644 --- a/src/api/providers/__tests__/openai-native.spec.ts +++ b/src/api/providers/__tests__/openai-native.spec.ts @@ -202,7 +202,7 @@ describe("OpenAiNativeHandler", () => { openAiNativeApiKey: "test-api-key", }) const modelInfo = handlerWithoutModel.getModel() - expect(modelInfo.id).toBe("gpt-5-2025-08-07") // Default model + expect(modelInfo.id).toBe("gpt-5.1") // Default model expect(modelInfo.info).toBeDefined() }) }) @@ -247,7 +247,7 @@ describe("OpenAiNativeHandler", () => { handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", }) const stream = handler.createMessage(systemPrompt, messages) @@ -271,7 +271,7 @@ describe("OpenAiNativeHandler", () => { ) const body1 = (mockFetch.mock.calls[0][1] as any).body as string const parsedBody = JSON.parse(body1) - expect(parsedBody.model).toBe("gpt-5-2025-08-07") + expect(parsedBody.model).toBe("gpt-5.1") expect(parsedBody.instructions).toBe("You are a helpful assistant.") // Now using structured format with content arrays (no system prompt in input; it's provided via `instructions`) expect(parsedBody.input).toEqual([ @@ -399,7 +399,7 @@ describe("OpenAiNativeHandler", () => { handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", verbosity: "low", // Set verbosity through options }) @@ -442,7 +442,7 @@ describe("OpenAiNativeHandler", () => { handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", reasoningEffort: "minimal" as any, // GPT-5 supports minimal }) @@ -461,6 +461,44 @@ describe("OpenAiNativeHandler", () => { ) }) + it("should omit reasoning when selection is 'disable'", async () => { + // Mock fetch for Responses API + const mockFetch = vitest.fn().mockResolvedValue({ + ok: true, + body: new ReadableStream({ + start(controller) { + controller.enqueue( + new TextEncoder().encode( + 'data: {"type":"response.output_item.added","item":{"type":"text","text":"No reasoning"}}\n\n', + ), + ) + controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n")) + controller.close() + }, + }), + }) + global.fetch = mockFetch as any + + // Mock SDK to fail + mockResponsesCreate.mockRejectedValue(new Error("SDK not available")) + + const handler = new OpenAiNativeHandler({ + ...mockOptions, + apiModelId: "gpt-5.1", + reasoningEffort: "disable" as any, + }) + + const stream = handler.createMessage(systemPrompt, messages) + for await (const _ of stream) { + // drain + } + + const bodyStr = (mockFetch.mock.calls[0][1] as any).body as string + const parsed = JSON.parse(bodyStr) + expect(parsed.reasoning).toBeUndefined() + expect(parsed.include).toBeUndefined() + }) + it("should support low reasoning effort for GPT-5", async () => { // Mock fetch for Responses API const mockFetch = vitest.fn().mockResolvedValue({ @@ -484,7 +522,7 @@ describe("OpenAiNativeHandler", () => { handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", reasoningEffort: "low", }) @@ -503,7 +541,7 @@ describe("OpenAiNativeHandler", () => { ) const body2 = (mockFetch.mock.calls[0][1] as any).body as string const parsedBody = JSON.parse(body2) - expect(parsedBody.model).toBe("gpt-5-2025-08-07") + expect(parsedBody.model).toBe("gpt-5.1") expect(parsedBody.reasoning?.effort).toBe("low") expect(parsedBody.reasoning?.summary).toBe("auto") expect(parsedBody.text?.verbosity).toBe("medium") @@ -535,7 +573,7 @@ describe("OpenAiNativeHandler", () => { handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", verbosity: "high", reasoningEffort: "minimal" as any, }) @@ -555,7 +593,7 @@ describe("OpenAiNativeHandler", () => { ) const body3 = (mockFetch.mock.calls[0][1] as any).body as string const parsedBody = JSON.parse(body3) - expect(parsedBody.model).toBe("gpt-5-2025-08-07") + expect(parsedBody.model).toBe("gpt-5.1") expect(parsedBody.reasoning?.effort).toBe("minimal") expect(parsedBody.reasoning?.summary).toBe("auto") expect(parsedBody.text?.verbosity).toBe("high") @@ -613,7 +651,7 @@ describe("OpenAiNativeHandler", () => { handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", }) const stream = handler.createMessage(systemPrompt, messages) @@ -669,7 +707,7 @@ describe("OpenAiNativeHandler", () => { handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", }) const stream = handler.createMessage(systemPrompt, messages) @@ -714,7 +752,7 @@ describe("OpenAiNativeHandler", () => { handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", }) const stream = handler.createMessage(systemPrompt, messages) @@ -755,7 +793,7 @@ describe("OpenAiNativeHandler", () => { const gpt5Handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", }) const stream = gpt5Handler.createMessage(systemPrompt, messages, { @@ -800,7 +838,7 @@ describe("OpenAiNativeHandler", () => { handler = new OpenAiNativeHandler({ ...mockOptions, - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", }) const stream = handler.createMessage(systemPrompt, messages) @@ -850,7 +888,7 @@ describe("GPT-5 streaming event coverage (additional)", () => { mockResponsesCreate.mockRejectedValue(new Error("SDK not available")) const handler = new OpenAiNativeHandler({ - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", openAiNativeApiKey: "test-api-key", }) @@ -893,7 +931,7 @@ describe("GPT-5 streaming event coverage (additional)", () => { mockResponsesCreate.mockRejectedValue(new Error("SDK not available")) const handler = new OpenAiNativeHandler({ - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", openAiNativeApiKey: "test-api-key", }) @@ -942,7 +980,7 @@ describe("GPT-5 streaming event coverage (additional)", () => { mockResponsesCreate.mockRejectedValue(new Error("SDK not available")) const handler = new OpenAiNativeHandler({ - apiModelId: "gpt-5-2025-08-07", + apiModelId: "gpt-5.1", openAiNativeApiKey: "test-api-key", }) diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index 7555a49d498..7d51e2ab2c5 100644 --- a/src/api/providers/__tests__/roo.spec.ts +++ b/src/api/providers/__tests__/roo.spec.ts @@ -511,16 +511,17 @@ describe("RooHandler", () => { // Consume stream } - expect(mockCreate).toHaveBeenCalledWith( + const firstCallBody = mockCreate.mock.calls[0][0] + expect(firstCallBody).toEqual( expect.objectContaining({ model: mockOptions.apiModelId, messages: expect.any(Array), stream: true, stream_options: { include_usage: true }, - reasoning: { enabled: false }, }), - undefined, ) + expect(firstCallBody.reasoning).toBeUndefined() + expect(mockCreate.mock.calls[0][1]).toBeUndefined() }) it("should include reasoning with enabled: false when explicitly disabled", async () => { @@ -595,7 +596,7 @@ describe("RooHandler", () => { ) }) - it("should not include reasoning for minimal (treated as none)", async () => { + it("should include reasoning for minimal", async () => { handler = new RooHandler({ ...mockOptions, reasoningEffort: "minimal", @@ -605,9 +606,8 @@ describe("RooHandler", () => { // Consume stream } - // minimal should result in no reasoning parameter const callArgs = mockCreate.mock.calls[0][0] - expect(callArgs.reasoning).toBeUndefined() + expect(callArgs.reasoning).toEqual({ enabled: true, effort: "minimal" }) }) it("should handle enableReasoningEffort: false overriding reasoningEffort setting", async () => { diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index 49bdf41a047..2d51c92a2b3 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -7,10 +7,9 @@ import { OpenAiNativeModelId, openAiNativeModels, OPENAI_NATIVE_DEFAULT_TEMPERATURE, - GPT5_DEFAULT_TEMPERATURE, type ReasoningEffort, type VerbosityLevel, - type ReasoningEffortWithMinimal, + type ReasoningEffortExtended, type ServiceTier, } from "@roo-code/types" @@ -26,11 +25,6 @@ import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from ". export type OpenAiNativeModel = ReturnType -// GPT-5 specific types - -// Constants for model identification -const GPT5_MODEL_PREFIX = "gpt-5" - export class OpenAiNativeHandler extends BaseProvider implements SingleCompletionHandler { protected options: ApiHandlerOptions private client: OpenAI @@ -170,9 +164,6 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio metadata, ) - // Temporary debug logging - // console.log("[OpenAI Native] Request body:", requestBody) - // Make the request (pass systemPrompt and messages for potential retry) yield* this.executeRequest(requestBody, model, metadata, systemPrompt, messages) } @@ -182,7 +173,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio formattedInput: any, systemPrompt: string, verbosity: any, - reasoningEffort: ReasoningEffortWithMinimal | undefined, + reasoningEffort: ReasoningEffortExtended | undefined, metadata?: ApiHandlerCreateMessageMetadata, ): any { // Build a request body @@ -192,7 +183,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio model: string input: Array<{ role: "user" | "assistant"; content: any[] } | { type: string; content: string }> stream: boolean - reasoning?: { effort?: ReasoningEffortWithMinimal; summary?: "auto" } + reasoning?: { effort?: ReasoningEffortExtended; summary?: "auto" } text?: { verbosity: VerbosityLevel } temperature?: number max_output_tokens?: number @@ -228,11 +219,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio : {}), // Only include temperature if the model supports it ...(model.info.supportsTemperature !== false && { - temperature: - this.options.modelTemperature ?? - (model.id.startsWith(GPT5_MODEL_PREFIX) - ? GPT5_DEFAULT_TEMPERATURE - : OPENAI_NATIVE_DEFAULT_TEMPERATURE), + temperature: this.options.modelTemperature ?? OPENAI_NATIVE_DEFAULT_TEMPERATURE, }), // Explicitly include the calculated max output tokens. // Use the per-request reserved output computed by Roo (params.maxTokens from getModelParams). @@ -984,20 +971,10 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio } } - private getReasoningEffort(model: OpenAiNativeModel): ReasoningEffortWithMinimal | undefined { - const { reasoning, info } = model - - // Check if reasoning effort is configured - if (reasoning && "reasoning_effort" in reasoning) { - const effort = reasoning.reasoning_effort as string - // Support all effort levels - if (effort === "minimal" || effort === "low" || effort === "medium" || effort === "high") { - return effort as ReasoningEffortWithMinimal - } - } - - // Use the model's default from types if available - return info.reasoningEffort as ReasoningEffortWithMinimal | undefined + private getReasoningEffort(model: OpenAiNativeModel): ReasoningEffortExtended | undefined { + // Single source of truth: user setting overrides, else model default (from types). + const selected = (this.options.reasoningEffort as any) ?? (model.info.reasoningEffort as any) + return selected && selected !== "disable" ? (selected as any) : undefined } /** @@ -1035,19 +1012,11 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio modelId: id, model: info, settings: this.options, - defaultTemperature: id.startsWith(GPT5_MODEL_PREFIX) - ? GPT5_DEFAULT_TEMPERATURE - : OPENAI_NATIVE_DEFAULT_TEMPERATURE, + defaultTemperature: OPENAI_NATIVE_DEFAULT_TEMPERATURE, }) - // For models using the Responses API, ensure we support reasoning effort - const effort = - (this.options.reasoningEffort as ReasoningEffortWithMinimal | undefined) ?? - (info.reasoningEffort as ReasoningEffortWithMinimal | undefined) - - if (effort) { - ;(params.reasoning as any) = { reasoning_effort: effort } - } + // Reasoning effort inclusion is handled by getModelParams/getOpenAiReasoning. + // Do not re-compute or filter efforts here. // The o3 models are named like "o3-mini-[reasoning-effort]", which are // not valid model ids, so we need to strip the suffix. @@ -1120,11 +1089,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio // Only include temperature if the model supports it if (model.info.supportsTemperature !== false) { - requestBody.temperature = - this.options.modelTemperature ?? - (model.id.startsWith(GPT5_MODEL_PREFIX) - ? GPT5_DEFAULT_TEMPERATURE - : OPENAI_NATIVE_DEFAULT_TEMPERATURE) + requestBody.temperature = this.options.modelTemperature ?? OPENAI_NATIVE_DEFAULT_TEMPERATURE } // Include max_output_tokens if available diff --git a/src/api/providers/requesty.ts b/src/api/providers/requesty.ts index 1c0e9ed6407..979146b378f 100644 --- a/src/api/providers/requesty.ts +++ b/src/api/providers/requesty.ts @@ -28,6 +28,16 @@ interface RequestyUsage extends OpenAI.CompletionUsage { total_cost?: number } +type RequestyChatCompletionParamsStreaming = OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming & { + requesty?: { + trace_id?: string + extra?: { + mode?: string + } + } + thinking?: AnthropicReasoningParams +} + type RequestyChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & { requesty?: { trace_id?: string @@ -118,12 +128,17 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan ...convertToOpenAiMessages(messages), ] - const completionParams: RequestyChatCompletionParams = { + // Map extended efforts to OpenAI Chat Completions-accepted values (omit unsupported) + const allowedEffort = (["low", "medium", "high"] as const).includes(reasoning_effort as any) + ? (reasoning_effort as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming["reasoning_effort"]) + : undefined + + const completionParams: RequestyChatCompletionParamsStreaming = { messages: openAiMessages, model, max_tokens, temperature, - ...(reasoning_effort && reasoning_effort !== "minimal" && { reasoning_effort }), + ...(allowedEffort && { reasoning_effort: allowedEffort }), ...(thinking && { thinking }), stream: true, stream_options: { include_usage: true }, @@ -132,6 +147,7 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan let stream try { + // With streaming params type, SDK returns an async iterable stream stream = await this.client.chat.completions.create(completionParams) } catch (error) { throw handleOpenAIError(error, this.providerName) diff --git a/src/api/transform/__tests__/model-params.spec.ts b/src/api/transform/__tests__/model-params.spec.ts index bd75e7eafb9..b2d009b8c58 100644 --- a/src/api/transform/__tests__/model-params.spec.ts +++ b/src/api/transform/__tests__/model-params.spec.ts @@ -545,6 +545,79 @@ describe("getModelParams", () => { expect(result.reasoning).toEqual({ effort: "medium" }) }) + it("should include 'minimal' effort for openai format", () => { + const model: ModelInfo = { + ...baseModel, + // Array capability explicitly includes minimal + supportsReasoningEffort: ["minimal", "low", "medium", "high"] as any, + } + + const result = getModelParams({ + ...openaiParams, + settings: { reasoningEffort: "minimal" as any }, + model, + }) + + expect(result.reasoningEffort).toBe("minimal") + expect(result.reasoning).toEqual({ reasoning_effort: "minimal" }) + }) + + it("should include 'none' effort for openai format", () => { + const model: ModelInfo = { + ...baseModel, + // Array capability explicitly includes none + supportsReasoningEffort: ["none", "low", "medium", "high"] as any, + } + + const result = getModelParams({ + ...openaiParams, + settings: { reasoningEffort: "none" as any }, + model, + }) + + expect(result.reasoningEffort).toBe("none") + expect(result.reasoning).toEqual({ reasoning_effort: "none" }) + }) + + it("should omit reasoning for 'disable' selection", () => { + const model: ModelInfo = { + ...baseModel, + supportsReasoningEffort: true, + } + + const result = getModelParams({ + ...openaiParams, + settings: { reasoningEffort: "disable" as any }, + model, + }) + + expect(result.reasoningEffort).toBeUndefined() + expect(result.reasoning).toBeUndefined() + }) + + it("should include 'minimal' and 'none' for openrouter format", () => { + const model: ModelInfo = { + ...baseModel, + // Array capability explicitly includes both + supportsReasoningEffort: ["none", "minimal", "low", "medium", "high"] as any, + } + + const minimalRes = getModelParams({ + ...openrouterParams, + settings: { reasoningEffort: "minimal" as any }, + model, + }) + expect(minimalRes.reasoningEffort).toBe("minimal") + expect(minimalRes.reasoning).toEqual({ effort: "minimal" }) + + const noneRes = getModelParams({ + ...openrouterParams, + settings: { reasoningEffort: "none" as any }, + model, + }) + expect(noneRes.reasoningEffort).toBe("none") + expect(noneRes.reasoning).toEqual({ effort: "none" }) + }) it("should not use reasoning effort for anthropic format", () => { const model: ModelInfo = { ...baseModel, diff --git a/src/api/transform/__tests__/reasoning.spec.ts b/src/api/transform/__tests__/reasoning.spec.ts index ae565e9628b..3c7c40a6d26 100644 --- a/src/api/transform/__tests__/reasoning.spec.ts +++ b/src/api/transform/__tests__/reasoning.spec.ts @@ -529,7 +529,7 @@ describe("reasoning.ts", () => { const result = getOpenAiReasoning(optionsWithoutEffort) - expect(result).toEqual({ reasoning_effort: undefined }) + expect(result).toBeUndefined() }) it("should handle all reasoning effort values", () => { @@ -826,10 +826,10 @@ describe("reasoning.ts", () => { } const result = getRooReasoning(options) - expect(result).toEqual({ enabled: false }) + expect(result).toBeUndefined() }) - it("should not return reasoning params for minimal effort", () => { + it("should include reasoning params for minimal effort", () => { const modelWithSupported: ModelInfo = { ...baseModel, supportsReasoningEffort: true, @@ -847,7 +847,7 @@ describe("reasoning.ts", () => { } const result = getRooReasoning(options) - expect(result).toBeUndefined() + expect(result).toEqual({ enabled: true, effort: "minimal" }) }) it("should handle all valid reasoning effort values", () => { @@ -889,7 +889,7 @@ describe("reasoning.ts", () => { } const result = getRooReasoning(options) - expect(result).toEqual({ enabled: false }) + expect(result).toBeUndefined() }) }) }) diff --git a/src/api/transform/model-params.ts b/src/api/transform/model-params.ts index 933697c0a53..b305431e139 100644 --- a/src/api/transform/model-params.ts +++ b/src/api/transform/model-params.ts @@ -2,7 +2,7 @@ import { type ModelInfo, type ProviderSettings, type VerbosityLevel, - type ReasoningEffortWithMinimal, + type ReasoningEffortExtended, ANTHROPIC_DEFAULT_MAX_TOKENS, } from "@roo-code/types" @@ -39,7 +39,7 @@ type GetModelParamsOptions = { type BaseModelParams = { maxTokens: number | undefined temperature: number | undefined - reasoningEffort: ReasoningEffortWithMinimal | undefined + reasoningEffort: ReasoningEffortExtended | undefined reasoningBudget: number | undefined verbosity: VerbosityLevel | undefined } @@ -129,8 +129,17 @@ export function getModelParams({ temperature = 1.0 } else if (shouldUseReasoningEffort({ model, settings })) { // "Traditional" reasoning models use the `reasoningEffort` parameter. - const effort = customReasoningEffort ?? model.reasoningEffort - reasoningEffort = effort as ReasoningEffortWithMinimal + const effort = (customReasoningEffort ?? model.reasoningEffort) as any + // Do not propagate "disable" into model params; treat as omission + if (effort && effort !== "disable") { + if (model.supportsReasoningEffort === true) { + // Boolean capability: accept extended efforts; UI still exposes low/medium/high by default + reasoningEffort = effort as ReasoningEffortExtended + } else { + // Array capability: honor exactly what's defined by the model + reasoningEffort = effort as ReasoningEffortExtended + } + } } const params: BaseModelParams = { maxTokens, temperature, reasoningEffort, reasoningBudget, verbosity } diff --git a/src/api/transform/reasoning.ts b/src/api/transform/reasoning.ts index 8d64fe46b11..63bf362957f 100644 --- a/src/api/transform/reasoning.ts +++ b/src/api/transform/reasoning.ts @@ -2,19 +2,19 @@ import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta" import OpenAI from "openai" import type { GenerateContentConfig } from "@google/genai" -import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@roo-code/types" +import type { ModelInfo, ProviderSettings, ReasoningEffortExtended } from "@roo-code/types" import { shouldUseReasoningBudget, shouldUseReasoningEffort } from "../../shared/api" export type OpenRouterReasoningParams = { - effort?: ReasoningEffortWithMinimal + effort?: ReasoningEffortExtended max_tokens?: number exclude?: boolean } export type RooReasoningParams = { enabled?: boolean - effort?: ReasoningEffortWithMinimal + effort?: ReasoningEffortExtended } export type AnthropicReasoningParams = BetaThinkingConfigParam @@ -26,7 +26,7 @@ export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"] export type GetModelReasoningOptions = { model: ModelInfo reasoningBudget: number | undefined - reasoningEffort: ReasoningEffortWithMinimal | undefined + reasoningEffort: ReasoningEffortExtended | "disable" | undefined settings: ProviderSettings } @@ -39,8 +39,8 @@ export const getOpenRouterReasoning = ({ shouldUseReasoningBudget({ model, settings }) ? { max_tokens: reasoningBudget } : shouldUseReasoningEffort({ model, settings }) - ? reasoningEffort - ? { effort: reasoningEffort } + ? reasoningEffort && reasoningEffort !== "disable" + ? { effort: reasoningEffort as ReasoningEffortExtended } : undefined : undefined @@ -50,27 +50,24 @@ export const getRooReasoning = ({ settings, }: GetModelReasoningOptions): RooReasoningParams | undefined => { // Check if model supports reasoning effort - if (!model.supportsReasoningEffort) { - return undefined - } + if (!model.supportsReasoningEffort) return undefined - // If enableReasoningEffort is explicitly false, return enabled: false + // If disabled via toggle, send explicit disabled flag for back-compat if (settings.enableReasoningEffort === false) { return { enabled: false } } - // If reasoning effort is provided, return it with enabled: true - if (reasoningEffort && reasoningEffort !== "minimal") { - return { enabled: true, effort: reasoningEffort } + // If the selection is "disable", omit the field entirely (no reasoning param) + if (reasoningEffort === "disable") { + return undefined } - // If reasoningEffort is explicitly undefined (None selected), disable reasoning - // This ensures we explicitly tell the backend not to use reasoning - if (reasoningEffort === undefined) { - return { enabled: false } + // When an effort is provided (including "none" and "minimal"), enable with effort + if (reasoningEffort) { + return { enabled: true, effort: reasoningEffort as ReasoningEffortExtended } } - // Default: no reasoning parameter (reasoning not enabled) + // No explicit selection -> omit field return undefined } @@ -86,17 +83,11 @@ export const getOpenAiReasoning = ({ reasoningEffort, settings, }: GetModelReasoningOptions): OpenAiReasoningParams | undefined => { - if (!shouldUseReasoningEffort({ model, settings })) { - return undefined - } - - // If model has reasoning effort capability, return object even if effort is undefined - // This preserves the reasoning_effort field in the API call - if (reasoningEffort === "minimal") { - return undefined - } + if (!shouldUseReasoningEffort({ model, settings })) return undefined + if (reasoningEffort === "disable" || !reasoningEffort) return undefined - return { reasoning_effort: reasoningEffort } + // Include "none" | "minimal" | "low" | "medium" | "high" literally + return { reasoning_effort: reasoningEffort as any } } export const getGeminiReasoning = ({ diff --git a/src/shared/__tests__/api.spec.ts b/src/shared/__tests__/api.spec.ts index aaeb1bf4447..61839d14a5a 100644 --- a/src/shared/__tests__/api.spec.ts +++ b/src/shared/__tests__/api.spec.ts @@ -473,4 +473,45 @@ describe("shouldUseReasoningEffort", () => { expect(shouldUseReasoningEffort({ model, settings: settingsMedium })).toBe(true) expect(shouldUseReasoningEffort({ model, settings: settingsHigh })).toBe(true) }) + + // New cases for extended capability surface + test("array capability includes 'disable' with selection 'disable' -> false", () => { + const model: ModelInfo = { + contextWindow: 100_000, + supportsPromptCache: true, + supportsReasoningEffort: ["disable", "low", "medium", "high"] as unknown as any, + } + const settings: ProviderSettings = { enableReasoningEffort: true, reasoningEffort: "disable" as any } + expect(shouldUseReasoningEffort({ model, settings })).toBe(false) + }) + + test("array capability includes 'none' with settings.reasoningEffort='none' -> true", () => { + const model: ModelInfo = { + contextWindow: 100_000, + supportsPromptCache: true, + supportsReasoningEffort: ["none", "minimal", "low", "medium", "high"] as unknown as any, + } + const settings: ProviderSettings = { enableReasoningEffort: true, reasoningEffort: "none" as any } + expect(shouldUseReasoningEffort({ model, settings })).toBe(true) + }) + + test("array capability includes 'minimal' with settings.reasoningEffort='minimal' -> true", () => { + const model: ModelInfo = { + contextWindow: 100_000, + supportsPromptCache: true, + supportsReasoningEffort: ["none", "minimal", "low", "medium", "high"] as unknown as any, + } + const settings: ProviderSettings = { enableReasoningEffort: true, reasoningEffort: "minimal" as any } + expect(shouldUseReasoningEffort({ model, settings })).toBe(true) + }) + + test("boolean true with 'none' and 'minimal' -> true", () => { + const model: ModelInfo = { + contextWindow: 100_000, + supportsPromptCache: true, + supportsReasoningEffort: true, + } + expect(shouldUseReasoningEffort({ model, settings: { reasoningEffort: "none" as any } })).toBe(true) + expect(shouldUseReasoningEffort({ model, settings: { reasoningEffort: "minimal" as any } })).toBe(true) + }) }) diff --git a/src/shared/api.ts b/src/shared/api.ts index 802654adaad..101d7f9b668 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -63,15 +63,44 @@ export const shouldUseReasoningEffort = ({ model: ModelInfo settings?: ProviderSettings }): boolean => { - // If enableReasoningEffort is explicitly set to false, reasoning should be disabled - if (settings?.enableReasoningEffort === false) { - return false + // Explicit off switch + if (settings?.enableReasoningEffort === false) return false + + // Selected effort from settings or model default + const selectedEffort = (settings?.reasoningEffort ?? (model as any).reasoningEffort) as + | "disable" + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | undefined + + // "disable" explicitly omits reasoning + if (selectedEffort === "disable") return false + + const cap = model.supportsReasoningEffort as unknown + + // Capability array: use only if selected is included (treat "none"/"minimal" as valid) + if (Array.isArray(cap)) { + return !!selectedEffort && (cap as ReadonlyArray).includes(selectedEffort as string) } - // Otherwise, use reasoning if: - // 1. Model supports reasoning effort AND settings provide reasoning effort, OR - // 2. Model itself has a reasoningEffort property - return (!!model.supportsReasoningEffort && !!settings?.reasoningEffort) || !!model.reasoningEffort + // Boolean capability: true → require a selected effort + if (model.supportsReasoningEffort === true) { + return !!selectedEffort + } + + // Not explicitly supported: only allow when the model itself defines a default effort + // Ignore settings-only selections when capability is absent/false + const modelDefaultEffort = (model as any).reasoningEffort as + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | undefined + return !!modelDefaultEffort } export const DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS = 16_384 diff --git a/webview-ui/src/components/settings/SimpleThinkingBudget.tsx b/webview-ui/src/components/settings/SimpleThinkingBudget.tsx index 60b163738dd..973ba1e9c66 100644 --- a/webview-ui/src/components/settings/SimpleThinkingBudget.tsx +++ b/webview-ui/src/components/settings/SimpleThinkingBudget.tsx @@ -1,3 +1,36 @@ +/* +Semantics for Reasoning Effort (SimpleThinkingBudget) + +Capability surface: +- modelInfo.supportsReasoningEffort: boolean | Array<"disable" | "none" | "minimal" | "low" | "medium" | "high"> + - true → UI shows ["low","medium","high"] + - array → UI shows exactly the provided values + +Selection behavior: +- "disable": + - Label: t("settings:providers.reasoningEffort.none") + - set enableReasoningEffort = false + - persist reasoningEffort = "disable" + - request builders omit any reasoning parameter/body sections +- "none": + - Label: t("settings:providers.reasoningEffort.none") + - set enableReasoningEffort = true + - persist reasoningEffort = "none" + - request builders include reasoning with value "none" +- "minimal" | "low" | "medium" | "high": + - set enableReasoningEffort = true + - persist the selected value + - request builders include reasoning with the selected effort + +Required: +- If modelInfo.requiredReasoningEffort is true, do not synthesize a "None" choice. Only show values from the capability. +- On mount, if unset and a default exists, set enableReasoningEffort = true and use modelInfo.reasoningEffort. + +Notes: +- Current selection is normalized to the capability: unsupported persisted values are not shown. +- Both "disable" and "none" display as the "None" label per UX, but are wired differently as above. +- "minimal" uses t("settings:providers.reasoningEffort.minimal"). +*/ import { useEffect } from "react" import { type ProviderSettings, type ModelInfo, type ReasoningEffort, reasoningEfforts } from "@roo-code/types" @@ -15,8 +48,8 @@ interface SimpleThinkingBudgetProps { modelInfo?: ModelInfo } -// Extended type to include "none" option -type ReasoningEffortWithNone = ReasoningEffort | "none" +// Reasoning selection values including control values +type ReasoningSelectValue = "disable" | "none" | "minimal" | ReasoningEffort export const SimpleThinkingBudget = ({ apiConfiguration, @@ -25,57 +58,46 @@ export const SimpleThinkingBudget = ({ }: SimpleThinkingBudgetProps) => { const { t } = useAppTranslation() - // Check model capabilities - const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort - const isReasoningEffortRequired = !!modelInfo && modelInfo.requiredReasoningEffort + const isSupported = !!modelInfo?.supportsReasoningEffort - // Build available reasoning efforts list - // Include "none" option unless reasoning effort is required - const baseEfforts = [...reasoningEfforts] as ReasoningEffort[] - const availableReasoningEfforts: ReadonlyArray = isReasoningEffortRequired - ? baseEfforts - : (["none", ...baseEfforts] as ReasoningEffortWithNone[]) + const isReasoningEffortRequired = !!modelInfo?.requiredReasoningEffort - // Default reasoning effort - use model's default if available, otherwise "medium" - const modelDefaultReasoningEffort = modelInfo?.reasoningEffort as ReasoningEffort | undefined - const defaultReasoningEffort: ReasoningEffortWithNone = isReasoningEffortRequired - ? modelDefaultReasoningEffort || "medium" - : "none" + // Compute available options from capability + const supports = modelInfo?.supportsReasoningEffort + const availableOptions: readonly ReasoningSelectValue[] = + supports === true ? ([...reasoningEfforts] as const) : (supports as any) - // Current reasoning effort - treat undefined/null as "none" - const currentReasoningEffort: ReasoningEffortWithNone = - (apiConfiguration.reasoningEffort as ReasoningEffort | undefined) || defaultReasoningEffort + // Helper for labels + const labelFor = (v: ReasoningSelectValue) => { + if (v === "disable" || v === "none") return t("settings:providers.reasoningEffort.none") + if (v === "minimal") return t("settings:providers.reasoningEffort.minimal") + return t(`settings:providers.reasoningEffort.${v}`) + } - // Set default reasoning effort when model supports it and no value is set - useEffect(() => { - if (isReasoningEffortSupported && !apiConfiguration.reasoningEffort) { - // Only set a default if reasoning is required, otherwise leave as undefined (which maps to "none") - if (isReasoningEffortRequired && defaultReasoningEffort !== "none") { - setApiConfigurationField("reasoningEffort", defaultReasoningEffort as ReasoningEffort, false) - } - } - }, [ - isReasoningEffortSupported, - isReasoningEffortRequired, - apiConfiguration.reasoningEffort, - defaultReasoningEffort, - setApiConfigurationField, - ]) + // Determine current selection (normalize to capability) + let current: ReasoningSelectValue | undefined = apiConfiguration.reasoningEffort as ReasoningSelectValue | undefined + if (!current && isReasoningEffortRequired && modelInfo.reasoningEffort) { + current = modelInfo.reasoningEffort as ReasoningSelectValue + } + // If persisted value isn't supported by capability (e.g., "minimal" while supports=true), don't show it + const normalizedCurrent: ReasoningSelectValue | undefined = + current && (availableOptions as readonly any[]).includes(current) ? current : undefined + // Default when required: set to model default on mount (no synthetic "None") useEffect(() => { - if (!isReasoningEffortSupported) return - const shouldEnable = isReasoningEffortRequired || currentReasoningEffort !== "none" - if (shouldEnable && apiConfiguration.enableReasoningEffort !== true) { + if (!isReasoningEffortRequired) return + if (!apiConfiguration.reasoningEffort && modelInfo?.reasoningEffort) { setApiConfigurationField("enableReasoningEffort", true, false) + setApiConfigurationField("reasoningEffort", modelInfo?.reasoningEffort as any, false) } }, [ - isReasoningEffortSupported, isReasoningEffortRequired, - currentReasoningEffort, - apiConfiguration.enableReasoningEffort, + apiConfiguration.reasoningEffort, + modelInfo?.reasoningEffort, setApiConfigurationField, ]) - if (!modelInfo || !isReasoningEffortSupported) { + + if (!isSupported) { return null } @@ -85,32 +107,25 @@ export const SimpleThinkingBudget = ({