acp-websocket-transport 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/examples/client.d.ts +9 -0
  2. package/dist/examples/client.d.ts.map +1 -0
  3. package/dist/examples/client.js +51 -0
  4. package/dist/examples/client.js.map +1 -0
  5. package/dist/examples/echo-agent.d.ts +14 -0
  6. package/dist/examples/echo-agent.d.ts.map +1 -0
  7. package/dist/examples/echo-agent.js +67 -0
  8. package/dist/examples/echo-agent.js.map +1 -0
  9. package/dist/examples/server.d.ts +11 -0
  10. package/dist/examples/server.d.ts.map +1 -0
  11. package/dist/examples/server.js +36 -0
  12. package/dist/examples/server.js.map +1 -0
  13. package/dist/src/auth.d.ts +110 -0
  14. package/dist/src/auth.d.ts.map +1 -0
  15. package/dist/src/auth.js +135 -0
  16. package/dist/src/auth.js.map +1 -0
  17. package/dist/src/client.d.ts +109 -0
  18. package/dist/src/client.d.ts.map +1 -0
  19. package/dist/src/client.js +125 -0
  20. package/dist/src/client.js.map +1 -0
  21. package/dist/src/index.d.ts +58 -0
  22. package/dist/src/index.d.ts.map +1 -0
  23. package/dist/src/index.js +59 -0
  24. package/dist/src/index.js.map +1 -0
  25. package/dist/src/server.d.ts +117 -0
  26. package/dist/src/server.d.ts.map +1 -0
  27. package/dist/src/server.js +203 -0
  28. package/dist/src/server.js.map +1 -0
  29. package/dist/src/transport.d.ts +24 -0
  30. package/dist/src/transport.d.ts.map +1 -0
  31. package/dist/src/transport.js +120 -0
  32. package/dist/src/transport.js.map +1 -0
  33. package/dist/tests/auth.test.d.ts +2 -0
  34. package/dist/tests/auth.test.d.ts.map +1 -0
  35. package/dist/tests/auth.test.js +115 -0
  36. package/dist/tests/auth.test.js.map +1 -0
  37. package/dist/tests/client.test.d.ts +2 -0
  38. package/dist/tests/client.test.d.ts.map +1 -0
  39. package/dist/tests/client.test.js +121 -0
  40. package/dist/tests/client.test.js.map +1 -0
  41. package/dist/tests/e2e.test.d.ts +8 -0
  42. package/dist/tests/e2e.test.d.ts.map +1 -0
  43. package/dist/tests/e2e.test.js +130 -0
  44. package/dist/tests/e2e.test.js.map +1 -0
  45. package/dist/tests/server.test.d.ts +2 -0
  46. package/dist/tests/server.test.d.ts.map +1 -0
  47. package/dist/tests/server.test.js +173 -0
  48. package/dist/tests/server.test.js.map +1 -0
  49. package/dist/tests/transport.test.d.ts +2 -0
  50. package/dist/tests/transport.test.d.ts.map +1 -0
  51. package/dist/tests/transport.test.js +137 -0
  52. package/dist/tests/transport.test.js.map +1 -0
  53. package/package.json +41 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * WebSocket transport for ACP (Agent Client Protocol).
3
+ *
4
+ * Converts a `ws` WebSocket into the SDK's {@link Stream} interface,
5
+ * enabling ACP connections over WebSocket instead of stdio.
6
+ *
7
+ * Each WebSocket text frame carries exactly one JSON-encoded AnyMessage.
8
+ * Binary frames are silently ignored (ACP only uses text frames).
9
+ */
10
+ /**
11
+ * Safely converts ws `RawData` (Buffer | ArrayBuffer | Buffer[]) to a UTF-8 string.
12
+ * Handles all three variants that `ws` may produce for text frames.
13
+ */
14
+ function rawDataToString(data) {
15
+ if (Array.isArray(data)) {
16
+ // Buffer[] — concatenate all chunks first
17
+ return Buffer.concat(data).toString("utf8");
18
+ }
19
+ if (data instanceof ArrayBuffer) {
20
+ return Buffer.from(data).toString("utf8");
21
+ }
22
+ // Buffer (most common case for text frames)
23
+ return data.toString("utf8");
24
+ }
25
+ /**
26
+ * Creates an ACP {@link Stream} from an open (or opening) `ws` WebSocket.
27
+ *
28
+ * Stream lifetime is tied to the WebSocket:
29
+ * - WS `close` / `error` → readable stream closes / errors
30
+ * - WritableStream `close()` → `ws.close(1000, ...)`
31
+ * - WritableStream `abort()` → `ws.terminate()` (hard close)
32
+ *
33
+ * @param ws - An open or opening `ws` WebSocket instance
34
+ * @returns Bidirectional ACP message stream
35
+ */
36
+ export function wsStream(ws) {
37
+ // ─── Readable side ───────────────────────────────────────────────────
38
+ // Buffers incoming frames until the consumer pulls them.
39
+ // ReadableStream's built-in queue provides natural backpressure.
40
+ let readableController;
41
+ const readable = new ReadableStream({
42
+ start(controller) {
43
+ readableController = controller;
44
+ ws.on("message", (data, isBinary) => {
45
+ // ACP uses only text frames; drop binary frames silently
46
+ if (isBinary)
47
+ return;
48
+ const text = rawDataToString(data);
49
+ let message;
50
+ try {
51
+ message = JSON.parse(text);
52
+ }
53
+ catch {
54
+ // Malformed JSON frame — close the readable with an error
55
+ // so the connection layer can surface it to the caller.
56
+ try {
57
+ controller.error(new SyntaxError(`Invalid JSON in WebSocket frame: ${text.slice(0, 200)}`));
58
+ }
59
+ catch {
60
+ /* controller already closed/errored */
61
+ }
62
+ ws.close(1003, "Invalid JSON");
63
+ return;
64
+ }
65
+ try {
66
+ controller.enqueue(message);
67
+ }
68
+ catch {
69
+ /* controller already closed */
70
+ }
71
+ });
72
+ ws.on("close", () => {
73
+ try {
74
+ controller.close();
75
+ }
76
+ catch {
77
+ /* already closed */
78
+ }
79
+ });
80
+ ws.on("error", (err) => {
81
+ try {
82
+ controller.error(err);
83
+ }
84
+ catch {
85
+ /* already errored */
86
+ }
87
+ });
88
+ },
89
+ cancel() {
90
+ // Consumer cancelled the readable — close the WebSocket cleanly
91
+ ws.close(1000, "Stream cancelled");
92
+ },
93
+ });
94
+ // ─── Writable side ───────────────────────────────────────────────────
95
+ // Serialises each AnyMessage to a JSON text frame and calls ws.send().
96
+ // The Promise-based send callback provides backpressure to the
97
+ // WritableStream's internal queue.
98
+ const writable = new WritableStream({
99
+ write(message) {
100
+ return new Promise((resolve, reject) => {
101
+ const payload = JSON.stringify(message);
102
+ ws.send(payload, (err) => {
103
+ if (err != null)
104
+ reject(err);
105
+ else
106
+ resolve();
107
+ });
108
+ });
109
+ },
110
+ close() {
111
+ ws.close(1000, "Stream closed");
112
+ },
113
+ abort(_reason) {
114
+ // Hard close — destroy the TCP socket immediately without a close frame
115
+ ws.terminate();
116
+ },
117
+ });
118
+ return { readable, writable };
119
+ }
120
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../tests/auth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,115 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { bearerAuth, basicAuth, apiKeyAuth, anyAuth, } from "../src/auth.js";
3
+ /** Creates a minimal mock VerifyClientInfo with the given headers. */
4
+ function mockInfo(headers) {
5
+ // IncomingMessage headers are lowercase by the Node.js HTTP parser
6
+ const lowerHeaders = {};
7
+ for (const [k, v] of Object.entries(headers)) {
8
+ lowerHeaders[k.toLowerCase()] = v;
9
+ }
10
+ return {
11
+ req: { headers: lowerHeaders },
12
+ origin: "",
13
+ secure: false,
14
+ };
15
+ }
16
+ // ─── bearerAuth ───────────────────────────────────────────────────────────────
17
+ describe("bearerAuth", () => {
18
+ const verify = bearerAuth(["token-alice", "token-bob"]);
19
+ it("accepts a valid bearer token", () => {
20
+ expect(verify(mockInfo({ authorization: "Bearer token-alice" }))).toBe(true);
21
+ expect(verify(mockInfo({ authorization: "Bearer token-bob" }))).toBe(true);
22
+ });
23
+ it("rejects an unknown token", () => {
24
+ expect(verify(mockInfo({ authorization: "Bearer unknown" }))).toBe(false);
25
+ });
26
+ it("is case-sensitive for the token value", () => {
27
+ expect(verify(mockInfo({ authorization: "Bearer TOKEN-ALICE" }))).toBe(false);
28
+ });
29
+ it("is case-insensitive for the 'Bearer' scheme keyword", () => {
30
+ expect(verify(mockInfo({ authorization: "bearer token-alice" }))).toBe(true);
31
+ expect(verify(mockInfo({ authorization: "BEARER token-alice" }))).toBe(true);
32
+ });
33
+ it("rejects a non-bearer scheme", () => {
34
+ expect(verify(mockInfo({ authorization: "Basic dG9rZW4tYWxpY2U=" }))).toBe(false);
35
+ });
36
+ it("rejects a missing Authorization header", () => {
37
+ expect(verify(mockInfo({}))).toBe(false);
38
+ });
39
+ it("rejects a malformed Authorization header with no space", () => {
40
+ expect(verify(mockInfo({ authorization: "BearerNoSpace" }))).toBe(false);
41
+ });
42
+ });
43
+ // ─── basicAuth ────────────────────────────────────────────────────────────────
44
+ describe("basicAuth", () => {
45
+ const verify = basicAuth({ alice: "pass1", bob: "pass2" });
46
+ it("accepts valid credentials", () => {
47
+ const encoded = Buffer.from("alice:pass1").toString("base64");
48
+ expect(verify(mockInfo({ authorization: `Basic ${encoded}` }))).toBe(true);
49
+ });
50
+ it("rejects wrong password", () => {
51
+ const encoded = Buffer.from("alice:wrong").toString("base64");
52
+ expect(verify(mockInfo({ authorization: `Basic ${encoded}` }))).toBe(false);
53
+ });
54
+ it("rejects unknown user", () => {
55
+ const encoded = Buffer.from("charlie:pass1").toString("base64");
56
+ expect(verify(mockInfo({ authorization: `Basic ${encoded}` }))).toBe(false);
57
+ });
58
+ it("is case-insensitive for the 'Basic' scheme keyword", () => {
59
+ const encoded = Buffer.from("alice:pass1").toString("base64");
60
+ expect(verify(mockInfo({ authorization: `basic ${encoded}` }))).toBe(true);
61
+ });
62
+ it("handles passwords containing colons correctly", () => {
63
+ const verifyColonPass = basicAuth({ user: "pass:with:colons" });
64
+ const encoded = Buffer.from("user:pass:with:colons").toString("base64");
65
+ expect(verifyColonPass(mockInfo({ authorization: `Basic ${encoded}` }))).toBe(true);
66
+ });
67
+ it("rejects missing Authorization header", () => {
68
+ expect(verify(mockInfo({}))).toBe(false);
69
+ });
70
+ it("rejects a non-basic scheme", () => {
71
+ expect(verify(mockInfo({ authorization: "Bearer alice:pass1" }))).toBe(false);
72
+ });
73
+ });
74
+ // ─── apiKeyAuth ───────────────────────────────────────────────────────────────
75
+ describe("apiKeyAuth", () => {
76
+ const verify = apiKeyAuth("x-api-key", ["key1", "key2"]);
77
+ it("accepts a valid API key", () => {
78
+ expect(verify(mockInfo({ "x-api-key": "key1" }))).toBe(true);
79
+ expect(verify(mockInfo({ "x-api-key": "key2" }))).toBe(true);
80
+ });
81
+ it("rejects an unknown key", () => {
82
+ expect(verify(mockInfo({ "x-api-key": "key3" }))).toBe(false);
83
+ });
84
+ it("is case-insensitive for the header name", () => {
85
+ // Node.js lowercases header names, but the factory itself normalises too
86
+ const verifyUpper = apiKeyAuth("X-API-Key", ["key1"]);
87
+ expect(verifyUpper(mockInfo({ "x-api-key": "key1" }))).toBe(true);
88
+ });
89
+ it("rejects a missing header", () => {
90
+ expect(verify(mockInfo({}))).toBe(false);
91
+ });
92
+ it("rejects an empty key", () => {
93
+ expect(verify(mockInfo({ "x-api-key": "" }))).toBe(false);
94
+ });
95
+ });
96
+ // ─── anyAuth ──────────────────────────────────────────────────────────────────
97
+ describe("anyAuth", () => {
98
+ const bearer = bearerAuth(["bearer-tok"]);
99
+ const apiKey = apiKeyAuth("x-api-key", ["api-key-tok"]);
100
+ const combined = anyAuth(bearer, apiKey);
101
+ it("accepts if the first verifier passes", async () => {
102
+ expect(await combined(mockInfo({ authorization: "Bearer bearer-tok" }))).toBe(true);
103
+ });
104
+ it("accepts if the second verifier passes", async () => {
105
+ expect(await combined(mockInfo({ "x-api-key": "api-key-tok" }))).toBe(true);
106
+ });
107
+ it("rejects if no verifier passes", async () => {
108
+ expect(await combined(mockInfo({}))).toBe(false);
109
+ });
110
+ it("accepts with an async verifier", async () => {
111
+ const asyncVerifier = anyAuth(async (_info) => true);
112
+ expect(await asyncVerifier(mockInfo({}))).toBe(true);
113
+ });
114
+ });
115
+ //# sourceMappingURL=auth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../tests/auth.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../../tests/client.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,121 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { WebSocketServer } from "ws";
3
+ import { createClientConnection, withBearerToken, withBasicAuth, withApiKey, } from "../src/client.js";
4
+ /** Creates a minimal echo WebSocket server for testing. */
5
+ async function createEchoServer() {
6
+ const connections = [];
7
+ const wss = new WebSocketServer({ port: 0 });
8
+ wss.on("connection", (ws) => {
9
+ connections.push(ws);
10
+ // Echo all messages back (simulates an ACP agent stream)
11
+ ws.on("message", (data) => ws.send(data));
12
+ });
13
+ await new Promise((resolve) => wss.once("listening", resolve));
14
+ const port = wss.address().port;
15
+ return { wss, port, connections };
16
+ }
17
+ async function closeServer(wss) {
18
+ return new Promise((resolve) => {
19
+ wss.clients.forEach((ws) => ws.terminate());
20
+ wss.close(() => resolve());
21
+ setTimeout(resolve, 500);
22
+ });
23
+ }
24
+ describe("createClientConnection", () => {
25
+ let wss;
26
+ let port;
27
+ beforeEach(async () => {
28
+ ({ wss, port } = await createEchoServer());
29
+ });
30
+ afterEach(async () => {
31
+ await closeServer(wss);
32
+ });
33
+ it("opens a WebSocket and returns a ClientConnection", async () => {
34
+ const { connection, stream, close } = await createClientConnection(`ws://localhost:${port}`);
35
+ expect(connection).toBeDefined();
36
+ expect(stream).toBeDefined();
37
+ expect(typeof close).toBe("function");
38
+ close();
39
+ });
40
+ it("resolves the connection once the WebSocket is open", async () => {
41
+ const { close } = await createClientConnection(`ws://localhost:${port}`);
42
+ // If we got here without an error, the connection is open
43
+ expect(true).toBe(true);
44
+ close();
45
+ });
46
+ it("rejects if the server is not reachable", async () => {
47
+ await expect(createClientConnection("ws://localhost:1", { connectTimeout: 500 })).rejects.toThrow();
48
+ });
49
+ it("rejects if the connection times out", async () => {
50
+ // Point to a host that never responds to simulate a timeout
51
+ // Use an unrouteable address (TEST-NET-1) to force a timeout
52
+ await expect(createClientConnection("ws://192.0.2.1:9999", { connectTimeout: 200 })).rejects.toThrow(/timed out/i);
53
+ });
54
+ it("calls close() to shut down the connection", async () => {
55
+ const { close } = await createClientConnection(`ws://localhost:${port}`);
56
+ // Should not throw
57
+ expect(() => close()).not.toThrow();
58
+ });
59
+ });
60
+ // ─── Auth helpers ─────────────────────────────────────────────────────────────
61
+ describe("withBearerToken", () => {
62
+ it("returns wsOptions with Authorization: Bearer header", () => {
63
+ const opts = withBearerToken("my-token");
64
+ expect(opts.wsOptions?.headers).toMatchObject({
65
+ Authorization: "Bearer my-token",
66
+ });
67
+ });
68
+ });
69
+ describe("withBasicAuth", () => {
70
+ it("returns wsOptions with Authorization: Basic header", () => {
71
+ const opts = withBasicAuth("user", "pass");
72
+ const expected = Buffer.from("user:pass").toString("base64");
73
+ expect(opts.wsOptions?.headers).toMatchObject({
74
+ Authorization: `Basic ${expected}`,
75
+ });
76
+ });
77
+ it("correctly encodes credentials with special characters", () => {
78
+ const opts = withBasicAuth("user@example.com", "p@$$word:123");
79
+ const expected = Buffer.from("user@example.com:p@$$word:123").toString("base64");
80
+ expect(opts.wsOptions?.headers).toMatchObject({
81
+ Authorization: `Basic ${expected}`,
82
+ });
83
+ });
84
+ });
85
+ describe("withApiKey", () => {
86
+ it("returns wsOptions with the given header", () => {
87
+ const opts = withApiKey("x-api-key", "abc123");
88
+ expect(opts.wsOptions?.headers).toMatchObject({
89
+ "x-api-key": "abc123",
90
+ });
91
+ });
92
+ it("preserves the header name casing provided by the caller", () => {
93
+ const opts = withApiKey("X-Custom-Auth", "token");
94
+ expect(opts.wsOptions?.headers).toMatchObject({
95
+ "X-Custom-Auth": "token",
96
+ });
97
+ });
98
+ });
99
+ // ─── auth headers reach the server ───────────────────────────────────────────
100
+ describe("auth headers in upgrade request", () => {
101
+ it("sends the Authorization header during the WebSocket upgrade", async () => {
102
+ const receivedHeaders = [];
103
+ const wssAuth = new WebSocketServer({ port: 0 });
104
+ wssAuth.on("connection", (_ws, req) => {
105
+ receivedHeaders.push(req.headers);
106
+ });
107
+ await new Promise((resolve) => wssAuth.once("listening", resolve));
108
+ const authPort = wssAuth.address().port;
109
+ try {
110
+ const { close } = await createClientConnection(`ws://localhost:${authPort}`, withBearerToken("test-bearer-token"));
111
+ // Give the server a moment to record the headers
112
+ await new Promise((r) => setTimeout(r, 50));
113
+ expect(receivedHeaders[0]?.["authorization"]).toBe("Bearer test-bearer-token");
114
+ close();
115
+ }
116
+ finally {
117
+ await closeServer(wssAuth);
118
+ }
119
+ });
120
+ });
121
+ //# sourceMappingURL=client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../tests/client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * End-to-end tests: Client library → WebSocketACPServer → echo-agent process.
3
+ *
4
+ * These tests exercise the full stack:
5
+ * createClientConnection → WebSocketACPServer → echo-agent (stdio)
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=e2e.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2e.test.d.ts","sourceRoot":"","sources":["../../tests/e2e.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * End-to-end tests: Client library → WebSocketACPServer → echo-agent process.
3
+ *
4
+ * These tests exercise the full stack:
5
+ * createClientConnection → WebSocketACPServer → echo-agent (stdio)
6
+ */
7
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
8
+ import { WebSocketACPServer } from "../src/server.js";
9
+ import { createClientConnection, withBearerToken } from "../src/client.js";
10
+ import { bearerAuth } from "../src/auth.js";
11
+ const ECHO_AGENT = new URL("../dist/examples/echo-agent.js", import.meta.url)
12
+ .pathname;
13
+ // ─── No-auth E2E ──────────────────────────────────────────────────────────────
14
+ describe("E2E (no auth)", () => {
15
+ let server;
16
+ let wsUrl;
17
+ beforeAll(async () => {
18
+ server = new WebSocketACPServer({
19
+ port: 0,
20
+ command: "node",
21
+ args: [ECHO_AGENT],
22
+ });
23
+ await server.listening();
24
+ const { port } = server.address;
25
+ wsUrl = `ws://localhost:${port}`;
26
+ });
27
+ afterAll(async () => {
28
+ await server.close().catch(() => { });
29
+ });
30
+ it("completes initialize → session/new → prompt flow", async () => {
31
+ const updates = [];
32
+ const { connection, close } = await createClientConnection(wsUrl, {
33
+ toClient: (_agent) => ({
34
+ sessionUpdate: async (params) => {
35
+ const { update } = params;
36
+ if ("content" in update && update.content.type === "text") {
37
+ updates.push(update.content.text);
38
+ }
39
+ },
40
+ requestPermission: async () => ({ outcome: "allow_once" }),
41
+ }),
42
+ });
43
+ // 1. Initialize
44
+ const init = await connection.initialize({
45
+ protocolVersion: 0,
46
+ clientInfo: { name: "e2e-test", version: "0.0.1" },
47
+ clientCapabilities: {},
48
+ });
49
+ expect(init.agentInfo?.name).toBe("echo-agent");
50
+ // 2. Create session
51
+ const session = await connection.newSession({
52
+ cwd: "/tmp",
53
+ mcpServers: [],
54
+ });
55
+ expect(typeof session.sessionId).toBe("string");
56
+ expect(session.sessionId.length).toBeGreaterThan(0);
57
+ // 3. Send prompt
58
+ const result = await connection.prompt({
59
+ sessionId: session.sessionId,
60
+ prompt: [{ type: "text", text: "Hello, world!" }],
61
+ });
62
+ expect(result.stopReason).toBe("end_turn");
63
+ // 4. The echo agent sends a session/update notification before responding
64
+ expect(updates.some((t) => t.includes("Echo: Hello, world!"))).toBe(true);
65
+ close();
66
+ await new Promise((r) => setTimeout(r, 200));
67
+ });
68
+ it("handles multiple sessions on the same server", async () => {
69
+ // Open two independent client connections (each gets its own agent process)
70
+ const { connection: c1, close: close1 } = await createClientConnection(wsUrl);
71
+ const { connection: c2, close: close2 } = await createClientConnection(wsUrl);
72
+ const [init1, init2] = await Promise.all([
73
+ c1.initialize({ protocolVersion: 0, clientCapabilities: {} }),
74
+ c2.initialize({ protocolVersion: 0, clientCapabilities: {} }),
75
+ ]);
76
+ expect(init1.agentInfo?.name).toBe("echo-agent");
77
+ expect(init2.agentInfo?.name).toBe("echo-agent");
78
+ const [sess1, sess2] = await Promise.all([
79
+ c1.newSession({ cwd: "/tmp", mcpServers: [] }),
80
+ c2.newSession({ cwd: "/tmp", mcpServers: [] }),
81
+ ]);
82
+ // Each connection has its own independent session
83
+ expect(sess1.sessionId).not.toBe(sess2.sessionId);
84
+ close1();
85
+ close2();
86
+ await new Promise((r) => setTimeout(r, 200));
87
+ });
88
+ });
89
+ // ─── Bearer-auth E2E ──────────────────────────────────────────────────────────
90
+ describe("E2E (bearer auth)", () => {
91
+ const VALID_TOKEN = "e2e-test-token";
92
+ let server;
93
+ let wsUrl;
94
+ beforeAll(async () => {
95
+ server = new WebSocketACPServer({
96
+ port: 0,
97
+ command: "node",
98
+ args: [ECHO_AGENT],
99
+ verifyClient: bearerAuth([VALID_TOKEN]),
100
+ });
101
+ await server.listening();
102
+ const { port } = server.address;
103
+ wsUrl = `ws://localhost:${port}`;
104
+ });
105
+ afterAll(async () => {
106
+ await server.close().catch(() => { });
107
+ });
108
+ it("connects successfully with the correct token", async () => {
109
+ const { connection, close } = await createClientConnection(wsUrl, {
110
+ ...withBearerToken(VALID_TOKEN),
111
+ });
112
+ const init = await connection.initialize({
113
+ protocolVersion: 0,
114
+ clientCapabilities: {},
115
+ });
116
+ expect(init.agentInfo?.name).toBe("echo-agent");
117
+ close();
118
+ await new Promise((r) => setTimeout(r, 200));
119
+ });
120
+ it("rejects connection with a wrong token", async () => {
121
+ await expect(createClientConnection(wsUrl, {
122
+ ...withBearerToken("wrong-token"),
123
+ connectTimeout: 3000,
124
+ })).rejects.toThrow();
125
+ });
126
+ it("rejects connection with no token", async () => {
127
+ await expect(createClientConnection(wsUrl, { connectTimeout: 3000 })).rejects.toThrow();
128
+ });
129
+ });
130
+ //# sourceMappingURL=e2e.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2e.test.js","sourceRoot":"","sources":["../../tests/e2e.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.test.d.ts","sourceRoot":"","sources":["../../tests/server.test.ts"],"names":[],"mappings":""}