mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
GPT5 OpenAI Fix (#6864)
* fix: add explicit max_output_tokens for GPT-5 Responses API - Added max_output_tokens parameter to GPT-5 request body using model.maxTokens - This prevents GPT-5 from defaulting to very large token limits (e.g., 120k) - Updated tests to expect max_output_tokens in GPT-5 request bodies - Fixed test for handling unhandled stream events by properly mocking SDK fallback * fix: add missing translations for reasoningEffort.minimal in Indonesian and Dutch locales * fix: correct GPT-5 response ID persistence and usage - Renamed metadata field from 'previous_response_id' to 'response_id' for clarity - Fixed logic to correctly use the response_id from the previous message as previous_response_id for the next request - This resolves the 'Previous response with id not found' errors that occurred after multiple turns in the same session * feat: add robust error handling for GPT-5 previous_response_id failures - Automatically retry without previous_response_id when it's not found (400 error) - Clear stored lastResponseId to prevent reusing stale IDs - Handle errors in both SDK and SSE fallback paths - Log warnings when retrying to help with debugging * fix: handle GPT-5 response ID race condition with nano model - Add promise-based synchronization for response ID persistence - Wait for pending response ID from previous request before using it - Resolve promise when response ID is received or cleared - Add 100ms timeout to avoid blocking too long on ID resolution - Properly clean up resolver on errors to prevent memory leaks This fixes the race condition where fast nano model responses could cause the next request to be initiated before the response ID was fully persisted. * fix: address PR review comments for GPT-5 implementation - Extract usage normalization helper to reduce duplication - Suppress conversation continuity for first message (but respect explicit metadata) - Deduplicate response ID resolver logic - Remove dead enableGpt5ReasoningSummary option references - DRY up GPT-5 event/usage handling with normalizeGpt5Usage helper - Centralize default GPT-5 reasoning effort using model info - Fix Indonesian locale minimal string misplacement - Add clarifying comments for Developer prefix usage - Add TODO for future verbosity UI capability gating - Fix failing test in reasoning.spec.ts * fix(openai-native): address Roomote inline feedback\n\n- Delegate standard GPT-5 SSE event types to shared processor to reduce duplication\n- Add JSDoc for response ID accessors\n- Standardize key error messages for GPT-5 Responses API fallback\n- Extract persistGpt5Metadata() in Task to simplify metadata writes\n- Add malformed JSON SSE parsing test\n * fix(openai-native,gpt5): correct usage cost calc (use calculateApiCostOpenAI incl. cache); enforce 'skip once' continuity via suppressPreviousResponseId; dedupe responseId resolver on SSE 400; feat: gate reasoning.summary by enableGpt5ReasoningSummary; centralize default reasoning effort; types/ui: add ModelInfo.supportsVerbosity and gate Verbosity UI by capability; refactor: avoid duplicate usage emission in SSE done/completed * fix(gpt5): default enableGpt5ReasoningSummary=true to preserve tests and expected behavior * fix(gpt5): canonicalize GPT-5 metadata key to previous_response_id and align enableGpt5ReasoningSummary default docs * fix(openai-native): remove review artifact comments and guard GPT-5 in completePrompt
This commit is contained in:
parent
cdc31f7c26
commit
cda67a86f5
33 changed files with 2195 additions and 264 deletions
|
|
@ -176,6 +176,17 @@ export const clineMessageSchema = z.object({
|
|||
contextCondense: contextCondenseSchema.optional(),
|
||||
isProtected: z.boolean().optional(),
|
||||
apiProtocol: z.union([z.literal("openai"), z.literal("anthropic")]).optional(),
|
||||
metadata: z
|
||||
.object({
|
||||
gpt5: z
|
||||
.object({
|
||||
previous_response_id: z.string().optional(),
|
||||
instructions: z.string().optional(),
|
||||
reasoning_summary: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export type ClineMessage = z.infer<typeof clineMessageSchema>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ export const modelInfoSchema = z.object({
|
|||
supportsImages: z.boolean().optional(),
|
||||
supportsComputerUse: z.boolean().optional(),
|
||||
supportsPromptCache: z.boolean(),
|
||||
// Capability flag to indicate whether the model supports an output verbosity parameter
|
||||
supportsVerbosity: z.boolean().optional(),
|
||||
supportsReasoningBudget: z.boolean().optional(),
|
||||
requiredReasoningBudget: z.boolean().optional(),
|
||||
supportsReasoningEffort: z.boolean().optional(),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@ import { z } from "zod"
|
|||
import { reasoningEffortsSchema, verbosityLevelsSchema, modelInfoSchema } from "./model.js"
|
||||
import { codebaseIndexProviderSchema } from "./codebase-index.js"
|
||||
|
||||
// Extended schema that includes "minimal" for GPT-5 models
|
||||
export const extendedReasoningEffortsSchema = z.union([reasoningEffortsSchema, z.literal("minimal")])
|
||||
|
||||
export type ReasoningEffortWithMinimal = z.infer<typeof extendedReasoningEffortsSchema>
|
||||
|
||||
/**
|
||||
* ProviderName
|
||||
*/
|
||||
|
|
@ -76,7 +81,7 @@ const baseProviderSettingsSchema = z.object({
|
|||
|
||||
// Model reasoning.
|
||||
enableReasoningEffort: z.boolean().optional(),
|
||||
reasoningEffort: reasoningEffortsSchema.optional(),
|
||||
reasoningEffort: extendedReasoningEffortsSchema.optional(),
|
||||
modelMaxTokens: z.number().optional(),
|
||||
modelMaxThinkingTokens: z.number().optional(),
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@ export const openAiNativeModels = {
|
|||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: true,
|
||||
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
|
||||
supportsVerbosity: true,
|
||||
},
|
||||
"gpt-5-mini-2025-08-07": {
|
||||
maxTokens: 128000,
|
||||
|
|
@ -23,10 +26,12 @@ export const openAiNativeModels = {
|
|||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: true,
|
||||
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",
|
||||
supportsVerbosity: true,
|
||||
},
|
||||
"gpt-5-nano-2025-08-07": {
|
||||
maxTokens: 128000,
|
||||
|
|
@ -34,10 +39,12 @@ export const openAiNativeModels = {
|
|||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: true,
|
||||
reasoningEffort: "medium",
|
||||
inputPrice: 0.05,
|
||||
outputPrice: 0.4,
|
||||
cacheReadsPrice: 0.01,
|
||||
description: "GPT-5 Nano: Fastest, most cost-efficient version of GPT-5",
|
||||
supportsVerbosity: true,
|
||||
},
|
||||
"gpt-4.1": {
|
||||
maxTokens: 32_768,
|
||||
|
|
@ -229,5 +236,6 @@ 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"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ export interface SingleCompletionHandler {
|
|||
export interface ApiHandlerCreateMessageMetadata {
|
||||
mode?: string
|
||||
taskId: string
|
||||
previousResponseId?: string
|
||||
/**
|
||||
* When true, the provider must NOT fall back to internal continuity state
|
||||
* (e.g., lastResponseId) if previousResponseId is absent.
|
||||
* Used to enforce "skip once" after a condense operation.
|
||||
*/
|
||||
suppressPreviousResponseId?: boolean
|
||||
}
|
||||
|
||||
export interface ApiHandler {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -305,7 +305,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
],
|
||||
stream: true,
|
||||
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
|
||||
reasoning_effort: modelInfo.reasoningEffort,
|
||||
reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined,
|
||||
temperature: undefined,
|
||||
}
|
||||
|
||||
|
|
@ -330,7 +330,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
},
|
||||
...convertToOpenAiMessages(messages),
|
||||
],
|
||||
reasoning_effort: modelInfo.reasoningEffort,
|
||||
reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined,
|
||||
temperature: undefined,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan
|
|||
model,
|
||||
max_tokens,
|
||||
temperature,
|
||||
...(reasoning_effort && { reasoning_effort }),
|
||||
...(reasoning_effort && reasoning_effort !== "minimal" && { reasoning_effort }),
|
||||
...(thinking && { thinking }),
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
type ModelInfo,
|
||||
type ProviderSettings,
|
||||
type VerbosityLevel,
|
||||
type ReasoningEffortWithMinimal,
|
||||
ANTHROPIC_DEFAULT_MAX_TOKENS,
|
||||
} from "@roo-code/types"
|
||||
|
||||
|
|
@ -38,7 +39,7 @@ type GetModelParamsOptions<T extends Format> = {
|
|||
type BaseModelParams = {
|
||||
maxTokens: number | undefined
|
||||
temperature: number | undefined
|
||||
reasoningEffort: "low" | "medium" | "high" | undefined
|
||||
reasoningEffort: ReasoningEffortWithMinimal | undefined
|
||||
reasoningBudget: number | undefined
|
||||
verbosity: VerbosityLevel | undefined
|
||||
}
|
||||
|
|
@ -128,7 +129,8 @@ export function getModelParams({
|
|||
temperature = 1.0
|
||||
} else if (shouldUseReasoningEffort({ model, settings })) {
|
||||
// "Traditional" reasoning models use the `reasoningEffort` parameter.
|
||||
reasoningEffort = customReasoningEffort ?? model.reasoningEffort
|
||||
const effort = customReasoningEffort ?? model.reasoningEffort
|
||||
reasoningEffort = effort as ReasoningEffortWithMinimal
|
||||
}
|
||||
|
||||
const params: BaseModelParams = { maxTokens, temperature, reasoningEffort, reasoningBudget, verbosity }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta"
|
|||
import OpenAI from "openai"
|
||||
import type { GenerateContentConfig } from "@google/genai"
|
||||
|
||||
import type { ModelInfo, ProviderSettings } from "@roo-code/types"
|
||||
import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@roo-code/types"
|
||||
|
||||
import { shouldUseReasoningBudget, shouldUseReasoningEffort } from "../../shared/api"
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"]
|
|||
export type GetModelReasoningOptions = {
|
||||
model: ModelInfo
|
||||
reasoningBudget: number | undefined
|
||||
reasoningEffort: ReasoningEffort | undefined
|
||||
reasoningEffort: ReasoningEffortWithMinimal | undefined
|
||||
settings: ProviderSettings
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,9 @@ export const getOpenRouterReasoning = ({
|
|||
shouldUseReasoningBudget({ model, settings })
|
||||
? { max_tokens: reasoningBudget }
|
||||
: shouldUseReasoningEffort({ model, settings })
|
||||
? { effort: reasoningEffort }
|
||||
? reasoningEffort !== "minimal"
|
||||
? { effort: reasoningEffort }
|
||||
: undefined
|
||||
: undefined
|
||||
|
||||
export const getAnthropicReasoning = ({
|
||||
|
|
@ -50,8 +52,19 @@ export const getOpenAiReasoning = ({
|
|||
model,
|
||||
reasoningEffort,
|
||||
settings,
|
||||
}: GetModelReasoningOptions): OpenAiReasoningParams | undefined =>
|
||||
shouldUseReasoningEffort({ model, settings }) ? { reasoning_effort: reasoningEffort } : undefined
|
||||
}: 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
|
||||
}
|
||||
|
||||
return { reasoning_effort: reasoningEffort }
|
||||
}
|
||||
|
||||
export const getGeminiReasoning = ({
|
||||
model,
|
||||
|
|
|
|||
|
|
@ -252,6 +252,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
didCompleteReadingStream = false
|
||||
assistantMessageParser?: AssistantMessageParser
|
||||
isAssistantMessageParserEnabled = false
|
||||
private lastUsedInstructions?: string
|
||||
private skipPrevResponseIdOnce: boolean = false
|
||||
|
||||
constructor({
|
||||
provider,
|
||||
|
|
@ -824,6 +826,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
progressStatus?: ToolProgressStatus,
|
||||
options: {
|
||||
isNonInteractive?: boolean
|
||||
metadata?: Record<string, unknown>
|
||||
} = {},
|
||||
contextCondense?: ContextCondense,
|
||||
): Promise<undefined> {
|
||||
|
|
@ -861,6 +864,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
images,
|
||||
partial,
|
||||
contextCondense,
|
||||
metadata: options.metadata,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
|
@ -876,6 +880,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
lastMessage.images = images
|
||||
lastMessage.partial = false
|
||||
lastMessage.progressStatus = progressStatus
|
||||
if (options.metadata) {
|
||||
;(lastMessage as any).metadata = options.metadata
|
||||
}
|
||||
|
||||
// Instead of streaming partialMessage events, we do a save
|
||||
// and post like normal to persist to disk.
|
||||
|
|
@ -891,7 +898,15 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
this.lastMessageTs = sayTs
|
||||
}
|
||||
|
||||
await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, contextCondense })
|
||||
await this.addToClineMessages({
|
||||
ts: sayTs,
|
||||
type: "say",
|
||||
say: type,
|
||||
text,
|
||||
images,
|
||||
contextCondense,
|
||||
metadata: options.metadata,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1736,6 +1751,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
presentAssistantMessage(this)
|
||||
}
|
||||
|
||||
await this.persistGpt5Metadata(reasoningMessage)
|
||||
|
||||
updateApiReqMsg()
|
||||
await this.saveClineMessages()
|
||||
await this.providerRef.deref()?.postStateToWebview()
|
||||
|
|
@ -1954,6 +1971,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
Task.lastGlobalApiRequestTime = Date.now()
|
||||
|
||||
const systemPrompt = await this.getSystemPrompt()
|
||||
this.lastUsedInstructions = systemPrompt
|
||||
const { contextTokens } = this.getTokenUsage()
|
||||
|
||||
if (contextTokens) {
|
||||
|
|
@ -1992,6 +2010,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
if (truncateResult.error) {
|
||||
await this.say("condense_context_error", truncateResult.error)
|
||||
} else if (truncateResult.summary) {
|
||||
// A condense operation occurred; for the next GPT‑5 API call we should NOT
|
||||
// send previous_response_id so the request reflects the fresh condensed context.
|
||||
this.skipPrevResponseIdOnce = true
|
||||
|
||||
const { summary, cost, prevContextTokens, newContextTokens = 0 } = truncateResult
|
||||
const contextCondense: ContextCondense = { summary, cost, newContextTokens, prevContextTokens }
|
||||
await this.say(
|
||||
|
|
@ -2008,7 +2030,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
}
|
||||
|
||||
const messagesSinceLastSummary = getMessagesSinceLastSummary(this.apiConversationHistory)
|
||||
const cleanConversationHistory = maybeRemoveImageBlocks(messagesSinceLastSummary, this.api).map(
|
||||
let cleanConversationHistory = maybeRemoveImageBlocks(messagesSinceLastSummary, this.api).map(
|
||||
({ role, content }) => ({ role, content }),
|
||||
)
|
||||
|
||||
|
|
@ -2024,9 +2046,41 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
throw new Error("Auto-approval limit reached and user did not approve continuation")
|
||||
}
|
||||
|
||||
// Determine GPT‑5 previous_response_id from last persisted assistant turn (if available),
|
||||
// unless a condense just occurred (skip once after condense).
|
||||
let previousResponseId: string | undefined = undefined
|
||||
try {
|
||||
const modelId = this.api.getModel().id
|
||||
if (modelId && modelId.startsWith("gpt-5") && !this.skipPrevResponseIdOnce) {
|
||||
// Find the last assistant message that has a previous_response_id stored
|
||||
const idx = findLastIndex(
|
||||
this.clineMessages,
|
||||
(m) =>
|
||||
m.type === "say" &&
|
||||
(m as any).say === "text" &&
|
||||
(m as any).metadata?.gpt5?.previous_response_id,
|
||||
)
|
||||
if (idx !== -1) {
|
||||
// Use the previous_response_id from the last assistant message for this request
|
||||
previousResponseId = ((this.clineMessages[idx] as any).metadata.gpt5.previous_response_id ||
|
||||
undefined) as string | undefined
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// non-fatal
|
||||
}
|
||||
|
||||
const metadata: ApiHandlerCreateMessageMetadata = {
|
||||
mode: mode,
|
||||
taskId: this.taskId,
|
||||
...(previousResponseId ? { previousResponseId } : {}),
|
||||
// If a condense just occurred, explicitly suppress continuity fallback for the next call
|
||||
...(this.skipPrevResponseIdOnce ? { suppressPreviousResponseId: true } : {}),
|
||||
}
|
||||
|
||||
// Reset skip flag after applying (it only affects the immediate next call)
|
||||
if (this.skipPrevResponseIdOnce) {
|
||||
this.skipPrevResponseIdOnce = false
|
||||
}
|
||||
|
||||
const stream = this.api.createMessage(systemPrompt, cleanConversationHistory, metadata)
|
||||
|
|
@ -2172,6 +2226,35 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist GPT-5 per-turn metadata (previous_response_id, instructions, reasoning_summary)
|
||||
* onto the last complete assistant say("text") message.
|
||||
*/
|
||||
private async persistGpt5Metadata(reasoningMessage?: string): Promise<void> {
|
||||
try {
|
||||
const modelId = this.api.getModel().id
|
||||
if (!modelId || !modelId.startsWith("gpt-5")) return
|
||||
|
||||
const lastResponseId: string | undefined = (this.api as any)?.getLastResponseId?.()
|
||||
const idx = findLastIndex(
|
||||
this.clineMessages,
|
||||
(m) => m.type === "say" && (m as any).say === "text" && m.partial !== true,
|
||||
)
|
||||
if (idx !== -1) {
|
||||
const msg = this.clineMessages[idx] as any
|
||||
msg.metadata = msg.metadata ?? {}
|
||||
msg.metadata.gpt5 = {
|
||||
...(msg.metadata.gpt5 ?? {}),
|
||||
previous_response_id: lastResponseId,
|
||||
instructions: this.lastUsedInstructions,
|
||||
reasoning_summary: (reasoningMessage ?? "").trim() || undefined,
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Non-fatal error in metadata persistence
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
public get cwd() {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,15 @@ import {
|
|||
} from "@roo-code/types"
|
||||
|
||||
// ApiHandlerOptions
|
||||
|
||||
export type ApiHandlerOptions = Omit<ProviderSettings, "apiProvider">
|
||||
// Extend ProviderSettings (minus apiProvider) with handler-specific toggles.
|
||||
export type ApiHandlerOptions = Omit<ProviderSettings, "apiProvider"> & {
|
||||
/**
|
||||
* When true and using GPT‑5 Responses API, include reasoning.summary: "auto"
|
||||
* so the API returns reasoning summaries (we already parse and surface them).
|
||||
* Defaults to true; set to false to disable summaries.
|
||||
*/
|
||||
enableGpt5ReasoningSummary?: boolean
|
||||
}
|
||||
|
||||
// RouterName
|
||||
|
||||
|
|
|
|||
|
|
@ -576,6 +576,12 @@ const ApiOptions = ({
|
|||
if (value !== "custom-arn" && selectedProvider === "bedrock") {
|
||||
setApiConfigurationField("awsCustomArn", "")
|
||||
}
|
||||
|
||||
// Clear reasoning effort when switching models to allow the new model's default to take effect
|
||||
// This is especially important for GPT-5 models which default to "medium"
|
||||
if (selectedProvider === "openai-native") {
|
||||
setApiConfigurationField("reasoningEffort", undefined)
|
||||
}
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
|
|
@ -617,11 +623,14 @@ const ApiOptions = ({
|
|||
modelInfo={selectedModelInfo}
|
||||
/>
|
||||
|
||||
<Verbosity
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
modelInfo={selectedModelInfo}
|
||||
/>
|
||||
{/* Gate Verbosity UI by capability flag */}
|
||||
{selectedModelInfo?.supportsVerbosity && (
|
||||
<Verbosity
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
modelInfo={selectedModelInfo}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!fromWelcomeView && (
|
||||
<Collapsible open={isAdvancedSettingsOpen} onOpenChange={setIsAdvancedSettingsOpen}>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import { useEffect } from "react"
|
||||
import { Checkbox } from "vscrui"
|
||||
|
||||
import { type ProviderSettings, type ModelInfo, type ReasoningEffort, reasoningEfforts } from "@roo-code/types"
|
||||
import {
|
||||
type ProviderSettings,
|
||||
type ModelInfo,
|
||||
type ReasoningEffortWithMinimal,
|
||||
reasoningEfforts,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import {
|
||||
DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS,
|
||||
|
|
@ -27,10 +32,35 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
|
|||
const isGemini25Pro = selectedModelId && selectedModelId.includes("gemini-2.5-pro")
|
||||
const minThinkingTokens = isGemini25Pro ? GEMINI_25_PRO_MIN_THINKING_TOKENS : 1024
|
||||
|
||||
// Check if this is a GPT-5 model to show "minimal" option
|
||||
// Only show minimal for OpenAI Native provider GPT-5 models
|
||||
const isOpenAiNativeProvider = apiConfiguration.apiProvider === "openai-native"
|
||||
const isGpt5Model = isOpenAiNativeProvider && selectedModelId && selectedModelId.startsWith("gpt-5")
|
||||
// Add "minimal" option for GPT-5 models
|
||||
// Spread to convert readonly tuple into a mutable array, then expose as readonly for safety
|
||||
const baseEfforts = [...reasoningEfforts] as ReasoningEffortWithMinimal[]
|
||||
const availableReasoningEfforts: ReadonlyArray<ReasoningEffortWithMinimal> = isGpt5Model
|
||||
? (["minimal", ...baseEfforts] as ReasoningEffortWithMinimal[])
|
||||
: baseEfforts
|
||||
|
||||
// Default reasoning effort - use model's default if available
|
||||
// GPT-5 models have "medium" as their default in the model configuration
|
||||
const modelDefaultReasoningEffort = modelInfo?.reasoningEffort as ReasoningEffortWithMinimal | undefined
|
||||
const defaultReasoningEffort: ReasoningEffortWithMinimal = modelDefaultReasoningEffort || "medium"
|
||||
const currentReasoningEffort: ReasoningEffortWithMinimal =
|
||||
(apiConfiguration.reasoningEffort as ReasoningEffortWithMinimal | undefined) || defaultReasoningEffort
|
||||
|
||||
const isReasoningBudgetSupported = !!modelInfo && modelInfo.supportsReasoningBudget
|
||||
const isReasoningBudgetRequired = !!modelInfo && modelInfo.requiredReasoningBudget
|
||||
const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort
|
||||
|
||||
// Set default reasoning effort when model supports it and no value is set
|
||||
useEffect(() => {
|
||||
if (isReasoningEffortSupported && !apiConfiguration.reasoningEffort && defaultReasoningEffort) {
|
||||
setApiConfigurationField("reasoningEffort", defaultReasoningEffort)
|
||||
}
|
||||
}, [isReasoningEffortSupported, apiConfiguration.reasoningEffort, defaultReasoningEffort, setApiConfigurationField])
|
||||
|
||||
const enableReasoningEffort = apiConfiguration.enableReasoningEffort
|
||||
const customMaxOutputTokens = apiConfiguration.modelMaxTokens || DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS
|
||||
const customMaxThinkingTokens =
|
||||
|
|
@ -109,13 +139,21 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
|
|||
<label className="block font-medium mb-1">{t("settings:providers.reasoningEffort.label")}</label>
|
||||
</div>
|
||||
<Select
|
||||
value={apiConfiguration.reasoningEffort}
|
||||
onValueChange={(value) => setApiConfigurationField("reasoningEffort", value as ReasoningEffort)}>
|
||||
value={currentReasoningEffort}
|
||||
onValueChange={(value: ReasoningEffortWithMinimal) => {
|
||||
setApiConfigurationField("reasoningEffort", value)
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
<SelectValue
|
||||
placeholder={
|
||||
currentReasoningEffort
|
||||
? t(`settings:providers.reasoningEffort.${currentReasoningEffort}`)
|
||||
: t("settings:common.select")
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{reasoningEfforts.map((value) => (
|
||||
{availableReasoningEfforts.map((value) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{t(`settings:providers.reasoningEffort.${value}`)}
|
||||
</SelectItem>
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ca/settings.json
generated
1
webview-ui/src/i18n/locales/ca/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Esforç de raonament del model",
|
||||
"minimal": "Mínim (el més ràpid)",
|
||||
"high": "Alt",
|
||||
"medium": "Mitjà",
|
||||
"low": "Baix"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/de/settings.json
generated
1
webview-ui/src/i18n/locales/de/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Modell-Denkaufwand",
|
||||
"minimal": "Minimal (schnellste)",
|
||||
"high": "Hoch",
|
||||
"medium": "Mittel",
|
||||
"low": "Niedrig"
|
||||
|
|
|
|||
|
|
@ -428,9 +428,10 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Model Reasoning Effort",
|
||||
"high": "High",
|
||||
"minimal": "Minimal (Fastest)",
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"low": "Low"
|
||||
"high": "High"
|
||||
},
|
||||
"verbosity": {
|
||||
"label": "Output Verbosity",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/es/settings.json
generated
1
webview-ui/src/i18n/locales/es/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Esfuerzo de razonamiento del modelo",
|
||||
"minimal": "Mínimo (el más rápido)",
|
||||
"high": "Alto",
|
||||
"medium": "Medio",
|
||||
"low": "Bajo"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/fr/settings.json
generated
1
webview-ui/src/i18n/locales/fr/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Effort de raisonnement du modèle",
|
||||
"minimal": "Minimal (le plus rapide)",
|
||||
"high": "Élevé",
|
||||
"medium": "Moyen",
|
||||
"low": "Faible"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/hi/settings.json
generated
1
webview-ui/src/i18n/locales/hi/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "मॉडल तर्क प्रयास",
|
||||
"minimal": "न्यूनतम (सबसे तेज़)",
|
||||
"high": "उच्च",
|
||||
"medium": "मध्यम",
|
||||
"low": "निम्न"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/id/settings.json
generated
1
webview-ui/src/i18n/locales/id/settings.json
generated
|
|
@ -433,6 +433,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Upaya Reasoning Model",
|
||||
"minimal": "Minimal (Tercepat)",
|
||||
"high": "Tinggi",
|
||||
"medium": "Sedang",
|
||||
"low": "Rendah"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/it/settings.json
generated
1
webview-ui/src/i18n/locales/it/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Sforzo di ragionamento del modello",
|
||||
"minimal": "Minimo (più veloce)",
|
||||
"high": "Alto",
|
||||
"medium": "Medio",
|
||||
"low": "Basso"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ja/settings.json
generated
1
webview-ui/src/i18n/locales/ja/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "モデル推論の労力",
|
||||
"minimal": "最小 (最速)",
|
||||
"high": "高",
|
||||
"medium": "中",
|
||||
"low": "低"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ko/settings.json
generated
1
webview-ui/src/i18n/locales/ko/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "모델 추론 노력",
|
||||
"minimal": "최소 (가장 빠름)",
|
||||
"high": "높음",
|
||||
"medium": "중간",
|
||||
"low": "낮음"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/nl/settings.json
generated
1
webview-ui/src/i18n/locales/nl/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Model redeneervermogen",
|
||||
"minimal": "Minimaal (Snelst)",
|
||||
"high": "Hoog",
|
||||
"medium": "Middel",
|
||||
"low": "Laag"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/pl/settings.json
generated
1
webview-ui/src/i18n/locales/pl/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Wysiłek rozumowania modelu",
|
||||
"minimal": "Minimalny (najszybszy)",
|
||||
"high": "Wysoki",
|
||||
"medium": "Średni",
|
||||
"low": "Niski"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
1
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Esforço de raciocínio do modelo",
|
||||
"minimal": "Mínimo (mais rápido)",
|
||||
"high": "Alto",
|
||||
"medium": "Médio",
|
||||
"low": "Baixo"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ru/settings.json
generated
1
webview-ui/src/i18n/locales/ru/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Усилия по рассуждению модели",
|
||||
"minimal": "Минимальный (самый быстрый)",
|
||||
"high": "Высокие",
|
||||
"medium": "Средние",
|
||||
"low": "Низкие"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/tr/settings.json
generated
1
webview-ui/src/i18n/locales/tr/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Model Akıl Yürütme Çabası",
|
||||
"minimal": "Minimal (en hızlı)",
|
||||
"high": "Yüksek",
|
||||
"medium": "Orta",
|
||||
"low": "Düşük"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/vi/settings.json
generated
1
webview-ui/src/i18n/locales/vi/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Nỗ lực suy luận của mô hình",
|
||||
"minimal": "Tối thiểu (nhanh nhất)",
|
||||
"high": "Cao",
|
||||
"medium": "Trung bình",
|
||||
"low": "Thấp"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
1
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "模型推理强度",
|
||||
"minimal": "最小 (最快)",
|
||||
"high": "高",
|
||||
"medium": "中",
|
||||
"low": "低"
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
1
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
|
|
@ -429,6 +429,7 @@
|
|||
},
|
||||
"reasoningEffort": {
|
||||
"label": "模型推理強度",
|
||||
"minimal": "最小 (最快)",
|
||||
"high": "高",
|
||||
"medium": "中",
|
||||
"low": "低"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue