diff --git a/.pr/live_priority_fast_mode.ts b/.pr/live_priority_fast_mode.ts new file mode 100644 index 00000000..3a361bf0 --- /dev/null +++ b/.pr/live_priority_fast_mode.ts @@ -0,0 +1,106 @@ +import fs from "node:fs"; +import path from "node:path"; +import {pathToFileURL} from "node:url"; + +const sourceRoot = process.env.SOURCE_ROOT; +if (!sourceRoot) { + throw new Error("SOURCE_ROOT must point to the codex-acp checkout under test"); +} + +const importSource = async (relativePath: string) => { + return import(pathToFileURL(path.join(sourceRoot, relativePath)).href); +}; + +const {CodexAcpClient} = await importSource("src/CodexAcpClient.ts"); +const {CodexAppServerClient} = await importSource("src/CodexAppServerClient.ts"); +const {startCodexConnection} = await importSource("src/CodexJsonRpcConnection.ts"); +const {CodexAcpServer} = await importSource("src/CodexAcpServer.ts"); + +const codexPath = path.join( + sourceRoot, + "node_modules", + ".bin", + process.platform === "win32" ? "codex.cmd" : "codex", +); +if (!fs.existsSync(codexPath)) { + throw new Error(`Codex binary not found at ${codexPath}`); +} + +const transportEvents: any[] = []; +const acpEvents: Array<{method: string; args: unknown[]}> = []; +const acpConnection = new Proxy({} as any, { + get(_, property) { + return (...args: unknown[]) => { + acpEvents.push({method: String(property), args}); + return Promise.resolve({}); + }; + }, +}); + +const codexConnection = startCodexConnection(codexPath); +const appServer = new CodexAppServerClient(codexConnection.connection); +appServer.onClientTransportEvent((event: any) => transportEvents.push(event)); + +// Exercise the same production configuration path as CODEX_CONFIG. +const client = new CodexAcpClient(appServer, {service_tier: "priority"}); +const agent = new CodexAcpServer( + acpConnection, + client, + undefined, + () => codexConnection.process.exitCode, +); + +try { + await agent.initialize({protocolVersion: 1}); + if (await client.authRequired()) { + throw new Error("Codex authentication is required"); + } + + const session = await agent.newSession({cwd: sourceRoot, mcpServers: []}); + const fastOption = session.configOptions?.find((option: any) => option.id === "fast-mode"); + const stateAfterStart = agent.getSessionState(session.sessionId); + const promptResponse = await agent.prompt({ + sessionId: session.sessionId, + prompt: [{type: "text", text: "Reply with exactly: priority-fast-live-ok"}], + }); + + const threadStartRequest = transportEvents.find( + (event) => event.eventType === "request" && event.method === "thread/start", + ); + const threadStartIndex = transportEvents.indexOf(threadStartRequest); + const threadStartResponse = transportEvents + .slice(threadStartIndex + 1) + .find((event) => event.eventType === "response" && event.thread?.id === session.sessionId); + const turnStartRequest = transportEvents.find( + (event) => event.eventType === "request" && event.method === "turn/start", + ); + const finalMessage = transportEvents + .filter( + (event) => + event.eventType === "notification" && + event.method === "item/completed" && + event.params?.item?.type === "agentMessage", + ) + .at(-1)?.params?.item?.text; + + console.log(JSON.stringify({ + testedCommit: process.env.TESTED_COMMIT ?? null, + codexVersion: threadStartResponse?.thread?.cliVersion ?? null, + configuredServiceTier: threadStartRequest?.params?.config?.service_tier ?? null, + appServerServiceTier: threadStartResponse?.serviceTier ?? null, + fastConfigOption: fastOption + ? {type: fastOption.type, currentValue: fastOption.currentValue} + : null, + fastModeEnabledAfterStart: stateAfterStart.fastModeEnabled, + firstPromptServiceTier: turnStartRequest?.params?.serviceTier ?? null, + promptStopReason: promptResponse.stopReason, + finalMessage, + requestSequence: transportEvents + .filter((event) => event.eventType === "request") + .map((event) => event.method), + acpEventCount: acpEvents.length, + }, null, 2)); +} finally { + codexConnection.connection.end(); + codexConnection.process.kill(); +} diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index 12593a65..27befc67 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -842,7 +842,7 @@ export type SessionMetadata = { currentModelId: string, models: Model[], modelProvider?: string | null, - currentServiceTier?: ServiceTier | null, + currentServiceTier?: string | null, additionalDirectories: string[], } diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index 18fdf195..19a569f8 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -61,6 +61,7 @@ import { FAST_MODE_CONFIG_ID, FAST_MODE_OFF, FAST_MODE_ON, + isFastServiceTier, modelSupportsFast, resolveFastServiceTier, } from "./FastModeConfig"; @@ -412,7 +413,7 @@ export class CodexAcpServer { authProvider: authProvider, cwd: request.cwd, additionalDirectories: sessionMetadata.additionalDirectories, - fastModeEnabled: sessionMetadata.currentServiceTier === "fast", + fastModeEnabled: isFastServiceTier(sessionMetadata.currentServiceTier), currentModelSupportsFast: currentModelSupportsFast, sessionMcpServers: sessionMcpServers, terminalOutputMode: this.terminalOutputMode, @@ -941,7 +942,7 @@ export class CodexAcpServer { authProvider: authProvider, cwd: request.cwd, additionalDirectories: sessionMetadata.additionalDirectories, - fastModeEnabled: sessionMetadata.currentServiceTier === "fast", + fastModeEnabled: isFastServiceTier(sessionMetadata.currentServiceTier), currentModelSupportsFast: currentModelSupportsFast, sessionMcpServers: sessionMcpServers, terminalOutputMode: this.terminalOutputMode, diff --git a/src/FastModeConfig.ts b/src/FastModeConfig.ts index 8e681c24..d40593b5 100644 --- a/src/FastModeConfig.ts +++ b/src/FastModeConfig.ts @@ -18,6 +18,10 @@ export function resolveFastServiceTier(fastModeEnabled: boolean, currentModelSup return fastModeEnabled && currentModelSupportsFast ? "fast" : null; } +export function isFastServiceTier(serviceTier: string | null | undefined): boolean { + return serviceTier === "fast" || serviceTier === "priority"; +} + export function clientSupportsBooleanConfigOptions(clientCapabilities?: acp.ClientCapabilities | null): boolean { return clientCapabilities?.session?.configOptions?.boolean != null; } diff --git a/src/__tests__/CodexACPAgent/fast-mode-config.test.ts b/src/__tests__/CodexACPAgent/fast-mode-config.test.ts index 07d42444..16fa5302 100644 --- a/src/__tests__/CodexACPAgent/fast-mode-config.test.ts +++ b/src/__tests__/CodexACPAgent/fast-mode-config.test.ts @@ -24,7 +24,7 @@ describe("Fast mode session config", () => { }; async function createSession( - currentServiceTier: "fast" | "flex" | null = null, + currentServiceTier: "fast" | "priority" | "flex" | null = null, clientInfo: acp.Implementation | null = null, clientCapabilities?: acp.ClientCapabilities, ) { @@ -98,12 +98,15 @@ describe("Fast mode session config", () => { 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"); + it.each(["fast", "priority"] as const)( + "initializes Fast mode as On when the app-server session tier is %s", + async (serviceTier) => { + const {response, codexAcpAgent} = await createSession(serviceTier); - expect(response.configOptions).toContainEqual(createFastModeConfigOption(true)); - expect(codexAcpAgent.getSessionState("session-id").fastModeEnabled).toBe(true); - }); + expect(response.configOptions).toContainEqual(createFastModeConfigOption(true)); + expect(codexAcpAgent.getSessionState("session-id").fastModeEnabled).toBe(true); + }, + ); it("omits Fast mode config options for JetBrains 2026.1 IntelliJ clients", async () => { const {response} = await createSession(null, {