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
3 changes: 2 additions & 1 deletion readme-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Set `CODEX_PATH` to run a different Codex binary; versions other than the one sp
- `CODEX_API_KEY` - API key used when the API-key auth method is selected. Takes precedence over `OPENAI_API_KEY`.
- `OPENAI_API_KEY` - fallback API key used when the API-key auth method is selected.
- `CODEX_PATH` - run a specific Codex executable instead of the bundled package dependency.
- `CODEX_CONFIG` - JSON object merged into the Codex session config.
- `CODEX_CONFIG` - JSON object merged into the Codex session config. Supported keys include:
- `model_reasoning_summary` - controls reasoning summary verbosity per turn. Valid values: `"auto"`, `"concise"`, `"detailed"`, `"none"`. Defaults to `"auto"` when unset. Example: `CODEX_CONFIG='{"model_reasoning_summary":"detailed"}'`.
- `MODEL_PROVIDER` - model provider to pass to Codex for new sessions.
- `DEFAULT_AUTH_REQUEST` - ACP auth request JSON used when Codex requires authentication.
- `INITIAL_AGENT_MODE` - initial mode id: `read-only`, `agent`, or `agent-full-access`.
Expand Down
13 changes: 12 additions & 1 deletion src/CodexAcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {Disposable} from "vscode-jsonrpc";
import type {
ClientInfo,
ReasoningEffort,
ReasoningSummary,
ServiceTier,
ServerNotification
} from "./app-server";
Expand Down Expand Up @@ -672,7 +673,7 @@ export class CodexAcpClient {
input: input,
approvalPolicy: agentMode.approvalPolicy,
sandboxPolicy: addAdditionalDirectoriesToSandboxPolicy(agentMode.sandboxPolicy, additionalDirectories),
summary: disableSummary ? "none" : "auto",
summary: disableSummary ? "none" : (resolveConfiguredSummary(this.config) ?? "auto"),
effort: effort,
model: modelId.model,
serviceTier: serviceTier,
Expand Down Expand Up @@ -854,6 +855,16 @@ export type SessionMetadataWithThread = SessionMetadata & {
thread: Thread,
}

const validReasoningSummaries: ReadonlySet<string> = new Set<ReasoningSummary>(["auto", "concise", "detailed", "none"]);

function resolveConfiguredSummary(config: JsonObject): ReasoningSummary | undefined {
const value = config["model_reasoning_summary"];
if (typeof value === "string" && validReasoningSummaries.has(value)) {
return value as ReasoningSummary;
}
return undefined;
}

function buildPromptItems(prompt: acp.ContentBlock[]): UserInput[] {
return prompt.map((block): UserInput | null => {
switch (block.type) {
Expand Down
35 changes: 33 additions & 2 deletions src/__tests__/CodexACPAgent/CodexAcpClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
type TestFixture
} from "../acp-test-utils";
import type {ServerNotification} from "../../app-server";
import type {JsonObject} from "../../CodexAcpClient";
import type {SessionState} from "../../CodexAcpServer";
import {AgentMode} from "../../AgentMode";
import type {Model, ReviewStartResponse, ThreadGoal, TurnCompletedNotification, TurnStartParams} from "../../app-server/v2";
Expand Down Expand Up @@ -2968,8 +2969,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
* Sets up a mock fixture with turnStart/awaitTurnCompleted spied on,
* and a given session state. Returns the fixture and turnStart spy.
*/
function setupPromptFixture(sessionOverrides?: Partial<SessionState>) {
const mockFixture = createCodexMockTestFixture();
function setupPromptFixture(sessionOverrides?: Partial<SessionState>, options?: { codexConfig?: JsonObject }) {
const mockFixture = createCodexMockTestFixture(options);
const sessionState = createTestSessionState(sessionOverrides);
const turnStartSpy = vi.spyOn(mockFixture.getCodexAppServerClient(), "turnStart").mockResolvedValue({
turn: {
Expand Down Expand Up @@ -3092,6 +3093,36 @@ describe('ACP server test', { timeout: 40_000 }, () => {
expect(turnStartSpy).toHaveBeenCalledWith(expect.objectContaining({ summary: "auto" }));
});

it ('should honor configured model_reasoning_summary from CODEX_CONFIG', async () => {
const { mockFixture, turnStartSpy } = setupPromptFixture({
account: { type: "chatgpt", email: "test@example.com", planType: "pro" },
}, { codexConfig: { model_reasoning_summary: "detailed" } });

await mockFixture.getCodexAcpAgent().prompt({ sessionId: "id", prompt: [{ type: "text", text: "test" }] });

expect(turnStartSpy).toHaveBeenCalledWith(expect.objectContaining({ summary: "detailed" }));
});

it ('should fall back to auto for invalid model_reasoning_summary value', async () => {
const { mockFixture, turnStartSpy } = setupPromptFixture({
account: { type: "chatgpt", email: "test@example.com", planType: "pro" },
}, { codexConfig: { model_reasoning_summary: "verbose" } });

await mockFixture.getCodexAcpAgent().prompt({ sessionId: "id", prompt: [{ type: "text", text: "test" }] });

expect(turnStartSpy).toHaveBeenCalledWith(expect.objectContaining({ summary: "auto" }));
});

it ('should force none when disableSummary is true regardless of configured summary', async () => {
const { mockFixture, turnStartSpy } = setupPromptFixture({
account: { type: "apiKey" },
}, { codexConfig: { model_reasoning_summary: "detailed" } });

await mockFixture.getCodexAcpAgent().prompt({ sessionId: "id", prompt: [{ type: "text", text: "test" }] });

expect(turnStartSpy).toHaveBeenCalledWith(expect.objectContaining({ summary: "none" }));
});

it ('should reject prompt with images when model does not support image input', async () => {
const { mockFixture } = setupPromptFixture({
supportedInputModalities: ["text"],
Expand Down
10 changes: 6 additions & 4 deletions src/__tests__/acp-test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as acp from "@agentclientprotocol/sdk";
import type {CreateElicitationResponse, McpServerStdio, RequestPermissionResponse} from "@agentclientprotocol/sdk";
import {CodexAcpClient} from '../CodexAcpClient';
import {CodexAcpClient, type JsonObject} from '../CodexAcpClient';
import {CodexAppServerClient, type CodexConnectionEvent} from '../CodexAppServerClient';
import {startCodexConnection} from "../CodexJsonRpcConnection";
import {CodexAcpServer, type SessionState} from "../CodexAcpServer";
Expand Down Expand Up @@ -84,6 +84,7 @@ export interface ConnectionConfig {
connection: MessageConnection;
getExitCode: () => number | null;
acpConnection?: AcpConnectionConfig;
codexConfig?: JsonObject;
}

export function createBaseTestFixture(config: ConnectionConfig): TestFixture {
Expand All @@ -96,7 +97,7 @@ export function createBaseTestFixture(config: ConnectionConfig): TestFixture {
});

const codexAppServerClient = new CodexAppServerClient(config.connection);
const codexAcpClient = new CodexAcpClient(codexAppServerClient);
const codexAcpClient = new CodexAcpClient(codexAppServerClient, config.codexConfig);
const codexAcpAgent = new CodexAcpServer(acpConnection, codexAcpClient, undefined, config.getExitCode);

const transportEvents: CodexConnectionEvent[] = [];
Expand Down Expand Up @@ -254,7 +255,7 @@ export interface CodexMockTestFixture extends TestFixture {
* Provides `sendServerRequest()` to simulate server-initiated requests (e.g., approval requests).
* Provides `setPermissionResponse()` to control ACP permission dialog responses.
*/
export function createCodexMockTestFixture(): CodexMockTestFixture {
export function createCodexMockTestFixture(options?: { codexConfig?: JsonObject }): CodexMockTestFixture {
let unhandledNotificationHandler: ((notification: any) => void) | null = null;
const requestHandlers = new Map<string, (params: unknown) => Promise<unknown>>();

Expand Down Expand Up @@ -306,7 +307,8 @@ export function createCodexMockTestFixture(): CodexMockTestFixture {
connection: acpConnection,
events: acpConnectionEvents,
eventHandlers: acpEventHandlers,
}
},
...(options?.codexConfig != null ? { codexConfig: options.codexConfig } : {}),
});

return {
Expand Down