mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
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.
This commit is contained in:
parent
c479678d8e
commit
bbb31737f3
6 changed files with 326 additions and 1 deletions
|
|
@ -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.
|
||||
|
|
|
|||
226
src/api/providers/__tests__/openai-native-priority.spec.ts
Normal file
226
src/api/providers/__tests__/openai-native-priority.spec.ts
Normal file
|
|
@ -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<typeof vi.fn>
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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" && (
|
||||
<PriorityProcessing
|
||||
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">
|
||||
|
|
|
|||
70
webview-ui/src/components/settings/PriorityProcessing.tsx
Normal file
70
webview-ui/src/components/settings/PriorityProcessing.tsx
Normal file
|
|
@ -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: <K extends keyof ProviderSettings>(
|
||||
field: K,
|
||||
value: ProviderSettings[K],
|
||||
isUserAction?: boolean,
|
||||
) => void
|
||||
modelInfo: ModelInfo | undefined
|
||||
}
|
||||
|
||||
export const PriorityProcessing: React.FC<PriorityProcessingProps> = ({
|
||||
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 (
|
||||
<div className="flex flex-col gap-2" data-testid="priority-processing">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="priority-processing"
|
||||
checked={apiConfiguration.enablePriorityProcessing || false}
|
||||
onCheckedChange={(checked) =>
|
||||
setApiConfigurationField("enablePriorityProcessing", checked === true)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="priority-processing"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||
{t("settings:providers.priorityProcessing.label")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{apiConfiguration.enablePriorityProcessing && (
|
||||
<div className="ml-6 p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-md">
|
||||
<div className="flex items-start gap-2">
|
||||
<ExclamationTriangleIcon className="w-4 h-4 text-yellow-600 dark:text-yellow-400 mt-0.5 flex-shrink-0" />
|
||||
<div className="text-sm text-yellow-800 dark:text-yellow-200">
|
||||
<p className="font-semibold mb-1">{t("settings:providers.priorityProcessing.warning")}</p>
|
||||
<p>{t("settings:providers.priorityProcessing.description")}</p>
|
||||
<a
|
||||
href="https://platform.openai.com/docs/pricing?latest-pricing=priority"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block mt-2 text-yellow-700 dark:text-yellow-300 underline hover:no-underline">
|
||||
{t("settings:providers.priorityProcessing.viewPricing")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue