Files
claude-agent-acp/src/tests/acp-agent.test.ts
2025-08-27 16:05:52 -07:00

356 lines
11 KiB
TypeScript

import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { spawn } from "child_process";
import {
Agent,
Client,
ClientSideConnection,
ReadTextFileRequest,
ReadTextFileResponse,
RequestPermissionRequest,
RequestPermissionResponse,
SessionNotification,
WriteTextFileRequest,
WriteTextFileResponse,
} from "@zed-industries/agent-client-protocol";
import { nodeToWebWritable, nodeToWebReadable } from "../utils.js";
import { extractToolInfo } from "../tools.js";
import { toAcpNotifications } from "../acp-agent.js";
import { SDKAssistantMessage } from "@anthropic-ai/claude-code";
describe("ACP subprocess integration", () => {
let child: ReturnType<typeof spawn>;
beforeAll(async () => {
// Start the subprocess
child = spawn("npm", ["run", "--silent", "dev"], {
stdio: ["pipe", "pipe", "inherit"],
env: process.env,
});
});
afterAll(() => {
child.kill();
});
class TestClient implements Client {
agent: Agent;
constructor(agent: Agent) {
this.agent = agent;
}
requestPermission(
params: RequestPermissionRequest,
): Promise<RequestPermissionResponse> {
throw new Error("Method not implemented.");
}
async sessionUpdate(params: SessionNotification): Promise<void> {
console.error(params);
}
writeTextFile(
params: WriteTextFileRequest,
): Promise<WriteTextFileResponse> {
throw new Error("Method not implemented.");
}
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {
throw new Error("Method not implemented.");
}
}
it("should connect to the ACP subprocess", async () => {
const connection = new ClientSideConnection(
(agent) => {
return new TestClient(agent);
},
nodeToWebWritable(child.stdin!),
nodeToWebReadable(child.stdout!),
);
let session = await connection.newSession({ cwd: "./", mcpServers: [] });
await connection.prompt({
prompt: [{ type: "text", text: "Hello Claude!" }],
sessionId: session.sessionId,
});
});
});
describe("tool conversions", () => {
it("should handle Bash nicely", () => {
const tool_use = {
type: "tool_use",
id: "toolu_01VtsS2mxUFwpBJZYd7BmbC9",
name: "Bash",
input: {
command: "rm README.md.rm",
description: "Delete README.md.rm file",
},
};
expect(extractToolInfo(tool_use)).toStrictEqual({
kind: "execute",
title: "rm README.md.rm",
content: [
{
content: {
text: "Delete README.md.rm file",
type: "text",
},
type: "content",
},
],
});
});
it("should handle Glob nicely", () => {
const tool_use = {
type: "tool_use",
id: "toolu_01VtsS2mxUFwpBJZYd7BmbC9",
name: "Glob",
input: {
pattern: "*/**.ts",
},
};
expect(extractToolInfo(tool_use)).toStrictEqual({
kind: "search",
title: "Find */**.ts",
content: [],
});
});
it("should handle Task tool calls", () => {
const tool_use = {
type: "tool_use",
id: "toolu_01ANYHYDsXcDPKgxhg7us9bj",
name: "Task",
input: {
description: "Handle user's work request",
prompt:
'The user has asked me to "Create a Task to do the work!" but hasn\'t specified what specific work they want done. I need to:\n\n1. First understand what work needs to be done by examining the current state of the repository\n2. Look at the git status to see what files have been modified\n3. Check if there are any obvious tasks that need completion based on the current state\n4. If the work isn\'t clear from the context, ask the user to specify what work they want accomplished\n\nThe git status shows: "M src/tests/acp-agent.test.ts" - there\'s a modified test file that might need attention.\n\nPlease examine the repository state and determine what work needs to be done, then either complete it or ask the user for clarification on the specific task they want accomplished.',
subagent_type: "general-purpose",
},
};
expect(extractToolInfo(tool_use)).toStrictEqual({
kind: "think",
title: "Handle user's work request",
content: [
{
content: {
text: 'The user has asked me to "Create a Task to do the work!" but hasn\'t specified what specific work they want done. I need to:\n\n1. First understand what work needs to be done by examining the current state of the repository\n2. Look at the git status to see what files have been modified\n3. Check if there are any obvious tasks that need completion based on the current state\n4. If the work isn\'t clear from the context, ask the user to specify what work they want accomplished\n\nThe git status shows: "M src/tests/acp-agent.test.ts" - there\'s a modified test file that might need attention.\n\nPlease examine the repository state and determine what work needs to be done, then either complete it or ask the user for clarification on the specific task they want accomplished.',
type: "text",
},
type: "content",
},
],
});
});
it("should handle LS tool calls", () => {
const tool_use = {
type: "tool_use",
id: "toolu_01EEqsX7Eb9hpx87KAHVPTey",
name: "LS",
input: {
path: "/Users/benbrandt/github/claude-code-acp",
},
};
expect(extractToolInfo(tool_use)).toStrictEqual({
kind: "search",
title: "List Directory /Users/benbrandt/github/claude-code-acp",
content: [],
});
});
it("should handle Grep tool calls", () => {
const tool_use = {
type: "tool_use",
id: "toolu_016j8oGSD3eAZ9KT62Y7Jsjb",
name: "Grep",
input: {
pattern: ".*",
},
};
expect(extractToolInfo(tool_use)).toStrictEqual({
kind: "search",
title: 'grep ".*"',
content: [],
});
});
it("should handle WebFetch tool calls", () => {
const tool_use = {
type: "tool_use",
id: "toolu_01LxEjDn8ci9SAc3qG7LbbXV",
name: "WebFetch",
input: {
url: "https://agentclientprotocol.com",
prompt:
"Please provide a comprehensive summary of the content on this page, including what the Agent Client Protocol is, its main features, documentation links, and any other relevant information.",
},
};
expect(extractToolInfo(tool_use)).toStrictEqual({
kind: "fetch",
title: "Fetch https://agentclientprotocol.com",
content: [
{
content: {
text: "Please provide a comprehensive summary of the content on this page, including what the Agent Client Protocol is, its main features, documentation links, and any other relevant information.",
type: "text",
},
type: "content",
},
],
});
});
it("should handle WebSearch tool calls", () => {
const tool_use = {
type: "tool_use",
id: "toolu_01NYMwiZFbdoQFxYxuQDFZXQ",
name: "WebSearch",
input: {
query: "agentclientprotocol.com",
},
};
expect(extractToolInfo(tool_use)).toStrictEqual({
kind: "search",
title: '"agentclientprotocol.com"',
content: [],
});
});
it("should handle plan entries", () => {
const received: SDKAssistantMessage = {
type: "assistant",
message: {
id: "msg_017eNosJgww7F5qD4a8BcAcx",
type: "message",
role: "assistant",
model: "claude-sonnet-4-20250514",
content: [
{
type: "tool_use",
id: "toolu_01HaXZ4LfdchSeSR8ygt4zyq",
name: "TodoWrite",
input: {
todos: [
{
content: "Analyze existing test coverage and identify gaps",
status: "in_progress",
activeForm: "Analyzing existing test coverage",
},
{
content: "Add comprehensive edge case tests",
status: "pending",
activeForm: "Adding comprehensive edge case tests",
},
{
content: "Add performance and timing tests",
status: "pending",
activeForm: "Adding performance and timing tests",
},
{
content: "Add error handling and panic behavior tests",
status: "pending",
activeForm: "Adding error handling tests",
},
{
content: "Add concurrent access and race condition tests",
status: "pending",
activeForm: "Adding concurrent access tests",
},
{
content:
"Add tests for Each function with various data types",
status: "pending",
activeForm: "Adding Each function tests",
},
{
content: "Add benchmark tests for performance measurement",
status: "pending",
activeForm: "Adding benchmark tests",
},
{
content: "Improve test organization and helper functions",
status: "pending",
activeForm: "Improving test organization",
},
],
},
},
],
stop_reason: null,
stop_sequence: null,
usage: {
input_tokens: 6,
cache_creation_input_tokens: 326,
cache_read_input_tokens: 17265,
cache_creation: {
ephemeral_5m_input_tokens: 326,
ephemeral_1h_input_tokens: 0,
},
output_tokens: 1,
service_tier: "standard",
},
},
parent_tool_use_id: null,
session_id: "d056596f-e328-41e9-badd-b07122ae5227",
uuid: "b7c3330c-de8f-4bba-ac53-68c7f76ffeb5",
};
expect(toAcpNotifications(received, "test")).toStrictEqual([
{
sessionId: "test",
update: {
sessionUpdate: "plan",
entries: [
{
content: "Analyze existing test coverage and identify gaps",
priority: "medium",
status: "in_progress",
},
{
content: "Add comprehensive edge case tests",
priority: "medium",
status: "pending",
},
{
content: "Add performance and timing tests",
priority: "medium",
status: "pending",
},
{
content: "Add error handling and panic behavior tests",
priority: "medium",
status: "pending",
},
{
content: "Add concurrent access and race condition tests",
priority: "medium",
status: "pending",
},
{
content: "Add tests for Each function with various data types",
priority: "medium",
status: "pending",
},
{
content: "Add benchmark tests for performance measurement",
priority: "medium",
status: "pending",
},
{
content: "Improve test organization and helper functions",
priority: "medium",
status: "pending",
},
],
},
},
]);
});
});