Skip to content

Commit 910d057

Browse files
committed
refactor(api): centralize service tier keys
1 parent dd9c9cd commit 910d057

12 files changed

Lines changed: 76 additions & 50 deletions

File tree

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
getApiProtocol,
3+
OPEN_AI_CODEX_SERVICE_TIER_KEY,
34
PROVIDER_SETTINGS_KEYS,
45
providerSettingsSchema,
56
providerSettingsSchemaDiscriminated,
@@ -12,12 +13,12 @@ describe("OpenAI Codex provider settings", () => {
1213
const settings = {
1314
apiProvider: providerIdentifiers.openaiCodex,
1415
apiModelId: "gpt-5.6-sol",
15-
openAiCodexServiceTier: OpenAiCodexServiceTier.Priority,
16+
[OPEN_AI_CODEX_SERVICE_TIER_KEY]: OpenAiCodexServiceTier.Priority,
1617
}
1718

1819
expect(providerSettingsSchema.parse(settings)).toEqual(settings)
1920
expect(providerSettingsSchemaDiscriminated.parse(settings)).toEqual(settings)
20-
expect(PROVIDER_SETTINGS_KEYS).toContain("openAiCodexServiceTier")
21+
expect(PROVIDER_SETTINGS_KEYS).toContain(OPEN_AI_CODEX_SERVICE_TIER_KEY)
2122
})
2223

2324
it.each([undefined, OpenAiCodexServiceTier.Default])(
@@ -26,7 +27,7 @@ describe("OpenAI Codex provider settings", () => {
2627
const standardSettings = {
2728
apiProvider: providerIdentifiers.openaiCodex,
2829
apiModelId: "gpt-5.6-sol",
29-
...(openAiCodexServiceTier ? { openAiCodexServiceTier } : {}),
30+
...(openAiCodexServiceTier ? { [OPEN_AI_CODEX_SERVICE_TIER_KEY]: openAiCodexServiceTier } : {}),
3031
}
3132

3233
expect(providerSettingsSchemaDiscriminated.parse(standardSettings)).toEqual(standardSettings)
@@ -38,7 +39,7 @@ describe("OpenAI Codex provider settings", () => {
3839
providerSettingsSchemaDiscriminated.safeParse({
3940
apiProvider: providerIdentifiers.openaiCodex,
4041
apiModelId: "gpt-5.6-sol",
41-
openAiCodexServiceTier: OpenAiServiceTier.Flex,
42+
[OPEN_AI_CODEX_SERVICE_TIER_KEY]: OpenAiServiceTier.Flex,
4243
}).success,
4344
).toBe(false)
4445
})

packages/types/src/model.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ export const verbosityLevelsSchema = z.enum(verbosityLevels)
5454

5555
export type VerbosityLevel = z.infer<typeof verbosityLevelsSchema>
5656

57+
/** Serialized service tier field used in provider request payloads and responses. */
58+
export const SERVICE_TIER_KEY = "service_tier"
59+
5760
/**
5861
* Service tiers for the public OpenAI Responses API.
5962
*/

packages/types/src/provider-settings.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
*/
4343

4444
export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3
45+
export const OPEN_AI_CODEX_SERVICE_TIER_KEY = "openAiCodexServiceTier"
4546

4647
/**
4748
* DynamicProvider
@@ -284,7 +285,9 @@ const geminiCliSchema = apiModelIdProviderModelSchema.extend({
284285

285286
const openAiCodexSchema = apiModelIdProviderModelSchema.extend({
286287
// Codex "Fast" mode maps to the Responses API priority service tier.
287-
openAiCodexServiceTier: z.enum([OpenAiCodexServiceTier.Default, OpenAiCodexServiceTier.Priority]).optional(),
288+
[OPEN_AI_CODEX_SERVICE_TIER_KEY]: z
289+
.enum([OpenAiCodexServiceTier.Default, OpenAiCodexServiceTier.Priority])
290+
.optional(),
288291
})
289292

290293
const openAiNativeSchema = apiModelIdProviderModelSchema.extend({

src/api/providers/__tests__/bedrock.spec.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
BEDROCK_1M_CONTEXT_MODEL_IDS,
5959
BEDROCK_SERVICE_TIER_MODEL_IDS,
6060
bedrockModels,
61+
SERVICE_TIER_KEY,
6162
ApiProviderError,
6263
} from "@roo-code/types"
6364

@@ -1233,10 +1234,10 @@ describe("AwsBedrockHandler", () => {
12331234
const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any
12341235

12351236
// service_tier should be at the top level of the payload
1236-
expect(commandArg.service_tier).toBe("PRIORITY")
1237+
expect(commandArg[SERVICE_TIER_KEY]).toBe("PRIORITY")
12371238
// service_tier should NOT be in additionalModelRequestFields
12381239
if (commandArg.additionalModelRequestFields) {
1239-
expect(commandArg.additionalModelRequestFields.service_tier).toBeUndefined()
1240+
expect(commandArg.additionalModelRequestFields[SERVICE_TIER_KEY]).toBeUndefined()
12401241
}
12411242
})
12421243

@@ -1263,10 +1264,10 @@ describe("AwsBedrockHandler", () => {
12631264
const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any
12641265

12651266
// service_tier should be at the top level of the payload
1266-
expect(commandArg.service_tier).toBe("FLEX")
1267+
expect(commandArg[SERVICE_TIER_KEY]).toBe("FLEX")
12671268
// service_tier should NOT be in additionalModelRequestFields
12681269
if (commandArg.additionalModelRequestFields) {
1269-
expect(commandArg.additionalModelRequestFields.service_tier).toBeUndefined()
1270+
expect(commandArg.additionalModelRequestFields[SERVICE_TIER_KEY]).toBeUndefined()
12701271
}
12711272
})
12721273

@@ -1294,9 +1295,9 @@ describe("AwsBedrockHandler", () => {
12941295
const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any
12951296

12961297
// Service tier should NOT be included for unsupported models (at top level or in additionalModelRequestFields)
1297-
expect(commandArg.service_tier).toBeUndefined()
1298+
expect(commandArg[SERVICE_TIER_KEY]).toBeUndefined()
12981299
if (commandArg.additionalModelRequestFields) {
1299-
expect(commandArg.additionalModelRequestFields.service_tier).toBeUndefined()
1300+
expect(commandArg.additionalModelRequestFields[SERVICE_TIER_KEY]).toBeUndefined()
13001301
}
13011302
})
13021303

@@ -1323,9 +1324,9 @@ describe("AwsBedrockHandler", () => {
13231324
const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any
13241325

13251326
// Service tier should NOT be included when not specified (at top level or in additionalModelRequestFields)
1326-
expect(commandArg.service_tier).toBeUndefined()
1327+
expect(commandArg[SERVICE_TIER_KEY]).toBeUndefined()
13271328
if (commandArg.additionalModelRequestFields) {
1328-
expect(commandArg.additionalModelRequestFields.service_tier).toBeUndefined()
1329+
expect(commandArg.additionalModelRequestFields[SERVICE_TIER_KEY]).toBeUndefined()
13291330
}
13301331
})
13311332
})

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

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

1111
import { Anthropic } from "@anthropic-ai/sdk"
12-
import { OpenAiCodexServiceTier } from "@roo-code/types"
12+
import { OPEN_AI_CODEX_SERVICE_TIER_KEY, OpenAiCodexServiceTier, SERVICE_TIER_KEY } from "@roo-code/types"
1313
import { OpenAiCodexHandler, transformLunaResponsesLiteBody } from "../openai-codex"
1414
import { openAiCodexOAuthManager } from "../../../integrations/openai-codex/oauth"
1515

@@ -85,7 +85,7 @@ describe("OpenAiCodexHandler.createMessage", () => {
8585
it("sends the priority service tier in streaming SDK requests when Fast is selected", async () => {
8686
const handler = new OpenAiCodexHandler({
8787
apiModelId: "gpt-5.6-sol",
88-
openAiCodexServiceTier: OpenAiCodexServiceTier.Priority,
88+
[OPEN_AI_CODEX_SERVICE_TIER_KEY]: OpenAiCodexServiceTier.Priority,
8989
})
9090
vitest.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token")
9191
vitest.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test")
@@ -95,14 +95,17 @@ describe("OpenAiCodexHandler.createMessage", () => {
9595
await drainStream(handler.createMessage("System prompt", []))
9696

9797
const [body] = mockCreate.mock.calls[0]
98-
expect(body).toMatchObject({ stream: true, service_tier: OpenAiCodexServiceTier.Priority })
98+
expect(body).toMatchObject({
99+
stream: true,
100+
[SERVICE_TIER_KEY]: OpenAiCodexServiceTier.Priority,
101+
})
99102
})
100103

101104
it.each([
102105
["an absent preference", {}],
103106
[
104107
"an explicit Standard preference from an older profile",
105-
{ openAiCodexServiceTier: OpenAiCodexServiceTier.Default },
108+
{ [OPEN_AI_CODEX_SERVICE_TIER_KEY]: OpenAiCodexServiceTier.Default },
106109
],
107110
])("omits the service tier in streaming SDK requests for %s", async (_description, serviceTierOptions) => {
108111
const handler = new OpenAiCodexHandler({
@@ -116,13 +119,13 @@ describe("OpenAiCodexHandler.createMessage", () => {
116119

117120
await drainStream(handler.createMessage("System prompt", []))
118121

119-
expect(mockCreate.mock.calls[0][0]).not.toHaveProperty("service_tier")
122+
expect(mockCreate.mock.calls[0][0]).not.toHaveProperty(SERVICE_TIER_KEY)
120123
})
121124

122125
it("preserves the priority service tier in the manual streaming fallback", async () => {
123126
const handler = new OpenAiCodexHandler({
124127
apiModelId: "gpt-5.6-sol",
125-
openAiCodexServiceTier: OpenAiCodexServiceTier.Priority,
128+
[OPEN_AI_CODEX_SERVICE_TIER_KEY]: OpenAiCodexServiceTier.Priority,
126129
})
127130
vitest.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token")
128131
vitest.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test")
@@ -148,7 +151,7 @@ describe("OpenAiCodexHandler.createMessage", () => {
148151

149152
expect(JSON.parse(mockFetch.mock.calls[0][1].body)).toMatchObject({
150153
stream: true,
151-
service_tier: OpenAiCodexServiceTier.Priority,
154+
[SERVICE_TIER_KEY]: OpenAiCodexServiceTier.Priority,
152155
})
153156
})
154157

@@ -264,7 +267,7 @@ describe("OpenAiCodexHandler.completePrompt service tier", () => {
264267
])("uses the %s preference in non-streaming requests", async (_mode, configuredTier, expectedTier) => {
265268
const handler = new OpenAiCodexHandler({
266269
apiModelId: "gpt-5.6-sol",
267-
...(configuredTier ? { openAiCodexServiceTier: configuredTier } : {}),
270+
...(configuredTier ? { [OPEN_AI_CODEX_SERVICE_TIER_KEY]: configuredTier } : {}),
268271
})
269272
vitest.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token")
270273
vitest.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test")
@@ -279,9 +282,9 @@ describe("OpenAiCodexHandler.completePrompt service tier", () => {
279282
const body = JSON.parse(mockFetch.mock.calls[0][1].body)
280283
expect(body.stream).toBe(false)
281284
if (expectedTier) {
282-
expect(body.service_tier).toBe(expectedTier)
285+
expect(body[SERVICE_TIER_KEY]).toBe(expectedTier)
283286
} else {
284-
expect(body).not.toHaveProperty("service_tier")
287+
expect(body).not.toHaveProperty(SERVICE_TIER_KEY)
285288
}
286289
})
287290
})

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

Lines changed: 3 additions & 3 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, OpenAiServiceTier } from "@roo-code/types"
16+
import { ApiProviderError, OpenAiServiceTier, SERVICE_TIER_KEY } from "@roo-code/types"
1717

1818
import { OpenAiNativeHandler } from "../openai-native"
1919
import { ApiHandlerOptions } from "../../../shared/api"
@@ -139,7 +139,7 @@ describe("OpenAiNativeHandler", () => {
139139
}
140140

141141
expect(mockResponsesCreate).toHaveBeenCalledWith(
142-
expect.objectContaining({ service_tier: serviceTier }),
142+
expect.objectContaining({ [SERVICE_TIER_KEY]: serviceTier }),
143143
expect.any(Object),
144144
)
145145
},
@@ -259,7 +259,7 @@ describe("OpenAiNativeHandler", () => {
259259
expect(mockResponsesCreate).toHaveBeenCalledWith(
260260
expect.objectContaining({
261261
stream: false,
262-
service_tier: serviceTier,
262+
[SERVICE_TIER_KEY]: serviceTier,
263263
}),
264264
expect.any(Object),
265265
)

src/api/providers/bedrock.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
BEDROCK_GLOBAL_INFERENCE_MODEL_IDS,
3434
BEDROCK_SERVICE_TIER_MODEL_IDS,
3535
BEDROCK_SERVICE_TIER_PRICING,
36+
SERVICE_TIER_KEY,
3637
ApiProviderError,
3738
} from "@roo-code/types"
3839
import { TelemetryService } from "@roo-code/telemetry"
@@ -99,7 +100,7 @@ interface BedrockPayload {
99100
// AWS Bedrock service tiers (STANDARD, FLEX, PRIORITY) are specified at the top level
100101
// https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html
101102
type BedrockPayloadWithServiceTier = BedrockPayload & {
102-
service_tier?: BedrockServiceTier
103+
[SERVICE_TIER_KEY]?: BedrockServiceTier
103104
}
104105

105106
// Define specific types for content block events to avoid 'as any' usage
@@ -553,7 +554,7 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
553554
...(thinkingEnabled && { anthropic_version: "bedrock-2023-05-31" }),
554555
toolConfig,
555556
// Add service_tier as a top-level parameter (not inside additionalModelRequestFields)
556-
...(useServiceTier && { service_tier: this.options.awsBedrockServiceTier }),
557+
...(useServiceTier && { [SERVICE_TIER_KEY]: this.options.awsBedrockServiceTier }),
557558
}
558559

559560
// Create AbortController with 10 minute timeout

src/api/providers/openai-codex.ts

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

66
import {
77
type ModelInfo,
8+
OPEN_AI_CODEX_SERVICE_TIER_KEY,
89
OpenAiCodexServiceTier,
910
openAiCodexDefaultModelId,
1011
OpenAiCodexModelId,
1112
openAiCodexModels,
13+
SERVICE_TIER_KEY,
1214
type ReasoningEffort,
1315
type ReasoningEffortExtended,
1416
ApiProviderError,
@@ -41,7 +43,9 @@ const LUNA_MODEL_ID = "gpt-5.6-luna"
4143
const LUNA_CODEX_VERSION = "0.144.0"
4244

4345
const getOpenAiCodexServiceTier = (options: ApiHandlerOptions): OpenAiCodexRequestServiceTier | undefined =>
44-
options.openAiCodexServiceTier === OpenAiCodexServiceTier.Priority ? OpenAiCodexServiceTier.Priority : undefined
46+
options[OPEN_AI_CODEX_SERVICE_TIER_KEY] === OpenAiCodexServiceTier.Priority
47+
? OpenAiCodexServiceTier.Priority
48+
: undefined
4549

4650
function stripInputImageDetail(value: any): any {
4751
if (Array.isArray(value)) {
@@ -371,7 +375,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
371375
model: string
372376
input: Array<{ role: "user" | "assistant"; content: any[] } | { type: string; content: string }>
373377
stream: boolean
374-
service_tier?: OpenAiCodexRequestServiceTier
378+
[SERVICE_TIER_KEY]?: OpenAiCodexRequestServiceTier
375379
reasoning?: { effort?: ReasoningEffortExtended; summary?: "auto" }
376380
temperature?: number
377381
store?: boolean
@@ -397,7 +401,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
397401
stream: true,
398402
store: false,
399403
instructions: systemPrompt,
400-
...(serviceTier ? { service_tier: serviceTier } : {}),
404+
...(serviceTier ? { [SERVICE_TIER_KEY]: serviceTier } : {}),
401405
// Only include encrypted reasoning content when reasoning effort is set
402406
...(reasoningEffort ? { include: ["reasoning.encrypted_content"] } : {}),
403407
...(reasoningEffort
@@ -1282,7 +1286,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
12821286
],
12831287
stream: false,
12841288
store: false,
1285-
...(serviceTier ? { service_tier: serviceTier } : {}),
1289+
...(serviceTier ? { [SERVICE_TIER_KEY]: serviceTier } : {}),
12861290
...(reasoningEffort ? { include: ["reasoning.encrypted_content"] } : {}),
12871291
}
12881292

src/api/providers/openai-native.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
type VerbosityLevel,
1515
type ReasoningEffortExtended,
1616
OpenAiServiceTier,
17+
SERVICE_TIER_KEY,
1718
type ServiceTier,
1819
ApiProviderError,
1920
} from "@roo-code/types"
@@ -319,7 +320,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
319320
max_output_tokens?: number
320321
store?: boolean
321322
instructions?: string
322-
service_tier?: ServiceTier
323+
[SERVICE_TIER_KEY]?: ServiceTier
323324
include?: string[]
324325
/** Prompt cache retention policy: "in_memory" (default) or "24h" for extended caching */
325326
prompt_cache_retention?: "in_memory" | "24h"
@@ -371,7 +372,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
371372
// Include tier when selected and supported by the model, or when explicitly "default"
372373
...(requestedTier &&
373374
(requestedTier === OpenAiServiceTier.Default || allowedTierNames.has(requestedTier)) && {
374-
service_tier: requestedTier,
375+
[SERVICE_TIER_KEY]: requestedTier,
375376
}),
376377
// Enable extended prompt cache retention for models that support it.
377378
// This uses the OpenAI Responses API `prompt_cache_retention` parameter.
@@ -706,8 +707,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
706707
const parsed = JSON.parse(data)
707708

708709
// Capture resolved service tier if present
709-
if (parsed.response?.service_tier) {
710-
this.lastServiceTier = parsed.response.service_tier as ServiceTier
710+
if (parsed.response?.[SERVICE_TIER_KEY]) {
711+
this.lastServiceTier = parsed.response[SERVICE_TIER_KEY] as ServiceTier
711712
}
712713
// Capture complete output array (includes reasoning items with encrypted_content)
713714
if (parsed.response?.output && Array.isArray(parsed.response.output)) {
@@ -1017,8 +1018,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
10171018
}
10181019
} else if (parsed.type === "response.completed" || parsed.type === "response.done") {
10191020
// Capture resolved service tier if present
1020-
if (parsed.response?.service_tier) {
1021-
this.lastServiceTier = parsed.response.service_tier as ServiceTier
1021+
if (parsed.response?.[SERVICE_TIER_KEY]) {
1022+
this.lastServiceTier = parsed.response[SERVICE_TIER_KEY] as ServiceTier
10221023
}
10231024
// Capture top-level response id
10241025
if (parsed.response?.id) {
@@ -1147,8 +1148,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
11471148
*/
11481149
private async *processEvent(event: any, model: OpenAiNativeModel): ApiStream {
11491150
// Capture resolved service tier when available
1150-
if (event?.response?.service_tier) {
1151-
this.lastServiceTier = event.response.service_tier as ServiceTier
1151+
if (event?.response?.[SERVICE_TIER_KEY]) {
1152+
this.lastServiceTier = event.response[SERVICE_TIER_KEY] as ServiceTier
11521153
}
11531154
// Capture complete output array (includes reasoning items with encrypted_content)
11541155
if (event?.response?.output && Array.isArray(event.response.output)) {
@@ -1514,7 +1515,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
15141515
const requestedTier = (this.options.openAiNativeServiceTier as ServiceTier | undefined) || undefined
15151516
const allowedTierNames = new Set(model.info.tiers?.map((t) => t.name).filter(Boolean) || [])
15161517
if (requestedTier && (requestedTier === OpenAiServiceTier.Default || allowedTierNames.has(requestedTier))) {
1517-
requestBody.service_tier = requestedTier
1518+
requestBody[SERVICE_TIER_KEY] = requestedTier
15181519
}
15191520

15201521
// Add reasoning if supported

0 commit comments

Comments
 (0)