Read a remembered Codex fast_mode as its service tier

The create-agent preferences convert the legacy key on read, keyed on the
built-in codex provider id, so the Codex draft shows the Fast selection the user
last used. Tagged COMPAT(featureValues) with the other legacy-option shims.
This commit is contained in:
Mohamed Boudra
2026-11-08 12:59:25 +01:00
parent 8469e98765
commit 99f270962b
2 changed files with 55 additions and 3 deletions
@@ -80,7 +80,7 @@ describe("create agent preferences", () => {
modelId: "gpt-5.5",
modeId: "full-access",
thinkingOptionId: "high",
options: { fast_mode: true },
options: { service_tier: "fast" },
}),
);
@@ -95,7 +95,7 @@ describe("create agent preferences", () => {
model: "gpt-5.5",
mode: "full-access",
thinkingByModel: { "gpt-5.5": "high" },
options: { fast_mode: true },
options: { service_tier: "fast" },
},
},
});
@@ -220,6 +220,37 @@ describe("create agent preferences", () => {
});
});
it("reads a remembered Codex fast_mode as the service tier it meant, so the draft shows Fast", () => {
expect(
parseFormPreferences({
provider: "codex",
providerPreferences: {
codex: { options: { fast_mode: true, sandbox_mode: "read-only" } },
claude: { options: { fast_mode: true } },
},
}),
).toEqual({
provider: "codex",
providerPreferences: {
codex: { options: { service_tier: "fast", sandbox_mode: "read-only" } },
claude: { options: { fast_mode: true } },
},
});
expect(
parseFormPreferences({
providerPreferences: { codex: { featureValues: { fast_mode: false } } },
}),
).toEqual({ providerPreferences: { codex: { options: { service_tier: "default" } } } });
// A tier already remembered wins over the key it replaced.
expect(
parseFormPreferences({
providerPreferences: { codex: { options: { fast_mode: true, service_tier: "default" } } },
}),
).toEqual({ providerPreferences: { codex: { options: { service_tier: "default" } } } });
});
it("keeps options that are not booleans or strings, such as a profile's sandbox settings", () => {
const options = {
sandbox_workspace_write: { network_access: true, writable_roots: ["/tmp"] },
@@ -39,6 +39,24 @@ const providerPreferencesSchema: z.ZodType<ProviderPreferences> = z.preprocess(
}),
);
// COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01. Codex took `fast_mode`
// before `service_tier`; the daemon converts the option on every agent and profile it touches,
// and this is the one place the app reads a remembered value without the daemon in between. Keyed
// on the built-in provider id: a custom provider extending Codex keeps the old key and the daemon
// still converts it on create.
function convertLegacyCodexOptions(
preferences: Record<string, ProviderPreferences>,
): Record<string, ProviderPreferences> {
const codex = preferences.codex;
if (!codex?.options || !("fast_mode" in codex.options)) return preferences;
const { fast_mode: legacy, ...rest } = codex.options;
const options =
"service_tier" in rest || typeof legacy !== "boolean"
? rest
: { ...rest, service_tier: legacy ? "fast" : "default" };
return { ...preferences, codex: { ...codex, options } };
}
const launchTargetSchema: z.ZodType<LaunchTarget> = z.discriminatedUnion("kind", [
z.strictObject({ kind: z.literal("chat") }),
z.strictObject({ kind: z.literal("terminal"), profileId: z.string() }),
@@ -46,7 +64,10 @@ const launchTargetSchema: z.ZodType<LaunchTarget> = z.discriminatedUnion("kind",
export const FormPreferencesSchema = z.strictObject({
provider: z.string().optional(),
providerPreferences: z.record(z.string(), providerPreferencesSchema).optional(),
providerPreferences: z
.record(z.string(), providerPreferencesSchema)
.transform(convertLegacyCodexOptions)
.optional(),
// COMPAT(agentProfileFavoriteMigration): favourites were removed in v0.3.2.
// Keep the legacy payload alive until every capable host has had a chance to
// import it; ordinary preference writes must not erase it first.