feat: Update to new ACP SDK patterns (#790)

This commit is contained in:
Ben Brandt
2026-06-19 11:48:41 +02:00
committed by GitHub
parent 1c80bf8e56
commit 2554c7bf98
11 changed files with 191 additions and 105 deletions

8
package-lock.json generated
View File

@@ -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"

View File

@@ -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"
},

View File

@@ -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<void>;
requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse>;
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
unstable_createElicitation(params: CreateElicitationRequest): Promise<CreateElicitationResponse>;
unstable_completeElicitation(params: CompleteElicitationNotification): Promise<void>;
/** Send a custom (extension) notification, e.g. `_claude/sdkMessage`. */
extNotification(method: string, params: Record<string, unknown>): Promise<void>;
}
/**
* 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<void> {
return this.ctx.notify(methods.client.session.update, params);
}
requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {
return this.ctx.request(methods.client.session.requestPermission, params);
}
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {
return this.ctx.request(methods.client.fs.readTextFile, params);
}
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {
return this.ctx.request(methods.client.fs.writeTextFile, params);
}
unstable_createElicitation(params: CreateElicitationRequest): Promise<CreateElicitationResponse> {
return this.ctx.request(methods.client.elicitation.create, params);
}
unstable_completeElicitation(params: CompleteElicitationNotification): Promise<void> {
return this.ctx.notify(methods.client.elicitation.complete, params);
}
extNotification(method: string, params: Record<string, unknown>): Promise<void> {
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 };
}

View File

@@ -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() {

View File

@@ -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<string, string> = 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<AvailableCommand[]>;
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<PromptResponse>;
};
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<any>();
@@ -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<typeof streamEventToAcpNotifications>[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 },
);

View File

@@ -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(

View File

@@ -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);

View File

@@ -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 () => {

View File

@@ -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<typeof vi.fn>;
let applyFlagSettingsSpy: ReturnType<typeof vi.fn>;
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() {

View File

@@ -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 =
"<command-name>/model</command-name>\n <command-message>model</command-message>\n <command-args>opus</command-args>";
@@ -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 =

View File

@@ -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", () => {