From 0f1ebda94249cbdad694cce729c7ce2473dc95f4 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 1 Nov 2026 18:32:36 +0100 Subject: [PATCH] fix(server): a saved legacy Codex Fast preference wins over a configured service_tier default The defaults merge put a configured service_tier next to the agent's own legacy fast_mode, and Codex reads service_tier first, so the agent opened Normal. The agent's own alias now replaces the default of the key it stands for. --- .../src/server/agent/provider-options.ts | 25 +++++++++++++++++++ .../server/agent/provider-registry.test.ts | 18 +++++++++++++ .../src/server/agent/provider-registry.ts | 19 +++++++------- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/packages/server/src/server/agent/provider-options.ts b/packages/server/src/server/agent/provider-options.ts index 76a9d638b6..2967e0724f 100644 --- a/packages/server/src/server/agent/provider-options.ts +++ b/packages/server/src/server/agent/provider-options.ts @@ -99,6 +99,31 @@ export function mergeProviderOptions( return merged; } +// COMPAT(codexFastPreference): added in v0.10.0, remove after 2027-03-29 once saved Fast preferences have migrated. +const LEGACY_OPTION_ALIASES: Record> = { + codex: { fast_mode: "service_tier" }, +}; + +/** + * The options a provider opens with: `defaults` from config.json under the agent's `own` + * options. An agent's own legacy alias (a saved `fast_mode`) replaces the default of the key it + * stands for, so a configured `service_tier` does not override the agent's own Fast preference. + */ +export function mergeDefaultsUnderOwn(input: { + provider: string; + defaults: ProviderOptions | undefined; + own: ProviderOptions | undefined; +}): ProviderOptions | undefined { + const { defaults, own } = input; + const aliases = LEGACY_OPTION_ALIASES[input.provider]; + if (!aliases || !defaults || !own) return mergeProviderOptions(defaults, own); + const kept = { ...defaults }; + for (const [alias, canonical] of Object.entries(aliases)) { + if (own[alias] !== undefined && own[canonical] === undefined) delete kept[canonical]; + } + return mergeProviderOptions(kept, own); +} + function isPlainObject(value: unknown): value is ProviderOptions { if (value === null || typeof value !== "object") return false; const prototype = Object.getPrototypeOf(value); diff --git a/packages/server/src/server/agent/provider-registry.test.ts b/packages/server/src/server/agent/provider-registry.test.ts index 5cf0d67f25..b113bbd1b9 100644 --- a/packages/server/src/server/agent/provider-registry.test.ts +++ b/packages/server/src/server/agent/provider-registry.test.ts @@ -791,6 +791,24 @@ test("ACP provider configuration retains opaque options", () => { }); }); +test("a saved legacy Codex Fast preference wins over a configured service_tier default", () => { + const registry = buildProviderRegistry(logger, { + providerOverrides: { codex: { options: { service_tier: "default", plan_mode: false } } }, + }); + const client = registry.codex.createClient(logger); + + // Pre-v0.10 agents saved `fast_mode`; the agent's own preference beats the configured default. + expect(client.effectiveOptions?.({ fast_mode: true })).toEqual({ + fast_mode: true, + plan_mode: false, + }); + expect(client.effectiveOptions?.({ service_tier: "fast" })).toEqual({ + service_tier: "fast", + plan_mode: false, + }); + expect(client.effectiveOptions?.({})).toEqual({ service_tier: "default", plan_mode: false }); +}); + test("cursor provider extending acp uses CursorACPAgentClient", () => { const registry = buildProviderRegistry(logger, { providerOverrides: { diff --git a/packages/server/src/server/agent/provider-registry.ts b/packages/server/src/server/agent/provider-registry.ts index a470d1a01a..85561f00c1 100644 --- a/packages/server/src/server/agent/provider-registry.ts +++ b/packages/server/src/server/agent/provider-registry.ts @@ -48,7 +48,7 @@ import { PiRpcAgentClient } from "./providers/pi/agent.js"; import { TraeACPAgentClient } from "./providers/trae-acp-agent.js"; import { MockLoadTestAgentClient } from "./providers/mock-load-test-agent.js"; import { MockSlowProviderClient } from "./providers/mock-slow-provider.js"; -import { ToolPolicyUnsupportedError, mergeProviderOptions } from "./provider-options.js"; +import { ToolPolicyUnsupportedError, mergeDefaultsUnderOwn } from "./provider-options.js"; import { AGENT_PROVIDER_DEFINITIONS, BUILTIN_PROVIDER_IDS, @@ -498,6 +498,8 @@ function wrapClientProvider( const archiveNativeSession = inner.archiveNativeSession?.bind(inner); const unarchiveNativeSession = inner.unarchiveNativeSession?.bind(inner); const configuredModelIds = [...profileModels, ...additionalModels].map((model) => model.id); + const withDefaults = (own: ProviderOptions | undefined) => + mergeDefaultsUnderOwn({ provider: inner.provider, defaults: providerOptions, own }); return { provider, @@ -512,7 +514,7 @@ function wrapClientProvider( { ...config, provider: inner.provider, - providerOptions: mergeProviderOptions(providerOptions, config.providerOptions), + providerOptions: withDefaults(config.providerOptions), }, launchContext, { ...options, configuredModelIds }, @@ -529,7 +531,7 @@ function wrapClientProvider( { ...overrides, provider: inner.provider, - providerOptions: mergeProviderOptions(providerOptions, overrides?.providerOptions), + providerOptions: withDefaults(overrides?.providerOptions), }, launchContext, { ...options, configuredModelIds }, @@ -551,7 +553,7 @@ function wrapClientProvider( config: { ...config, provider: inner.provider, - providerOptions: mergeProviderOptions(providerOptions, config.providerOptions), + providerOptions: withDefaults(config.providerOptions), }, env, signal, @@ -560,13 +562,13 @@ function wrapClientProvider( resolveCreateConfig: inner.resolveCreateConfig?.bind(inner), resolveConfiguredModel: inner.resolveConfiguredModel?.bind(inner), isCreateConfigUnattended: inner.isCreateConfigUnattended?.bind(inner), - effectiveOptions: (own) => mergeProviderOptions(providerOptions, own) ?? {}, + effectiveOptions: (own) => withDefaults(own) ?? {}, listControls: listControls ? async (config) => await listControls({ ...config, provider: inner.provider, - providerOptions: mergeProviderOptions(providerOptions, config.providerOptions), + providerOptions: withDefaults(config.providerOptions), }) : undefined, listImportableSessions: listImportableSessions @@ -579,10 +581,7 @@ function wrapClientProvider( config: { ...context.config, provider: inner.provider, - providerOptions: mergeProviderOptions( - providerOptions, - context.config.providerOptions, - ), + providerOptions: withDefaults(context.config.providerOptions), }, storedConfig: { ...context.storedConfig,