Skip to content

Commit 615b20f

Browse files
authored
Update ACP SDK to 0.25.1 (#203)
Preserves old model behavior via extension method.
1 parent eda7d6f commit 615b20f

10 files changed

Lines changed: 136 additions & 38 deletions

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"vitest": "^4.0.10"
6262
},
6363
"dependencies": {
64-
"@agentclientprotocol/sdk": "^0.22.1",
64+
"@agentclientprotocol/sdk": "^0.25.1",
6565
"@openai/codex": "^0.139.0",
6666
"diff": "^8.0.3",
6767
"open": "^11.0.0",

src/AcpExtensions.ts

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,68 @@
1-
export type ExtMethodRequest = AuthenticationStatusRequest | AuthenticationLogoutRequest
1+
import type {
2+
ClientSideConnection,
3+
LoadSessionResponse,
4+
NewSessionResponse,
5+
ResumeSessionResponse,
6+
SessionId,
7+
} from "@agentclientprotocol/sdk";
8+
9+
export const LEGACY_SET_SESSION_MODEL_METHOD = "session/set_model";
10+
11+
export type LegacySessionModel = {
12+
modelId: string;
13+
name: string;
14+
description?: string | null;
15+
}
16+
17+
export type LegacySessionModelState = {
18+
availableModels: Array<LegacySessionModel>;
19+
currentModelId: string;
20+
}
21+
22+
export type LegacySetSessionModelRequest = {
23+
sessionId: SessionId;
24+
modelId: string;
25+
}
26+
27+
export type LegacySetSessionModelResponse = {}
28+
29+
export type LegacyNewSessionResponse = NewSessionResponse & {
30+
models?: LegacySessionModelState | null;
31+
}
32+
33+
export type LegacyLoadSessionResponse = LoadSessionResponse & {
34+
models?: LegacySessionModelState | null;
35+
}
36+
37+
export type LegacyResumeSessionResponse = ResumeSessionResponse & {
38+
models?: LegacySessionModelState | null;
39+
}
40+
41+
export type ExtMethodRequest =
42+
AuthenticationStatusRequest
43+
| AuthenticationLogoutRequest
44+
| LegacySetSessionModelExtRequest
245

346
export function isExtMethodRequest(request: { method: string, params: Record<string, unknown> }): request is ExtMethodRequest {
4-
return request.method === "authentication/status" || request.method === "authentication/logout";
47+
return request.method === "authentication/status"
48+
|| request.method === "authentication/logout"
49+
|| request.method === LEGACY_SET_SESSION_MODEL_METHOD;
550
}
651

752
export type AuthenticationStatusRequest = { method: "authentication/status", params: {} }
853
export type AuthenticationStatusResponse = { type: "api-key" } | { type: "chat-gpt", email: string } | { type: "gateway", name: string } | { type: "unauthenticated" }
954

1055
export type AuthenticationLogoutRequest = { method: "authentication/logout", params: {} }
11-
export type AuthenticationLogoutResponse = {}
56+
export type AuthenticationLogoutResponse = {}
57+
58+
export type LegacySetSessionModelExtRequest = {
59+
method: typeof LEGACY_SET_SESSION_MODEL_METHOD;
60+
params: LegacySetSessionModelRequest;
61+
}
62+
63+
export async function legacySetSessionModel(
64+
connection: Pick<ClientSideConnection, "extMethod">,
65+
params: LegacySetSessionModelRequest,
66+
): Promise<LegacySetSessionModelResponse> {
67+
return await connection.extMethod(LEGACY_SET_SESSION_MODEL_METHOD, params) as LegacySetSessionModelResponse;
68+
}

src/CodexAcpServer.ts

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as acp from "@agentclientprotocol/sdk";
2-
import {RequestError, type SessionId, type SessionModelState, type SessionModeState} from "@agentclientprotocol/sdk";
2+
import {RequestError, type SessionId, type SessionModeState} from "@agentclientprotocol/sdk";
33
import {CodexEventHandler} from "./CodexEventHandler";
44
import {CodexApprovalHandler} from "./CodexApprovalHandler";
55
import {CodexElicitationHandler} from "./CodexElicitationHandler";
@@ -33,7 +33,16 @@ import {CodexCommands} from "./CodexCommands";
3333
import type {QuotaMeta} from "./QuotaMeta";
3434
import {logger} from "./Logger";
3535
import {sanitizeMcpServerName} from "./McpServerName";
36-
import {isExtMethodRequest} from "./AcpExtensions";
36+
import {
37+
type LegacyLoadSessionResponse,
38+
type LegacyNewSessionResponse,
39+
type LegacyResumeSessionResponse,
40+
type LegacySessionModelState,
41+
type LegacySetSessionModelRequest,
42+
type LegacySetSessionModelResponse,
43+
isExtMethodRequest,
44+
LEGACY_SET_SESSION_MODEL_METHOD,
45+
} from "./AcpExtensions";
3746
import {
3847
createCommandExecutionUpdate,
3948
createDynamicToolCallUpdate,
@@ -189,9 +198,11 @@ export class CodexAcpServer implements acp.Agent {
189198
case "authentication/status":
190199
return await this.runWithProcessCheck(() => this.codexAcpClient.getAuthenticationStatus());
191200
case "authentication/logout": {
192-
await this.unstable_logout({});
201+
await this.logout({});
193202
return {};
194203
}
204+
case LEGACY_SET_SESSION_MODEL_METHOD:
205+
return await this.unstable_setSessionModel(this.parseLegacySetSessionModelParams(methodRequest.params));
195206
}
196207
}
197208

@@ -212,7 +223,7 @@ export class CodexAcpServer implements acp.Agent {
212223
}
213224
}
214225

215-
async getOrCreateSession(request: acp.NewSessionRequest | acp.ResumeSessionRequest): Promise<[SessionId, SessionModelState, SessionModeState]> {
226+
async getOrCreateSession(request: acp.NewSessionRequest | acp.ResumeSessionRequest): Promise<[SessionId, LegacySessionModelState, SessionModeState]> {
216227
try {
217228
return await this.tryCreateSession(request);
218229
} catch (e) {
@@ -292,7 +303,7 @@ export class CodexAcpServer implements acp.Agent {
292303
return generation;
293304
}
294305

295-
async tryCreateSession(request: acp.NewSessionRequest | acp.ResumeSessionRequest): Promise<[SessionId, SessionModelState, SessionModeState]> {
306+
async tryCreateSession(request: acp.NewSessionRequest | acp.ResumeSessionRequest): Promise<[SessionId, LegacySessionModelState, SessionModeState]> {
296307
const requestedSessionGeneration = "sessionId" in request
297308
? this.beginSessionOpen(request.sessionId)
298309
: null;
@@ -372,7 +383,7 @@ export class CodexAcpServer implements acp.Agent {
372383
}
373384

374385
this.publishAvailableCommandsAsync(sessionId);
375-
const sessionModelState: SessionModelState = this.createModelState(models, currentModelId);
386+
const sessionModelState: LegacySessionModelState = this.createModelState(models, currentModelId);
376387
const sessionModeState: SessionModeState = sessionState.agentMode.toSessionModeState();
377388

378389
return [sessionId, sessionModelState, sessionModeState];
@@ -386,7 +397,7 @@ export class CodexAcpServer implements acp.Agent {
386397
return accountResponse.account;
387398
}
388399

389-
async loadSession(params: acp.LoadSessionRequest): Promise<acp.LoadSessionResponse> {
400+
async loadSession(params: acp.LoadSessionRequest): Promise<LegacyLoadSessionResponse> {
390401
logger.log("Loading session...", {sessionId: params.sessionId});
391402
const {
392403
sessionId,
@@ -409,7 +420,7 @@ export class CodexAcpServer implements acp.Agent {
409420
};
410421
}
411422

412-
async resumeSession(params: acp.ResumeSessionRequest): Promise<acp.ResumeSessionResponse> {
423+
async resumeSession(params: acp.ResumeSessionRequest): Promise<LegacyResumeSessionResponse> {
413424
logger.log("Resuming session...", {sessionId: params.sessionId});
414425
const [sessionId, modelState, modeState] = await this.getOrCreateSession(params);
415426

@@ -465,7 +476,7 @@ export class CodexAcpServer implements acp.Agent {
465476
return {};
466477
}
467478

468-
async unstable_deleteSession(params: acp.DeleteSessionRequest): Promise<acp.DeleteSessionResponse> {
479+
async deleteSession(params: acp.DeleteSessionRequest): Promise<acp.DeleteSessionResponse> {
469480
logger.log("Deleting session...", {sessionId: params.sessionId});
470481
const sessionId = params.sessionId;
471482
const shouldCloseLocalSession = this.hasLocalSession(sessionId);
@@ -502,7 +513,7 @@ export class CodexAcpServer implements acp.Agent {
502513

503514
async newSession(
504515
params: acp.NewSessionRequest,
505-
): Promise<acp.NewSessionResponse> {
516+
): Promise<LegacyNewSessionResponse> {
506517
logger.log("Starting new session...");
507518
const [sessionId, modelState, modeState] = await this.getOrCreateSession(params);
508519

@@ -533,7 +544,7 @@ export class CodexAcpServer implements acp.Agent {
533544
return { };
534545
}
535546

536-
async unstable_logout(_params: acp.LogoutRequest): Promise<void> {
547+
async logout(_params: acp.LogoutRequest): Promise<void> {
537548
logger.log("Logout request received");
538549
await this.runWithProcessCheck(() => this.codexAcpClient.logout());
539550
logger.log("Logout request completed");
@@ -630,7 +641,7 @@ export class CodexAcpServer implements acp.Agent {
630641
sessionState.currentModelSupportsFast = modelSupportsFast(model);
631642
}
632643

633-
async unstable_setSessionModel(params: acp.SetSessionModelRequest): Promise<acp.SetSessionModelResponse | void> {
644+
async unstable_setSessionModel(params: LegacySetSessionModelRequest): Promise<LegacySetSessionModelResponse> {
634645
logger.log("Set session model requested", {
635646
sessionId: params.sessionId,
636647
modelId: params.modelId
@@ -661,6 +672,18 @@ export class CodexAcpServer implements acp.Agent {
661672
return {};
662673
}
663674

675+
private parseLegacySetSessionModelParams(params: Record<string, unknown>): LegacySetSessionModelRequest {
676+
const sessionId = params["sessionId"];
677+
const modelId = params["modelId"];
678+
if (typeof sessionId !== "string" || typeof modelId !== "string") {
679+
throw RequestError.invalidParams();
680+
}
681+
return {
682+
sessionId: sessionId,
683+
modelId: modelId,
684+
};
685+
}
686+
664687
private createSessionConfigOptions(sessionState: SessionState): Array<acp.SessionConfigOption> {
665688
const currentModelId = ModelId.fromString(sessionState.currentModelId);
666689
return [
@@ -703,7 +726,7 @@ export class CodexAcpServer implements acp.Agent {
703726
.join("-");
704727
}
705728

706-
private createModelState(availableModels: Model[], selectedModelId: string): SessionModelState {
729+
private createModelState(availableModels: Model[], selectedModelId: string): LegacySessionModelState {
707730
const allowedModels = availableModels
708731
.flatMap((model) =>
709732
model.supportedReasoningEfforts.map((effort) => ({
@@ -722,7 +745,7 @@ export class CodexAcpServer implements acp.Agent {
722745
request: acp.LoadSessionRequest
723746
): Promise<{
724747
sessionId: SessionId;
725-
modelState: SessionModelState;
748+
modelState: LegacySessionModelState;
726749
modeState: SessionModeState;
727750
thread: Thread;
728751
}> {
@@ -797,7 +820,7 @@ export class CodexAcpServer implements acp.Agent {
797820
}
798821

799822
await this.availableCommands.publish(sessionId);
800-
const sessionModelState: SessionModelState = this.createModelState(models, currentModelId);
823+
const sessionModelState: LegacySessionModelState = this.createModelState(models, currentModelId);
801824
const sessionModeState: SessionModeState = sessionState.agentMode.toSessionModeState();
802825

803826
return {

src/__tests__/CodexACPAgent/CodexAcpClient.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ describe('ACP server test', { timeout: 40_000 }, () => {
109109
const authenticatedResponse = await keyFixture.getCodexAcpAgent().extMethod("authentication/status", {});
110110
expect(authenticatedResponse).toEqual({type: "api-key"});
111111

112-
await keyFixture.getCodexAcpAgent().unstable_logout({});
112+
await keyFixture.getCodexAcpAgent().logout({});
113113
const logoutResponse = await keyFixture.getCodexAcpAgent().extMethod("authentication/status", {});
114114
expect(logoutResponse).toEqual({type: "unauthenticated"});
115115
});
@@ -210,7 +210,7 @@ describe('ACP server test', { timeout: 40_000 }, () => {
210210
const mockFixture = createCodexMockTestFixture();
211211
const codexAcpAgent = mockFixture.getCodexAcpAgent();
212212

213-
const logoutSpy = vi.spyOn(codexAcpAgent, "unstable_logout").mockResolvedValue();
213+
const logoutSpy = vi.spyOn(codexAcpAgent, "logout").mockResolvedValue();
214214

215215
await expect(codexAcpAgent.extMethod("authentication/logout", {})).resolves.toEqual({});
216216
expect(logoutSpy).toHaveBeenCalledWith({});

src/__tests__/CodexACPAgent/e2e/acp-e2e-session-persistence.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {afterEach, expect, it} from "vitest";
2+
import {legacySetSessionModel, type LegacyLoadSessionResponse} from "../../../AcpExtensions";
23
import {
34
createAuthenticatedFixture,
45
describeE2E,
@@ -22,7 +23,7 @@ describeE2E("E2E session persistence tests", () => {
2223
beforeRestartFixture = await createAuthenticatedFixture();
2324
const sessionId = (await beforeRestartFixture.createSession()).sessionId;
2425

25-
await beforeRestartFixture.connection.unstable_setSessionModel({sessionId, modelId: OTHER_TEST_MODEL_ID.toString()});
26+
await legacySetSessionModel(beforeRestartFixture.connection, {sessionId, modelId: OTHER_TEST_MODEL_ID.toString()});
2627
const memorizedToken = "token-for-tests-123";
2728
await beforeRestartFixture.expectPromptText(
2829
sessionId,
@@ -36,7 +37,7 @@ describeE2E("E2E session persistence tests", () => {
3637
sessionId,
3738
cwd: afterRestartFixture.workspaceDir,
3839
mcpServers: [],
39-
});
40+
}) as LegacyLoadSessionResponse;
4041
expect(loadSessionResponse.models?.currentModelId).toBe(OTHER_TEST_MODEL_ID.toString());
4142

4243
await afterRestartFixture.expectPromptText(

src/__tests__/CodexACPAgent/e2e/acp-e2e.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from "node:path";
22
import {afterEach, expect, it} from "vitest";
33
import {AgentMode} from "../../../AgentMode";
4+
import {legacySetSessionModel} from "../../../AcpExtensions";
45
import {
56
createAuthenticatedFixture,
67
createGatewayFixture,
@@ -49,7 +50,7 @@ describeE2E("E2E tests", () => {
4950
expect(models.availableModels.length).toBeGreaterThan(0);
5051
expect(models.currentModelId).toBe(DEFAULT_TEST_MODEL_ID.toString());
5152

52-
await fixture.connection.unstable_setSessionModel({
53+
await legacySetSessionModel(fixture.connection, {
5354
sessionId: session.sessionId,
5455
modelId: OTHER_TEST_MODEL_ID.toString(),
5556
});

src/__tests__/CodexACPAgent/e2e/spawned-agent-fixture.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {expect, vi} from "vitest";
77
import {ModelId} from "../../../ModelId";
88
import {removeDirectoryWithRetry, writeCodexHomeConfig} from "../../acp-test-utils";
99
import type {PermissionResponder} from "./permission-responders";
10+
import type {LegacyNewSessionResponse} from "../../../AcpExtensions";
1011

1112
export const DEFAULT_TEST_MODEL_ID = ModelId.create("gpt-5.2", "none");
1213
export const OTHER_TEST_MODEL_ID = ModelId.create("gpt-5.3-codex", "low");
@@ -20,7 +21,7 @@ export interface TestSkill {
2021
export interface SpawnedAgentFixture {
2122
readonly connection: acp.ClientSideConnection;
2223
readonly workspaceDir: string;
23-
createSession(mcpServers?: acp.McpServer[]): Promise<acp.NewSessionResponse>;
24+
createSession(mcpServers?: acp.McpServer[]): Promise<LegacyNewSessionResponse>;
2425
restart(): Promise<SpawnedAgentFixture>;
2526
writeSkill(skill: TestSkill, rootDir?: string): void;
2627
setPermissionResponder(responder: PermissionResponder): void;
@@ -166,11 +167,11 @@ class SpawnedAgentFixtureImpl implements SpawnedAgentFixture {
166167
return this.paths.workspaceDir;
167168
}
168169

169-
async createSession(mcpServers: acp.McpServer[] = []): Promise<acp.NewSessionResponse> {
170+
async createSession(mcpServers: acp.McpServer[] = []): Promise<LegacyNewSessionResponse> {
170171
return await this.connection.newSession({
171172
cwd: this.workspaceDir,
172173
mcpServers,
173-
});
174+
}) as LegacyNewSessionResponse;
174175
}
175176

176177
async restart(): Promise<SpawnedAgentFixture> {

src/__tests__/CodexACPAgent/session-config-options.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
REASONING_EFFORT_CONFIG_ID,
77
} from "../../ModelConfigOption";
88
import type {Model, ReasoningEffortOption} from "../../app-server/v2";
9+
import {LEGACY_SET_SESSION_MODEL_METHOD} from "../../AcpExtensions";
910

1011
const lowEffort: ReasoningEffortOption = {reasoningEffort: "low", description: "Fast"};
1112
const mediumEffort: ReasoningEffortOption = {reasoningEffort: "medium", description: "Balanced"};
@@ -43,7 +44,7 @@ async function createSession(currentModelId: string, availableModels: Array<Mode
4344
});
4445

4546
const response = await codexAcpAgent.newSession({cwd: "/test/cwd", mcpServers: []});
46-
return {codexAcpAgent, response};
47+
return {codexAcpAgent, codexAcpClient, response};
4748
}
4849

4950
describe("Session config options", () => {
@@ -189,6 +190,20 @@ describe("Session config options", () => {
189190
expect(sessionState.availableModels.map(m => m.id)).toEqual(["fast-model", "extra-model"]);
190191
});
191192

193+
it("changes the model through the legacy session/set_model extMethod", async () => {
194+
const {fast, slow} = buildModels();
195+
const {codexAcpAgent, codexAcpClient} = await createSession("fast-model[medium]", [fast]);
196+
vi.spyOn(codexAcpClient, "fetchAvailableModels").mockResolvedValue([fast, slow]);
197+
198+
const response = await codexAcpAgent.extMethod(LEGACY_SET_SESSION_MODEL_METHOD, {
199+
sessionId: "session-id",
200+
modelId: "slow-model[medium]",
201+
});
202+
203+
expect(response).toEqual({});
204+
expect(codexAcpAgent.getSessionState("session-id").currentModelId).toBe("slow-model[medium]");
205+
});
206+
192207
it("rejects unknown model, effort, and mode values", async () => {
193208
const {fast} = buildModels();
194209
const {codexAcpAgent} = await createSession("fast-model[medium]", [fast]);

0 commit comments

Comments
 (0)