Skip to content

Commit 42de997

Browse files
committed
refactor(openai): centralize service tier values
1 parent 82da39f commit 42de997

15 files changed

Lines changed: 116 additions & 85 deletions

File tree

packages/types/src/__tests__/provider-settings.test.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,41 @@ import {
44
providerSettingsSchema,
55
providerSettingsSchemaDiscriminated,
66
} from "../provider-settings.js"
7+
import { OpenAiServiceTier } from "../model.js"
8+
import { providerIdentifiers } from "../provider-identifiers.js"
79

810
describe("OpenAI Codex provider settings", () => {
911
it("preserves the Fast preference in general and provider-specific schemas", () => {
1012
const settings = {
11-
apiProvider: "openai-codex" as const,
13+
apiProvider: providerIdentifiers.openaiCodex,
1214
apiModelId: "gpt-5.6-sol",
13-
openAiCodexServiceTier: "priority" as const,
15+
openAiCodexServiceTier: OpenAiServiceTier.Priority,
1416
}
1517

1618
expect(providerSettingsSchema.parse(settings)).toEqual(settings)
1719
expect(providerSettingsSchemaDiscriminated.parse(settings)).toEqual(settings)
1820
expect(PROVIDER_SETTINGS_KEYS).toContain("openAiCodexServiceTier")
1921
})
2022

21-
it.each([undefined, "default"])("accepts %s as the Standard preference", (openAiCodexServiceTier) => {
22-
const standardSettings = {
23-
apiProvider: "openai-codex" as const,
24-
apiModelId: "gpt-5.6-sol",
25-
...(openAiCodexServiceTier ? { openAiCodexServiceTier } : {}),
26-
}
23+
it.each([undefined, OpenAiServiceTier.Default])(
24+
"accepts %s as the Standard preference",
25+
(openAiCodexServiceTier) => {
26+
const standardSettings = {
27+
apiProvider: providerIdentifiers.openaiCodex,
28+
apiModelId: "gpt-5.6-sol",
29+
...(openAiCodexServiceTier ? { openAiCodexServiceTier } : {}),
30+
}
2731

28-
expect(providerSettingsSchemaDiscriminated.parse(standardSettings)).toEqual(standardSettings)
29-
})
32+
expect(providerSettingsSchemaDiscriminated.parse(standardSettings)).toEqual(standardSettings)
33+
},
34+
)
3035

3136
it("rejects unsupported service tiers", () => {
3237
expect(
3338
providerSettingsSchemaDiscriminated.safeParse({
34-
apiProvider: "openai-codex",
39+
apiProvider: providerIdentifiers.openaiCodex,
3540
apiModelId: "gpt-5.6-sol",
36-
openAiCodexServiceTier: "flex",
41+
openAiCodexServiceTier: OpenAiServiceTier.Flex,
3742
}).success,
3843
).toBe(false)
3944
})

packages/types/src/model.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,15 @@ export type VerbosityLevel = z.infer<typeof verbosityLevelsSchema>
5757
/**
5858
* Service tiers (OpenAI Responses API)
5959
*/
60-
export const serviceTiers = ["default", "flex", "priority"] as const
60+
export enum OpenAiServiceTier {
61+
Default = "default",
62+
Flex = "flex",
63+
Priority = "priority",
64+
}
65+
66+
export const serviceTiers = Object.values(OpenAiServiceTier) as [`${OpenAiServiceTier}`, ...`${OpenAiServiceTier}`[]]
6167
export const serviceTierSchema = z.enum(serviceTiers)
62-
export type ServiceTier = z.infer<typeof serviceTierSchema>
68+
export type ServiceTier = `${OpenAiServiceTier}`
6369

6470
/**
6571
* ModelParameter

packages/types/src/provider-settings.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { z } from "zod"
22

3-
import { modelInfoSchema, reasoningEffortSettingSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js"
3+
import {
4+
modelInfoSchema,
5+
OpenAiServiceTier,
6+
reasoningEffortSettingSchema,
7+
verbosityLevelsSchema,
8+
serviceTierSchema,
9+
} from "./model.js"
410
import { codebaseIndexProviderSchema } from "./codebase-index.js"
511
import {
612
providerIdentifiers,
@@ -278,7 +284,7 @@ const geminiCliSchema = apiModelIdProviderModelSchema.extend({
278284

279285
const openAiCodexSchema = apiModelIdProviderModelSchema.extend({
280286
// Codex "Fast" mode maps to the Responses API priority service tier.
281-
openAiCodexServiceTier: z.enum(["default", "priority"]).optional(),
287+
openAiCodexServiceTier: z.enum([OpenAiServiceTier.Default, OpenAiServiceTier.Priority]).optional(),
282288
})
283289

284290
const openAiNativeSchema = apiModelIdProviderModelSchema.extend({

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ vitest.mock("@roo-code/telemetry", () => ({
99
}))
1010

1111
import { Anthropic } from "@anthropic-ai/sdk"
12+
import { OpenAiServiceTier } from "@roo-code/types"
1213
import { OpenAiCodexHandler, transformLunaResponsesLiteBody } from "../openai-codex"
1314
import { openAiCodexOAuthManager } from "../../../integrations/openai-codex/oauth"
1415

@@ -84,7 +85,7 @@ describe("OpenAiCodexHandler.createMessage", () => {
8485
it("sends the priority service tier in streaming SDK requests when Fast is selected", async () => {
8586
const handler = new OpenAiCodexHandler({
8687
apiModelId: "gpt-5.6-sol",
87-
openAiCodexServiceTier: "priority",
88+
openAiCodexServiceTier: OpenAiServiceTier.Priority,
8889
})
8990
vitest.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token")
9091
vitest.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test")
@@ -94,12 +95,15 @@ describe("OpenAiCodexHandler.createMessage", () => {
9495
await drainStream(handler.createMessage("System prompt", []))
9596

9697
const [body] = mockCreate.mock.calls[0]
97-
expect(body).toMatchObject({ stream: true, service_tier: "priority" })
98+
expect(body).toMatchObject({ stream: true, service_tier: OpenAiServiceTier.Priority })
9899
})
99100

100101
it.each([
101102
["an absent preference", {}],
102-
["an explicit Standard preference from an older profile", { openAiCodexServiceTier: "default" }],
103+
[
104+
"an explicit Standard preference from an older profile",
105+
{ openAiCodexServiceTier: OpenAiServiceTier.Default },
106+
],
103107
])("omits the service tier in streaming SDK requests for %s", async (_description, serviceTierOptions) => {
104108
const handler = new OpenAiCodexHandler({
105109
apiModelId: "gpt-5.6-sol",
@@ -118,7 +122,7 @@ describe("OpenAiCodexHandler.createMessage", () => {
118122
it("preserves the priority service tier in the manual streaming fallback", async () => {
119123
const handler = new OpenAiCodexHandler({
120124
apiModelId: "gpt-5.6-sol",
121-
openAiCodexServiceTier: "priority",
125+
openAiCodexServiceTier: OpenAiServiceTier.Priority,
122126
})
123127
vitest.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token")
124128
vitest.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test")
@@ -144,7 +148,7 @@ describe("OpenAiCodexHandler.createMessage", () => {
144148

145149
expect(JSON.parse(mockFetch.mock.calls[0][1].body)).toMatchObject({
146150
stream: true,
147-
service_tier: "priority",
151+
service_tier: OpenAiServiceTier.Priority,
148152
})
149153
})
150154

@@ -254,13 +258,13 @@ describe("OpenAiCodexHandler.completePrompt service tier", () => {
254258
vitest.unstubAllGlobals()
255259
})
256260

257-
it.each([
258-
["Fast", "priority", "priority"],
261+
it.each<[string, OpenAiServiceTier.Priority | undefined, OpenAiServiceTier.Priority | undefined]>([
262+
["Fast", OpenAiServiceTier.Priority, OpenAiServiceTier.Priority],
259263
["Standard", undefined, undefined],
260264
])("uses the %s preference in non-streaming requests", async (_mode, configuredTier, expectedTier) => {
261265
const handler = new OpenAiCodexHandler({
262266
apiModelId: "gpt-5.6-sol",
263-
...(configuredTier ? { openAiCodexServiceTier: configuredTier as "priority" } : {}),
267+
...(configuredTier ? { openAiCodexServiceTier: configuredTier } : {}),
264268
})
265269
vitest.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token")
266270
vitest.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test")

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect, beforeEach } from "vitest"
22
import { OpenAiNativeHandler } from "../openai-native"
3-
import { openAiNativeModels } from "@roo-code/types"
3+
import { OpenAiServiceTier, openAiNativeModels } from "@roo-code/types"
44

55
describe("OpenAiNativeHandler - normalizeUsage", () => {
66
let handler: OpenAiNativeHandler
@@ -468,7 +468,7 @@ describe("OpenAiNativeHandler - normalizeUsage", () => {
468468
it("should not apply GPT-5.4 long-context pricing to priority tier", () => {
469469
handler = new OpenAiNativeHandler({
470470
openAiNativeApiKey: "test-key",
471-
openAiNativeServiceTier: "priority",
471+
openAiNativeServiceTier: OpenAiServiceTier.Priority,
472472
})
473473

474474
const usage = {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ vitest.mock("@roo-code/telemetry", () => ({
1313
import { Anthropic } from "@anthropic-ai/sdk"
1414
import OpenAI from "openai"
1515

16-
import { ApiProviderError } from "@roo-code/types"
16+
import { ApiProviderError, OpenAiServiceTier } from "@roo-code/types"
1717

1818
import { OpenAiNativeHandler } from "../openai-native"
1919
import { ApiHandlerOptions } from "../../../shared/api"
@@ -332,7 +332,7 @@ describe("OpenAiNativeHandler", () => {
332332
expect(modelInfo.info.longContextPricing).toBeUndefined()
333333
expect(modelInfo.info.tiers).toEqual([
334334
expect.objectContaining({
335-
name: "flex",
335+
name: OpenAiServiceTier.Flex,
336336
outputPrice: 0.625,
337337
}),
338338
])

src/api/providers/openai-codex.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import OpenAI from "openai"
55

66
import {
77
type ModelInfo,
8+
OpenAiServiceTier,
89
openAiCodexDefaultModelId,
910
OpenAiCodexModelId,
1011
openAiCodexModels,
@@ -29,7 +30,7 @@ import { t } from "../../i18n"
2930

3031
export type OpenAiCodexModel = ReturnType<OpenAiCodexHandler["getModel"]>
3132

32-
type OpenAiCodexRequestServiceTier = "priority"
33+
type OpenAiCodexRequestServiceTier = OpenAiServiceTier.Priority
3334

3435
/**
3536
* OpenAI Codex base URL for API requests
@@ -39,9 +40,8 @@ const CODEX_API_BASE_URL = "https://chatgpt.com/backend-api/codex"
3940
const LUNA_MODEL_ID = "gpt-5.6-luna"
4041
const LUNA_CODEX_VERSION = "0.144.0"
4142

42-
function getOpenAiCodexServiceTier(options: ApiHandlerOptions): OpenAiCodexRequestServiceTier | undefined {
43-
return options.openAiCodexServiceTier === "priority" ? "priority" : undefined
44-
}
43+
const getOpenAiCodexServiceTier = (options: ApiHandlerOptions): OpenAiCodexRequestServiceTier | undefined =>
44+
options.openAiCodexServiceTier === OpenAiServiceTier.Priority ? OpenAiServiceTier.Priority : undefined
4545

4646
function stripInputImageDetail(value: any): any {
4747
if (Array.isArray(value)) {

src/api/providers/openai-native.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
type ReasoningEffort,
1414
type VerbosityLevel,
1515
type ReasoningEffortExtended,
16+
OpenAiServiceTier,
1617
type ServiceTier,
1718
ApiProviderError,
1819
} from "@roo-code/types"
@@ -369,7 +370,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
369370
...(model.maxTokens ? { max_output_tokens: model.maxTokens } : {}),
370371
// Include tier when selected and supported by the model, or when explicitly "default"
371372
...(requestedTier &&
372-
(requestedTier === "default" || allowedTierNames.has(requestedTier)) && {
373+
(requestedTier === OpenAiServiceTier.Default || allowedTierNames.has(requestedTier)) && {
373374
service_tier: requestedTier,
374375
}),
375376
// Enable extended prompt cache retention for models that support it.
@@ -1418,7 +1419,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
14181419
* If no tier or no overrides exist, the original ModelInfo is returned.
14191420
*/
14201421
private applyServiceTierPricing(info: ModelInfo, tier?: ServiceTier): ModelInfo {
1421-
if (!tier || tier === "default") return info
1422+
if (!tier || tier === OpenAiServiceTier.Default) return info
14221423

14231424
// Find the tier with matching name in the tiers array
14241425
const tierInfo = info.tiers?.find((t) => t.name === tier)
@@ -1512,7 +1513,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
15121513
// Include service tier if selected and supported
15131514
const requestedTier = (this.options.openAiNativeServiceTier as ServiceTier | undefined) || undefined
15141515
const allowedTierNames = new Set(model.info.tiers?.map((t) => t.name).filter(Boolean) || [])
1515-
if (requestedTier && (requestedTier === "default" || allowedTierNames.has(requestedTier))) {
1516+
if (requestedTier && (requestedTier === OpenAiServiceTier.Default || allowedTierNames.has(requestedTier))) {
15161517
requestBody.service_tier = requestedTier
15171518
}
15181519

src/core/config/__tests__/ProviderSettingsManager.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { ExtensionContext } from "vscode"
44

5-
import type { ProviderSettings } from "@roo-code/types"
5+
import { OpenAiServiceTier, providerIdentifiers, type ProviderSettings } from "@roo-code/types"
66

77
import { ProviderSettingsManager, ProviderProfiles, SyncCloudProfilesResult } from "../ProviderSettingsManager"
88

@@ -451,7 +451,7 @@ describe("ProviderSettingsManager", () => {
451451
expect(storedConfig).toEqual(expectedConfig)
452452
})
453453

454-
it.each(["default", "priority"] as const)(
454+
it.each([OpenAiServiceTier.Default, OpenAiServiceTier.Priority] as const)(
455455
"should persist the OpenAI Codex %s speed preference",
456456
async (openAiCodexServiceTier) => {
457457
mockSecrets.get.mockResolvedValue(
@@ -463,14 +463,14 @@ describe("ProviderSettingsManager", () => {
463463
)
464464

465465
await providerSettingsManager.saveConfig("codex", {
466-
apiProvider: "openai-codex",
466+
apiProvider: providerIdentifiers.openaiCodex,
467467
apiModelId: "gpt-5.6-sol",
468468
openAiCodexServiceTier,
469469
})
470470

471471
const storedProfiles = JSON.parse(mockSecrets.store.mock.calls.at(-1)?.[1])
472472
expect(storedProfiles.apiConfigs.codex).toMatchObject({
473-
apiProvider: "openai-codex",
473+
apiProvider: providerIdentifiers.openaiCodex,
474474
apiModelId: "gpt-5.6-sol",
475475
openAiCodexServiceTier,
476476
})

src/shared/cost.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { ModelInfo } from "@roo-code/types"
2-
import type { ServiceTier } from "@roo-code/types"
1+
import { OpenAiServiceTier, type ModelInfo, type ServiceTier } from "@roo-code/types"
32

43
export interface ApiCostResult {
54
totalInputTokens: number
@@ -13,7 +12,7 @@ function applyLongContextPricing(modelInfo: ModelInfo, totalInputTokens: number,
1312
return modelInfo
1413
}
1514

16-
const effectiveServiceTier = serviceTier ?? "default"
15+
const effectiveServiceTier = serviceTier ?? OpenAiServiceTier.Default
1716
if (pricing.appliesToServiceTiers && !pricing.appliesToServiceTiers.includes(effectiveServiceTier)) {
1817
return modelInfo
1918
}

0 commit comments

Comments
 (0)