Skip to content

Commit ae7e88b

Browse files
[codex] Sync app-server protocol, service tiers, and provider startup (#3036)
Co-authored-by: codex <codex@users.noreply.github.com>
1 parent 1916ac6 commit ae7e88b

20 files changed

Lines changed: 4233 additions & 2079 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { assert, it } from "@effect/vitest";
2+
3+
import { ProviderInstanceId } from "@t3tools/contracts";
4+
import { createModelSelection } from "@t3tools/shared/model";
5+
6+
import { getCodexServiceTierOptionValue } from "./codexModelOptions.ts";
7+
8+
it("returns the selected Codex service tier id", () => {
9+
const selection = createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.5", [
10+
{ id: "serviceTier", value: "flex" },
11+
]);
12+
13+
assert.equal(getCodexServiceTierOptionValue(selection), "flex");
14+
});
15+
16+
it("keeps legacy persisted fast mode selections working", () => {
17+
const selection = createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.4", [
18+
{ id: "fastMode", value: true },
19+
]);
20+
21+
assert.equal(getCodexServiceTierOptionValue(selection), "fast");
22+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { ModelSelection } from "@t3tools/contracts";
2+
import {
3+
getModelSelectionBooleanOptionValue,
4+
getModelSelectionStringOptionValue,
5+
} from "@t3tools/shared/model";
6+
7+
export function getCodexServiceTierOptionValue(
8+
modelSelection: ModelSelection | null | undefined,
9+
): string | undefined {
10+
return (
11+
getModelSelectionStringOptionValue(modelSelection, "serviceTier") ??
12+
(getModelSelectionBooleanOptionValue(modelSelection, "fastMode") === true ? "fast" : undefined)
13+
);
14+
}

apps/server/src/provider/Layers/CodexAdapter.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ validationLayer("CodexAdapterLive validation", (it) => {
271271
provider: ProviderDriverKind.make("codex"),
272272
threadId: asThreadId("thread-1"),
273273
modelSelection: createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.3-codex", [
274-
{ id: "fastMode", value: true },
274+
{ id: "serviceTier", value: "priority" },
275275
]),
276276
runtimeMode: "full-access",
277277
});
@@ -281,7 +281,7 @@ validationLayer("CodexAdapterLive validation", (it) => {
281281
cwd: process.cwd(),
282282
model: "gpt-5.3-codex",
283283
providerInstanceId: ProviderInstanceId.make("codex"),
284-
serviceTier: "fast",
284+
serviceTier: "priority",
285285
threadId: asThreadId("thread-1"),
286286
runtimeMode: "full-access",
287287
});
@@ -344,7 +344,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
344344
input: "hello",
345345
modelSelection: createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.3-codex", [
346346
{ id: "reasoningEffort", value: "high" },
347-
{ id: "fastMode", value: true },
347+
{ id: "serviceTier", value: "priority" },
348348
]),
349349
attachments: [],
350350
}),
@@ -354,7 +354,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
354354
input: "hello",
355355
model: "gpt-5.3-codex",
356356
effort: "high",
357-
serviceTier: "fast",
357+
serviceTier: "priority",
358358
});
359359
}),
360360
);
@@ -398,7 +398,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
398398
"gpt-5.3-codex",
399399
[
400400
{ id: "reasoningEffort", value: "high" },
401-
{ id: "fastMode", value: true },
401+
{ id: "serviceTier", value: "flex" },
402402
],
403403
),
404404
attachments: [],
@@ -409,7 +409,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
409409
input: "hello",
410410
model: "gpt-5.3-codex",
411411
effort: "high",
412-
serviceTier: "fast",
412+
serviceTier: "flex",
413413
});
414414
}).pipe(Effect.provide(customLayer));
415415
});

apps/server/src/provider/Layers/CodexAdapter.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,8 @@ import { ChildProcessSpawner } from "effect/unstable/process";
3737
import * as CodexErrors from "effect-codex-app-server/errors";
3838
import * as EffectCodexSchema from "effect-codex-app-server/schema";
3939

40-
import {
41-
getModelSelectionBooleanOptionValue,
42-
getModelSelectionStringOptionValue,
43-
} from "@t3tools/shared/model";
40+
import { getModelSelectionStringOptionValue } from "@t3tools/shared/model";
41+
import { getCodexServiceTierOptionValue } from "../../codexModelOptions.ts";
4442

4543
import {
4644
ProviderAdapterRequestError,
@@ -1380,6 +1378,10 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
13801378
yield* Effect.suspend(() => stopSessionInternal(existing));
13811379
}
13821380

1381+
const serviceTier =
1382+
input.modelSelection?.instanceId === boundInstanceId
1383+
? getCodexServiceTierOptionValue(input.modelSelection)
1384+
: undefined;
13831385
const runtimeInput: CodexSessionRuntimeOptions = {
13841386
threadId: input.threadId,
13851387
providerInstanceId: boundInstanceId,
@@ -1394,10 +1396,7 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
13941396
...(input.modelSelection?.instanceId === boundInstanceId
13951397
? { model: input.modelSelection.model }
13961398
: {}),
1397-
...(input.modelSelection?.instanceId === boundInstanceId &&
1398-
getModelSelectionBooleanOptionValue(input.modelSelection, "fastMode") === true
1399-
? { serviceTier: "fast" }
1400-
: {}),
1399+
...(serviceTier ? { serviceTier } : {}),
14011400
};
14021401
const sessionScope = yield* Scope.make("sequential");
14031402
let sessionScopeTransferred = false;
@@ -1513,9 +1512,9 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
15131512
input.modelSelection?.instanceId === boundInstanceId
15141513
? getModelSelectionStringOptionValue(input.modelSelection, "reasoningEffort")
15151514
: undefined;
1516-
const fastMode =
1515+
const serviceTier =
15171516
input.modelSelection?.instanceId === boundInstanceId
1518-
? getModelSelectionBooleanOptionValue(input.modelSelection, "fastMode")
1517+
? getCodexServiceTierOptionValue(input.modelSelection)
15191518
: undefined;
15201519
return yield* session.runtime
15211520
.sendTurn({
@@ -1528,7 +1527,7 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
15281527
effort: reasoningEffort as EffectCodexSchema.V2TurnStartParams__ReasoningEffort,
15291528
}
15301529
: {}),
1531-
...(fastMode === true ? { serviceTier: "fast" } : {}),
1530+
...(serviceTier ? { serviceTier } : {}),
15321531
...(input.interactionMode !== undefined ? { interactionMode: input.interactionMode } : {}),
15331532
...(codexAttachments.length > 0 ? { attachments: codexAttachments } : {}),
15341533
})
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { assert, it } from "@effect/vitest";
2+
3+
import { mapCodexModelCapabilities } from "./CodexProvider.ts";
4+
5+
it("maps current Codex model capability fields", () => {
6+
const capabilities = mapCodexModelCapabilities({
7+
additionalSpeedTiers: [],
8+
defaultReasoningEffort: "super-high",
9+
description: "Test model",
10+
displayName: "GPT Test",
11+
hidden: false,
12+
id: "gpt-test",
13+
isDefault: true,
14+
model: "gpt-test",
15+
defaultServiceTier: "flex",
16+
serviceTiers: [
17+
{
18+
id: "priority",
19+
name: "Fast",
20+
description: "Lower latency responses.",
21+
},
22+
{
23+
id: "flex",
24+
name: "Flex",
25+
description: "Lower-cost asynchronous routing.",
26+
},
27+
],
28+
supportedReasoningEfforts: [
29+
{
30+
description: "Maximum reasoning",
31+
reasoningEffort: "super-high",
32+
},
33+
],
34+
});
35+
36+
assert.deepStrictEqual(capabilities.optionDescriptors, [
37+
{
38+
id: "reasoningEffort",
39+
label: "Reasoning",
40+
type: "select",
41+
options: [{ id: "super-high", label: "super-high", isDefault: true }],
42+
currentValue: "super-high",
43+
},
44+
{
45+
id: "serviceTier",
46+
label: "Service Tier",
47+
type: "select",
48+
options: [
49+
{ id: "default", label: "Standard" },
50+
{
51+
id: "priority",
52+
label: "Fast",
53+
description: "Lower latency responses.",
54+
},
55+
{
56+
id: "flex",
57+
label: "Flex",
58+
description: "Lower-cost asynchronous routing.",
59+
isDefault: true,
60+
},
61+
],
62+
currentValue: "flex",
63+
},
64+
]);
65+
});
66+
67+
it("uses standard routing when the catalog has no default service tier", () => {
68+
const capabilities = mapCodexModelCapabilities({
69+
additionalSpeedTiers: ["fast"],
70+
defaultReasoningEffort: "medium",
71+
defaultServiceTier: null,
72+
description: "Test model",
73+
displayName: "GPT Test",
74+
hidden: false,
75+
id: "gpt-test",
76+
isDefault: true,
77+
model: "gpt-test",
78+
serviceTiers: [
79+
{
80+
id: "priority",
81+
name: "Fast",
82+
description: "1.5x speed, increased usage",
83+
},
84+
],
85+
supportedReasoningEfforts: [],
86+
});
87+
88+
assert.deepStrictEqual(capabilities.optionDescriptors, [
89+
{
90+
id: "serviceTier",
91+
label: "Service Tier",
92+
type: "select",
93+
options: [
94+
{ id: "default", label: "Standard", isDefault: true },
95+
{
96+
id: "priority",
97+
label: "Fast",
98+
description: "1.5x speed, increased usage",
99+
},
100+
],
101+
currentValue: "default",
102+
},
103+
]);
104+
});

apps/server/src/provider/Layers/CodexProvider.ts

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
ServerProvider,
1818
ServerProviderState,
1919
ModelCapabilities,
20+
ProviderOptionDescriptor,
2021
ServerProviderModel,
2122
ServerProviderSkill,
2223
} from "@t3tools/contracts";
@@ -44,7 +45,7 @@ export interface CodexAppServerProviderSnapshot {
4445
readonly skills: ReadonlyArray<ServerProviderSkill>;
4546
}
4647

47-
const REASONING_EFFORT_LABELS: Record<CodexSchema.V2ModelListResponse__ReasoningEffort, string> = {
48+
const REASONING_EFFORT_LABELS: Readonly<Record<string, string>> = {
4849
none: "None",
4950
minimal: "Minimal",
5051
low: "Low",
@@ -53,6 +54,12 @@ const REASONING_EFFORT_LABELS: Record<CodexSchema.V2ModelListResponse__Reasoning
5354
xhigh: "Extra High",
5455
};
5556

57+
const DEFAULT_SERVICE_TIER_ID = "default";
58+
59+
function reasoningEffortLabel(reasoningEffort: string): string {
60+
return REASONING_EFFORT_LABELS[reasoningEffort] ?? reasoningEffort;
61+
}
62+
5663
function codexAccountAuthLabel(account: CodexSchema.V2GetAccountResponse["account"]) {
5764
if (!account) return undefined;
5865
if (account.type === "apiKey") return "OpenAI API Key";
@@ -93,46 +100,71 @@ function codexAccountEmail(account: CodexSchema.V2GetAccountResponse["account"])
93100
return account.email;
94101
}
95102

96-
function mapCodexModelCapabilities(
103+
export function mapCodexModelCapabilities(
97104
model: CodexSchema.V2ModelListResponse__Model,
98105
): ModelCapabilities {
99106
const reasoningOptions = model.supportedReasoningEfforts.map(({ reasoningEffort }) =>
100107
reasoningEffort === model.defaultReasoningEffort
101108
? {
102109
id: reasoningEffort,
103-
label: REASONING_EFFORT_LABELS[reasoningEffort],
110+
label: reasoningEffortLabel(reasoningEffort),
104111
isDefault: true,
105112
}
106113
: {
107114
id: reasoningEffort,
108-
label: REASONING_EFFORT_LABELS[reasoningEffort],
115+
label: reasoningEffortLabel(reasoningEffort),
109116
},
110117
);
111118
const defaultReasoning = reasoningOptions.find((option) => option.isDefault)?.id;
112-
const supportsFastMode = (model.additionalSpeedTiers ?? []).includes("fast");
119+
const serviceTiers =
120+
model.serviceTiers && model.serviceTiers.length > 0
121+
? model.serviceTiers
122+
: (model.additionalSpeedTiers ?? []).map((id) => ({
123+
id,
124+
name: id === "fast" ? "Fast" : id,
125+
description: "",
126+
}));
127+
const catalogDefaultServiceTier = serviceTiers.some(
128+
(tier) => tier.id === model.defaultServiceTier,
129+
)
130+
? model.defaultServiceTier
131+
: null;
132+
const defaultServiceTier = catalogDefaultServiceTier ?? DEFAULT_SERVICE_TIER_ID;
133+
const optionDescriptors: ProviderOptionDescriptor[] = [];
134+
135+
if (reasoningOptions.length > 0) {
136+
optionDescriptors.push({
137+
id: "reasoningEffort",
138+
label: "Reasoning",
139+
type: "select",
140+
options: reasoningOptions,
141+
...(defaultReasoning ? { currentValue: defaultReasoning } : {}),
142+
});
143+
}
144+
if (serviceTiers.length > 0) {
145+
optionDescriptors.push({
146+
id: "serviceTier",
147+
label: "Service Tier",
148+
type: "select",
149+
options: [
150+
{
151+
id: DEFAULT_SERVICE_TIER_ID,
152+
label: "Standard",
153+
...(defaultServiceTier === DEFAULT_SERVICE_TIER_ID ? { isDefault: true } : {}),
154+
},
155+
...serviceTiers.map((tier) => ({
156+
id: tier.id,
157+
label: tier.name,
158+
...(tier.description ? { description: tier.description } : {}),
159+
...(defaultServiceTier === tier.id ? { isDefault: true } : {}),
160+
})),
161+
],
162+
currentValue: defaultServiceTier,
163+
});
164+
}
165+
113166
return createModelCapabilities({
114-
optionDescriptors: [
115-
...(reasoningOptions.length > 0
116-
? [
117-
{
118-
id: "reasoningEffort",
119-
label: "Reasoning",
120-
type: "select" as const,
121-
options: reasoningOptions,
122-
...(defaultReasoning ? { currentValue: defaultReasoning } : {}),
123-
},
124-
]
125-
: []),
126-
...(supportsFastMode
127-
? [
128-
{
129-
id: "fastMode",
130-
label: "Fast Mode",
131-
type: "boolean" as const,
132-
},
133-
]
134-
: []),
135-
],
167+
optionDescriptors,
136168
});
137169
}
138170

0 commit comments

Comments
 (0)