Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions src/CodexAcpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
formatWebSearchTitle,
} from "./CodexToolCallMapper";
import {
clientSupportsBooleanConfigOptions,
createFastModeConfigOption,
FAST_MODE_CONFIG_ID,
FAST_MODE_OFF,
Expand Down Expand Up @@ -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<string, SessionState>;
private readonly pendingMcpStartupSessions: Map<string, PendingMcpStartupSession>;
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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();
Expand All @@ -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) {
Expand Down Expand Up @@ -784,9 +795,12 @@ export class CodexAcpServer {
createReasoningEffortConfigOption(sessionState.supportedReasoningEfforts, currentModelId.effort),
);
}
if (sessionState.currentModelSupportsFast) {
configOptions.push(createFastModeConfigOption(sessionState.fastModeEnabled));
}
if (sessionState.currentModelSupportsFast) {
configOptions.push(createFastModeConfigOption(
sessionState.fastModeEnabled,
this.booleanConfigOptionsSupported,
));
}
return configOptions;
}

Expand Down
21 changes: 19 additions & 2 deletions src/FastModeConfig.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
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";

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";

Expand All @@ -16,12 +18,27 @@ 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_CATEGORY,
type: "boolean",
currentValue: fastModeEnabled,
};
}

return {
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: [
Expand Down
59 changes: 58 additions & 1 deletion src/__tests__/CodexACPAgent/fast-mode-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@ import {
import {MODEL_CONFIG_ID} from "../../ModelConfigOption";

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();
Expand All @@ -41,6 +50,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: []});
Expand All @@ -63,6 +73,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");

Expand Down Expand Up @@ -139,6 +174,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();

Expand Down