From 2554c7bf980472760a6e7810b826f030d2c3af25 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 19 Jun 2026 11:48:41 +0200 Subject: [PATCH] feat: Update to new ACP SDK patterns (#790) --- package-lock.json | 8 +- package.json | 2 +- src/acp-agent.ts | 109 ++++++++++++++++++++--- src/tests/acp-agent-settings.test.ts | 7 +- src/tests/acp-agent.test.ts | 105 +++++++++++----------- src/tests/additional-roots.test.ts | 6 +- src/tests/authorization.test.ts | 5 +- src/tests/create-session-options.test.ts | 8 +- src/tests/session-config-options.test.ts | 8 +- src/tests/session-load.test.ts | 12 +-- src/tests/tools.test.ts | 26 +++--- 11 files changed, 191 insertions(+), 105 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c2ab535..ec4e2312 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.47.0", "license": "Apache-2.0", "dependencies": { - "@agentclientprotocol/sdk": "0.27.1", + "@agentclientprotocol/sdk": "0.28.1", "@anthropic-ai/claude-agent-sdk": "0.3.181", "zod": "^3.25.0 || ^4.0.0" }, @@ -32,9 +32,9 @@ } }, "node_modules/@agentclientprotocol/sdk": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.27.1.tgz", - "integrity": "sha512-5Y7UeEVxGyVA73e0vSJvd/4xot+nxXI1WZINF0LKlNpPq2yVY9ooPghtt2Svr3RrKaETJ3l6OqF6ex+by3o8dQ==", + "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" diff --git a/package.json b/package.json index e0724642..86a53631 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "author": "Zed Industries", "license": "Apache-2.0", "dependencies": { - "@agentclientprotocol/sdk": "0.27.1", + "@agentclientprotocol/sdk": "0.28.1", "@anthropic-ai/claude-agent-sdk": "0.3.181", "zod": "^3.25.0 || ^4.0.0" }, diff --git a/src/acp-agent.ts b/src/acp-agent.ts index 9b189013..7529acd2 100644 --- a/src/acp-agent.ts +++ b/src/acp-agent.ts @@ -1,11 +1,14 @@ import { - Agent, - AgentSideConnection, + agent as acpAgent, + AgentContext, AuthenticateRequest, AuthMethod, AvailableCommand, CancelNotification, ClientCapabilities, + CompleteElicitationNotification, + CreateElicitationRequest, + CreateElicitationResponse, ForkSessionRequest, ForkSessionResponse, InitializeRequest, @@ -14,6 +17,7 @@ import { ListSessionsResponse, LoadSessionRequest, LoadSessionResponse, + methods, ndJsonStream, NewSessionRequest, NewSessionResponse, @@ -23,6 +27,8 @@ import { ReadTextFileRequest, ReadTextFileResponse, RequestError, + RequestPermissionRequest, + RequestPermissionResponse, ResumeSessionRequest, ResumeSessionResponse, SessionConfigOption, @@ -612,12 +618,66 @@ export function describeAlwaysAllow( return `Always Allow ${parts.join(" and ")}`; } -// Implement the ACP Agent interface -export class ClaudeAcpAgent implements Agent { +/** + * Client-facing surface the agent calls back into. This is the subset of ACP + * client methods the agent actually uses, expressed as a narrow interface so + * tests can supply lightweight mocks. In production it is backed by + * {@link ClientConnection} over the SDK's typed `AgentContext`. + */ +export interface AcpClient { + sessionUpdate(params: SessionNotification): Promise; + requestPermission(params: RequestPermissionRequest): Promise; + readTextFile(params: ReadTextFileRequest): Promise; + writeTextFile(params: WriteTextFileRequest): Promise; + unstable_createElicitation(params: CreateElicitationRequest): Promise; + unstable_completeElicitation(params: CompleteElicitationNotification): Promise; + /** Send a custom (extension) notification, e.g. `_claude/sdkMessage`. */ + extNotification(method: string, params: Record): Promise; +} + +/** + * Bridges {@link AcpClient} to the connection-scoped {@link AgentContext} + * exposed by `AgentApp.connect(...)` as `connection.client`. The peer handle is + * valid for the entire connection lifetime, so it is captured once at + * construction. + */ +class ClientConnection implements AcpClient { + constructor(private readonly ctx: AgentContext) {} + + sessionUpdate(params: SessionNotification): Promise { + return this.ctx.notify(methods.client.session.update, params); + } + + requestPermission(params: RequestPermissionRequest): Promise { + return this.ctx.request(methods.client.session.requestPermission, params); + } + + readTextFile(params: ReadTextFileRequest): Promise { + return this.ctx.request(methods.client.fs.readTextFile, params); + } + + writeTextFile(params: WriteTextFileRequest): Promise { + return this.ctx.request(methods.client.fs.writeTextFile, params); + } + + unstable_createElicitation(params: CreateElicitationRequest): Promise { + return this.ctx.request(methods.client.elicitation.create, params); + } + + unstable_completeElicitation(params: CompleteElicitationNotification): Promise { + return this.ctx.notify(methods.client.elicitation.complete, params); + } + + extNotification(method: string, params: Record): Promise { + return this.ctx.notify(method, params); + } +} + +export class ClaudeAcpAgent { sessions: { [key: string]: Session; }; - client: AgentSideConnection; + client: AcpClient; clientCapabilities?: ClientCapabilities; logger: Logger; gatewayAuthRequest?: GatewayAuthRequest; @@ -626,7 +686,7 @@ export class ClaudeAcpAgent implements Agent { * tests can shrink it. */ forceCancelGraceMs: number = DEFAULT_FORCE_CANCEL_GRACE_MS; - constructor(client: AgentSideConnection, logger?: Logger) { + constructor(client: AcpClient, logger?: Logger) { this.sessions = {}; this.client = client; this.logger = logger ?? console; @@ -3789,7 +3849,7 @@ export function toAcpNotifications( role: "assistant" | "user", sessionId: string, toolUseCache: ToolUseCache, - client: AgentSideConnection, + client: AcpClient, logger: Logger, options?: { registerHooks?: boolean; @@ -4111,7 +4171,7 @@ export function streamEventToAcpNotifications( message: SDKPartialAssistantMessage, sessionId: string, toolUseCache: ToolUseCache, - client: AgentSideConnection, + client: AcpClient, logger: Logger, options?: { clientCapabilities?: ClientCapabilities; @@ -4176,11 +4236,34 @@ export function runAcp() { const output = nodeToWebReadable(process.stdin); const stream = ndJsonStream(input, output); - let agent!: ClaudeAcpAgent; - const connection = new AgentSideConnection((client) => { - agent = new ClaudeAcpAgent(client); - return agent; - }, stream); + + // `connect(...)` returns a connection-scoped peer handle (`connection.client`) + // that stays valid for the whole connection, so the agent captures it once. + // Handlers close over `agent`, which is assigned synchronously right after + // `connect()` returns — before the connection processes any inbound message. + // It cannot be `const`: its value depends on `connection.client`, which does + // not exist until `connect()` has been called. + // eslint-disable-next-line prefer-const + let agent: ClaudeAcpAgent; + const connection = acpAgent({ name: "claude-code-acp" }) + .onRequest(methods.agent.initialize, (ctx) => agent.initialize(ctx.params)) + .onRequest(methods.agent.session.new, (ctx) => agent.newSession(ctx.params)) + .onRequest(methods.agent.session.load, (ctx) => agent.loadSession(ctx.params)) + .onRequest(methods.agent.session.fork, (ctx) => agent.unstable_forkSession(ctx.params)) + .onRequest(methods.agent.session.list, (ctx) => agent.listSessions(ctx.params)) + .onRequest(methods.agent.session.delete, (ctx) => agent.deleteSession(ctx.params)) + .onRequest(methods.agent.session.resume, (ctx) => agent.resumeSession(ctx.params)) + .onRequest(methods.agent.session.close, (ctx) => agent.closeSession(ctx.params)) + .onRequest(methods.agent.session.setMode, (ctx) => agent.setSessionMode(ctx.params)) + .onRequest(methods.agent.session.setConfigOption, (ctx) => + agent.setSessionConfigOption(ctx.params), + ) + .onRequest(methods.agent.authenticate, (ctx) => agent.authenticate(ctx.params)) + .onRequest(methods.agent.session.prompt, (ctx) => agent.prompt(ctx.params)) + .onNotification(methods.agent.session.cancel, (ctx) => agent.cancel(ctx.params)) + .connect(stream); + + agent = new ClaudeAcpAgent(new ClientConnection(connection.client)); return { connection, agent }; } diff --git a/src/tests/acp-agent-settings.test.ts b/src/tests/acp-agent-settings.test.ts index dd0f954d..d000dc59 100644 --- a/src/tests/acp-agent-settings.test.ts +++ b/src/tests/acp-agent-settings.test.ts @@ -2,8 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import * as fs from "node:fs"; import * as path from "node:path"; import * as os from "node:os"; -import type { AgentSideConnection } from "@agentclientprotocol/sdk"; -import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js"; +import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js"; const { querySpy } = vi.hoisted(() => ({ querySpy: vi.fn(), @@ -21,13 +20,13 @@ describe("ClaudeAcpAgent settings", () => { let tempDir: string; let originalClaudeConfigDir: string | undefined; - function createMockClient(): AgentSideConnection { + function createMockClient(): AcpClient { return { sessionUpdate: async () => {}, requestPermission: async () => ({ outcome: { outcome: "cancelled" } }), readTextFile: async () => ({ content: "" }), writeTextFile: async () => ({}), - } as unknown as AgentSideConnection; + } as unknown as AcpClient; } function mockQuery() { diff --git a/src/tests/acp-agent.test.ts b/src/tests/acp-agent.test.ts index 9d7084b0..d3951306 100644 --- a/src/tests/acp-agent.test.ts +++ b/src/tests/acp-agent.test.ts @@ -1,15 +1,15 @@ import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from "vitest"; import { spawn, spawnSync } from "child_process"; import { - Agent, - AgentSideConnection, AvailableCommand, - Client, - ClientSideConnection, + client as acpClient, CreateElicitationRequest, CreateElicitationResponse, + methods, ndJsonStream, NewSessionResponse, + PromptRequest, + PromptResponse, ReadTextFileRequest, ReadTextFileResponse, RequestPermissionRequest, @@ -36,6 +36,7 @@ import { describeAlwaysAllow, streamEventToAcpNotifications, messageIdForGrouping, + type AcpClient, type SDKMessageFilter, } from "../acp-agent.js"; import { Pushable } from "../utils.js"; @@ -158,8 +159,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration" child.kill(); }); - class TestClient implements Client { - agent: Agent; + class TestClient { files: Map = new Map(); receivedText: string = ""; // Records for the AskUserQuestion elicitation test. @@ -169,8 +169,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration" resolveAvailableCommands: (commands: AvailableCommand[]) => void; availableCommandsPromise: Promise; - constructor(agent: Agent) { - this.agent = agent; + constructor() { this.resolveAvailableCommands = () => {}; this.availableCommandsPromise = new Promise((resolve) => { this.resolveAvailableCommands = resolve; @@ -248,21 +247,35 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration" } } + type TestConnection = { + prompt(params: PromptRequest): Promise; + }; + async function setupTestSession(cwd: string): Promise<{ client: TestClient; - connection: ClientSideConnection; + connection: TestConnection; newSessionResponse: NewSessionResponse; }> { - let client; const input = nodeToWebWritable(child.stdin!); const output = nodeToWebReadable(child.stdout!); const stream = ndJsonStream(input, output); - const connection = new ClientSideConnection((agent) => { - client = new TestClient(agent); - return client; - }, stream); - await connection.initialize({ + const client = new TestClient(); + // `connect(...)` keeps the connection open and exposes the agent-side peer + // handle as `connection.agent`, valid for the lifetime of the connection. + const { agent: ctx } = acpClient({ name: "test-client" }) + .onNotification(methods.client.session.update, (c) => client.sessionUpdate(c.params)) + .onRequest(methods.client.session.requestPermission, (c) => + client.requestPermission(c.params), + ) + .onRequest(methods.client.fs.readTextFile, (c) => client.readTextFile(c.params)) + .onRequest(methods.client.fs.writeTextFile, (c) => client.writeTextFile(c.params)) + .onRequest(methods.client.elicitation.create, (c) => + client.unstable_createElicitation(c.params), + ) + .connect(stream); + + await ctx.request(methods.agent.initialize, { protocolVersion: 1, clientCapabilities: { fs: { @@ -275,12 +288,16 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration" }, }); - const newSessionResponse = await connection.newSession({ + const newSessionResponse = await ctx.request(methods.agent.session.new, { cwd, mcpServers: [], }); - return { client: client!, connection, newSessionResponse }; + const connection: TestConnection = { + prompt: (params) => ctx.request(methods.agent.session.prompt, params), + }; + + return { client, connection, newSessionResponse }; } it("should connect to the ACP subprocess", async () => { @@ -848,7 +865,7 @@ describe("tool conversions", () => { received.message.role, "test", {}, - {} as AgentSideConnection, + {} as AcpClient, console, ), ).toStrictEqual([ @@ -1766,7 +1783,7 @@ describe("stop reason propagation", () => { function createMockAgent() { const mockClient = { sessionUpdate: async () => {}, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); } @@ -2096,7 +2113,7 @@ describe("stop reason propagation", () => { sessionUpdate: async (u: any) => { sessionUpdates.push(u); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); const input = new Pushable(); @@ -2263,7 +2280,7 @@ describe("session/close", () => { function createMockAgent() { const mockClient = { sessionUpdate: async () => {}, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); } @@ -2352,7 +2369,7 @@ describe("session/delete", () => { function createMockAgent() { const mockClient = { sessionUpdate: async () => {}, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); } @@ -2439,7 +2456,7 @@ describe("getOrCreateSession param change detection", () => { function createMockAgent() { const mockClient = { sessionUpdate: async () => {}, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); } @@ -2664,7 +2681,7 @@ describe("usage_update computation", () => { sessionUpdate: async (notification: any) => { updates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); return { agent, updates }; } @@ -3586,7 +3603,7 @@ describe("assembled assistant text fallback", () => { sessionUpdate: async (notification: any) => { updates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); return { agent, updates }; } @@ -3917,7 +3934,7 @@ describe("emitRawSDKMessages", () => { extNotification: async (method: string, params: any) => { extNotifications.push({ method, params }); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); return { agent, updates, extNotifications }; } @@ -4138,7 +4155,7 @@ describe("result origin handling", () => { sessionUpdate: async (notification: any) => { updates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); return { agent, updates }; } @@ -4292,7 +4309,7 @@ describe("memory_recall handling", () => { sessionUpdate: async (notification: any) => { updates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); return { agent, updates }; } @@ -4449,7 +4466,7 @@ describe("post-error recovery", () => { function createMockAgent() { const mockClient = { sessionUpdate: async () => {}, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); } @@ -5047,7 +5064,7 @@ describe("session/cancel wedge recovery (issue #680)", () => { function createMockAgent() { const mockClient = { sessionUpdate: async () => {}, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); } @@ -5268,14 +5285,9 @@ describe("streamEventToAcpNotifications", () => { }, } as Parameters[0]; - const result = streamEventToAcpNotifications( - message, - "test", - {}, - {} as AgentSideConnection, - console, - { messageId }, - ); + const result = streamEventToAcpNotifications(message, "test", {}, {} as AcpClient, console, { + messageId, + }); expect(result).toEqual([ { @@ -5299,7 +5311,7 @@ describe("toAcpNotifications messageId", () => { "assistant", "test", {}, - {} as AgentSideConnection, + {} as AcpClient, console, { messageId }, ); @@ -5322,7 +5334,7 @@ describe("toAcpNotifications messageId", () => { "user", "test", {}, - {} as AgentSideConnection, + {} as AcpClient, console, { messageId }, ); @@ -5336,7 +5348,7 @@ describe("toAcpNotifications messageId", () => { "assistant", "test", {}, - {} as AgentSideConnection, + {} as AcpClient, console, { messageId }, ); @@ -5347,14 +5359,7 @@ describe("toAcpNotifications messageId", () => { }); it("omits messageId when none is supplied", () => { - const result = toAcpNotifications( - "hello", - "assistant", - "test", - {}, - {} as AgentSideConnection, - console, - ); + const result = toAcpNotifications("hello", "assistant", "test", {}, {} as AcpClient, console); expect(result[0].update).not.toHaveProperty("messageId"); }); @@ -5371,7 +5376,7 @@ describe("toAcpNotifications messageId", () => { "assistant", "test", {}, - {} as AgentSideConnection, + {} as AcpClient, console, { messageId, registerHooks: false }, ); diff --git a/src/tests/additional-roots.test.ts b/src/tests/additional-roots.test.ts index a9609e00..408a0b23 100644 --- a/src/tests/additional-roots.test.ts +++ b/src/tests/additional-roots.test.ts @@ -1,9 +1,9 @@ import { mkdtemp, rm } from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; -import { AgentSideConnection, SessionNotification } from "@agentclientprotocol/sdk"; +import { SessionNotification } from "@agentclientprotocol/sdk"; import type { Options } from "@anthropic-ai/claude-agent-sdk"; -import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js"; +import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; let capturedOptions: Options | undefined; @@ -52,7 +52,7 @@ describe("additionalRoots", () => { requestPermission: async () => ({ outcome: { outcome: "cancelled" } }), readTextFile: async () => ({ content: "" }), writeTextFile: async () => ({}), - } as unknown as AgentSideConnection); + } as unknown as AcpClient); }); afterEach( diff --git a/src/tests/authorization.test.ts b/src/tests/authorization.test.ts index 6ff721b2..954aed1d 100644 --- a/src/tests/authorization.test.ts +++ b/src/tests/authorization.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it, Mock, vi, afterEach, beforeEach } from "vitest"; -import { ClaudeAcpAgent } from "../acp-agent.js"; -import { AgentSideConnection } from "@agentclientprotocol/sdk"; +import { AcpClient, ClaudeAcpAgent } from "../acp-agent.js"; const mockQuery = vi.hoisted(() => vi.fn(() => ({ @@ -39,7 +38,7 @@ describe("authorization", () => { async function createAgentMock(): Promise<[ClaudeAcpAgent, Mock]> { const connectionMock = { sessionUpdate: async (_: any) => {}, - } as AgentSideConnection; + } as AcpClient; const agent = new ClaudeAcpAgent(connectionMock); diff --git a/src/tests/create-session-options.test.ts b/src/tests/create-session-options.test.ts index 4098d240..24b0fe5e 100644 --- a/src/tests/create-session-options.test.ts +++ b/src/tests/create-session-options.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { AgentSideConnection, RequestError, SessionNotification } from "@agentclientprotocol/sdk"; +import { RequestError, SessionNotification } from "@agentclientprotocol/sdk"; import type { Options } from "@anthropic-ai/claude-agent-sdk"; -import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js"; +import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; @@ -47,13 +47,13 @@ describe("createSession options merging", () => { let agent: ClaudeAcpAgentType; let ClaudeAcpAgent: typeof ClaudeAcpAgentType; - function createMockClient(): AgentSideConnection { + function createMockClient(): AcpClient { return { sessionUpdate: async (_notification: SessionNotification) => {}, requestPermission: async () => ({ outcome: { outcome: "cancelled" } }), readTextFile: async () => ({ content: "" }), writeTextFile: async () => ({}), - } as unknown as AgentSideConnection; + } as unknown as AcpClient; } beforeEach(async () => { diff --git a/src/tests/session-config-options.test.ts b/src/tests/session-config-options.test.ts index e455162c..1e39bd60 100644 --- a/src/tests/session-config-options.test.ts +++ b/src/tests/session-config-options.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; -import { AgentSideConnection, SessionNotification } from "@agentclientprotocol/sdk"; +import { SessionNotification } from "@agentclientprotocol/sdk"; import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk"; -import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js"; +import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js"; const { registerHookCallbackSpy } = vi.hoisted(() => ({ registerHookCallbackSpy: vi.fn(), @@ -84,7 +84,7 @@ describe("session config options", () => { let setModelSpy: ReturnType; let applyFlagSettingsSpy: ReturnType; - function createMockClient(): AgentSideConnection { + function createMockClient(): AcpClient { return { sessionUpdate: async (notification: SessionNotification) => { sessionUpdates.push(notification); @@ -92,7 +92,7 @@ describe("session config options", () => { requestPermission: async () => ({ outcome: { outcome: "cancelled" } }), readTextFile: async () => ({ content: "" }), writeTextFile: async () => ({}), - } as unknown as AgentSideConnection; + } as unknown as AcpClient; } function populateSession() { diff --git a/src/tests/session-load.test.ts b/src/tests/session-load.test.ts index 7b3fb57e..4c5e2672 100644 --- a/src/tests/session-load.test.ts +++ b/src/tests/session-load.test.ts @@ -1,18 +1,18 @@ import { describe, it, expect } from "vitest"; import { randomUUID } from "crypto"; -import { AgentSideConnection, RequestError, SessionNotification } from "@agentclientprotocol/sdk"; +import { RequestError, SessionNotification } from "@agentclientprotocol/sdk"; import { query, getSessionMessages } from "@anthropic-ai/claude-agent-sdk"; -import { ClaudeAcpAgent } from "../acp-agent.js"; +import { AcpClient, ClaudeAcpAgent } from "../acp-agent.js"; import { Pushable } from "../utils.js"; import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk"; -function createMockClient(): AgentSideConnection { +function createMockClient(): AcpClient { return { sessionUpdate: async (_notification: SessionNotification) => {}, requestPermission: async () => ({ outcome: { outcome: "cancelled" } }), readTextFile: async () => ({ content: "" }), writeTextFile: async () => ({}), - } as unknown as AgentSideConnection; + } as unknown as AcpClient; } describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("session load/resume lifecycle", () => { @@ -165,7 +165,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("session load/resume lifecyc requestPermission: async () => ({ outcome: { outcome: "cancelled" } }), readTextFile: async () => ({ content: "" }), writeTextFile: async () => ({}), - } as unknown as AgentSideConnection; + } as unknown as AcpClient; const commandName = "/model\n model\n opus"; @@ -274,7 +274,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("session load/resume lifecyc requestPermission: async () => ({ outcome: { outcome: "cancelled" } }), readTextFile: async () => ({ content: "" }), writeTextFile: async () => ({}), - } as unknown as AgentSideConnection; + } as unknown as AcpClient; // Exact shape from https://github.com/zed-industries/claude-code-acp/issues/579. const concatenated = diff --git a/src/tests/tools.test.ts b/src/tests/tools.test.ts index c4a9970b..bab9ae77 100644 --- a/src/tests/tools.test.ts +++ b/src/tests/tools.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from "vitest"; -import { AgentSideConnection, ClientCapabilities } from "@agentclientprotocol/sdk"; +import { ClientCapabilities } from "@agentclientprotocol/sdk"; import { ImageBlockParam, ToolResultBlockParam } from "@anthropic-ai/sdk/resources"; import { BetaMCPToolResultBlock, @@ -10,7 +10,7 @@ import { BetaBashCodeExecutionResultBlock, BetaBashCodeExecutionToolResultBlockParam, } from "@anthropic-ai/sdk/resources/beta.mjs"; -import { toAcpNotifications, ToolUseCache, Logger } from "../acp-agent.js"; +import { AcpClient, toAcpNotifications, ToolUseCache, Logger } from "../acp-agent.js"; import { toolUpdateFromToolResult, createPostToolUseHook, @@ -25,7 +25,7 @@ import { } from "../tools.js"; describe("rawOutput in tool call updates", () => { - const mockClient = {} as AgentSideConnection; + const mockClient = {} as AcpClient; const mockLogger: Logger = { log: () => {}, error: () => {} }; it("should include rawOutput with string content for tool_result", () => { @@ -425,7 +425,7 @@ describe("rawOutput in tool call updates", () => { }); describe("Bash terminal output", () => { - const mockClient = {} as AgentSideConnection; + const mockClient = {} as AcpClient; const mockLogger: Logger = { log: () => {}, error: () => {} }; const bashToolUse = { @@ -929,7 +929,7 @@ describe("Bash terminal output", () => { sessionUpdate: async (notification: any) => { hookUpdates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; // Register hook callback by processing tool_use toAcpNotifications( @@ -1008,7 +1008,7 @@ describe("Bash terminal output", () => { sessionUpdate: async (notification: any) => { hookUpdates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; toAcpNotifications( [ @@ -1093,7 +1093,7 @@ describe("Bash terminal output", () => { sessionUpdate: async (notification: any) => { hookUpdates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; toAcpNotifications( [ @@ -1147,7 +1147,7 @@ describe("Bash terminal output", () => { sessionUpdate: async (notification: any) => { hookUpdates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; toAcpNotifications( [ @@ -1223,7 +1223,7 @@ describe("Bash terminal output", () => { sessionUpdate: async (notification: any) => { hookUpdates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; toAcpNotifications( [ @@ -1304,7 +1304,7 @@ describe("Bash terminal output", () => { sessionUpdate: async (notification: any) => { hookUpdates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; // Step 1: Process tool_use chunk — registers the PostToolUse hook callback const toolUseChunk = { @@ -1404,7 +1404,7 @@ describe("Bash terminal output", () => { sessionUpdate: async (notification: any) => { hookUpdates.push(notification); }, - } as unknown as AgentSideConnection; + } as unknown as AcpClient; // Process tool_use (registers hook) toAcpNotifications( @@ -1569,7 +1569,7 @@ describe("planEntries - undefined input regression", () => { }); describe("toAcpNotifications - TodoWrite with undefined input regression", () => { - const mockClient = {} as AgentSideConnection; + const mockClient = {} as AcpClient; const mockLogger: Logger = { log: () => {}, error: () => {} }; it("should not throw when TodoWrite tool_use has undefined input", () => { @@ -1704,7 +1704,7 @@ describe("applyTaskCreate / applyTaskUpdate", () => { }); describe("toAcpNotifications - Task* tools", () => { - const mockClient = {} as AgentSideConnection; + const mockClient = {} as AcpClient; const mockLogger: Logger = { log: () => {}, error: () => {} }; it("suppresses tool_call for TaskCreate/TaskUpdate/TaskList/TaskGet on tool_use", () => {