diff --git a/packages/types/src/model.ts b/packages/types/src/model.ts index 3bd66782cf..a09790578b 100644 --- a/packages/types/src/model.ts +++ b/packages/types/src/model.ts @@ -10,6 +10,16 @@ export const reasoningEffortsSchema = z.enum(reasoningEfforts) export type ReasoningEffort = z.infer +/** + * Verbosity + */ + +export const verbosityLevels = ["low", "medium", "high"] as const + +export const verbosityLevelsSchema = z.enum(verbosityLevels) + +export type VerbosityLevel = z.infer + /** * ModelParameter */ diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index dc51188df9..f0c90101fc 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -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. diff --git a/packages/types/src/providers/openai.ts b/packages/types/src/providers/openai.ts index 0afdd46feb..b319be2a5f 100644 --- a/packages/types/src/providers/openai.ts +++ b/packages/types/src/providers/openai.ts @@ -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, diff --git a/src/api/providers/__tests__/openai-native.spec.ts b/src/api/providers/__tests__/openai-native.spec.ts index 64080b4cac..fdd71ba3f6 100644 --- a/src/api/providers/__tests__/openai-native.spec.ts +++ b/src/api/providers/__tests__/openai-native.spec.ts @@ -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", + }), + ) + }) + }) }) diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index 3f14e65cc6..5e498bee45 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -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 +// 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, + 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> { + // 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 + + // Convert the stream to GPT-5 response format + return this.convertChatStreamToGpt5Format(stream) + } + + private async *convertChatStreamToGpt5Format( + stream: AsyncIterable, + ): AsyncIterable { + 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, + 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 { 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) { diff --git a/src/api/transform/__tests__/model-params.spec.ts b/src/api/transform/__tests__/model-params.spec.ts index b5a02f534e..bd75e7eafb 100644 --- a/src/api/transform/__tests__/model-params.spec.ts +++ b/src/api/transform/__tests__/model-params.spec.ts @@ -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 + }) + }) }) diff --git a/src/api/transform/model-params.ts b/src/api/transform/model-params.ts index 9ad4261b76..cc30aa5605 100644 --- a/src/api/transform/model-params.ts +++ b/src/api/transform/model-params.ts @@ -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 { diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 204abe9c0f..74ba885d25 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -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} /> + + {!fromWelcomeView && ( diff --git a/webview-ui/src/components/settings/Verbosity.tsx b/webview-ui/src/components/settings/Verbosity.tsx new file mode 100644 index 0000000000..ee612d66cf --- /dev/null +++ b/webview-ui/src/components/settings/Verbosity.tsx @@ -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: (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 ( +
+
+ +
+ +
{t("settings:providers.verbosity.description")}
+
+ ) +} diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index e3f2713ffe..3a534fb031 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index a4a62b8391..d13050cc7a 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 6e0f137504..224ad4fdd7 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index e2db1463af..79ac8c5510 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 26018a344b..8a8738ed9b 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 7c8b280427..18c5061a13 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -433,6 +433,13 @@ "medium": "मध्यम", "low": "निम्न" }, + "verbosity": { + "label": "आउटपुट वर्बोसिटी", + "high": "उच्च", + "medium": "मध्यम", + "low": "कम", + "description": "मॉडल की प्रतिक्रियाएं कितनी विस्तृत हैं, इसे नियंत्रित करता है। कम वर्बोसिटी संक्षिप्त उत्तर देती है, जबकि उच्च वर्बोसिटी विस्तृत स्पष्टीकरण प्रदान करती है।" + }, "setReasoningLevel": "तर्क प्रयास सक्षम करें", "claudeCode": { "pathLabel": "क्लाउड कोड पथ", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index b4f9b113b3..3a4800f4f2 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 82d7b2d041..e116bf2ae3 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index dfa62ab32b..407d31e457 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -433,6 +433,13 @@ "medium": "中", "low": "低" }, + "verbosity": { + "label": "出力の冗長性", + "high": "高", + "medium": "中", + "low": "低", + "description": "モデルの応答の詳細度を制御します。冗長性が低いと簡潔な回答が生成され、高いと詳細な説明が提供されます。" + }, "setReasoningLevel": "推論労力を有効にする", "claudeCode": { "pathLabel": "クロードコードパス", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 219a5de54a..3cdb2e8b4f 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -433,6 +433,13 @@ "medium": "중간", "low": "낮음" }, + "verbosity": { + "label": "출력 상세도", + "high": "높음", + "medium": "중간", + "low": "낮음", + "description": "모델 응답의 상세도를 제어합니다. 낮은 상세도는 간결한 답변을 생성하고, 높은 상세도는 상세한 설명을 제공합니다." + }, "setReasoningLevel": "추론 노력 활성화", "claudeCode": { "pathLabel": "클로드 코드 경로", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 735339bb66..2061474d17 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index e25eee34cf..b081005400 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index e7243aa6f6..a71340391d 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 38e986ab88..00e9b79074 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -433,6 +433,13 @@ "medium": "Средние", "low": "Низкие" }, + "verbosity": { + "label": "Подробность вывода", + "high": "Высокая", + "medium": "Средняя", + "low": "Низкая", + "description": "Контролирует, насколько подробны ответы модели. Низкая подробность дает краткие ответы, а высокая — подробные объяснения." + }, "setReasoningLevel": "Включить усилие рассуждения", "claudeCode": { "pathLabel": "Путь к Claude Code", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index b4b9fd13e4..e79db2a3b2 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -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ı açıklamalar sunar." + }, "setReasoningLevel": "Akıl Yürütme Çabasını Etkinleştir", "claudeCode": { "pathLabel": "Claude Code Yolu", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index cdae509d5e..10de98d4da 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -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", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index aca901cc3e..a6971d288f 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -433,6 +433,13 @@ "medium": "中", "low": "低" }, + "verbosity": { + "label": "输出详细程度", + "high": "高", + "medium": "中", + "low": "低", + "description": "控制模型响应的详细程度。低详细度产生简洁的回答,而高详细度提供详尽的解释。" + }, "setReasoningLevel": "启用推理工作量", "claudeCode": { "pathLabel": "Claude Code 路径", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index db3fc3c2cd..bec2ffd5e9 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -433,6 +433,13 @@ "medium": "中", "low": "低" }, + "verbosity": { + "label": "輸出詳細程度", + "high": "高", + "medium": "中", + "low": "低", + "description": "控制模型回應的詳細程度。低詳細度產生簡潔的回答,而高詳細度提供詳盡的解釋。" + }, "setReasoningLevel": "啟用推理工作量", "claudeCode": { "pathLabel": "Claude Code 路徑",