839 lines
30 KiB
JavaScript
839 lines
30 KiB
JavaScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
import { ClientSideConnection, AgentSideConnection, PROTOCOL_VERSION, ndJsonStream, } from "./acp.js";
|
|
describe("Connection", () => {
|
|
let clientToAgent;
|
|
let agentToClient;
|
|
beforeEach(() => {
|
|
clientToAgent = new TransformStream();
|
|
agentToClient = new TransformStream();
|
|
});
|
|
it("handles errors in bidirectional communication", async () => {
|
|
// Create client that throws errors
|
|
class TestClient {
|
|
async writeTextFile(_) {
|
|
throw new Error("Write failed");
|
|
}
|
|
async readTextFile(_) {
|
|
throw new Error("Read failed");
|
|
}
|
|
async requestPermission(_) {
|
|
throw new Error("Permission denied");
|
|
}
|
|
async sessionUpdate(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Create agent that throws errors
|
|
class TestAgent {
|
|
async initialize(_) {
|
|
throw new Error("Failed to initialize");
|
|
}
|
|
async newSession(_) {
|
|
throw new Error("Failed to create session");
|
|
}
|
|
async loadSession(_) {
|
|
throw new Error("Failed to load session");
|
|
}
|
|
async authenticate(_) {
|
|
throw new Error("Authentication failed");
|
|
}
|
|
async prompt(_) {
|
|
throw new Error("Prompt failed");
|
|
}
|
|
async cancel(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Set up connections
|
|
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Test error handling in client->agent direction
|
|
await expect(clientConnection.writeTextFile({
|
|
path: "/test.txt",
|
|
content: "test",
|
|
sessionId: "test-session",
|
|
})).rejects.toThrow();
|
|
// Test error handling in agent->client direction
|
|
await expect(agentConnection.newSession({
|
|
cwd: "/test",
|
|
mcpServers: [],
|
|
})).rejects.toThrow();
|
|
});
|
|
it("handles concurrent requests", async () => {
|
|
let requestCount = 0;
|
|
// Create client
|
|
class TestClient {
|
|
async writeTextFile(_) {
|
|
requestCount++;
|
|
const currentCount = requestCount;
|
|
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
console.log(`Write request ${currentCount} completed`);
|
|
return {};
|
|
}
|
|
async readTextFile(params) {
|
|
return { content: `Content of ${params.path}` };
|
|
}
|
|
async requestPermission(_) {
|
|
return {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "allow",
|
|
},
|
|
};
|
|
}
|
|
async sessionUpdate(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Create agent
|
|
class TestAgent {
|
|
async initialize(_) {
|
|
return {
|
|
protocolVersion: 1,
|
|
agentCapabilities: { loadSession: false },
|
|
authMethods: [],
|
|
};
|
|
}
|
|
async newSession(_) {
|
|
return {
|
|
sessionId: "test-session",
|
|
};
|
|
}
|
|
async loadSession(_) {
|
|
return {};
|
|
}
|
|
async authenticate(_) {
|
|
// no-op
|
|
}
|
|
async prompt(_) {
|
|
return { stopReason: "end_turn" };
|
|
}
|
|
async cancel(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Set up connections
|
|
new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Send multiple concurrent requests
|
|
const promises = [
|
|
clientConnection.writeTextFile({
|
|
path: "/file1.txt",
|
|
content: "content1",
|
|
sessionId: "session1",
|
|
}),
|
|
clientConnection.writeTextFile({
|
|
path: "/file2.txt",
|
|
content: "content2",
|
|
sessionId: "session1",
|
|
}),
|
|
clientConnection.writeTextFile({
|
|
path: "/file3.txt",
|
|
content: "content3",
|
|
sessionId: "session1",
|
|
}),
|
|
];
|
|
const results = await Promise.all(promises);
|
|
// Verify all requests completed successfully
|
|
expect(results).toHaveLength(3);
|
|
expect(results[0]).toEqual({});
|
|
expect(results[1]).toEqual({});
|
|
expect(results[2]).toEqual({});
|
|
expect(requestCount).toBe(3);
|
|
});
|
|
it("handles message ordering correctly", async () => {
|
|
const messageLog = [];
|
|
// Create client
|
|
class TestClient {
|
|
async writeTextFile(params) {
|
|
messageLog.push(`writeTextFile called: ${params.path}`);
|
|
return {};
|
|
}
|
|
async readTextFile(params) {
|
|
messageLog.push(`readTextFile called: ${params.path}`);
|
|
return { content: "test content" };
|
|
}
|
|
async requestPermission(params) {
|
|
messageLog.push(`requestPermission called: ${params.toolCall.title}`);
|
|
return {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "allow",
|
|
},
|
|
};
|
|
}
|
|
async sessionUpdate(_params) {
|
|
messageLog.push("sessionUpdate called");
|
|
}
|
|
}
|
|
// Create agent
|
|
class TestAgent {
|
|
async initialize(_) {
|
|
return {
|
|
protocolVersion: 1,
|
|
agentCapabilities: { loadSession: false },
|
|
authMethods: [],
|
|
};
|
|
}
|
|
async newSession(request) {
|
|
messageLog.push(`newSession called: ${request.cwd}`);
|
|
return {
|
|
sessionId: "test-session",
|
|
};
|
|
}
|
|
async loadSession(params) {
|
|
messageLog.push(`loadSession called: ${params.sessionId}`);
|
|
return {};
|
|
}
|
|
async authenticate(params) {
|
|
messageLog.push(`authenticate called: ${params.methodId}`);
|
|
}
|
|
async prompt(params) {
|
|
messageLog.push(`prompt called: ${params.sessionId}`);
|
|
return { stopReason: "end_turn" };
|
|
}
|
|
async cancel(params) {
|
|
messageLog.push(`cancelled called: ${params.sessionId}`);
|
|
}
|
|
}
|
|
// Set up connections
|
|
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Send requests in specific order
|
|
await agentConnection.newSession({
|
|
cwd: "/test",
|
|
mcpServers: [],
|
|
});
|
|
await clientConnection.writeTextFile({
|
|
path: "/test.txt",
|
|
content: "test",
|
|
sessionId: "test-session",
|
|
});
|
|
await clientConnection.readTextFile({
|
|
path: "/test.txt",
|
|
sessionId: "test-session",
|
|
});
|
|
await clientConnection.requestPermission({
|
|
sessionId: "test-session",
|
|
toolCall: {
|
|
title: "Execute command",
|
|
kind: "execute",
|
|
status: "pending",
|
|
toolCallId: "tool-123",
|
|
content: [
|
|
{
|
|
type: "content",
|
|
content: {
|
|
type: "text",
|
|
text: "ls -la",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
options: [
|
|
{
|
|
kind: "allow_once",
|
|
name: "Allow",
|
|
optionId: "allow",
|
|
},
|
|
{
|
|
kind: "reject_once",
|
|
name: "Reject",
|
|
optionId: "reject",
|
|
},
|
|
],
|
|
});
|
|
// Verify order
|
|
expect(messageLog).toEqual([
|
|
"newSession called: /test",
|
|
"writeTextFile called: /test.txt",
|
|
"readTextFile called: /test.txt",
|
|
"requestPermission called: Execute command",
|
|
]);
|
|
});
|
|
it("handles notifications correctly", async () => {
|
|
const notificationLog = [];
|
|
// Create client
|
|
class TestClient {
|
|
async writeTextFile(_) {
|
|
return {};
|
|
}
|
|
async readTextFile(_) {
|
|
return { content: "test" };
|
|
}
|
|
async requestPermission(_) {
|
|
return {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "allow",
|
|
},
|
|
};
|
|
}
|
|
async sessionUpdate(notification) {
|
|
if (notification.update &&
|
|
"sessionUpdate" in notification.update &&
|
|
notification.update.sessionUpdate === "agent_message_chunk") {
|
|
notificationLog.push(`agent message: ${notification.update.content.text}`);
|
|
}
|
|
}
|
|
}
|
|
// Create agent
|
|
class TestAgent {
|
|
async initialize(_) {
|
|
return {
|
|
protocolVersion: 1,
|
|
agentCapabilities: { loadSession: false },
|
|
authMethods: [],
|
|
};
|
|
}
|
|
async newSession(_) {
|
|
return {
|
|
sessionId: "test-session",
|
|
};
|
|
}
|
|
async loadSession(_) {
|
|
return {};
|
|
}
|
|
async authenticate(_) {
|
|
// no-op
|
|
}
|
|
async prompt(_) {
|
|
return { stopReason: "end_turn" };
|
|
}
|
|
async cancel(params) {
|
|
notificationLog.push(`cancelled: ${params.sessionId}`);
|
|
}
|
|
}
|
|
// Create shared instances
|
|
const testClient = () => new TestClient();
|
|
const testAgent = () => new TestAgent();
|
|
// Set up connections
|
|
const agentConnection = new ClientSideConnection(testClient, ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
const clientConnection = new AgentSideConnection(testAgent, ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Send notifications
|
|
await clientConnection.sessionUpdate({
|
|
sessionId: "test-session",
|
|
update: {
|
|
sessionUpdate: "agent_message_chunk",
|
|
content: {
|
|
type: "text",
|
|
text: "Hello from agent",
|
|
},
|
|
},
|
|
});
|
|
await agentConnection.cancel({
|
|
sessionId: "test-session",
|
|
});
|
|
// Wait a bit for async handlers
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
// Verify notifications were received
|
|
expect(notificationLog).toContain("agent message: Hello from agent");
|
|
expect(notificationLog).toContain("cancelled: test-session");
|
|
});
|
|
it("handles initialize method", async () => {
|
|
// Create client
|
|
class TestClient {
|
|
async writeTextFile(_) {
|
|
return {};
|
|
}
|
|
async readTextFile(_) {
|
|
return { content: "test" };
|
|
}
|
|
async requestPermission(_) {
|
|
return {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "allow",
|
|
},
|
|
};
|
|
}
|
|
async sessionUpdate(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Create agent
|
|
class TestAgent {
|
|
async initialize(params) {
|
|
return {
|
|
protocolVersion: params.protocolVersion,
|
|
agentCapabilities: { loadSession: true },
|
|
authMethods: [
|
|
{
|
|
id: "oauth",
|
|
name: "OAuth",
|
|
description: "Authenticate with OAuth",
|
|
},
|
|
],
|
|
};
|
|
}
|
|
async newSession(_) {
|
|
return { sessionId: "test-session" };
|
|
}
|
|
async loadSession(_) {
|
|
return {};
|
|
}
|
|
async authenticate(_) {
|
|
// no-op
|
|
}
|
|
async prompt(_) {
|
|
return { stopReason: "end_turn" };
|
|
}
|
|
async cancel(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Set up connections
|
|
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Test initialize request
|
|
const response = await agentConnection.initialize({
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
clientCapabilities: {
|
|
fs: {
|
|
readTextFile: false,
|
|
writeTextFile: false,
|
|
},
|
|
},
|
|
});
|
|
expect(response.protocolVersion).toBe(PROTOCOL_VERSION);
|
|
expect(response.agentCapabilities?.loadSession).toBe(true);
|
|
expect(response.authMethods).toHaveLength(1);
|
|
expect(response.authMethods?.[0].id).toBe("oauth");
|
|
});
|
|
it("handles extension methods and notifications", async () => {
|
|
const extensionLog = [];
|
|
// Create client with extension method support
|
|
class TestClient {
|
|
async writeTextFile(_) {
|
|
return {};
|
|
}
|
|
async readTextFile(_) {
|
|
return { content: "test" };
|
|
}
|
|
async requestPermission(_) {
|
|
return {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "allow",
|
|
},
|
|
};
|
|
}
|
|
async sessionUpdate(_) {
|
|
// no-op
|
|
}
|
|
async extMethod(method, params) {
|
|
if (method === "example.com/ping") {
|
|
return { response: "pong", params };
|
|
}
|
|
throw new Error(`Unknown method: ${method}`);
|
|
}
|
|
async extNotification(method, _params) {
|
|
extensionLog.push(`client extNotification: ${method}`);
|
|
}
|
|
}
|
|
// Create agent with extension method support
|
|
class TestAgent {
|
|
async initialize(_) {
|
|
return {
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
agentCapabilities: { loadSession: false },
|
|
};
|
|
}
|
|
async newSession(_) {
|
|
return { sessionId: "test-session" };
|
|
}
|
|
async authenticate(_) {
|
|
// no-op
|
|
}
|
|
async prompt(_) {
|
|
return { stopReason: "end_turn" };
|
|
}
|
|
async cancel(_) {
|
|
// no-op
|
|
}
|
|
async extMethod(method, params) {
|
|
if (method === "example.com/echo") {
|
|
return { echo: params };
|
|
}
|
|
throw new Error(`Unknown method: ${method}`);
|
|
}
|
|
async extNotification(method, _params) {
|
|
extensionLog.push(`agent extNotification: ${method}`);
|
|
}
|
|
}
|
|
// Set up connections
|
|
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Test agent calling client extension method
|
|
const clientResponse = await clientConnection.extMethod("example.com/ping", {
|
|
data: "test",
|
|
});
|
|
expect(clientResponse).toEqual({
|
|
response: "pong",
|
|
params: { data: "test" },
|
|
});
|
|
// Test client calling agent extension method
|
|
const agentResponse = await agentConnection.extMethod("example.com/echo", {
|
|
message: "hello",
|
|
});
|
|
expect(agentResponse).toEqual({ echo: { message: "hello" } });
|
|
// Test extension notifications
|
|
await clientConnection.extNotification("example.com/client/notify", {
|
|
info: "client notification",
|
|
});
|
|
await agentConnection.extNotification("example.com/agent/notify", {
|
|
info: "agent notification",
|
|
});
|
|
// Wait a bit for async handlers
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
// Verify notifications were logged
|
|
expect(extensionLog).toContain("client extNotification: example.com/client/notify");
|
|
expect(extensionLog).toContain("agent extNotification: example.com/agent/notify");
|
|
});
|
|
it("handles optional extension methods correctly", async () => {
|
|
// Create client WITHOUT extension methods
|
|
class TestClientWithoutExtensions {
|
|
async writeTextFile(_) {
|
|
return {};
|
|
}
|
|
async readTextFile(_) {
|
|
return { content: "test" };
|
|
}
|
|
async requestPermission(_) {
|
|
return {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "allow",
|
|
},
|
|
};
|
|
}
|
|
async sessionUpdate(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Create agent WITHOUT extension methods
|
|
class TestAgentWithoutExtensions {
|
|
async initialize(_) {
|
|
return {
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
agentCapabilities: { loadSession: false },
|
|
};
|
|
}
|
|
async newSession(_) {
|
|
return { sessionId: "test-session" };
|
|
}
|
|
async authenticate(_) {
|
|
// no-op
|
|
}
|
|
async prompt(_) {
|
|
return { stopReason: "end_turn" };
|
|
}
|
|
async cancel(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Set up connections
|
|
const agentConnection = new ClientSideConnection(() => new TestClientWithoutExtensions(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
const clientConnection = new AgentSideConnection(() => new TestAgentWithoutExtensions(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Test that calling extension methods on connections without them throws method not found
|
|
try {
|
|
await clientConnection.extMethod("_example.com/ping", { data: "test" });
|
|
expect.fail("Should have thrown method not found error");
|
|
}
|
|
catch (error) {
|
|
expect(error.code).toBe(-32601); // Method not found
|
|
expect(error.data.method).toBe("_example.com/ping");
|
|
}
|
|
try {
|
|
await agentConnection.extMethod("_example.com/echo", {
|
|
message: "hello",
|
|
});
|
|
expect.fail("Should have thrown method not found error");
|
|
}
|
|
catch (error) {
|
|
expect(error.code).toBe(-32601); // Method not found
|
|
expect(error.data.method).toBe("_example.com/echo");
|
|
}
|
|
// Notifications should be ignored when not implemented (no error thrown)
|
|
await clientConnection.extNotification("example.com/notify", {
|
|
info: "test",
|
|
});
|
|
await agentConnection.extNotification("example.com/notify", {
|
|
info: "test",
|
|
});
|
|
});
|
|
it("resolves closed promise when stream ends", async () => {
|
|
const closeLog = [];
|
|
// Create simple client and agent
|
|
class TestClient {
|
|
async writeTextFile(_) {
|
|
return {};
|
|
}
|
|
async readTextFile(_) {
|
|
return { content: "test" };
|
|
}
|
|
async requestPermission(_) {
|
|
return {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "allow",
|
|
},
|
|
};
|
|
}
|
|
async sessionUpdate(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
class TestAgent {
|
|
async initialize(_) {
|
|
return {
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
agentCapabilities: { loadSession: false },
|
|
};
|
|
}
|
|
async newSession(_) {
|
|
return { sessionId: "test-session" };
|
|
}
|
|
async authenticate(_) {
|
|
// no-op
|
|
}
|
|
async prompt(_) {
|
|
return { stopReason: "end_turn" };
|
|
}
|
|
async cancel(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Set up connections
|
|
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Listen for close via signal
|
|
agentConnection.signal.addEventListener("abort", () => {
|
|
closeLog.push("agent connection closed (signal)");
|
|
});
|
|
clientConnection.signal.addEventListener("abort", () => {
|
|
closeLog.push("client connection closed (signal)");
|
|
});
|
|
// Verify connections are not closed yet
|
|
expect(agentConnection.signal.aborted).toBe(false);
|
|
expect(clientConnection.signal.aborted).toBe(false);
|
|
expect(closeLog).toHaveLength(0);
|
|
// Close the streams by closing the writable ends
|
|
await clientToAgent.writable.close();
|
|
await agentToClient.writable.close();
|
|
// Wait for closed promises to resolve
|
|
await agentConnection.closed;
|
|
await clientConnection.closed;
|
|
// Verify connections are now closed
|
|
expect(agentConnection.signal.aborted).toBe(true);
|
|
expect(clientConnection.signal.aborted).toBe(true);
|
|
expect(closeLog).toContain("agent connection closed (signal)");
|
|
expect(closeLog).toContain("client connection closed (signal)");
|
|
});
|
|
it("supports removing signal event listeners", async () => {
|
|
const closeLog = [];
|
|
// Create simple client and agent
|
|
class TestClient {
|
|
async writeTextFile(_) {
|
|
return {};
|
|
}
|
|
async readTextFile(_) {
|
|
return { content: "test" };
|
|
}
|
|
async requestPermission(_) {
|
|
return {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "allow",
|
|
},
|
|
};
|
|
}
|
|
async sessionUpdate(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
class TestAgent {
|
|
async initialize(_) {
|
|
return {
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
agentCapabilities: { loadSession: false },
|
|
};
|
|
}
|
|
async newSession(_) {
|
|
return { sessionId: "test-session" };
|
|
}
|
|
async authenticate(_) {
|
|
// no-op
|
|
}
|
|
async prompt(_) {
|
|
return { stopReason: "end_turn" };
|
|
}
|
|
async cancel(_) {
|
|
// no-op
|
|
}
|
|
}
|
|
// Set up connections
|
|
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Register and then remove a listener
|
|
const listener = () => {
|
|
closeLog.push("this should not be called");
|
|
};
|
|
agentConnection.signal.addEventListener("abort", listener);
|
|
agentConnection.signal.removeEventListener("abort", listener);
|
|
// Register another listener that should be called
|
|
agentConnection.signal.addEventListener("abort", () => {
|
|
closeLog.push("agent connection closed");
|
|
});
|
|
// Close the streams
|
|
await clientToAgent.writable.close();
|
|
await agentToClient.writable.close();
|
|
// Wait for closed promise
|
|
await agentConnection.closed;
|
|
// Verify only the non-removed listener was called
|
|
expect(closeLog).toEqual(["agent connection closed"]);
|
|
expect(closeLog).not.toContain("this should not be called");
|
|
});
|
|
it("handles methods returning response objects with _meta or void", async () => {
|
|
// Create client that returns both response objects and void
|
|
class TestClient {
|
|
async writeTextFile(_params) {
|
|
// Return response object with _meta
|
|
return {
|
|
_meta: {
|
|
timestamp: new Date().toISOString(),
|
|
version: "1.0.0",
|
|
},
|
|
};
|
|
}
|
|
async readTextFile(_params) {
|
|
return {
|
|
content: "test content",
|
|
_meta: {
|
|
encoding: "utf-8",
|
|
},
|
|
};
|
|
}
|
|
async requestPermission(_params) {
|
|
return {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "allow",
|
|
},
|
|
_meta: {
|
|
userId: "test-user",
|
|
},
|
|
};
|
|
}
|
|
async sessionUpdate(_params) {
|
|
// Returns void
|
|
}
|
|
}
|
|
// Create agent that returns both response objects and void
|
|
class TestAgent {
|
|
async initialize(params) {
|
|
return {
|
|
protocolVersion: params.protocolVersion,
|
|
agentCapabilities: { loadSession: true },
|
|
_meta: {
|
|
agentVersion: "2.0.0",
|
|
},
|
|
};
|
|
}
|
|
async newSession(_params) {
|
|
return {
|
|
sessionId: "test-session",
|
|
_meta: {
|
|
sessionType: "ephemeral",
|
|
},
|
|
};
|
|
}
|
|
async loadSession(_params) {
|
|
// Test returning minimal response
|
|
return {};
|
|
}
|
|
async authenticate(params) {
|
|
if (params.methodId === "none") {
|
|
// Test returning void
|
|
return;
|
|
}
|
|
// Test returning response with _meta
|
|
return {
|
|
_meta: {
|
|
authenticated: true,
|
|
method: params.methodId,
|
|
},
|
|
};
|
|
}
|
|
async prompt(_params) {
|
|
return { stopReason: "end_turn" };
|
|
}
|
|
async cancel(_params) {
|
|
// Returns void
|
|
}
|
|
}
|
|
// Set up connections
|
|
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
// Test writeTextFile returns response with _meta
|
|
const writeResponse = await clientConnection.writeTextFile({
|
|
path: "/test.txt",
|
|
content: "test",
|
|
sessionId: "test-session",
|
|
});
|
|
expect(writeResponse).toEqual({
|
|
_meta: {
|
|
timestamp: expect.any(String),
|
|
version: "1.0.0",
|
|
},
|
|
});
|
|
// Test readTextFile returns response with content and _meta
|
|
const readResponse = await clientConnection.readTextFile({
|
|
path: "/test.txt",
|
|
sessionId: "test-session",
|
|
});
|
|
expect(readResponse.content).toBe("test content");
|
|
expect(readResponse._meta).toEqual({
|
|
encoding: "utf-8",
|
|
});
|
|
// Test initialize with _meta
|
|
const initResponse = await agentConnection.initialize({
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
clientCapabilities: {},
|
|
});
|
|
expect(initResponse._meta).toEqual({
|
|
agentVersion: "2.0.0",
|
|
});
|
|
// Test authenticate returning void
|
|
const authResponseVoid = await agentConnection.authenticate({
|
|
methodId: "none",
|
|
});
|
|
expect(authResponseVoid).toEqual({});
|
|
// Test authenticate returning response with _meta
|
|
const authResponse = await agentConnection.authenticate({
|
|
methodId: "oauth",
|
|
});
|
|
expect(authResponse).toEqual({
|
|
_meta: {
|
|
authenticated: true,
|
|
method: "oauth",
|
|
},
|
|
});
|
|
// Test newSession with _meta
|
|
const sessionResponse = await agentConnection.newSession({
|
|
cwd: "/test",
|
|
mcpServers: [],
|
|
});
|
|
expect(sessionResponse._meta).toEqual({
|
|
sessionType: "ephemeral",
|
|
});
|
|
// Test loadSession returning minimal response
|
|
const loadResponse = await agentConnection.loadSession({
|
|
sessionId: "test-session",
|
|
mcpServers: [],
|
|
cwd: "/test",
|
|
});
|
|
expect(loadResponse).toEqual({});
|
|
});
|
|
});
|
|
//# sourceMappingURL=acp.test.js.map
|