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
2 changes: 2 additions & 0 deletions src/CodexAcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ export class CodexAcpClient {
const response = await this.codexClient.threadResume({
config: await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []),
cwd: request.cwd,
excludeTurns: true,
modelProvider: await this.getResumeModelProvider(),
threadId: request.sessionId,
});
Expand All @@ -417,6 +418,7 @@ export class CodexAcpClient {
const response = await this.codexClient.threadResume({
config: await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []),
cwd: request.cwd,
excludeTurns: true,
modelProvider: await this.getResumeModelProvider(),
threadId: request.sessionId,
});
Expand Down
14 changes: 12 additions & 2 deletions src/CodexAppServerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ export class CodexAppServerClient {
return await this.sendRequest({ method: "thread/start", params: params });
}

async threadResume(params: ThreadResumeParams): Promise<ThreadResumeResponse> {
async threadResume(params: ExperimentalThreadResumeParams): Promise<ThreadResumeResponse> {
return await this.sendRequest({ method: "thread/resume", params: params });
}

Expand Down Expand Up @@ -974,7 +974,11 @@ export type CompactionCompletedNotification =
| { method: "thread/compacted", params: Extract<ServerNotification, { method: "thread/compacted" }>["params"] }
| { method: "item/completed", params: ItemCompletedNotification & { item: Extract<ItemCompletedNotification["item"], { type: "contextCompaction" }> } };

type CodexRequest = DistributiveOmit<ClientRequest, "id">
type StableCodexRequest = DistributiveOmit<ClientRequest, "id">

type CodexRequest =
| Exclude<StableCodexRequest, { method: "thread/resume" }>
| { method: "thread/resume", params: ExperimentalThreadResumeParams }

type DistributiveOmit<T, K extends keyof any> = T extends any
? Omit<T, K>
Expand All @@ -992,6 +996,12 @@ export interface ExperimentalThreadSettingsUpdateParams {
};
}

// The adapter opts into app-server's experimental API, while the checked-in
// generated bindings currently contain only stable fields.
type ExperimentalThreadResumeParams = ThreadResumeParams & {
excludeTurns?: boolean;
};

type McpServerStartupSnapshot = {
status: McpServerStartupState;
error: string | null;
Expand Down
88 changes: 88 additions & 0 deletions src/__tests__/CodexACPAgent/CodexAcpClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
import {CODEX_API_KEY_ENV_VAR, OPENAI_API_KEY_ENV_VAR, type CodexAuthRequest} from "../../CodexAuthMethod";
import type * as acp from "@agentclientprotocol/sdk";
import {
createBaseTestFixture,
createCodexMockTestFixture,
createTestFixture,
createTestModel,
createTestSessionState,
type TestFixture
} from "../acp-test-utils";
import type {MessageConnection} from "vscode-jsonrpc/node";
import type {ServerNotification} from "../../app-server";
import type {SessionState} from "../../CodexAcpServer";
import {AgentMode} from "../../AgentMode";
Expand Down Expand Up @@ -533,6 +535,92 @@ describe('ACP server test', { timeout: 40_000 }, () => {
});
});

it('excludes unused app-server turns when resuming and loading sessions', async () => {
const mockFixture = createCodexMockTestFixture();
const codexAcpClient = mockFixture.getCodexAcpClient();
const codexAppServerClient = mockFixture.getCodexAppServerClient();

vi.spyOn(codexAppServerClient, "skillsExtraRootsSet").mockResolvedValue(undefined);
vi.spyOn(codexAppServerClient, "listSkills").mockResolvedValue({data: []});
const threadResumeSpy = vi.spyOn(codexAppServerClient, "threadResume").mockResolvedValue({
thread: {id: "thread-id"} as any,
model: "gpt-5",
modelProvider: "openai",
reasoningEffort: "medium",
serviceTier: null,
} as any);
const threadReadSpy = vi.spyOn(codexAppServerClient, "threadRead").mockResolvedValue({
thread: {id: "thread-id"} as any,
});
vi.spyOn(codexAppServerClient, "listModels").mockResolvedValue({
data: [createTestModel({id: "gpt-5"})],
nextCursor: null,
});

await codexAcpClient.resumeSession({
sessionId: "resume-id",
cwd: "/workspace",
});
await codexAcpClient.loadSession({
sessionId: "load-id",
cwd: "/workspace",
mcpServers: [],
});

expect(threadResumeSpy).toHaveBeenNthCalledWith(1, expect.objectContaining({
threadId: "resume-id",
excludeTurns: true,
}));
expect(threadResumeSpy).toHaveBeenNthCalledWith(2, expect.objectContaining({
threadId: "load-id",
excludeTurns: true,
}));
expect(threadReadSpy).toHaveBeenCalledOnce();
expect(threadReadSpy).toHaveBeenCalledWith({
threadId: "thread-id",
includeTurns: true,
});
});

it('sends excludeTurns through the app-server transport', async () => {
const sendRequest = vi.fn().mockResolvedValue({
thread: {id: "thread-id"},
model: "gpt-5",
modelProvider: "openai",
reasoningEffort: "medium",
serviceTier: null,
});
const connection = {
sendRequest,
onUnhandledNotification: () => {},
onNotification: () => {},
onRequest: () => {},
end: () => {},
} as unknown as MessageConnection;
const transportFixture = createBaseTestFixture({
connection,
getExitCode: () => null,
});

await transportFixture.getCodexAppServerClient().threadResume({
threadId: "thread-id",
excludeTurns: true,
});

expect(sendRequest).toHaveBeenCalledWith("thread/resume", {
threadId: "thread-id",
excludeTurns: true,
});
expect(transportFixture.getCodexConnectionEvents([])[0]).toEqual({
eventType: "request",
method: "thread/resume",
params: {
threadId: "thread-id",
excludeTurns: true,
},
});
});

it('restores collaboration mode for resumed and loaded sessions', async () => {
const mockFixture = createCodexMockTestFixture();
const codexAcpAgent = mockFixture.getCodexAcpAgent();
Expand Down