fix(server): Codex keeps a Fast chosen by alias as the catalog's tier id

A live fast_mode or service_tier "fast" was stored as the alias while the apply reported the catalog id, so a later switch to a model with a different Fast id ran Fast under an option that said otherwise. The session now stores the id it accepted and drops the folded fast_mode.
This commit is contained in:
Mohamed Boudra
2026-11-01 18:41:01 +01:00
parent 0f1ebda942
commit 1b5731cebe
2 changed files with 43 additions and 2 deletions
@@ -326,6 +326,44 @@ describe("Codex app-server provider controls", () => {
}
});
test("a Fast chosen by alias is kept as the catalog's id, so a model with a different Fast id resets it", async () => {
const { session, appServer } = await createConnectedSession(
{ model: "model-a" },
{
models: [
{
id: "model-a",
isDefault: true,
serviceTiers: [{ id: "priority", name: "Fast", description: "Faster" }],
},
{
id: "model-b",
serviceTiers: [{ id: "rapid", name: "Fast", description: "Faster" }],
},
],
},
);
const events: AgentStreamEvent[] = [];
session.subscribe((event) => events.push(event));
try {
await expect(session.applyOptions?.({ fast_mode: true })).resolves.toEqual({
service_tier: "priority",
fast_mode: null,
});
await session.setModel("model-b");
// The option the session reports and the tier the turn runs are the same speed.
expect(events.filter((event) => event.type === "options_changed")).toEqual([
expect.objectContaining({ patch: { service_tier: "default" } }),
]);
await session.startTurn("hello");
await expect(appServer.waitForTurnStart()).resolves.toMatchObject({ serviceTier: "default" });
} finally {
await session.close();
}
});
test("maps legacy Fast commands to the catalog's request id", async () => {
const { session, appServer } = await createConnectedSession();
try {
@@ -4729,8 +4729,11 @@ export class CodexAppServerAgentSession implements AgentSession {
);
}
}
this.providerOptions = { ...valid, ...(requestedTier ? { service_tier: requestedTier } : {}) };
this.requestedServiceTier = requestedTier;
// The session keeps the catalog's id for the tier, so a later model switch sees what runs.
const accepted = requestedTier && (this.resolveServiceTier(requestedTier) ?? requestedTier);
const { fast_mode: _folded, ...kept } = valid;
this.providerOptions = { ...kept, ...(accepted ? { service_tier: accepted } : {}) };
this.requestedServiceTier = accepted || null;
if ("plan_mode" in options) this.setPlanMode(options.plan_mode === true);
this.cachedRuntimeInfo = null;
// COMPAT(codexFastPreference): added in v0.10.0, remove after 2027-03-29 once clients use service_tier.