From f7bddcc7d0a0a6ed2c94d0e4836145230aafe9ab Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 19 Jun 2026 14:07:08 +0200 Subject: [PATCH] Migrate to ACP SDK 0.28 API --- package-lock.json | 27 ++++++++++++------ package.json | 5 ++-- src/ACPSessionConnection.ts | 10 ++++--- src/AcpExtensions.ts | 6 ++-- src/CodexAcpServer.ts | 14 ++++----- src/CodexApprovalHandler.ts | 11 ++++---- src/CodexCommands.ts | 8 +++--- src/CodexElicitationHandler.ts | 9 +++--- src/CodexEventHandler.ts | 6 ++-- src/__tests__/acp-test-utils.ts | 42 +++++++++++++++++++-------- src/index.ts | 50 +++++++++++++++++++++++++++++++-- 11 files changed, 135 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11210420..d8eb3ac6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,12 @@ "version": "0.0.46", "license": "Apache-2.0", "dependencies": { - "@agentclientprotocol/sdk": "^0.25.1", + "@agentclientprotocol/sdk": "^0.28.1", "@openai/codex": "^0.141.0", "diff": "^8.0.3", "open": "^11.0.0", - "vscode-jsonrpc": "^8.2.1" + "vscode-jsonrpc": "^8.2.1", + "zod": "^4.0.0" }, "bin": { "codex-acp": "dist/index.js" @@ -28,9 +29,9 @@ } }, "node_modules/@agentclientprotocol/sdk": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.25.1.tgz", - "integrity": "sha512-jx2rF3bdpGwZ75Q/meyEDLLbYmbtxk82Uh9hDCdxDvcEedBnNSF5hZAnL/kJR5VNz56JqwOmqnAqasC84MwwkQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.28.1.tgz", + "integrity": "sha512-Z2Frs6YtPhnZZ+XwFXyQkRDXY0fn8FjCalEs0W4yUhQnY4TztmNq0/RnfzWdFN3vqT3h0jTz5klzYbZHGxCDyQ==", "license": "Apache-2.0", "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" @@ -2700,6 +2701,16 @@ "react-dom": ">=18" } }, + "node_modules/mcp-hello-world/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3809,9 +3820,9 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 86237d01..96c95b10 100644 --- a/package.json +++ b/package.json @@ -61,10 +61,11 @@ "vitest": "^4.0.10" }, "dependencies": { - "@agentclientprotocol/sdk": "^0.25.1", + "@agentclientprotocol/sdk": "^0.28.1", "@openai/codex": "^0.141.0", "diff": "^8.0.3", "open": "^11.0.0", - "vscode-jsonrpc": "^8.2.1" + "vscode-jsonrpc": "^8.2.1", + "zod": "^4.0.0" } } diff --git a/src/ACPSessionConnection.ts b/src/ACPSessionConnection.ts index 89b43916..286630ac 100644 --- a/src/ACPSessionConnection.ts +++ b/src/ACPSessionConnection.ts @@ -1,21 +1,23 @@ import * as acp from "@agentclientprotocol/sdk"; import type {SessionNotification} from "@agentclientprotocol/sdk"; +export type AcpClientConnection = Pick; + export class ACPSessionConnection { - private readonly connection: acp.AgentSideConnection; + private readonly connection: AcpClientConnection; readonly sessionId: string; - constructor(connection: acp.AgentSideConnection, sessionId: string) { + constructor(connection: AcpClientConnection, sessionId: string) { this.connection = connection; this.sessionId = sessionId; } async update(update: UpdateSessionEvent) { - await this.connection.sessionUpdate({ + await this.connection.notify(acp.methods.client.session.update, { sessionId: this.sessionId, update: update }); } } -export type UpdateSessionEvent = SessionNotification["update"]; \ No newline at end of file +export type UpdateSessionEvent = SessionNotification["update"]; diff --git a/src/AcpExtensions.ts b/src/AcpExtensions.ts index 5d411d2f..2a6b359e 100644 --- a/src/AcpExtensions.ts +++ b/src/AcpExtensions.ts @@ -1,5 +1,5 @@ import type { - ClientSideConnection, + ClientContext, LoadSessionResponse, NewSessionResponse, ResumeSessionResponse, @@ -61,8 +61,8 @@ export type LegacySetSessionModelExtRequest = { } export async function legacySetSessionModel( - connection: Pick, + connection: Pick, params: LegacySetSessionModelRequest, ): Promise { - return await connection.extMethod(LEGACY_SET_SESSION_MODEL_METHOD, params) as LegacySetSessionModelResponse; + return await connection.request(LEGACY_SET_SESSION_MODEL_METHOD, params); } diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index 954d5154..8117e82f 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -6,7 +6,7 @@ import {CodexElicitationHandler} from "./CodexElicitationHandler"; import {type CodexAuthRequest, getCodexAuthMethods} from "./CodexAuthMethod"; import {CodexAcpClient, type SessionMetadata, type SessionMetadataWithThread} from "./CodexAcpClient"; import type {McpStartupResult} from "./CodexAppServerClient"; -import {ACPSessionConnection, type UpdateSessionEvent} from "./ACPSessionConnection"; +import {ACPSessionConnection, type AcpClientConnection, type UpdateSessionEvent} from "./ACPSessionConnection"; import type {InputModality, ReasoningEffort} from "./app-server"; import type { Account, @@ -102,7 +102,7 @@ interface ActivePrompt { complete: () => void; } -export class CodexAcpServer implements acp.Agent { +export class CodexAcpServer { private static readonly MODEL_NAME_TOKEN_OVERRIDES: Record = { gpt: "GPT", mini: "Mini", @@ -110,7 +110,7 @@ export class CodexAcpServer implements acp.Agent { }; private readonly codexAcpClient: CodexAcpClient; - private readonly connection: acp.AgentSideConnection; + private readonly connection: AcpClientConnection; private readonly defaultAuthRequest: CodexAuthRequest | null; private readonly getExitCode: () => number | null; private readonly getRecentStderr: () => string; @@ -127,7 +127,7 @@ export class CodexAcpServer implements acp.Agent { private readonly sessionOpenGenerations: Map; constructor( - connection: acp.AgentSideConnection, + connection: AcpClientConnection, codexAcpClient: CodexAcpClient, defaultAuthRequest?: CodexAuthRequest, getExitCode?: () => number | null, @@ -1110,7 +1110,7 @@ export class CodexAcpServer implements acp.Agent { : mcpStartup; for (const update of CodexEventHandler.createMcpStartupUpdates(filteredStartup)) { - await this.connection.sessionUpdate({ + await this.connection.notify(acp.methods.client.session.update, { sessionId, update, }); @@ -1281,7 +1281,7 @@ export class CodexAcpServer implements acp.Agent { await this.codexAcpClient.waitForSessionNotifications(params.sessionId); if (commandResult.turnCompleted?.turn.status === "interrupted") { if (!this.sessionIsClosing(params.sessionId) && this.sessions.has(params.sessionId)) { - await this.connection.sessionUpdate({ + await this.connection.notify(acp.methods.client.session.update, { sessionId: params.sessionId, update: { sessionUpdate: "agent_message_chunk", @@ -1382,7 +1382,7 @@ export class CodexAcpServer implements acp.Agent { // Check if turn was interrupted (cancelled) if (turnCompleted.turn.status === "interrupted") { if (!this.sessionIsClosing(params.sessionId) && this.sessions.has(params.sessionId)) { - await this.connection.sessionUpdate({ + await this.connection.notify(acp.methods.client.session.update, { sessionId: params.sessionId, update: { sessionUpdate: "agent_message_chunk", diff --git a/src/CodexApprovalHandler.ts b/src/CodexApprovalHandler.ts index 85316610..868bbe73 100644 --- a/src/CodexApprovalHandler.ts +++ b/src/CodexApprovalHandler.ts @@ -17,6 +17,7 @@ import type { import {logger} from "./Logger"; import {stripShellPrefix} from "./CodexEventHandler"; import {ApprovalOptionId} from "./ApprovalOptionId"; +import type {AcpClientConnection} from "./ACPSessionConnection"; type CommandDecisionOption = { option: acp.PermissionOption; @@ -43,11 +44,11 @@ function permissionOption( } export class CodexApprovalHandler implements ApprovalHandler { - private readonly connection: acp.AgentSideConnection; + private readonly connection: AcpClientConnection; private readonly sessionState: SessionState; constructor( - connection: acp.AgentSideConnection, + connection: AcpClientConnection, sessionState: SessionState ) { this.connection = connection; @@ -60,7 +61,7 @@ export class CodexApprovalHandler implements ApprovalHandler { try { const sessionId = this.sessionState.sessionId; const acpRequest = this.buildCommandPermissionRequest(sessionId, params); - const response = await this.connection.requestPermission(acpRequest); + const response = await this.connection.request(acp.methods.client.session.requestPermission, acpRequest); return this.convertCommandResponse(params, response); } catch (error) { logger.error("Error requesting command execution permission", error); @@ -74,7 +75,7 @@ export class CodexApprovalHandler implements ApprovalHandler { try { const sessionId = this.sessionState.sessionId; const acpRequest = this.buildFileChangePermissionRequest(sessionId, params); - const response = await this.connection.requestPermission(acpRequest); + const response = await this.connection.request(acp.methods.client.session.requestPermission, acpRequest); return this.convertFileChangeResponse(params, response); } catch (error) { logger.error("Error requesting file change permission", error); @@ -88,7 +89,7 @@ export class CodexApprovalHandler implements ApprovalHandler { try { const sessionId = this.sessionState.sessionId; const acpRequest = this.buildPermissionsRequest(sessionId, params); - const response = await this.connection.requestPermission(acpRequest); + const response = await this.connection.request(acp.methods.client.session.requestPermission, acpRequest); return this.convertPermissionsResponse(params, response); } catch (error) { logger.error("Error requesting permissions", error); diff --git a/src/CodexCommands.ts b/src/CodexCommands.ts index 25b8ac2f..e4c6f8c8 100644 --- a/src/CodexCommands.ts +++ b/src/CodexCommands.ts @@ -1,6 +1,6 @@ import type * as acp from "@agentclientprotocol/sdk"; -import type {AgentSideConnection, AvailableCommand} from "@agentclientprotocol/sdk"; -import {ACPSessionConnection} from "./ACPSessionConnection"; +import type {AvailableCommand} from "@agentclientprotocol/sdk"; +import {ACPSessionConnection, type AcpClientConnection} from "./ACPSessionConnection"; import type {CodexAcpClient} from "./CodexAcpClient"; import type {RateLimitSnapshot, ReviewTarget, SkillsListEntry, TurnCompletedNotification} from "./app-server/v2"; import type {SessionState} from "./CodexAcpServer"; @@ -18,12 +18,12 @@ export type CommandHandleResult = | { handled: true, turnCompleted?: TurnCompletedNotification }; export class CodexCommands { - private readonly connection: AgentSideConnection; + private readonly connection: AcpClientConnection; private readonly codexAcpClient: CodexAcpClient; private readonly runWithProcessCheck: (operation: () => Promise) => Promise; constructor( - connection: AgentSideConnection, + connection: AcpClientConnection, codexAcpClient: CodexAcpClient, runWithProcessCheck: (operation: () => Promise) => Promise ) { diff --git a/src/CodexElicitationHandler.ts b/src/CodexElicitationHandler.ts index 68a49947..3b19827f 100644 --- a/src/CodexElicitationHandler.ts +++ b/src/CodexElicitationHandler.ts @@ -10,6 +10,7 @@ import type { } from "./app-server/v2"; import { logger } from "./Logger"; import { McpApprovalOptionId } from "./McpApprovalOptionId"; +import type {AcpClientConnection} from "./ACPSessionConnection"; // Standard elicitation options (non-tool-call approval). const ELICITATION_OPTIONS: acp.PermissionOption[] = [ @@ -66,7 +67,7 @@ function buildToolApprovalOptions(persistOptions: Set): acp.Permis } export class CodexElicitationHandler implements ElicitationHandler { - private readonly connection: acp.AgentSideConnection; + private readonly connection: AcpClientConnection; private readonly sessionState: SessionState; // In Rust, the MCP elicitation handler receives ElicitationRequestEvent directly from the MCP // protocol layer, where id is set to "mcp_tool_call_approval_" — the call ID is extracted @@ -85,7 +86,7 @@ export class CodexElicitationHandler implements ElicitationHandler { // (threadId, serverName). private readonly pendingMcpApprovals = new Map(); - constructor(connection: acp.AgentSideConnection, sessionState: SessionState) { + constructor(connection: AcpClientConnection, sessionState: SessionState) { this.connection = connection; this.sessionState = sessionState; } @@ -111,11 +112,11 @@ export class CodexElicitationHandler implements ElicitationHandler { ): Promise { try { const { request, correlatedCallId } = this.buildPermissionRequest(params); - const response = await this.connection.requestPermission(request); + const response = await this.connection.request(acp.methods.client.session.requestPermission, request); if (correlatedCallId !== undefined && response.outcome.outcome !== "cancelled") { const optionId = response.outcome.optionId; if (optionId !== McpApprovalOptionId.Decline) { - await this.connection.sessionUpdate({ + await this.connection.notify(acp.methods.client.session.update, { sessionId: this.sessionState.sessionId, update: { sessionUpdate: "tool_call_update", toolCallId: correlatedCallId, status: "in_progress" }, }); diff --git a/src/CodexEventHandler.ts b/src/CodexEventHandler.ts index a793f26f..e9fffc57 100644 --- a/src/CodexEventHandler.ts +++ b/src/CodexEventHandler.ts @@ -6,7 +6,7 @@ import type { import type {SessionState} from "./CodexAcpServer"; import * as acp from "@agentclientprotocol/sdk"; import {type PlanEntry, RequestError} from "@agentclientprotocol/sdk"; -import {ACPSessionConnection, type UpdateSessionEvent} from "./ACPSessionConnection"; +import {ACPSessionConnection, type AcpClientConnection, type UpdateSessionEvent} from "./ACPSessionConnection"; import type { AccountRateLimitsUpdatedNotification, AgentMessageDeltaNotification, @@ -59,7 +59,7 @@ export { stripShellPrefix }; export class CodexEventHandler { - private readonly connection: acp.AgentSideConnection; + private readonly connection: AcpClientConnection; private readonly sessionState: SessionState; private failure: RequestError | null = null; private readonly activeFuzzyFileSearchSessions = new Set(); @@ -70,7 +70,7 @@ export class CodexEventHandler { private readonly terminalCommandIds = new Set(); private readonly terminalCommandOutputIds = new Set(); - constructor(connection: acp.AgentSideConnection, sessionState: SessionState) { + constructor(connection: AcpClientConnection, sessionState: SessionState) { this.connection = connection; this.sessionState = sessionState; } diff --git a/src/__tests__/acp-test-utils.ts b/src/__tests__/acp-test-utils.ts index 3d8b186a..8e74751f 100644 --- a/src/__tests__/acp-test-utils.ts +++ b/src/__tests__/acp-test-utils.ts @@ -1,8 +1,10 @@ -import type {AgentSideConnection, McpServerStdio, RequestPermissionResponse} from "@agentclientprotocol/sdk"; +import * as acp from "@agentclientprotocol/sdk"; +import type {McpServerStdio, RequestPermissionResponse} from "@agentclientprotocol/sdk"; import {CodexAcpClient} from '../CodexAcpClient'; import {CodexAppServerClient, type CodexConnectionEvent} from '../CodexAppServerClient'; import {startCodexConnection} from "../CodexJsonRpcConnection"; import {CodexAcpServer, type SessionState} from "../CodexAcpServer"; +import type {AcpClientConnection} from "../ACPSessionConnection"; import type {ServerNotification} from "../app-server"; import type {MessageConnection} from "vscode-jsonrpc/node"; import path from "node:path"; @@ -15,7 +17,7 @@ import type {Model, ReasoningEffortOption} from "../app-server/v2"; export type MethodCallEvent = { method: string; args: any[] }; export interface SmartMockConfig { - returnValues?: Map any>; + returnValues?: Map any>; } export function createSmartMock( @@ -28,7 +30,7 @@ export function createSmartMock( onCall({ method: String(prop), args }); const returnValueFn = config?.returnValues?.get(String(prop)); if (returnValueFn) { - return returnValueFn(); + return returnValueFn(args); } return { mock: "Mocked return" }; }; @@ -36,6 +38,16 @@ export function createSmartMock( }); } +function normalizeAcpConnectionEvent(event: MethodCallEvent): MethodCallEvent { + if (event.method === "request" && event.args[0] === acp.methods.client.session.requestPermission) { + return {method: "requestPermission", args: [event.args[1]]}; + } + if (event.method === "notify" && event.args[0] === acp.methods.client.session.update) { + return {method: "sessionUpdate", args: [event.args[1]]}; + } + return event; +} + export interface TestFixture { getCodexAppServerClient(): CodexAppServerClient, getCodexAcpClient(): CodexAcpClient, @@ -57,7 +69,7 @@ export interface CodexConnectionDumpOptions { } export interface AcpConnectionConfig { - connection: AgentSideConnection; + connection: AcpClientConnection; events: MethodCallEvent[]; eventHandlers: ((event: MethodCallEvent) => void)[]; } @@ -71,9 +83,10 @@ export interface ConnectionConfig { export function createBaseTestFixture(config: ConnectionConfig): TestFixture { const acpConnectionEvents = config.acpConnection?.events ?? []; const acpEventHandlers = config.acpConnection?.eventHandlers ?? []; - const acpConnection = config.acpConnection?.connection ?? createSmartMock((event) => { - acpConnectionEvents.push(event); - acpEventHandlers.forEach(handler => handler(event)); + const acpConnection = config.acpConnection?.connection ?? createSmartMock((event) => { + const normalizedEvent = normalizeAcpConnectionEvent(event); + acpConnectionEvents.push(normalizedEvent); + acpEventHandlers.forEach(handler => handler(normalizedEvent)); }); const codexAppServerClient = new CodexAppServerClient(config.connection); @@ -258,12 +271,19 @@ export function createCodexMockTestFixture(): CodexMockTestFixture { // Create ACP connection with configurable permission response const acpConnectionEvents: MethodCallEvent[] = []; const acpEventHandlers: ((event: MethodCallEvent) => void)[] = []; - const returnValues = new Map any>(); + const returnValues = new Map any>(); + returnValues.set('request', (args) => { + if (args[0] === acp.methods.client.session.requestPermission) { + return permissionState.response; + } + return { mock: "Mocked return" }; + }); returnValues.set('requestPermission', () => permissionState.response); - const acpConnection = createSmartMock((event) => { - acpConnectionEvents.push(event); - acpEventHandlers.forEach(handler => handler(event)); + const acpConnection = createSmartMock((event) => { + const normalizedEvent = normalizeAcpConnectionEvent(event); + acpConnectionEvents.push(normalizedEvent); + acpEventHandlers.forEach(handler => handler(normalizedEvent)); }, { returnValues }); const baseFixture = createBaseTestFixture({ diff --git a/src/index.ts b/src/index.ts index 01436ff6..6437a671 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node import * as acp from "@agentclientprotocol/sdk"; +import {z} from "zod"; import {startCodexConnection} from "./CodexJsonRpcConnection"; import {CodexAcpServer} from "./CodexAcpServer"; import {createJsonStream} from "./StdUtils"; @@ -11,6 +12,17 @@ import packageJson from "../package.json"; import {logger} from "./Logger"; import {runLoginCommand} from "./login"; import {runCodexCli} from "./CodexCli"; +import {LEGACY_SET_SESSION_MODEL_METHOD} from "./AcpExtensions"; + +const emptyExtensionParamsParser = z.preprocess( + (params) => params ?? {}, + z.object({}).passthrough() +); + +const legacySetSessionModelParamsParser = z.object({ + sessionId: z.string(), + modelId: z.string(), +}).passthrough(); if (process.argv.includes("--version")) { console.log(`${packageJson.name} ${packageJson.version}`); @@ -77,11 +89,45 @@ function startAcpServer() { const acpJsonStream = createJsonStream(process.stdin, process.stdout); - function createAgent(connection: acp.AgentSideConnection): CodexAcpServer { + function createAgent(connection: acp.AgentContext): CodexAcpServer { const appServerClient = new CodexAppServerClient(codexConnection.connection); const codexClient = new CodexAcpClient(appServerClient, config, modelProvider); return new CodexAcpServer(connection, codexClient, defaultAuthRequest, () => codexConnection.process.exitCode, () => stderr); } - new acp.AgentSideConnection(createAgent, acpJsonStream); + let codexAcpServer: CodexAcpServer | null = null; + const getAgent = (): CodexAcpServer => { + if (!codexAcpServer) { + throw acp.RequestError.internalError("ACP agent is not connected"); + } + return codexAcpServer; + }; + + acp.agent({name: packageJson.name}) + .onConnect((connection) => { + const agent = createAgent(connection.client); + codexAcpServer = agent; + connection.signal.addEventListener("abort", () => { + if (codexAcpServer === agent) { + codexAcpServer = null; + } + }); + }) + .onRequest(acp.methods.agent.initialize, (ctx) => getAgent().initialize(ctx.params)) + .onRequest(acp.methods.agent.session.new, (ctx) => getAgent().newSession(ctx.params)) + .onRequest(acp.methods.agent.session.load, (ctx) => getAgent().loadSession(ctx.params)) + .onRequest(acp.methods.agent.session.list, (ctx) => getAgent().listSessions(ctx.params)) + .onRequest(acp.methods.agent.session.delete, (ctx) => getAgent().deleteSession(ctx.params)) + .onRequest(acp.methods.agent.session.resume, (ctx) => getAgent().resumeSession(ctx.params)) + .onRequest(acp.methods.agent.session.close, (ctx) => getAgent().closeSession(ctx.params)) + .onRequest(acp.methods.agent.session.setMode, (ctx) => getAgent().setSessionMode(ctx.params)) + .onRequest(acp.methods.agent.session.setConfigOption, (ctx) => getAgent().setSessionConfigOption(ctx.params)) + .onRequest(acp.methods.agent.authenticate, (ctx) => getAgent().authenticate(ctx.params)) + .onRequest(acp.methods.agent.logout, (ctx) => getAgent().logout(ctx.params)) + .onRequest(acp.methods.agent.session.prompt, (ctx) => getAgent().prompt(ctx.params)) + .onNotification(acp.methods.agent.session.cancel, (ctx) => getAgent().cancel(ctx.params)) + .onRequest("authentication/status", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/status", ctx.params)) + .onRequest("authentication/logout", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/logout", ctx.params)) + .onRequest(LEGACY_SET_SESSION_MODEL_METHOD, legacySetSessionModelParamsParser, (ctx) => getAgent().extMethod(LEGACY_SET_SESSION_MODEL_METHOD, ctx.params)) + .connect(acpJsonStream); }