From bbb31737f3b7835278c1696302c21081bdf1bb51 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Wed, 27 Aug 2025 11:39:24 +0000 Subject: [PATCH] feat: add priority processing support for GPT-5 models - Add enablePriorityProcessing field to provider settings schema - Update OpenAI native provider to use priority endpoint when enabled - Add UI component with warning about increased costs - Add localization strings for priority processing - Add comprehensive tests for priority processing functionality This feature allows users to enable priority processing (fast mode) for GPT-5 and GPT-5-mini models, which reduces response time but increases costs. --- packages/types/src/provider-settings.ts | 3 + .../__tests__/openai-native-priority.spec.ts | 226 ++++++++++++++++++ src/api/providers/openai-native.ts | 12 +- .../src/components/settings/ApiOptions.tsx | 10 + .../settings/PriorityProcessing.tsx | 70 ++++++ webview-ui/src/i18n/locales/en/settings.json | 6 + 6 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 src/api/providers/__tests__/openai-native-priority.spec.ts create mode 100644 webview-ui/src/components/settings/PriorityProcessing.tsx diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index eb404f7129..f8d89d02c1 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -111,6 +111,9 @@ const baseProviderSettingsSchema = z.object({ // Model verbosity. verbosity: verbosityLevelsSchema.optional(), + + // Priority processing for GPT-5 models. + enablePriorityProcessing: z.boolean().optional(), }) // Several of the providers share common model config properties. diff --git a/src/api/providers/__tests__/openai-native-priority.spec.ts b/src/api/providers/__tests__/openai-native-priority.spec.ts new file mode 100644 index 0000000000..6f89caab7f --- /dev/null +++ b/src/api/providers/__tests__/openai-native-priority.spec.ts @@ -0,0 +1,226 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import OpenAI from "openai" +import { OpenAiNativeHandler } from "../openai-native" +import type { ApiHandlerOptions } from "../../../shared/api" +import type { Anthropic } from "@anthropic-ai/sdk" + +vi.mock("openai") + +describe("OpenAI Native Priority Processing", () => { + let handler: OpenAiNativeHandler + let mockCreate: ReturnType + let mockOptions: ApiHandlerOptions + + beforeEach(() => { + mockCreate = vi.fn() + ;(OpenAI as any).mockImplementation(() => ({ + chat: { + completions: { + create: mockCreate, + }, + }, + responses: { + create: vi.fn(), + }, + })) + + mockOptions = { + openAiNativeApiKey: "test-api-key", + apiModelId: "gpt-5-2025-08-07", + } + }) + + describe("Priority Processing for GPT-5 models", () => { + it("should use priority endpoint when priority processing is enabled", () => { + const handlerWithPriority = new OpenAiNativeHandler({ + ...mockOptions, + enablePriorityProcessing: true, + }) + + // Check that the OpenAI client was initialized with the priority endpoint + expect(OpenAI).toHaveBeenCalledWith({ + baseURL: "https://api.openai.com/v1/priority", + apiKey: "test-api-key", + }) + }) + + it("should use standard endpoint when priority processing is disabled", () => { + const handlerWithoutPriority = new OpenAiNativeHandler({ + ...mockOptions, + enablePriorityProcessing: false, + }) + + // Check that the OpenAI client was initialized with the standard endpoint + expect(OpenAI).toHaveBeenCalledWith({ + baseURL: undefined, + apiKey: "test-api-key", + }) + }) + + it("should respect custom base URL even with priority processing enabled", () => { + const customBaseUrl = "https://custom.api.com/v1" + const handlerWithCustomUrl = new OpenAiNativeHandler({ + ...mockOptions, + openAiNativeBaseUrl: customBaseUrl, + enablePriorityProcessing: true, + }) + + // Check that the custom URL is preserved + expect(OpenAI).toHaveBeenCalledWith({ + baseURL: customBaseUrl, + apiKey: "test-api-key", + }) + }) + + it("should include priority in GPT-5 request body when enabled", async () => { + const handler = new OpenAiNativeHandler({ + ...mockOptions, + apiModelId: "gpt-5-2025-08-07", + enablePriorityProcessing: true, + }) + + // Mock the responses.create method + const mockResponsesCreate = vi.fn().mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + yield { type: "response.done", response: { id: "test-id" } } + }, + }) + ;(handler as any).client.responses = { create: mockResponsesCreate } + + const systemPrompt = "You are a helpful assistant" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }] + + const stream = handler.createMessage(systemPrompt, messages) + for await (const chunk of stream) { + // Process stream + } + + // Check that the request included the priority flag + expect(mockResponsesCreate).toHaveBeenCalledWith( + expect.objectContaining({ + priority: true, + }), + ) + }) + + it("should not include priority in GPT-5 request body when disabled", async () => { + const handler = new OpenAiNativeHandler({ + ...mockOptions, + apiModelId: "gpt-5-2025-08-07", + enablePriorityProcessing: false, + }) + + // Mock the responses.create method + const mockResponsesCreate = vi.fn().mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + yield { type: "response.done", response: { id: "test-id" } } + }, + }) + ;(handler as any).client.responses = { create: mockResponsesCreate } + + const systemPrompt = "You are a helpful assistant" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }] + + const stream = handler.createMessage(systemPrompt, messages) + for await (const chunk of stream) { + // Process stream + } + + // Check that the request did not include the priority flag + expect(mockResponsesCreate).toHaveBeenCalledWith( + expect.not.objectContaining({ + priority: true, + }), + ) + }) + + it("should work with GPT-5-mini models", async () => { + const handler = new OpenAiNativeHandler({ + ...mockOptions, + apiModelId: "gpt-5-mini-2025-08-07", + enablePriorityProcessing: true, + }) + + // Mock the responses.create method + const mockResponsesCreate = vi.fn().mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + yield { type: "response.done", response: { id: "test-id" } } + }, + }) + ;(handler as any).client.responses = { create: mockResponsesCreate } + + const systemPrompt = "You are a helpful assistant" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }] + + const stream = handler.createMessage(systemPrompt, messages) + for await (const chunk of stream) { + // Process stream + } + + // Check that the request included the priority flag for GPT-5-mini + expect(mockResponsesCreate).toHaveBeenCalledWith( + expect.objectContaining({ + priority: true, + }), + ) + }) + }) + + describe("Priority Processing for non-GPT-5 models", () => { + it("should not affect GPT-4 models", async () => { + const handler = new OpenAiNativeHandler({ + ...mockOptions, + apiModelId: "gpt-4o", + enablePriorityProcessing: true, + }) + + // Mock streaming response + mockCreate.mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + yield { + choices: [{ delta: { content: "Hello" } }], + } + }, + }) + + const systemPrompt = "You are a helpful assistant" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }] + + const stream = handler.createMessage(systemPrompt, messages) + for await (const chunk of stream) { + // Process stream + } + + // For GPT-4, it should still use the regular chat completions API + expect(mockCreate).toHaveBeenCalled() + }) + + it("should not affect o1 models", async () => { + const handler = new OpenAiNativeHandler({ + ...mockOptions, + apiModelId: "o1", + enablePriorityProcessing: true, + }) + + // Mock streaming response + mockCreate.mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + yield { + choices: [{ delta: { content: "Hello" } }], + } + }, + }) + + const systemPrompt = "You are a helpful assistant" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }] + + const stream = handler.createMessage(systemPrompt, messages) + for await (const chunk of stream) { + // Process stream + } + + // For o1 models, it should use the regular chat completions API + expect(mockCreate).toHaveBeenCalled() + }) + }) +}) diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index 2ba8566963..13ec35e90a 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -57,7 +57,14 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio this.options.enableGpt5ReasoningSummary = true } const apiKey = this.options.openAiNativeApiKey ?? "not-provided" - this.client = new OpenAI({ baseURL: this.options.openAiNativeBaseUrl, apiKey }) + const baseURL = this.options.openAiNativeBaseUrl + + // If priority processing is enabled, modify the base URL to use the priority endpoint + // This is a workaround since the OpenAI SDK doesn't directly support the priority parameter + const finalBaseURL = + this.options.enablePriorityProcessing && !baseURL ? "https://api.openai.com/v1/priority" : baseURL + + this.client = new OpenAI({ baseURL: finalBaseURL, apiKey }) } private normalizeGpt5Usage(usage: any, model: OpenAiNativeModel): ApiStreamUsageChunk | undefined { @@ -276,6 +283,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio temperature?: number max_output_tokens?: number previous_response_id?: string + priority?: boolean } const requestBody: Gpt5RequestBody = { @@ -294,6 +302,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio // Use the per-request reserved output computed by Roo (params.maxTokens from getModelParams). ...(model.maxTokens ? { max_output_tokens: model.maxTokens } : {}), ...(requestPreviousResponseId && { previous_response_id: requestPreviousResponseId }), + // Add priority processing if enabled for GPT-5 models + ...(this.options.enablePriorityProcessing && this.isGpt5Model(model.id) ? { priority: true } : {}), } try { diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index a9bf7c7013..8f821ad088 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -101,6 +101,7 @@ import { ModelInfoView } from "./ModelInfoView" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" import { Verbosity } from "./Verbosity" +import { PriorityProcessing } from "./PriorityProcessing" import { DiffSettingsControl } from "./DiffSettingsControl" import { TodoListSettingsControl } from "./TodoListSettingsControl" import { TemperatureControl } from "./TemperatureControl" @@ -727,6 +728,15 @@ const ApiOptions = ({ /> )} + {/* Priority Processing for GPT-5 models */} + {selectedProvider === "openai-native" && ( + + )} + {!fromWelcomeView && ( diff --git a/webview-ui/src/components/settings/PriorityProcessing.tsx b/webview-ui/src/components/settings/PriorityProcessing.tsx new file mode 100644 index 0000000000..343f3a974d --- /dev/null +++ b/webview-ui/src/components/settings/PriorityProcessing.tsx @@ -0,0 +1,70 @@ +import React from "react" +import { type ProviderSettings, type ModelInfo } from "@roo-code/types" +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { Checkbox } from "@src/components/ui" +import { ExclamationTriangleIcon } from "@radix-ui/react-icons" + +interface PriorityProcessingProps { + apiConfiguration: ProviderSettings + setApiConfigurationField: ( + field: K, + value: ProviderSettings[K], + isUserAction?: boolean, + ) => void + modelInfo: ModelInfo | undefined +} + +export const PriorityProcessing: React.FC = ({ + apiConfiguration, + setApiConfigurationField, + modelInfo, +}) => { + const { t } = useAppTranslation() + + // Only show for GPT-5 and GPT-5-mini models + const isGpt5Model = + modelInfo && + (apiConfiguration.apiModelId?.includes("gpt-5") || apiConfiguration.apiModelId?.includes("gpt-5-mini")) + + if (!isGpt5Model) { + return null + } + + return ( +
+
+ + setApiConfigurationField("enablePriorityProcessing", checked === true) + } + /> + +
+ + {apiConfiguration.enablePriorityProcessing && ( +
+
+ +
+

{t("settings:providers.priorityProcessing.warning")}

+

{t("settings:providers.priorityProcessing.description")}

+ + {t("settings:providers.priorityProcessing.viewPricing")} + +
+
+
+ )} +
+ ) +} diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index c8cad691a8..ad1b2fe712 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -457,6 +457,12 @@ "low": "Low", "description": "Controls how detailed the model's responses are. Low verbosity produces concise answers, while high verbosity provides thorough explanations." }, + "priorityProcessing": { + "label": "Enable Priority Processing (Fast Mode)", + "warning": "Higher costs will apply!", + "description": "Priority processing significantly reduces response time for GPT-5 and GPT-5-mini models by using dedicated compute resources. This feature increases costs substantially compared to standard processing.", + "viewPricing": "View pricing details" + }, "setReasoningLevel": "Enable Reasoning Effort", "claudeCode": { "pathLabel": "Claude Code Path",