From 11146b7354470cdb1a65b367ae4cb36be64cc438 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 30 Jun 2026 11:35:12 +0200 Subject: [PATCH 1/2] Support boolean Fast mode config options If the client expresses support for boolean config options, we can provide it. --- src/CodexAcpServer.ts | 36 +++++++---- src/FastModeConfig.ts | 18 +++++- .../CodexACPAgent/fast-mode-config.test.ts | 59 ++++++++++++++++++- 3 files changed, 100 insertions(+), 13 deletions(-) diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index e80280e2..92c3e6f4 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -56,6 +56,7 @@ import { formatWebSearchTitle, } from "./CodexToolCallMapper"; import { + clientSupportsBooleanConfigOptions, createFastModeConfigOption, FAST_MODE_CONFIG_ID, FAST_MODE_OFF, @@ -138,6 +139,7 @@ export class CodexAcpServer { private readonly availableCommands: CodexCommands; private clientInfo: acp.Implementation | null; private terminalOutputMode: TerminalOutputMode; + private booleanConfigOptionsSupported: boolean; private readonly sessions: Map; private readonly pendingMcpStartupSessions: Map; @@ -168,6 +170,7 @@ export class CodexAcpServer { this.getRecentStderr = getRecentStderr ?? (() => ""); this.clientInfo = null; this.terminalOutputMode = "terminal_output_delta"; + this.booleanConfigOptionsSupported = false; this.availableCommands = new CodexCommands( connection, codexAcpClient, @@ -182,6 +185,7 @@ export class CodexAcpServer { logger.log("Initialize request received"); this.clientInfo = _params.clientInfo ?? null; this.terminalOutputMode = resolveTerminalOutputMode(_params.clientCapabilities); + this.booleanConfigOptionsSupported = clientSupportsBooleanConfigOptions(_params.clientCapabilities); await this.runWithProcessCheck(() => this.codexAcpClient.initialize(_params)); return { protocolVersion: acp.PROTOCOL_VERSION, @@ -657,23 +661,18 @@ export class CodexAcpServer { const sessionState = this.sessions.get(params.sessionId); if (!sessionState) throw new Error(`Session ${params.sessionId} not found`); - if (typeof params.value !== "string") { - throw RequestError.invalidParams(); - } - const value = params.value; - switch (params.configId) { case FAST_MODE_CONFIG_ID: - this.applyFastModeChange(sessionState, value); + this.applyFastModeChange(sessionState, params); break; case MODE_CONFIG_ID: - this.applyModeChange(sessionState, value); + this.applyModeChange(sessionState, this.stringConfigValue(params)); break; case MODEL_CONFIG_ID: - this.applyModelChange(sessionState, value); + this.applyModelChange(sessionState, this.stringConfigValue(params)); break; case REASONING_EFFORT_CONFIG_ID: - this.applyReasoningEffortChange(sessionState, value); + this.applyReasoningEffortChange(sessionState, this.stringConfigValue(params)); break; default: throw RequestError.invalidParams(); @@ -684,13 +683,25 @@ export class CodexAcpServer { }; } - private applyFastModeChange(sessionState: SessionState, value: string): void { + private applyFastModeChange(sessionState: SessionState, params: acp.SetSessionConfigOptionRequest): void { + const value = params.value; + if (typeof value === "boolean") { + sessionState.fastModeEnabled = value; + return; + } if (value !== FAST_MODE_ON && value !== FAST_MODE_OFF) { throw RequestError.invalidParams(); } sessionState.fastModeEnabled = value === FAST_MODE_ON; } + private stringConfigValue(params: acp.SetSessionConfigOptionRequest): string { + if (typeof params.value !== "string") { + throw RequestError.invalidParams(); + } + return params.value; + } + private applyModeChange(sessionState: SessionState, value: string): void { const newMode = AgentMode.find(value); if (!newMode) { @@ -784,7 +795,10 @@ export class CodexAcpServer { createReasoningEffortConfigOption(sessionState.supportedReasoningEfforts, currentModelId.effort), ); } - configOptions.push(createFastModeConfigOption(sessionState.fastModeEnabled)); + configOptions.push(createFastModeConfigOption( + sessionState.fastModeEnabled, + this.booleanConfigOptionsSupported, + )); return configOptions; } diff --git a/src/FastModeConfig.ts b/src/FastModeConfig.ts index 4187aaa3..62911f6a 100644 --- a/src/FastModeConfig.ts +++ b/src/FastModeConfig.ts @@ -1,4 +1,5 @@ import type {SessionConfigOption} from "@agentclientprotocol/sdk"; +import type * as acp from "@agentclientprotocol/sdk"; import type {ServiceTier} from "./app-server"; import type {Model} from "./app-server/v2"; @@ -16,7 +17,22 @@ export function resolveFastServiceTier(fastModeEnabled: boolean, currentModelSup return fastModeEnabled && currentModelSupportsFast ? "fast" : null; } -export function createFastModeConfigOption(fastModeEnabled: boolean): SessionConfigOption { +export function clientSupportsBooleanConfigOptions(clientCapabilities?: acp.ClientCapabilities | null): boolean { + return clientCapabilities?.session?.configOptions?.boolean != null; +} + +export function createFastModeConfigOption(fastModeEnabled: boolean, useBooleanConfigOption = false): SessionConfigOption { + if (useBooleanConfigOption) { + return { + id: FAST_MODE_CONFIG_ID, + name: "Fast mode", + description: FAST_MODE_DESCRIPTION, + category: FAST_MODE_CONFIG_ID, + type: "boolean", + currentValue: fastModeEnabled, + }; + } + return { id: FAST_MODE_CONFIG_ID, name: "Fast mode", diff --git a/src/__tests__/CodexACPAgent/fast-mode-config.test.ts b/src/__tests__/CodexACPAgent/fast-mode-config.test.ts index 70716c8a..da856de4 100644 --- a/src/__tests__/CodexACPAgent/fast-mode-config.test.ts +++ b/src/__tests__/CodexACPAgent/fast-mode-config.test.ts @@ -14,9 +14,18 @@ import { } from "../../FastModeConfig"; describe("Fast mode session config", () => { + const booleanConfigCapabilities: acp.ClientCapabilities = { + session: { + configOptions: { + boolean: {}, + }, + }, + }; + async function createSession( currentServiceTier: "fast" | "flex" | null = null, - clientInfo: acp.Implementation | null = null + clientInfo: acp.Implementation | null = null, + clientCapabilities?: acp.ClientCapabilities, ) { const fixture = createCodexMockTestFixture(); const codexAcpAgent = fixture.getCodexAcpAgent(); @@ -39,6 +48,7 @@ describe("Fast mode session config", () => { await codexAcpAgent.initialize({ protocolVersion: acp.PROTOCOL_VERSION, clientInfo, + ...(clientCapabilities ? {clientCapabilities} : {}), }); const response = await codexAcpAgent.newSession({cwd: "/test/cwd", mcpServers: []}); @@ -61,6 +71,31 @@ describe("Fast mode session config", () => { expect(response.configOptions).toContainEqual(createFastModeConfigOption(false)); }); + it("returns the Fast mode config option as a boolean when the client supports it", async () => { + const {response} = await createSession(null, null, booleanConfigCapabilities); + + expect(response.configOptions).toContainEqual(createFastModeConfigOption(false, true)); + const option = response.configOptions?.find(option => option.id === FAST_MODE_CONFIG_ID); + expect(option).toMatchObject({ + id: FAST_MODE_CONFIG_ID, + type: "boolean", + currentValue: false, + }); + expect(option).not.toHaveProperty("options"); + }); + + it("keeps the Fast mode select option when boolean support is explicitly absent", async () => { + const {response} = await createSession(null, null, { + session: { + configOptions: { + boolean: null, + }, + }, + }); + + expect(response.configOptions).toContainEqual(createFastModeConfigOption(false)); + }); + it("initializes Fast mode as On when the app-server session tier is fast", async () => { const {response, codexAcpAgent} = await createSession("fast"); @@ -137,6 +172,28 @@ describe("Fast mode session config", () => { expect(codexAcpAgent.getSessionState("session-id").fastModeEnabled).toBe(false); }); + it("toggles Fast mode through boolean session config options", async () => { + const {codexAcpAgent} = await createSession(null, null, booleanConfigCapabilities); + + const onResponse = await codexAcpAgent.setSessionConfigOption({ + sessionId: "session-id", + configId: FAST_MODE_CONFIG_ID, + type: "boolean", + value: true, + }); + expect(onResponse.configOptions).toContainEqual(createFastModeConfigOption(true, true)); + expect(codexAcpAgent.getSessionState("session-id").fastModeEnabled).toBe(true); + + const offResponse = await codexAcpAgent.setSessionConfigOption({ + sessionId: "session-id", + configId: FAST_MODE_CONFIG_ID, + type: "boolean", + value: false, + }); + expect(offResponse.configOptions).toContainEqual(createFastModeConfigOption(false, true)); + expect(codexAcpAgent.getSessionState("session-id").fastModeEnabled).toBe(false); + }); + it("rejects unknown Fast mode config ids and values", async () => { const {codexAcpAgent} = await createSession(); From 336e62e7fd435917c4397870569c53c5c2e5299f Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 30 Jun 2026 11:37:24 +0200 Subject: [PATCH 2/2] Set fast mode config category to model config --- src/FastModeConfig.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/FastModeConfig.ts b/src/FastModeConfig.ts index 62911f6a..8e681c24 100644 --- a/src/FastModeConfig.ts +++ b/src/FastModeConfig.ts @@ -4,6 +4,7 @@ import type {ServiceTier} from "./app-server"; import type {Model} from "./app-server/v2"; export const FAST_MODE_CONFIG_ID = "fast-mode"; +export const FAST_MODE_CATEGORY = "model_config"; export const FAST_MODE_ON = "on"; export const FAST_MODE_OFF = "off"; @@ -27,7 +28,7 @@ export function createFastModeConfigOption(fastModeEnabled: boolean, useBooleanC id: FAST_MODE_CONFIG_ID, name: "Fast mode", description: FAST_MODE_DESCRIPTION, - category: FAST_MODE_CONFIG_ID, + category: FAST_MODE_CATEGORY, type: "boolean", currentValue: fastModeEnabled, }; @@ -37,7 +38,7 @@ export function createFastModeConfigOption(fastModeEnabled: boolean, useBooleanC id: FAST_MODE_CONFIG_ID, name: "Fast mode", description: FAST_MODE_DESCRIPTION, - category: FAST_MODE_CONFIG_ID, + category: FAST_MODE_CATEGORY, type: "select", currentValue: fastModeEnabled ? FAST_MODE_ON : FAST_MODE_OFF, options: [