Skip to content

Commit 5f0179c

Browse files
committed
refactor(api): address service tier review feedback
1 parent 66ae5a9 commit 5f0179c

4 files changed

Lines changed: 69 additions & 60 deletions

File tree

packages/types/src/model.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ export const SERVICE_TIER_KEY = "service_tier"
6060
/**
6161
* Service tiers for the public OpenAI Responses API.
6262
*/
63-
export enum OpenAiServiceTier {
64-
Default = "default",
65-
Flex = "flex",
66-
Priority = "priority",
67-
}
63+
export const OpenAiServiceTier = {
64+
Default: "default",
65+
Flex: "flex",
66+
Priority: "priority",
67+
} as const
6868

69-
export const serviceTiers = Object.values(OpenAiServiceTier) as [`${OpenAiServiceTier}`, ...`${OpenAiServiceTier}`[]]
69+
export const serviceTiers = [OpenAiServiceTier.Default, OpenAiServiceTier.Flex, OpenAiServiceTier.Priority] as const
7070
export const serviceTierSchema = z.enum(serviceTiers)
71-
export type ServiceTier = `${OpenAiServiceTier}`
71+
export type ServiceTier = z.infer<typeof serviceTierSchema>
7272

7373
/**
7474
* ModelParameter

src/api/providers/__tests__/openai-native.spec.ts

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,60 @@ describe("OpenAiNativeHandler", () => {
196196
},
197197
)
198198

199+
it.each([
200+
{
201+
name: "an explicitly selected default tier",
202+
modelId: "gpt-5.4" as const,
203+
requestedTier: OpenAiServiceTier.Default,
204+
resolvedTier: undefined,
205+
expectedCost: 0.22,
206+
},
207+
{
208+
name: "no selected service tier",
209+
modelId: "gpt-5.4" as const,
210+
requestedTier: undefined,
211+
resolvedTier: undefined,
212+
expectedCost: 0.22,
213+
},
214+
{
215+
name: "a resolved service tier without a pricing entry",
216+
modelId: "gpt-5.6-luna" as const,
217+
requestedTier: OpenAiServiceTier.Default,
218+
resolvedTier: OpenAiServiceTier.Priority,
219+
expectedCost: 0.088,
220+
},
221+
])("retains standard pricing for $name", async ({ modelId, requestedTier, resolvedTier, expectedCost }) => {
222+
mockResponsesCreate.mockResolvedValue({
223+
async *[Symbol.asyncIterator]() {
224+
yield {
225+
type: "response.done",
226+
response: {
227+
...(resolvedTier ? { [SERVICE_TIER_KEY]: resolvedTier } : {}),
228+
usage: {
229+
input_tokens: 100_000,
230+
output_tokens: 1_000,
231+
cache_read_input_tokens: 20_000,
232+
},
233+
},
234+
}
235+
},
236+
})
237+
handler = new OpenAiNativeHandler({
238+
...mockOptions,
239+
apiModelId: modelId,
240+
openAiNativeServiceTier: requestedTier,
241+
})
242+
243+
const chunks = []
244+
for await (const chunk of handler.createMessage(systemPrompt, messages)) {
245+
chunks.push(chunk)
246+
}
247+
248+
const usageChunk = chunks.find((chunk) => chunk.type === "usage")
249+
expect(usageChunk).toBeDefined()
250+
expect(usageChunk?.totalCost).toBeCloseTo(expectedCost, 6)
251+
})
252+
199253
it.each(serviceTierPricingCases)(
200254
"requests $requestedTier but prices manual SSE fallback usage using OpenAI's resolved $resolvedTier tier",
201255
async ({ requestedTier, resolvedTier, expectedCost }) => {
@@ -550,49 +604,6 @@ describe("OpenAiNativeHandler", () => {
550604
])
551605
})
552606

553-
it("should retain standard pricing for an explicitly selected default tier", () => {
554-
const defaultTierHandler = new OpenAiNativeHandler({
555-
...mockOptions,
556-
apiModelId: "gpt-5.4",
557-
openAiNativeServiceTier: OpenAiServiceTier.Default,
558-
})
559-
const model = defaultTierHandler.getModel()
560-
const normalizeUsage = Reflect.get(defaultTierHandler, "normalizeUsage")
561-
562-
const result = Reflect.apply(normalizeUsage, defaultTierHandler, [
563-
{
564-
input_tokens: 100_000,
565-
output_tokens: 1_000,
566-
cache_read_input_tokens: 20_000,
567-
},
568-
model,
569-
]) as { totalCost: number }
570-
571-
expect(result.totalCost).toBeCloseTo(0.22, 6)
572-
})
573-
574-
it("should retain standard pricing when no service tier is selected", () => {
575-
const model = handler.getModel()
576-
const applyServiceTierPricing = Reflect.get(handler, "applyServiceTierPricing")
577-
578-
const result = Reflect.apply(applyServiceTierPricing, handler, [model.info])
579-
580-
expect(result).toBe(model.info)
581-
})
582-
583-
it("should retain standard pricing when the resolved service tier has no pricing entry", () => {
584-
const model = handler.getModel()
585-
const infoWithoutTiers = { ...model.info, tiers: undefined }
586-
const applyServiceTierPricing = Reflect.get(handler, "applyServiceTierPricing")
587-
588-
const result = Reflect.apply(applyServiceTierPricing, handler, [
589-
infoWithoutTiers,
590-
OpenAiServiceTier.Priority,
591-
])
592-
593-
expect(result).toBe(infoWithoutTiers)
594-
})
595-
596607
it("should return GPT-5.3 Chat model info when selected", () => {
597608
const chatHandler = new OpenAiNativeHandler({
598609
...mockOptions,

src/api/providers/openai-native.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
336336
}
337337

338338
// Validate requested tier against model support; if not supported, omit.
339-
const allowedTierNames = new Set(model.info.tiers?.map((t) => t.name).filter(Boolean) || [])
340-
const serviceTier = this.getAllowedServiceTier(this.options.openAiNativeServiceTier, allowedTierNames)
339+
const serviceTier = this.getAllowedServiceTier(model)
341340

342341
// Decide whether to enable extended prompt cache retention for this request
343342
const promptCacheRetention = this.getPromptCacheRetention(model)
@@ -1428,10 +1427,10 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
14281427
}
14291428
}
14301429

1431-
private getAllowedServiceTier(
1432-
requestedTier: ServiceTier | undefined,
1433-
allowedTierNames: ReadonlySet<string | undefined>,
1434-
): ServiceTier | undefined {
1430+
private getAllowedServiceTier(model: OpenAiNativeModel): ServiceTier | undefined {
1431+
const requestedTier = this.options.openAiNativeServiceTier
1432+
const allowedTierNames = new Set(model.info.tiers?.map(({ name }) => name).filter(Boolean))
1433+
14351434
return requestedTier === OpenAiServiceTier.Default || (requestedTier && allowedTierNames.has(requestedTier))
14361435
? requestedTier
14371436
: undefined
@@ -1514,8 +1513,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
15141513
}
15151514

15161515
// Include service tier if selected and supported
1517-
const allowedTierNames = new Set(model.info.tiers?.map((t) => t.name).filter(Boolean) || [])
1518-
const serviceTier = this.getAllowedServiceTier(this.options.openAiNativeServiceTier, allowedTierNames)
1516+
const serviceTier = this.getAllowedServiceTier(model)
15191517
if (serviceTier) {
15201518
requestBody[SERVICE_TIER_KEY] = serviceTier
15211519
}

webview-ui/src/components/settings/ModelInfoView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
22

3-
import { OpenAiServiceTier, providerIdentifiers, type ModelInfo } from "@roo-code/types"
3+
import { OpenAiServiceTier, providerIdentifiers, type ModelInfo, type ServiceTier } from "@roo-code/types"
44

55
import { formatPrice } from "@src/utils/formatPrice"
66
import { cn } from "@src/lib/utils"
@@ -18,7 +18,7 @@ type ModelInfoViewProps = {
1818
}
1919

2020
type TierPricingRowProps = {
21-
tier: OpenAiServiceTier
21+
tier: ServiceTier
2222
label: string
2323
modelInfo?: ModelInfo
2424
}

0 commit comments

Comments
 (0)