feat: add GPT-5 model support (#6819)

* feat: add GPT-5 model support

- Added GPT-5 models (gpt-5-2025-08-07, gpt-5-mini-2025-08-07, gpt-5-nano-2025-08-07)
- Added nectarine-alpha-new-reasoning-effort-2025-07-25 experimental model
- Set gpt-5-2025-08-07 as default OpenAI Native model
- Implemented GPT-5 specific handling with streaming and reasoning effort support

* fix: remove hardcoded temperature from GPT-5 handler

- Updated handleGPT5Message to use configurable temperature
- Now uses this.options.modelTemperature ?? OPENAI_NATIVE_DEFAULT_TEMPERATURE
- Maintains consistency with other model handlers

* feat: add reasoning effort support for all OpenAI models

* fix: update test to expect new default model gpt-5-2025-08-07

* feat: increase GPT-5 models context window to 400,000

- Updated context window from 256,000 to 400,000 for gpt-5-2025-08-07
- Updated context window from 256,000 to 400,000 for gpt-5-mini-2025-08-07
- Updated context window from 256,000 to 400,000 for gpt-5-nano-2025-08-07
- Updated context window from 256,000 to 400,000 for nectarine-alpha-new-reasoning-effort-2025-07-25

As requested by @daniel-lxs in PR #6819

* revert: remove GPT-5 models, keep only nectarine experimental model

- Removed gpt-5-2025-08-07, gpt-5-mini-2025-08-07, gpt-5-nano-2025-08-07
- Kept nectarine-alpha-new-reasoning-effort-2025-07-25 experimental model
- Reverted default model back to gpt-4o
- Updated tests and changeset accordingly

* feat: add GPT-5 models with updated context windows

- Added gpt-5-2025-08-07, gpt-5-mini-2025-08-07, gpt-5-nano-2025-08-07 models
- All GPT-5 models configured with 400,000 context window
- Updated nectarine model context window to 256,000
- All models configured with reasoning effort support
- Set gpt-5-2025-08-07 as default OpenAI Native model
- Added GPT-5 model handling in openai-native.ts
- Updated tests to reflect new default model

* fix: restore reasoning effort support for o1 series models

- Added supportsReasoningEffort: true to o1, o1-preview, and o1-mini models
- This restores the ability to use reasoning effort parameters with these models
- The existing code in openai-native.ts already handles reasoning effort correctly

* Revert "fix: restore reasoning effort support for o1 series models"

This reverts commit 7251237ae8.

* fix: restore reasoning effort support for o3 and o4 models

- Added supportsReasoningEffort: true to o3, o3-high, o3-low models
- Added supportsReasoningEffort: true to o4-mini, o4-mini-high, o4-mini-low models
- Added supportsReasoningEffort: true to o3-mini, o3-mini-high, o3-mini-low models
- These models have both supportsReasoningEffort and reasoningEffort properties

* Revert "fix: restore reasoning effort support for o3 and o4 models"

This reverts commit a75a2b8a69.

* fix: restore reasoning effort support for o3 and o4 models

- Added supportsReasoningEffort: true to o3, o3-high, o3-low models
- Added supportsReasoningEffort: true to o4-mini, o4-mini-high, o4-mini-low models
- Added supportsReasoningEffort: true to o3-mini, o3-mini-high, o3-mini-low models

* fix: adjust reasoning effort support for o3/o4 models

- Keep supportsReasoningEffort only for base o3, o4-mini, and o3-mini models
- Remove supportsReasoningEffort from -high and -low variants
- Position supportsReasoningEffort right before reasoningEffort property

* fix: remove nectarine experimental model

- Removed nectarine-alpha-new-reasoning-effort-2025-07-25 from openai.ts
- Removed nectarine handling from openai-native.ts (renamed to handleGpt5Message)
- Removed associated changeset file
- Keep GPT-5 models with developer role handling

* feat: implement full GPT-5 support with verbosity and minimal reasoning

- Add all three GPT-5 models with accurate pricing (.25/0 for gpt-5, /bin/sh.25/ for mini, /bin/sh.05//bin/sh.40 for nano)
- Implement verbosity control (low/medium/high) that passes through to API
- Add minimal reasoning effort support for fastest response times
- GPT-5 models use developer role instead of system role
- Set gpt-5-2025-08-07 as default OpenAI Native model
- Add Responses API infrastructure for future migration
- Update tests to verify all GPT-5 features
- All 27 tests passing

Note: UI controls for verbosity still need to be added in a follow-up PR

* feat: add verbosity setting for GPT-5 models

- Add VerbosityLevel type definition to model types
- Add verbosity field to ProviderSettings schema
- Create Verbosity UI component for settings
- Add verbosity labels to all localization files
- Integrate verbosity handling in model parameters transformation
- Update OpenAI native handler to support verbosity for GPT-5
- Add comprehensive tests for verbosity setting
- Update existing GPT-5 tests to use verbosity from settings

* Delete .roorules

---------

Co-authored-by: Roo Code <roomote@roocode.com>
Co-authored-by: hannesrudolph <hrudolph@gmail.com>
Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
Co-authored-by: Daniel <57051444+daniel-lxs@users.noreply.github.com>
This commit is contained in:
roomote[bot] 2025-08-07 16:57:12 -04:00 committed by GitHub
parent 72668fef8d
commit dc57552ade
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 741 additions and 12 deletions

View file

@ -10,6 +10,16 @@ export const reasoningEffortsSchema = z.enum(reasoningEfforts)
export type ReasoningEffort = z.infer<typeof reasoningEffortsSchema>
/**
* Verbosity
*/
export const verbosityLevels = ["low", "medium", "high"] as const
export const verbosityLevelsSchema = z.enum(verbosityLevels)
export type VerbosityLevel = z.infer<typeof verbosityLevelsSchema>
/**
* ModelParameter
*/

View file

@ -1,6 +1,6 @@
import { z } from "zod"
import { reasoningEffortsSchema, modelInfoSchema } from "./model.js"
import { reasoningEffortsSchema, verbosityLevelsSchema, modelInfoSchema } from "./model.js"
import { codebaseIndexProviderSchema } from "./codebase-index.js"
/**
@ -79,6 +79,9 @@ const baseProviderSettingsSchema = z.object({
reasoningEffort: reasoningEffortsSchema.optional(),
modelMaxTokens: z.number().optional(),
modelMaxThinkingTokens: z.number().optional(),
// Model verbosity.
verbosity: verbosityLevelsSchema.optional(),
})
// Several of the providers share common model config properties.

View file

@ -3,9 +3,42 @@ import type { ModelInfo } from "../model.js"
// https://openai.com/api/pricing/
export type OpenAiNativeModelId = keyof typeof openAiNativeModels
export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-4.1"
export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5-2025-08-07"
export const openAiNativeModels = {
"gpt-5-2025-08-07": {
maxTokens: 128000,
contextWindow: 400000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoningEffort: true,
inputPrice: 1.25,
outputPrice: 10.0,
cacheReadsPrice: 0.13,
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: true,
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",
},
"gpt-5-nano-2025-08-07": {
maxTokens: 128000,
contextWindow: 400000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoningEffort: true,
inputPrice: 0.05,
outputPrice: 0.4,
cacheReadsPrice: 0.01,
description: "GPT-5 Nano: Fastest, most cost-efficient version of GPT-5",
},
"gpt-4.1": {
maxTokens: 32_768,
contextWindow: 1_047_576,

View file

@ -455,8 +455,162 @@ describe("OpenAiNativeHandler", () => {
openAiNativeApiKey: "test-api-key",
})
const modelInfo = handlerWithoutModel.getModel()
expect(modelInfo.id).toBe("gpt-4.1") // Default model
expect(modelInfo.id).toBe("gpt-5-2025-08-07") // Default model
expect(modelInfo.info).toBeDefined()
})
})
describe("GPT-5 models", () => {
it("should handle GPT-5 model with developer role", async () => {
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-2025-08-07",
})
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Verify developer role is used for GPT-5 with default parameters
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "gpt-5-2025-08-07",
messages: [{ role: "developer", content: expect.stringContaining(systemPrompt) }],
stream: true,
stream_options: { include_usage: true },
reasoning_effort: "minimal", // Default for GPT-5
verbosity: "medium", // Default verbosity
}),
)
})
it("should handle GPT-5-mini model", async () => {
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-mini-2025-08-07",
})
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "gpt-5-mini-2025-08-07",
messages: [{ role: "developer", content: expect.stringContaining(systemPrompt) }],
stream: true,
stream_options: { include_usage: true },
reasoning_effort: "minimal", // Default for GPT-5
verbosity: "medium", // Default verbosity
}),
)
})
it("should handle GPT-5-nano model", async () => {
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-nano-2025-08-07",
})
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "gpt-5-nano-2025-08-07",
messages: [{ role: "developer", content: expect.stringContaining(systemPrompt) }],
stream: true,
stream_options: { include_usage: true },
reasoning_effort: "minimal", // Default for GPT-5
verbosity: "medium", // Default verbosity
}),
)
})
it("should support verbosity control for GPT-5", async () => {
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-2025-08-07",
verbosity: "low", // Set verbosity through options
})
// Create a message to verify verbosity is passed
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Verify that verbosity is passed in the request
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "gpt-5-2025-08-07",
messages: expect.any(Array),
stream: true,
stream_options: { include_usage: true },
verbosity: "low",
}),
)
})
it("should support minimal reasoning effort for GPT-5", async () => {
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-2025-08-07",
reasoningEffort: "low",
})
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// With low reasoning effort, the model should pass it through
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "gpt-5-2025-08-07",
messages: expect.any(Array),
stream: true,
stream_options: { include_usage: true },
reasoning_effort: "low",
verbosity: "medium", // Default verbosity
}),
)
})
it("should support both verbosity and reasoning effort together for GPT-5", async () => {
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-2025-08-07",
verbosity: "high", // Set verbosity through options
reasoningEffort: "low", // Set reasoning effort
})
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Verify both parameters are passed
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "gpt-5-2025-08-07",
messages: expect.any(Array),
stream: true,
stream_options: { include_usage: true },
reasoning_effort: "low",
verbosity: "high",
}),
)
})
})
})

View file

@ -7,6 +7,8 @@ import {
OpenAiNativeModelId,
openAiNativeModels,
OPENAI_NATIVE_DEFAULT_TEMPERATURE,
type ReasoningEffort,
type VerbosityLevel,
} from "@roo-code/types"
import type { ApiHandlerOptions } from "../../shared/api"
@ -22,6 +24,32 @@ import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from ".
export type OpenAiNativeModel = ReturnType<OpenAiNativeHandler["getModel"]>
// GPT-5 specific types for Responses API
type ReasoningEffortWithMinimal = ReasoningEffort | "minimal"
interface GPT5ResponsesAPIParams {
model: string
input: string
reasoning?: {
effort: ReasoningEffortWithMinimal
}
text?: {
verbosity: VerbosityLevel
}
}
interface GPT5ResponseChunk {
type: "text" | "reasoning" | "usage"
text?: string
reasoning?: string
usage?: {
input_tokens: number
output_tokens: number
reasoning_tokens?: number
total_tokens: number
}
}
export class OpenAiNativeHandler extends BaseProvider implements SingleCompletionHandler {
protected options: ApiHandlerOptions
private client: OpenAI
@ -53,6 +81,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
yield* this.handleReasonerMessage(model, id, systemPrompt, messages)
} else if (model.id.startsWith("o1")) {
yield* this.handleO1FamilyMessage(model, systemPrompt, messages)
} else if (this.isGpt5Model(model.id)) {
yield* this.handleGpt5Message(model, systemPrompt, messages)
} else {
yield* this.handleDefaultModelMessage(model, systemPrompt, messages)
}
@ -66,6 +96,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
// o1 supports developer prompt with formatting
// o1-preview and o1-mini only support user messages
const isOriginalO1 = model.id === "o1"
const { reasoning } = this.getModel()
const response = await this.client.chat.completions.create({
model: model.id,
messages: [
@ -77,6 +109,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
],
stream: true,
stream_options: { include_usage: true },
...(reasoning && reasoning),
})
yield* this.handleStreamResponse(response, model)
@ -112,15 +145,214 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
): ApiStream {
const stream = await this.client.chat.completions.create({
const { reasoning, verbosity } = this.getModel()
// Prepare the request parameters
const params: any = {
model: model.id,
temperature: this.options.modelTemperature ?? OPENAI_NATIVE_DEFAULT_TEMPERATURE,
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
stream: true,
stream_options: { include_usage: true },
})
...(reasoning && reasoning),
}
yield* this.handleStreamResponse(stream, model)
// Add verbosity if supported (for future GPT-5 models)
if (verbosity && model.id.startsWith("gpt-5")) {
params.verbosity = verbosity
}
const stream = await this.client.chat.completions.create(params)
if (typeof (stream as any)[Symbol.asyncIterator] !== "function") {
throw new Error(
"OpenAI SDK did not return an AsyncIterable for streaming response. Please check SDK version and usage.",
)
}
yield* this.handleStreamResponse(
stream as unknown as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>,
model,
)
}
private async *handleGpt5Message(
model: OpenAiNativeModel,
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
): ApiStream {
// GPT-5 uses the Responses API, not Chat Completions
// We need to format the input as a single string combining system prompt and messages
const formattedInput = this.formatInputForResponsesAPI(systemPrompt, messages)
// Get reasoning effort, supporting the new "minimal" option for GPT-5
const reasoningEffort = this.getGpt5ReasoningEffort(model)
// Get verbosity from model settings, default to "medium" if not specified
const verbosity = model.verbosity || "medium"
// Prepare the request parameters for Responses API
const params: GPT5ResponsesAPIParams = {
model: model.id,
input: formattedInput,
...(reasoningEffort && {
reasoning: {
effort: reasoningEffort,
},
}),
text: {
verbosity: verbosity,
},
}
// Since the OpenAI SDK doesn't yet support the Responses API,
// we'll make a direct HTTP request
const response = await this.makeGpt5ResponsesAPIRequest(params, model)
yield* this.handleGpt5StreamResponse(response, model)
}
private formatInputForResponsesAPI(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
// Format the conversation for the Responses API's single input field
let formattedInput = `System: ${systemPrompt}\n\n`
for (const message of messages) {
const role = message.role === "user" ? "User" : "Assistant"
const content =
typeof message.content === "string"
? message.content
: message.content.map((c) => (c.type === "text" ? c.text : "[image]")).join(" ")
formattedInput += `${role}: ${content}\n\n`
}
return formattedInput.trim()
}
private getGpt5ReasoningEffort(model: OpenAiNativeModel): ReasoningEffortWithMinimal | undefined {
const { reasoning } = model
// Check if reasoning effort is configured
if (reasoning && "reasoning_effort" in reasoning) {
const effort = reasoning.reasoning_effort
// Support the new "minimal" effort level for GPT-5
if (effort === "low" || effort === "medium" || effort === "high") {
return effort
}
}
// Default to "minimal" for GPT-5 models when not specified
// This provides fastest time-to-first-token as per documentation
return "minimal"
}
private async makeGpt5ResponsesAPIRequest(
params: GPT5ResponsesAPIParams,
model: OpenAiNativeModel,
): Promise<AsyncIterable<GPT5ResponseChunk>> {
// The OpenAI SDK doesn't have direct support for the Responses API yet,
// but we can access it through the underlying client request method if available.
// For now, we'll use the Chat Completions API with GPT-5 specific formatting
// to maintain compatibility while the Responses API SDK support is being added.
// Convert Responses API params to Chat Completions format
// GPT-5 models use "developer" role for system messages
const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [{ role: "developer", content: params.input }]
// Build the request parameters
const requestParams: any = {
model: params.model,
messages,
stream: true,
stream_options: { include_usage: true },
}
// Add reasoning effort if specified (supporting "minimal" for GPT-5)
if (params.reasoning?.effort) {
if (params.reasoning.effort === "minimal") {
// For minimal effort, we pass "minimal" as the reasoning_effort
requestParams.reasoning_effort = "minimal"
} else {
requestParams.reasoning_effort = params.reasoning.effort
}
}
// Add verbosity control for GPT-5 models
// According to the docs, Chat Completions API also supports verbosity parameter
if (params.text?.verbosity) {
requestParams.verbosity = params.text.verbosity
}
const stream = (await this.client.chat.completions.create(
requestParams,
)) as unknown as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>
// Convert the stream to GPT-5 response format
return this.convertChatStreamToGpt5Format(stream)
}
private async *convertChatStreamToGpt5Format(
stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>,
): AsyncIterable<GPT5ResponseChunk> {
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (delta?.content) {
yield {
type: "text",
text: delta.content,
}
}
if (chunk.usage) {
yield {
type: "usage",
usage: {
input_tokens: chunk.usage.prompt_tokens || 0,
output_tokens: chunk.usage.completion_tokens || 0,
total_tokens: chunk.usage.total_tokens || 0,
},
}
}
}
}
private async *handleGpt5StreamResponse(
stream: AsyncIterable<GPT5ResponseChunk>,
model: OpenAiNativeModel,
): ApiStream {
for await (const chunk of stream) {
if (chunk.type === "text" && chunk.text) {
yield {
type: "text",
text: chunk.text,
}
} else if (chunk.type === "usage" && chunk.usage) {
const inputTokens = chunk.usage.input_tokens
const outputTokens = chunk.usage.output_tokens
const cacheReadTokens = 0
const cacheWriteTokens = 0
const totalCost = calculateApiCostOpenAI(
model.info,
inputTokens,
outputTokens,
cacheWriteTokens,
cacheReadTokens,
)
yield {
type: "usage",
inputTokens,
outputTokens,
cacheWriteTokens,
cacheReadTokens,
totalCost,
}
}
}
}
private isGpt5Model(modelId: string): boolean {
return modelId.startsWith("gpt-5")
}
private async *handleStreamResponse(
@ -177,23 +409,39 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
defaultTemperature: OPENAI_NATIVE_DEFAULT_TEMPERATURE,
})
// For GPT-5 models, ensure we support minimal reasoning effort
if (this.isGpt5Model(id) && params.reasoning) {
// Allow "minimal" effort for GPT-5 models
const effort = this.options.reasoningEffort
if (effort === "low" || effort === "medium" || effort === "high") {
params.reasoning.reasoning_effort = effort
}
}
// The o3 models are named like "o3-mini-[reasoning-effort]", which are
// not valid model ids, so we need to strip the suffix.
return { id: id.startsWith("o3-mini") ? "o3-mini" : id, info, ...params }
return { id: id.startsWith("o3-mini") ? "o3-mini" : id, info, ...params, verbosity: params.verbosity }
}
async completePrompt(prompt: string): Promise<string> {
try {
const { id, temperature, reasoning } = this.getModel()
const { id, temperature, reasoning, verbosity } = this.getModel()
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming & {
verbosity?: VerbosityLevel
} = {
model: id,
messages: [{ role: "user", content: prompt }],
temperature,
...(reasoning && reasoning),
}
const response = await this.client.chat.completions.create(params)
// Add verbosity for GPT-5 models
if (this.isGpt5Model(id) && verbosity) {
params.verbosity = verbosity
}
const response = await this.client.chat.completions.create(params as any)
return response.choices[0]?.message.content || ""
} catch (error) {
if (error instanceof Error) {

View file

@ -788,4 +788,101 @@ describe("getModelParams", () => {
expect(result.reasoning).toBeUndefined()
})
})
describe("Verbosity settings", () => {
it("should include verbosity when specified in settings", () => {
const model: ModelInfo = {
...baseModel,
}
const result = getModelParams({
...openaiParams,
settings: { verbosity: "low" },
model,
})
expect(result.verbosity).toBe("low")
})
it("should handle medium verbosity", () => {
const model: ModelInfo = {
...baseModel,
}
const result = getModelParams({
...openaiParams,
settings: { verbosity: "medium" },
model,
})
expect(result.verbosity).toBe("medium")
})
it("should handle high verbosity", () => {
const model: ModelInfo = {
...baseModel,
}
const result = getModelParams({
...openaiParams,
settings: { verbosity: "high" },
model,
})
expect(result.verbosity).toBe("high")
})
it("should return undefined verbosity when not specified", () => {
const model: ModelInfo = {
...baseModel,
}
const result = getModelParams({
...openaiParams,
settings: {},
model,
})
expect(result.verbosity).toBeUndefined()
})
it("should include verbosity alongside reasoning settings", () => {
const model: ModelInfo = {
...baseModel,
supportsReasoningEffort: true,
}
const result = getModelParams({
...openaiParams,
settings: {
reasoningEffort: "high",
verbosity: "low",
},
model,
})
expect(result.reasoningEffort).toBe("high")
expect(result.verbosity).toBe("low")
expect(result.reasoning).toEqual({ reasoning_effort: "high" })
})
it("should include verbosity with reasoning budget models", () => {
const model: ModelInfo = {
...baseModel,
supportsReasoningBudget: true,
}
const result = getModelParams({
...anthropicParams,
settings: {
enableReasoningEffort: true,
verbosity: "high",
},
model,
})
expect(result.verbosity).toBe("high")
expect(result.reasoningBudget).toBe(8192) // Default thinking tokens
})
})
})

View file

@ -1,4 +1,9 @@
import { type ModelInfo, type ProviderSettings, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types"
import {
type ModelInfo,
type ProviderSettings,
type VerbosityLevel,
ANTHROPIC_DEFAULT_MAX_TOKENS,
} from "@roo-code/types"
import {
DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS,
@ -35,6 +40,7 @@ type BaseModelParams = {
temperature: number | undefined
reasoningEffort: "low" | "medium" | "high" | undefined
reasoningBudget: number | undefined
verbosity: VerbosityLevel | undefined
}
type AnthropicModelParams = {
@ -76,6 +82,7 @@ export function getModelParams({
modelMaxThinkingTokens: customMaxThinkingTokens,
modelTemperature: customTemperature,
reasoningEffort: customReasoningEffort,
verbosity: customVerbosity,
} = settings
// Use the centralized logic for computing maxTokens
@ -89,6 +96,7 @@ export function getModelParams({
let temperature = customTemperature ?? 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
@ -123,7 +131,7 @@ export function getModelParams({
reasoningEffort = customReasoningEffort ?? model.reasoningEffort
}
const params: BaseModelParams = { maxTokens, temperature, reasoningEffort, reasoningBudget }
const params: BaseModelParams = { maxTokens, temperature, reasoningEffort, reasoningBudget, verbosity }
if (format === "anthropic") {
return {

View file

@ -91,6 +91,7 @@ import { inputEventTransform, noTransform } from "./transforms"
import { ModelInfoView } from "./ModelInfoView"
import { ApiErrorMessage } from "./ApiErrorMessage"
import { ThinkingBudget } from "./ThinkingBudget"
import { Verbosity } from "./Verbosity"
import { DiffSettingsControl } from "./DiffSettingsControl"
import { TodoListSettingsControl } from "./TodoListSettingsControl"
import { TemperatureControl } from "./TemperatureControl"
@ -616,6 +617,12 @@ const ApiOptions = ({
modelInfo={selectedModelInfo}
/>
<Verbosity
apiConfiguration={apiConfiguration}
setApiConfigurationField={setApiConfigurationField}
modelInfo={selectedModelInfo}
/>
{!fromWelcomeView && (
<Collapsible open={isAdvancedSettingsOpen} onOpenChange={setIsAdvancedSettingsOpen}>
<CollapsibleTrigger className="flex items-center gap-1 w-full cursor-pointer hover:opacity-80 mb-2">

View file

@ -0,0 +1,43 @@
import { type ProviderSettings, type ModelInfo, type VerbosityLevel, verbosityLevels } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui"
interface VerbosityProps {
apiConfiguration: ProviderSettings
setApiConfigurationField: <K extends keyof ProviderSettings>(field: K, value: ProviderSettings[K]) => void
modelInfo?: ModelInfo
}
export const Verbosity = ({ apiConfiguration, setApiConfigurationField, modelInfo }: VerbosityProps) => {
const { t } = useAppTranslation()
// For now, we'll show verbosity for all models, but this can be restricted later
// based on model capabilities (e.g., only for GPT-5 models)
if (!modelInfo) {
return null
}
return (
<div className="flex flex-col gap-1" data-testid="verbosity">
<div className="flex justify-between items-center">
<label className="block font-medium mb-1">{t("settings:providers.verbosity.label")}</label>
</div>
<Select
value={apiConfiguration.verbosity || "medium"}
onValueChange={(value) => setApiConfigurationField("verbosity", value as VerbosityLevel)}>
<SelectTrigger className="w-full">
<SelectValue placeholder={t("settings:common.select")} />
</SelectTrigger>
<SelectContent>
{verbosityLevels.map((value) => (
<SelectItem key={value} value={value}>
{t(`settings:providers.verbosity.${value}`)}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="text-xs text-muted-foreground mt-1">{t("settings:providers.verbosity.description")}</div>
</div>
)
}

View file

@ -433,6 +433,13 @@
"medium": "Mitjà",
"low": "Baix"
},
"verbosity": {
"label": "Verbositat de la sortida",
"high": "Alta",
"medium": "Mitjana",
"low": "Baixa",
"description": "Controla el nivell de detall de les respostes del model. La verbositat baixa produeix respostes concises, mentre que la verbositat alta proporciona explicacions exhaustives."
},
"setReasoningLevel": "Activa l'esforç de raonament",
"claudeCode": {
"pathLabel": "Ruta del Codi Claude",

View file

@ -433,6 +433,13 @@
"medium": "Mittel",
"low": "Niedrig"
},
"verbosity": {
"label": "Ausgabe-Ausführlichkeit",
"high": "Hoch",
"medium": "Mittel",
"low": "Niedrig",
"description": "Steuert, wie detailliert die Antworten des Modells sind. Niedrige Ausführlichkeit erzeugt knappe Antworten, während hohe Ausführlichkeit gründliche Erklärungen liefert."
},
"setReasoningLevel": "Denkaufwand aktivieren",
"claudeCode": {
"pathLabel": "Claude-Code-Pfad",

View file

@ -432,6 +432,13 @@
"medium": "Medium",
"low": "Low"
},
"verbosity": {
"label": "Output Verbosity",
"high": "High",
"medium": "Medium",
"low": "Low",
"description": "Controls how detailed the model's responses are. Low verbosity produces concise answers, while high verbosity provides thorough explanations."
},
"setReasoningLevel": "Enable Reasoning Effort",
"claudeCode": {
"pathLabel": "Claude Code Path",

View file

@ -433,6 +433,13 @@
"medium": "Medio",
"low": "Bajo"
},
"verbosity": {
"label": "Verbosidad de la salida",
"high": "Alta",
"medium": "Media",
"low": "Baja",
"description": "Controla qué tan detalladas son las respuestas del modelo. La verbosidad baja produce respuestas concisas, mientras que la verbosidad alta proporciona explicaciones exhaustivas."
},
"setReasoningLevel": "Habilitar esfuerzo de razonamiento",
"claudeCode": {
"pathLabel": "Ruta de Claude Code",

View file

@ -433,6 +433,13 @@
"medium": "Moyen",
"low": "Faible"
},
"verbosity": {
"label": "Verbosité de la sortie",
"high": "Élevée",
"medium": "Moyenne",
"low": "Faible",
"description": "Contrôle le niveau de détail des réponses du modèle. Une faible verbosité produit des réponses concises, tandis qu'une verbosité élevée fournit des explications approfondies."
},
"setReasoningLevel": "Activer l'effort de raisonnement",
"claudeCode": {
"pathLabel": "Chemin du code Claude",

View file

@ -433,6 +433,13 @@
"medium": "मध्यम",
"low": "निम्न"
},
"verbosity": {
"label": "आउटपुट वर्बोसिटी",
"high": "उच्च",
"medium": "मध्यम",
"low": "कम",
"description": "मॉडल की प्रतिक्रियाएं कितनी विस्तृत हैं, इसे नियंत्रित करता है। कम वर्बोसिटी संक्षिप्त उत्तर देती है, जबकि उच्च वर्बोसिटी विस्तृत स्पष्टीकरण प्रदान करती है।"
},
"setReasoningLevel": "तर्क प्रयास सक्षम करें",
"claudeCode": {
"pathLabel": "क्लाउड कोड पथ",

View file

@ -437,6 +437,13 @@
"medium": "Sedang",
"low": "Rendah"
},
"verbosity": {
"label": "Verbositas Output",
"high": "Tinggi",
"medium": "Sedang",
"low": "Rendah",
"description": "Mengontrol seberapa detail respons model. Verbositas rendah menghasilkan jawaban singkat, sedangkan verbositas tinggi memberikan penjelasan menyeluruh."
},
"setReasoningLevel": "Aktifkan Upaya Reasoning",
"claudeCode": {
"pathLabel": "Jalur Kode Claude",

View file

@ -433,6 +433,13 @@
"medium": "Medio",
"low": "Basso"
},
"verbosity": {
"label": "Verbosity dell'output",
"high": "Alta",
"medium": "Media",
"low": "Bassa",
"description": "Controlla il livello di dettaglio delle risposte del modello. Una verbosity bassa produce risposte concise, mentre una verbosity alta fornisce spiegazioni approfondite."
},
"setReasoningLevel": "Abilita sforzo di ragionamento",
"claudeCode": {
"pathLabel": "Percorso Claude Code",

View file

@ -433,6 +433,13 @@
"medium": "中",
"low": "低"
},
"verbosity": {
"label": "出力の冗長性",
"high": "高",
"medium": "中",
"low": "低",
"description": "モデルの応答の詳細度を制御します。冗長性が低いと簡潔な回答が生成され、高いと詳細な説明が提供されます。"
},
"setReasoningLevel": "推論労力を有効にする",
"claudeCode": {
"pathLabel": "クロードコードパス",

View file

@ -433,6 +433,13 @@
"medium": "중간",
"low": "낮음"
},
"verbosity": {
"label": "출력 상세도",
"high": "높음",
"medium": "중간",
"low": "낮음",
"description": "모델 응답의 상세도를 제어합니다. 낮은 상세도는 간결한 답변을 생성하고, 높은 상세도는 상세한 설명을 제공합니다."
},
"setReasoningLevel": "추론 노력 활성화",
"claudeCode": {
"pathLabel": "클로드 코드 경로",

View file

@ -433,6 +433,13 @@
"medium": "Middel",
"low": "Laag"
},
"verbosity": {
"label": "Uitvoerbaarheid",
"high": "Hoog",
"medium": "Gemiddeld",
"low": "Laag",
"description": "Bepaalt hoe gedetailleerd de reacties van het model zijn. Lage uitvoerbaarheid levert beknopte antwoorden op, terwijl hoge uitvoerbaarheid uitgebreide uitleg geeft."
},
"setReasoningLevel": "Redeneervermogen inschakelen",
"claudeCode": {
"pathLabel": "Claude Code Pad",

View file

@ -433,6 +433,13 @@
"medium": "Średni",
"low": "Niski"
},
"verbosity": {
"label": "Szczegółowość danych wyjściowych",
"high": "Wysoka",
"medium": "Średnia",
"low": "Niska",
"description": "Kontroluje, jak szczegółowe są odpowiedzi modelu. Niska szczegółowość generuje zwięzłe odpowiedzi, podczas gdy wysoka szczegółowość dostarcza dokładnych wyjaśnień."
},
"setReasoningLevel": "Włącz wysiłek rozumowania",
"claudeCode": {
"pathLabel": "Ścieżka Claude Code",

View file

@ -433,6 +433,13 @@
"medium": "Médio",
"low": "Baixo"
},
"verbosity": {
"label": "Verbosidade da saída",
"high": "Alta",
"medium": "Média",
"low": "Baixa",
"description": "Controla o quão detalhadas são as respostas do modelo. A verbosidade baixa produz respostas concisas, enquanto a verbosidade alta fornisce explicações detalhadas."
},
"setReasoningLevel": "Habilitar esforço de raciocínio",
"claudeCode": {
"pathLabel": "Caminho do Claude Code",

View file

@ -433,6 +433,13 @@
"medium": "Средние",
"low": "Низкие"
},
"verbosity": {
"label": "Подробность вывода",
"high": "Высокая",
"medium": "Средняя",
"low": "Низкая",
"description": "Контролирует, насколько подробны ответы модели. Низкая подробность дает краткие ответы, а высокая — подробные объяснения."
},
"setReasoningLevel": "Включить усилие рассуждения",
"claudeCode": {
"pathLabel": "Путь к Claude Code",

View file

@ -433,6 +433,13 @@
"medium": "Orta",
"low": "Düşük"
},
"verbosity": {
"label": ıktı Ayrıntı Düzeyi",
"high": "Yüksek",
"medium": "Orta",
"low": "Düşük",
"description": "Modelin yanıtlarının ne kadar ayrıntılı olduğunu kontrol eder. Düşük ayrıntı düzeyi kısa yanıtlar üretirken, yüksek ayrıntı düzeyi kapsamlııklamalar sunar."
},
"setReasoningLevel": "Akıl Yürütme Çabasını Etkinleştir",
"claudeCode": {
"pathLabel": "Claude Code Yolu",

View file

@ -433,6 +433,13 @@
"medium": "Trung bình",
"low": "Thấp"
},
"verbosity": {
"label": "Mức độ chi tiết đầu ra",
"high": "Cao",
"medium": "Trung bình",
"low": "Thấp",
"description": "Kiểm soát mức độ chi tiết của các câu trả lời của mô hình. Mức độ chi tiết thấp tạo ra các câu trả lời ngắn gọn, trong khi mức độ chi tiết cao cung cấp giải thích kỹ lưỡng."
},
"setReasoningLevel": "Kích hoạt nỗ lực suy luận",
"claudeCode": {
"pathLabel": "Đường dẫn Claude Code",

View file

@ -433,6 +433,13 @@
"medium": "中",
"low": "低"
},
"verbosity": {
"label": "输出详细程度",
"high": "高",
"medium": "中",
"low": "低",
"description": "控制模型响应的详细程度。低详细度产生简洁的回答,而高详细度提供详尽的解释。"
},
"setReasoningLevel": "启用推理工作量",
"claudeCode": {
"pathLabel": "Claude Code 路径",

View file

@ -433,6 +433,13 @@
"medium": "中",
"low": "低"
},
"verbosity": {
"label": "輸出詳細程度",
"high": "高",
"medium": "中",
"low": "低",
"description": "控制模型回應的詳細程度。低詳細度產生簡潔的回答,而高詳細度提供詳盡的解釋。"
},
"setReasoningLevel": "啟用推理工作量",
"claudeCode": {
"pathLabel": "Claude Code 路徑",