-
Couldn't load subscription status.
- Fork 2.3k
feat: OpenAI Responses API service tiers (flex/priority) — UI selector, pricing, and tests #7646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds comprehensive support for OpenAI Responses API service tiers (flex/priority) to the openai-native provider, including UI controls, pricing display, and backend integration.
- Introduces service tier types and schema definitions with tier-specific pricing metadata
- Implements provider logic to handle tier selection and compute costs using appropriate pricing
- Moves service tier selector to model info view for better contextual placement alongside pricing tables
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/types/src/model.ts | Defines ServiceTier types and schema with tier pricing structure |
| packages/types/src/provider-settings.ts | Adds openAiNativeServiceTier to provider settings |
| packages/types/src/providers/openai.ts | Updates model metadata with allowed tiers and tier-specific pricing |
| src/api/providers/openai-native.ts | Implements tier handling in API requests and usage cost calculation |
| src/api/providers/tests/openai-native-service-tier.spec.ts | Test coverage for tier behavior across streaming/non-streaming scenarios |
| webview-ui/src/components/settings/ApiOptions.tsx | Passes configuration props to enable tier selector |
| webview-ui/src/components/settings/ModelInfoView.tsx | Adds tier selector and pricing table display |
| webview-ui/src/components/settings/providers/OpenAI.tsx | Removes inline tier selector, keeping only API key controls |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| </StandardTooltip> | ||
| </div> | ||
| <Select | ||
| value={(apiConfiguration?.openAiNativeServiceTier as any) || "default"} |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using as any type assertions bypasses TypeScript's type safety. Consider using proper type casting with ServiceTier type or creating a type-safe conversion function.
| <Select | ||
| value={(apiConfiguration?.openAiNativeServiceTier as any) || "default"} | ||
| onValueChange={(value) => | ||
| setApiConfigurationField("openAiNativeServiceTier", value as any) |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using as any type assertions bypasses TypeScript's type safety. Consider using proper type casting with ServiceTier type or creating a type-safe conversion function.
| const allowedTiers = | ||
| (modelInfo?.allowedServiceTiers || []).filter((tier) => tier === "flex" || tier === "priority") ?? [] |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The filter condition hardcodes tier names and uses redundant nullish coalescing. Since the expression already provides a default empty array with || [], the ?? [] is unnecessary. Consider: (modelInfo?.allowedServiceTiers || []).filter((tier) => tier !== 'default')
| const allowedTiers = | |
| (modelInfo?.allowedServiceTiers || []).filter((tier) => tier === "flex" || tier === "priority") ?? [] | |
| (modelInfo?.allowedServiceTiers || []).filter((tier) => ["flex", "priority"].includes(tier)) |
src/api/providers/openai-native.ts
Outdated
| (requestBody.service_tier && | ||
| (/service[_ ]tier/i.test(errorMessage) || | ||
| errorMessage.toLowerCase().includes("unsupported service tier") || | ||
| errorMessage.toLowerCase().includes("invalid service tier"))) || | ||
| false |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error detection logic is overly complex and the || false at the end is redundant since the boolean expression already evaluates to a boolean. Consider simplifying to: const isTierError = requestBody.service_tier && (/service[_ ]tier/i.test(errorMessage) || errorMessage.toLowerCase().includes('unsupported service tier') || errorMessage.toLowerCase().includes('invalid service tier'))
| (requestBody.service_tier && | |
| (/service[_ ]tier/i.test(errorMessage) || | |
| errorMessage.toLowerCase().includes("unsupported service tier") || | |
| errorMessage.toLowerCase().includes("invalid service tier"))) || | |
| false | |
| requestBody.service_tier && | |
| (/service[_ ]tier/i.test(errorMessage) || | |
| errorMessage.toLowerCase().includes("unsupported service tier") || | |
| errorMessage.toLowerCase().includes("invalid service tier")); |
src/api/providers/openai-native.ts
Outdated
| const isTierError = | ||
| requestBody.service_tier && | ||
| (/service[_ ]tier/i.test(errorDetails) || | ||
| errorDetails.toLowerCase().includes("unsupported service tier") || | ||
| errorDetails.toLowerCase().includes("invalid service tier")) |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error detection logic is duplicated from the earlier instance (lines 293-298). Consider extracting this into a shared helper function to avoid code duplication and ensure consistent error handling.
src/api/providers/openai-native.ts
Outdated
| const is400Error = sdkErr?.status === 400 || sdkErr?.response?.status === 400 | ||
| const isPreviousResponseError = | ||
| errorMessage.includes("Previous response") || errorMessage.includes("not found") | ||
| const isTierError = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The retry logic for handling 400 errors due to an invalid or unsupported 'service_tier' is duplicated. Consider refactoring this block into a shared utility and adding logging to aid debugging.
This comment was generated because it violated a code review rule: irule_tTqpIuNs8DV0QFGj.
| {setApiConfigurationField && ( | ||
| <div className="flex flex-col gap-1 mb-2" data-testid="openai-service-tier"> | ||
| <div className="flex items-center gap-1"> | ||
| <label className="block font-medium mb-1">Service tier</label> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded strings for service tier labels ('Service tier', 'Standard', 'Flex', 'Priority') and table headers are not internationalized. Please replace them with translation keys to support multiple languages.
| <label className="block font-medium mb-1">Service tier</label> | |
| <label className="block font-medium mb-1">{t("settings:modelInfo.serviceTier")}</label> |
This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your contribution! I've reviewed the OpenAI Responses API service tier implementation and found it to be well-structured overall. The feature adds valuable flexibility for users to choose between different service tiers based on their needs. I've left some suggestions inline that could improve code maintainability and test coverage.
src/api/providers/openai-native.ts
Outdated
| } | ||
| } | ||
|
|
||
| if (is400Error && requestBody.service_tier && isTierError) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed the service tier retry logic is duplicated between the SDK path (lines 333-358) and the SSE fallback path (lines 527-557). Could we extract this into a shared helper method to reduce duplication and ensure consistency?
For example:
| } | ||
| }) | ||
|
|
||
| describe("OpenAiNativeHandler - service tier + pricing", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great test coverage for the basic scenarios! Could we add a few more edge cases to make the tests more robust?
- Test what happens when the API returns a different tier than requested (e.g., request "flex" but API returns "priority")
- Test error handling when an invalid tier is provided
- Test the retry logic when service_tier causes a 400 error
These additional tests would help ensure the implementation handles all possible scenarios gracefully.
| </div> | ||
| )} | ||
| <div className="text-xs text-vscode-descriptionForeground mb-1"> | ||
| Pricing by service tier (price per 1M tokens) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this string be internationalized for consistency with the rest of the UI? I noticed other UI strings use the translation system.
| </StandardTooltip> | ||
| </div> | ||
| <Select | ||
| value={(apiConfiguration?.openAiNativeServiceTier as any) || "default"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we improve type safety here by properly typing the openAiNativeServiceTier field instead of casting to ? This would help catch potential type mismatches at compile time.
| * Returns a shallow-cloned ModelInfo with pricing overridden for the given tier, if available. | ||
| * If no tier or no overrides exist, the original ModelInfo is returned. | ||
| */ | ||
| private applyServiceTierPricing(info: ModelInfo, tier?: ServiceTier): ModelInfo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be helpful to add JSDoc comments explaining when service_tier is included vs omitted in requests? This would help future maintainers understand the streaming vs non-streaming behavior difference.
src/api/providers/openai-native.ts
Outdated
|
|
||
| // Only include reasoningTokens if present | ||
| if (reasoningTokens !== undefined) { | ||
| ;(result as any).reasoningTokens = reasoningTokens |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider extending the ApiStreamUsageChunk type to include 'reasoningTokens' instead of casting to any for better type safety.
ca50ad6 to
31f5205
Compare
21e137d to
d5833f3
Compare
|
|
||
| // Validate requested tier against model support; if not supported, omit. | ||
| const requestedTier = (this.options.openAiNativeServiceTier as ServiceTier | undefined) || undefined | ||
| const allowedTierNames = new Set(model.info.tiers?.map((t) => t.name).filter(Boolean) || []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider extracting the logic that builds the set of allowed tier names (allowedTierNames) into a shared helper function. This pattern is repeated (e.g. in buildRequestBody and completePrompt), and a helper would improve maintainability if the logic ever changes.
This comment was generated because it violated a code review rule: irule_tTqpIuNs8DV0QFGj.
d5833f3 to
3e2aed9
Compare
… hide redundant price rows
…tadata, handler support, and UI selection
- Fixed documentation comment about streaming behavior (service_tier IS sent for streaming) - Added internationalization for all UI strings (Service tier, Standard, Flex, Priority, pricing table) - Fixed TypeScript type safety by using proper ServiceTier type instead of 'any' casts - All tests passing, TypeScript compilation successful
- Added translations for service tier UI strings to all 17 supported languages - Fixes CI check-translations failure - Ensures complete i18n coverage for the new feature
3e2aed9 to
6ca429d
Compare
Related GitHub Issue
Closes: N/A
Roo Code Task Context (Optional)
No Roo Code task context for this PR
Description
Adds OpenAI Responses API service tier support (flex/priority) for the openai-native provider, including:
Key changes:
Test Procedure
Pre-Submission Checklist
Screenshots / Videos
No UI screenshots included (UI change is a selector relocation/visibility).
Documentation Updates
Additional Notes
Created from staged local changes and validated by lint checks. If an approved issue exists, we can update this PR to link it.
Get in Touch
N/A
Important
Adds service tier support for OpenAI Responses API, including backend logic, UI updates, and tests for tier behavior.
openai-nativeprovider.openai-native.tsto handleservice_tierin non-stream requests and adjust usage based on tier pricing.ModelInfoView.tsxandOpenAI.tsxto include tier selector and pricing table.serviceTierSchemainmodel.ts.provider-settings.tsto includeopenAiNativeServiceTier.openai.tsfor supported models.openai-native-service-tier.spec.tsto test tier behavior for streaming and non-streaming requests.This description was created by
for 6ca429d. You can customize this summary. It will automatically update as commits are pushed.