@automatalabs/acp-server 0.1.0 → 0.2.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.
- package/README.md +282 -10
- package/dist/cli.js +88 -2
- package/dist/http-server.d.ts +41 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +200 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
# `@automatalabs/acp-server`
|
|
2
2
|
|
|
3
3
|
Connection-pinned ACP V1 proxy for the backends configured through
|
|
4
|
-
`@automatalabs/acp-agents`. The
|
|
5
|
-
|
|
4
|
+
`@automatalabs/acp-agents`. The `agentprism-acp-server` executable defaults to stdio and can also
|
|
5
|
+
listen for Streamable HTTP and WebSocket clients on one endpoint.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx @automatalabs/acp-server
|
|
8
|
+
npx @automatalabs/acp-server # stdio
|
|
9
|
+
npx @automatalabs/acp-server --http # http://127.0.0.1:7331/acp + ws://…
|
|
10
|
+
npx @automatalabs/acp-server --http --host 127.0.0.1 --port 8080 --path /acp
|
|
9
11
|
agentprism-acp-server --version
|
|
10
12
|
```
|
|
11
13
|
|
|
14
|
+
`--host`, `--port`, and `--path` apply only with `--http`; their defaults are `127.0.0.1`, `7331`,
|
|
15
|
+
and `/acp`. Each accepted HTTP or WebSocket connection has an independent discovery/backend mode
|
|
16
|
+
and pinned downstream connection.
|
|
17
|
+
|
|
12
18
|
The server accepts only clients that negotiate AgentPrism router extension version 1 under
|
|
13
19
|
`clientCapabilities._meta["@automatalabs/agentprism"].acpRouter`.
|
|
14
20
|
|
|
@@ -72,17 +78,283 @@ proxy stores no session-routing table.
|
|
|
72
78
|
Built-in backend IDs are `claude`, `codex`, `opencode`, and `pi`. Custom backends use the existing
|
|
73
79
|
`AGENTPRISM_BACKENDS` registry.
|
|
74
80
|
|
|
81
|
+
## Using the official TypeScript client SDK
|
|
82
|
+
|
|
83
|
+
Install the router, the official ACP TypeScript SDK, and a TypeScript runner:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pnpm add @automatalabs/acp-server @agentclientprotocol/sdk ws
|
|
87
|
+
pnpm add --save-dev tsx @types/node @types/ws
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The following examples are consecutive sections of one `router-client.ts` file. They follow the
|
|
91
|
+
official SDK's [current client example](https://github.com/agentclientprotocol/typescript-sdk/blob/main/src/examples/client.ts):
|
|
92
|
+
`client(...).onRequest(...).connectWith(...)`, `ctx.request(...)`, and the
|
|
93
|
+
`buildSession(...).withSession(...)` active-session API. The stdio helper starts a fresh
|
|
94
|
+
`agentprism-acp-server` process for each connection. The example permission handler rejects
|
|
95
|
+
safely; replace it with the application's actual user-confirmation UI.
|
|
96
|
+
|
|
97
|
+
### Shared client and stdio setup
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { spawn } from "node:child_process";
|
|
101
|
+
import { Readable, Writable } from "node:stream";
|
|
102
|
+
import * as acp from "@agentclientprotocol/sdk";
|
|
103
|
+
import { createHttpStream } from "@agentclientprotocol/sdk/experimental/http-client";
|
|
104
|
+
import { createWebSocketStream } from "@agentclientprotocol/sdk/experimental/ws-client";
|
|
105
|
+
import { WebSocket } from "ws";
|
|
106
|
+
|
|
107
|
+
const ROUTER_NAMESPACE = "@automatalabs/agentprism";
|
|
108
|
+
const PROBE_METHOD = "_automatalabs/agentprism/backends/probe";
|
|
109
|
+
|
|
110
|
+
type ProbeBackendsParams = {
|
|
111
|
+
cwd: string;
|
|
112
|
+
additionalDirectories?: string[];
|
|
113
|
+
mcpServers: acp.McpServer[];
|
|
114
|
+
_meta?: Record<string, unknown> | null;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
type BackendProbe =
|
|
118
|
+
| {
|
|
119
|
+
id: string;
|
|
120
|
+
name: string;
|
|
121
|
+
available: true;
|
|
122
|
+
agentInfo?: acp.Implementation | null;
|
|
123
|
+
agentCapabilities?: acp.AgentCapabilities;
|
|
124
|
+
modes?: acp.SessionModeState | null;
|
|
125
|
+
configOptions?: acp.SessionConfigOption[] | null;
|
|
126
|
+
initializeMeta?: Record<string, unknown> | null;
|
|
127
|
+
sessionMeta?: Record<string, unknown> | null;
|
|
128
|
+
}
|
|
129
|
+
| {
|
|
130
|
+
id: string;
|
|
131
|
+
name: string;
|
|
132
|
+
available: false;
|
|
133
|
+
stage: "initialize" | "session/new";
|
|
134
|
+
error: string;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
type ProbeBackendsResult = { backends: BackendProbe[] };
|
|
138
|
+
|
|
139
|
+
class RouterClient implements acp.Client {
|
|
140
|
+
async requestPermission(
|
|
141
|
+
params: acp.RequestPermissionRequest,
|
|
142
|
+
): Promise<acp.RequestPermissionResponse> {
|
|
143
|
+
const reject = params.options.find(
|
|
144
|
+
(option) => option.kind === "reject_once" || option.kind === "reject_always",
|
|
145
|
+
);
|
|
146
|
+
return reject
|
|
147
|
+
? { outcome: { outcome: "selected", optionId: reject.optionId } }
|
|
148
|
+
: { outcome: { outcome: "cancelled" } };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async sessionUpdate(params: acp.SessionNotification): Promise<void> {
|
|
152
|
+
const update = params.update;
|
|
153
|
+
if (
|
|
154
|
+
update.sessionUpdate === "agent_message_chunk" &&
|
|
155
|
+
update.content.type === "text"
|
|
156
|
+
) {
|
|
157
|
+
process.stdout.write(update.content.text);
|
|
158
|
+
} else {
|
|
159
|
+
console.error(`[${update.sessionUpdate}]`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const client = new RouterClient();
|
|
165
|
+
|
|
166
|
+
function initializeRequest(
|
|
167
|
+
mode: "discovery" | "backend",
|
|
168
|
+
backend?: string,
|
|
169
|
+
): acp.InitializeRequest {
|
|
170
|
+
return {
|
|
171
|
+
protocolVersion: acp.PROTOCOL_VERSION,
|
|
172
|
+
clientInfo: { name: "agentprism-router-example", version: "1.0.0" },
|
|
173
|
+
clientCapabilities: {
|
|
174
|
+
_meta: {
|
|
175
|
+
[ROUTER_NAMESPACE]: {
|
|
176
|
+
acpRouter: { versions: [1] },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
_meta: {
|
|
181
|
+
[ROUTER_NAMESPACE]: {
|
|
182
|
+
acpRouter:
|
|
183
|
+
mode === "discovery"
|
|
184
|
+
? { version: 1, mode }
|
|
185
|
+
: { version: 1, mode, backend },
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function withRouter<T>(
|
|
192
|
+
operation: (ctx: acp.ClientContext) => Promise<T>,
|
|
193
|
+
): Promise<T> {
|
|
194
|
+
const pnpm = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
195
|
+
const child = spawn(pnpm, ["exec", "agentprism-acp-server"], {
|
|
196
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
197
|
+
});
|
|
198
|
+
if (!child.stdin || !child.stdout) throw new Error("router stdio unavailable");
|
|
199
|
+
|
|
200
|
+
const stream = acp.ndJsonStream(
|
|
201
|
+
Writable.toWeb(child.stdin) as WritableStream<Uint8Array>,
|
|
202
|
+
Readable.toWeb(child.stdout) as ReadableStream<Uint8Array>,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
return await connectRouter(stream, operation);
|
|
207
|
+
} finally {
|
|
208
|
+
if (child.exitCode === null && child.signalCode === null) child.kill();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function connectRouter<T>(
|
|
213
|
+
stream: acp.Stream,
|
|
214
|
+
operation: (ctx: acp.ClientContext) => Promise<T>,
|
|
215
|
+
): Promise<T> {
|
|
216
|
+
return acp
|
|
217
|
+
.client({ name: "agentprism-router-example" })
|
|
218
|
+
.onRequest(acp.methods.client.session.requestPermission, (ctx) =>
|
|
219
|
+
client.requestPermission(ctx.params),
|
|
220
|
+
)
|
|
221
|
+
.connectWith(stream, operation);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function withNetworkRouter<T>(
|
|
225
|
+
transport: "http" | "websocket",
|
|
226
|
+
operation: (ctx: acp.ClientContext) => Promise<T>,
|
|
227
|
+
): Promise<T> {
|
|
228
|
+
const stream =
|
|
229
|
+
transport === "http"
|
|
230
|
+
? createHttpStream("http://127.0.0.1:7331/acp")
|
|
231
|
+
: createWebSocketStream("ws://127.0.0.1:7331/acp", { WebSocket });
|
|
232
|
+
try {
|
|
233
|
+
return await connectRouter(stream, operation);
|
|
234
|
+
} finally {
|
|
235
|
+
await stream.writable.close().catch(() => undefined);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function withConfiguredRouter<T>(
|
|
240
|
+
operation: (ctx: acp.ClientContext) => Promise<T>,
|
|
241
|
+
): Promise<T> {
|
|
242
|
+
const transport = process.env.ACP_TRANSPORT ?? "stdio";
|
|
243
|
+
if (transport === "stdio") return withRouter(operation);
|
|
244
|
+
if (transport === "http" || transport === "websocket") {
|
|
245
|
+
return withNetworkRouter(transport, operation);
|
|
246
|
+
}
|
|
247
|
+
throw new Error(`ACP_TRANSPORT must be stdio, http, or websocket; got ${transport}`);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The network adapters are the same official experimental transport APIs used by the SDK's current
|
|
252
|
+
HTTP server, HTTP client, and WebSocket client examples; the messages negotiated through them
|
|
253
|
+
remain ACP V1.
|
|
254
|
+
|
|
255
|
+
### Discover configured backends
|
|
256
|
+
|
|
257
|
+
Discovery uses one initialized connection and the router-owned probe extension. The generic
|
|
258
|
+
`request<Response, Params>()` overload is the official SDK path for custom methods:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
const cwd = process.cwd();
|
|
262
|
+
|
|
263
|
+
const { backends } = await withConfiguredRouter(async (ctx) => {
|
|
264
|
+
await ctx.request(
|
|
265
|
+
acp.methods.agent.initialize,
|
|
266
|
+
initializeRequest("discovery"),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
return ctx.request<ProbeBackendsResult, ProbeBackendsParams>(PROBE_METHOD, {
|
|
270
|
+
cwd,
|
|
271
|
+
mcpServers: [],
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
for (const backend of backends) {
|
|
276
|
+
if (backend.available) {
|
|
277
|
+
console.log(`${backend.id}: available (${backend.agentInfo?.name ?? backend.name})`);
|
|
278
|
+
} else {
|
|
279
|
+
console.log(`${backend.id}: unavailable at ${backend.stage}: ${backend.error}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Pin a backend, create a session, and prompt
|
|
285
|
+
|
|
286
|
+
Backend operation uses a second initialized connection. The selection is repeated in
|
|
287
|
+
`session/new`; the SDK's active-session helper then sends ordinary ACP traffic without additional
|
|
288
|
+
router metadata. `session.sessionId` is the selected backend's native session ID.
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
const selected = process.argv[2] ?? backends.find((backend) => backend.available)?.id;
|
|
292
|
+
if (!selected) throw new Error("No configured backend is available");
|
|
293
|
+
|
|
294
|
+
await withConfiguredRouter(async (ctx) => {
|
|
295
|
+
const initialized = await ctx.request(
|
|
296
|
+
acp.methods.agent.initialize,
|
|
297
|
+
initializeRequest("backend", selected),
|
|
298
|
+
);
|
|
299
|
+
console.log(`Connected to ${selected} using ACP v${initialized.protocolVersion}`);
|
|
300
|
+
|
|
301
|
+
await ctx
|
|
302
|
+
.buildSession({
|
|
303
|
+
cwd,
|
|
304
|
+
mcpServers: [],
|
|
305
|
+
_meta: {
|
|
306
|
+
[ROUTER_NAMESPACE]: {
|
|
307
|
+
acpRouter: { version: 1, backend: selected },
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
})
|
|
311
|
+
.withSession(async (session) => {
|
|
312
|
+
console.log(`Native backend session: ${session.sessionId}`);
|
|
313
|
+
session.prompt("Summarize this repository in three bullets.");
|
|
314
|
+
|
|
315
|
+
for (;;) {
|
|
316
|
+
const message = await session.nextUpdate();
|
|
317
|
+
if (message.kind === "stop") {
|
|
318
|
+
console.log(`\nStop reason: ${message.stopReason}`);
|
|
319
|
+
return message.response;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
await client.sessionUpdate(message.notification);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Run the file over stdio, or start `agentprism-acp-server --http` and select a network transport:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
pnpm exec tsx router-client.ts codex
|
|
332
|
+
ACP_TRANSPORT=http pnpm exec tsx router-client.ts codex
|
|
333
|
+
ACP_TRANSPORT=websocket pnpm exec tsx router-client.ts codex
|
|
334
|
+
```
|
|
335
|
+
|
|
75
336
|
## Library API
|
|
76
337
|
|
|
77
338
|
```ts
|
|
78
|
-
import {
|
|
339
|
+
import {
|
|
340
|
+
listenAcpHttpServer,
|
|
341
|
+
serveAcpServer,
|
|
342
|
+
} from "@automatalabs/acp-server";
|
|
79
343
|
|
|
80
|
-
await serveAcpServer();
|
|
344
|
+
await serveAcpServer(); // one stdio connection
|
|
345
|
+
|
|
346
|
+
const server = await listenAcpHttpServer({
|
|
347
|
+
host: "127.0.0.1",
|
|
348
|
+
port: 7331,
|
|
349
|
+
path: "/acp",
|
|
350
|
+
});
|
|
351
|
+
console.log(server.url, server.webSocketUrl);
|
|
352
|
+
await server.close();
|
|
81
353
|
```
|
|
82
354
|
|
|
83
355
|
Embedding hosts can pass a custom ACP `stream`, custom backend registrations, exact `targets`, a
|
|
84
|
-
version string, and an abort signal through `ServeAcpServerOptions`.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
356
|
+
version string, and an abort signal through `ServeAcpServerOptions`. `ListenAcpHttpServerOptions`
|
|
357
|
+
adds `host`, `port`, `path`, and `maxRequestBodyBytes`; the returned handle exposes the bound URLs,
|
|
358
|
+
a `closed` promise, and idempotent `close()`. The package also exports the default network listener
|
|
359
|
+
constants, router constants, request parsers, response helpers, probe types, and backend-target
|
|
360
|
+
resolver.
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,17 @@ if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
|
5
5
|
process.stdout.write(`${manifest.version}\n`);
|
|
6
6
|
process.exit(0);
|
|
7
7
|
}
|
|
8
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
9
|
+
process.stdout.write(`Usage:
|
|
10
|
+
agentprism-acp-server
|
|
11
|
+
agentprism-acp-server --http [--host <host>] [--port <port>] [--path <path>]
|
|
12
|
+
|
|
13
|
+
With no transport flag, the server speaks ACP over stdio. --http starts both
|
|
14
|
+
Streamable HTTP and WebSocket transports on one endpoint (default:
|
|
15
|
+
http://127.0.0.1:7331/acp and ws://127.0.0.1:7331/acp).
|
|
16
|
+
`);
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
8
19
|
console.log = console.error;
|
|
9
20
|
console.info = console.error;
|
|
10
21
|
console.warn = console.error;
|
|
@@ -16,8 +27,23 @@ const abortController = new AbortController();
|
|
|
16
27
|
process.once("SIGTERM", () => abortController.abort(new Error("SIGTERM")));
|
|
17
28
|
process.once("SIGINT", () => abortController.abort(new Error("SIGINT")));
|
|
18
29
|
try {
|
|
19
|
-
const
|
|
20
|
-
|
|
30
|
+
const options = parseCliOptions(process.argv.slice(2));
|
|
31
|
+
if (options.mode === "stdio") {
|
|
32
|
+
const { serveAcpServer } = await import("./server.js");
|
|
33
|
+
await serveAcpServer({ signal: abortController.signal });
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const { listenAcpHttpServer } = await import("./http-server.js");
|
|
37
|
+
const server = await listenAcpHttpServer({
|
|
38
|
+
host: options.host,
|
|
39
|
+
port: options.port,
|
|
40
|
+
path: options.path,
|
|
41
|
+
signal: abortController.signal,
|
|
42
|
+
});
|
|
43
|
+
console.error(`ACP Streamable HTTP endpoint listening at ${server.url}`);
|
|
44
|
+
console.error(`ACP WebSocket endpoint listening at ${server.webSocketUrl}`);
|
|
45
|
+
await server.closed;
|
|
46
|
+
}
|
|
21
47
|
}
|
|
22
48
|
catch (error) {
|
|
23
49
|
if (!abortController.signal.aborted) {
|
|
@@ -25,4 +51,64 @@ catch (error) {
|
|
|
25
51
|
process.exitCode = 1;
|
|
26
52
|
}
|
|
27
53
|
}
|
|
54
|
+
function parseCliOptions(args) {
|
|
55
|
+
let http = false;
|
|
56
|
+
let host = "127.0.0.1";
|
|
57
|
+
let port = 7331;
|
|
58
|
+
let path = "/acp";
|
|
59
|
+
let networkOption = false;
|
|
60
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
61
|
+
const argument = args[index];
|
|
62
|
+
if (argument === "--http") {
|
|
63
|
+
http = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (argument === "--host" || argument === "--port" || argument === "--path") {
|
|
67
|
+
const value = args[index + 1];
|
|
68
|
+
if (value === undefined || value.startsWith("--")) {
|
|
69
|
+
throw new Error(`${argument} requires a value`);
|
|
70
|
+
}
|
|
71
|
+
networkOption = true;
|
|
72
|
+
index += 1;
|
|
73
|
+
if (argument === "--host")
|
|
74
|
+
host = value;
|
|
75
|
+
if (argument === "--path")
|
|
76
|
+
path = value;
|
|
77
|
+
if (argument === "--port")
|
|
78
|
+
port = parsePort(value);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (argument.startsWith("--host=")) {
|
|
82
|
+
host = argument.slice("--host=".length);
|
|
83
|
+
networkOption = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (argument.startsWith("--port=")) {
|
|
87
|
+
port = parsePort(argument.slice("--port=".length));
|
|
88
|
+
networkOption = true;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (argument.startsWith("--path=")) {
|
|
92
|
+
path = argument.slice("--path=".length);
|
|
93
|
+
networkOption = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`unknown argument ${JSON.stringify(argument)}; run with --help for usage`);
|
|
97
|
+
}
|
|
98
|
+
if (!http) {
|
|
99
|
+
if (networkOption)
|
|
100
|
+
throw new Error("--host, --port, and --path require --http");
|
|
101
|
+
return { mode: "stdio" };
|
|
102
|
+
}
|
|
103
|
+
return { mode: "http", host, port, path };
|
|
104
|
+
}
|
|
105
|
+
function parsePort(value) {
|
|
106
|
+
if (!/^\d+$/.test(value))
|
|
107
|
+
throw new Error("--port must be an integer from 0 through 65535");
|
|
108
|
+
const port = Number(value);
|
|
109
|
+
if (!Number.isSafeInteger(port) || port < 0 || port > 65_535) {
|
|
110
|
+
throw new Error("--port must be an integer from 0 through 65535");
|
|
111
|
+
}
|
|
112
|
+
return port;
|
|
113
|
+
}
|
|
28
114
|
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CustomBackendConfig } from "@automatalabs/acp-agents";
|
|
2
|
+
import { type BackendTarget } from "./backends.js";
|
|
3
|
+
export declare const DEFAULT_ACP_HTTP_HOST: "127.0.0.1";
|
|
4
|
+
export declare const DEFAULT_ACP_HTTP_PORT: 7331;
|
|
5
|
+
export declare const DEFAULT_ACP_HTTP_PATH: "/acp";
|
|
6
|
+
export interface ListenAcpHttpServerOptions {
|
|
7
|
+
/** Interface to bind. Defaults to loopback. */
|
|
8
|
+
host?: string;
|
|
9
|
+
/** TCP port to bind. Pass zero to allocate an ephemeral port. */
|
|
10
|
+
port?: number;
|
|
11
|
+
/** Exact HTTP and WebSocket endpoint path. Defaults to /acp. */
|
|
12
|
+
path?: string;
|
|
13
|
+
/** Maximum JSON request body accepted by the Streamable HTTP adapter. */
|
|
14
|
+
maxRequestBodyBytes?: number;
|
|
15
|
+
/** Programmatic custom backends merged over AGENTPRISM_BACKENDS. */
|
|
16
|
+
backends?: Record<string, CustomBackendConfig>;
|
|
17
|
+
/** Exact backend targets, primarily for embedding and deterministic tests. */
|
|
18
|
+
targets?: readonly BackendTarget[];
|
|
19
|
+
/** Package version advertised on discovery connections. */
|
|
20
|
+
version?: string;
|
|
21
|
+
/** Stops the listener and every active transport connection when aborted. */
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
}
|
|
24
|
+
export interface AcpHttpServerHandle {
|
|
25
|
+
readonly host: string;
|
|
26
|
+
readonly port: number;
|
|
27
|
+
readonly path: string;
|
|
28
|
+
readonly url: string;
|
|
29
|
+
readonly webSocketUrl: string;
|
|
30
|
+
readonly closed: Promise<void>;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Listen for ACP V1 Streamable HTTP and WebSocket connections on one endpoint.
|
|
35
|
+
*
|
|
36
|
+
* Each accepted transport connection receives an independent connection-pinned router. The
|
|
37
|
+
* experimental SDK surface used here is the official ACP HTTP/WebSocket transport implementation;
|
|
38
|
+
* the router protocol carried over it remains ACP V1.
|
|
39
|
+
*/
|
|
40
|
+
export declare function listenAcpHttpServer(options?: ListenAcpHttpServerOptions): Promise<AcpHttpServerHandle>;
|
|
41
|
+
//# sourceMappingURL=http-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAG1E,eAAO,MAAM,qBAAqB,EAAG,WAAoB,CAAC;AAC1D,eAAO,MAAM,qBAAqB,EAAG,IAAa,CAAC;AACnD,eAAO,MAAM,qBAAqB,EAAG,MAAe,CAAC;AAErD,MAAM,WAAW,0BAA0B;IACzC,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC/C,8EAA8E;IAC9E,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,mBAAmB,CAAC,CA8H9B"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { AcpServer } from "@agentclientprotocol/sdk/experimental/server";
|
|
3
|
+
import { createNodeHttpHandler, createNodeWebSocketUpgradeHandler, } from "@agentclientprotocol/sdk/experimental/node";
|
|
4
|
+
import { WebSocketServer } from "ws";
|
|
5
|
+
import { resolveBackendTargets } from "./backends.js";
|
|
6
|
+
import { serveAcpServer } from "./server.js";
|
|
7
|
+
export const DEFAULT_ACP_HTTP_HOST = "127.0.0.1";
|
|
8
|
+
export const DEFAULT_ACP_HTTP_PORT = 7331;
|
|
9
|
+
export const DEFAULT_ACP_HTTP_PATH = "/acp";
|
|
10
|
+
/**
|
|
11
|
+
* Listen for ACP V1 Streamable HTTP and WebSocket connections on one endpoint.
|
|
12
|
+
*
|
|
13
|
+
* Each accepted transport connection receives an independent connection-pinned router. The
|
|
14
|
+
* experimental SDK surface used here is the official ACP HTTP/WebSocket transport implementation;
|
|
15
|
+
* the router protocol carried over it remains ACP V1.
|
|
16
|
+
*/
|
|
17
|
+
export async function listenAcpHttpServer(options = {}) {
|
|
18
|
+
options.signal?.throwIfAborted();
|
|
19
|
+
const host = requireHost(options.host ?? DEFAULT_ACP_HTTP_HOST);
|
|
20
|
+
const port = requirePort(options.port ?? DEFAULT_ACP_HTTP_PORT);
|
|
21
|
+
const path = requirePath(options.path ?? DEFAULT_ACP_HTTP_PATH);
|
|
22
|
+
const targets = options.targets
|
|
23
|
+
? [...options.targets]
|
|
24
|
+
: resolveBackendTargets({ backends: options.backends });
|
|
25
|
+
requireUniqueTargets(targets);
|
|
26
|
+
const transportServer = new AcpServer({
|
|
27
|
+
createAgent: () => ({
|
|
28
|
+
connect(stream) {
|
|
29
|
+
// AcpServer exposes a batch-capable transport stream so it can also host draft ACP V2.
|
|
30
|
+
// This router rejects non-V1 initialize requests, after which the SDK guarantees that V1
|
|
31
|
+
// connections contain individual messages only.
|
|
32
|
+
const closed = serveAcpServer({
|
|
33
|
+
stream: stream,
|
|
34
|
+
targets,
|
|
35
|
+
...(options.version === undefined ? {} : { version: options.version }),
|
|
36
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
37
|
+
}).catch((error) => {
|
|
38
|
+
// One failed client connection must not become a process-level unhandled rejection or
|
|
39
|
+
// stop the shared listener. AcpServer observes this lifecycle promise to tear down only
|
|
40
|
+
// the affected transport connection.
|
|
41
|
+
console.error("ACP network connection failed:", error);
|
|
42
|
+
});
|
|
43
|
+
return { closed };
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
const httpHandler = createNodeHttpHandler(transportServer, {
|
|
48
|
+
...(options.maxRequestBodyBytes === undefined
|
|
49
|
+
? {}
|
|
50
|
+
: { maxRequestBodyBytes: options.maxRequestBodyBytes }),
|
|
51
|
+
});
|
|
52
|
+
const webSocketServer = new WebSocketServer({ noServer: true });
|
|
53
|
+
const upgradeHandler = createNodeWebSocketUpgradeHandler(transportServer, webSocketServer);
|
|
54
|
+
const httpServer = createServer((request, response) => {
|
|
55
|
+
if (!isAcpPath(request, path)) {
|
|
56
|
+
notFound(response);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
httpHandler(request, response);
|
|
60
|
+
});
|
|
61
|
+
httpServer.on("upgrade", (request, socket, head) => {
|
|
62
|
+
if (!isAcpPath(request, path)) {
|
|
63
|
+
socket.destroy();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
upgradeHandler(request, socket, head);
|
|
67
|
+
});
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
const onError = (error) => {
|
|
70
|
+
httpServer.off("listening", onListening);
|
|
71
|
+
reject(error);
|
|
72
|
+
};
|
|
73
|
+
const onListening = () => {
|
|
74
|
+
httpServer.off("error", onError);
|
|
75
|
+
resolve();
|
|
76
|
+
};
|
|
77
|
+
httpServer.once("error", onError);
|
|
78
|
+
httpServer.once("listening", onListening);
|
|
79
|
+
httpServer.listen(port, host);
|
|
80
|
+
});
|
|
81
|
+
const address = httpServer.address();
|
|
82
|
+
if (!isAddressInfo(address)) {
|
|
83
|
+
await closeNodeServer(httpServer).catch(() => undefined);
|
|
84
|
+
await transportServer.close();
|
|
85
|
+
throw new Error("ACP HTTP server did not bind to a TCP address");
|
|
86
|
+
}
|
|
87
|
+
let resolveClosed;
|
|
88
|
+
let rejectClosed;
|
|
89
|
+
const closed = new Promise((resolve, reject) => {
|
|
90
|
+
resolveClosed = resolve;
|
|
91
|
+
rejectClosed = reject;
|
|
92
|
+
});
|
|
93
|
+
closed.catch(() => { });
|
|
94
|
+
let closePromise;
|
|
95
|
+
let runtimeError;
|
|
96
|
+
const close = () => {
|
|
97
|
+
closePromise ??= (async () => {
|
|
98
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
99
|
+
const stopListening = closeNodeServer(httpServer);
|
|
100
|
+
const results = await Promise.allSettled([
|
|
101
|
+
transportServer.close(),
|
|
102
|
+
stopListening,
|
|
103
|
+
]);
|
|
104
|
+
for (const client of webSocketServer.clients)
|
|
105
|
+
client.terminate();
|
|
106
|
+
await closeWebSocketServer(webSocketServer).catch((error) => {
|
|
107
|
+
results.push({ status: "rejected", reason: error });
|
|
108
|
+
});
|
|
109
|
+
const errors = results
|
|
110
|
+
.filter((result) => result.status === "rejected")
|
|
111
|
+
.map((result) => result.reason);
|
|
112
|
+
if (runtimeError !== undefined)
|
|
113
|
+
errors.unshift(runtimeError);
|
|
114
|
+
if (errors.length > 0)
|
|
115
|
+
throw new AggregateError(errors, "Failed to close ACP HTTP server");
|
|
116
|
+
})();
|
|
117
|
+
closePromise.then(resolveClosed, rejectClosed);
|
|
118
|
+
return closePromise;
|
|
119
|
+
};
|
|
120
|
+
const onAbort = () => {
|
|
121
|
+
void close();
|
|
122
|
+
};
|
|
123
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
124
|
+
httpServer.on("error", (error) => {
|
|
125
|
+
runtimeError = error;
|
|
126
|
+
void close();
|
|
127
|
+
});
|
|
128
|
+
if (options.signal?.aborted)
|
|
129
|
+
void close();
|
|
130
|
+
const endpointHost = formatUrlHost(host);
|
|
131
|
+
return {
|
|
132
|
+
host,
|
|
133
|
+
port: address.port,
|
|
134
|
+
path,
|
|
135
|
+
url: `http://${endpointHost}:${address.port}${path}`,
|
|
136
|
+
webSocketUrl: `ws://${endpointHost}:${address.port}${path}`,
|
|
137
|
+
closed,
|
|
138
|
+
close,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function requireHost(value) {
|
|
142
|
+
const host = value.trim();
|
|
143
|
+
if (host.length === 0)
|
|
144
|
+
throw new TypeError("ACP HTTP host must not be empty");
|
|
145
|
+
return host;
|
|
146
|
+
}
|
|
147
|
+
function requirePort(value) {
|
|
148
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 65_535) {
|
|
149
|
+
throw new RangeError("ACP HTTP port must be an integer from 0 through 65535");
|
|
150
|
+
}
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
function requirePath(value) {
|
|
154
|
+
if (!value.startsWith("/") || value.includes("?") || value.includes("#")) {
|
|
155
|
+
throw new TypeError("ACP HTTP path must be an absolute path without a query or fragment");
|
|
156
|
+
}
|
|
157
|
+
const parsed = new URL(value, "http://localhost");
|
|
158
|
+
if (parsed.pathname !== value) {
|
|
159
|
+
throw new TypeError("ACP HTTP path must be a canonical URL path");
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
function requireUniqueTargets(targets) {
|
|
164
|
+
const ids = new Set(targets.map((target) => target.id));
|
|
165
|
+
if (ids.size !== targets.length)
|
|
166
|
+
throw new Error("ACP backend target ids must be unique");
|
|
167
|
+
}
|
|
168
|
+
function isAcpPath(request, path) {
|
|
169
|
+
return new URL(request.url ?? "/", "http://localhost").pathname === path;
|
|
170
|
+
}
|
|
171
|
+
function notFound(response) {
|
|
172
|
+
response.writeHead(404, { "Content-Type": "text/plain" });
|
|
173
|
+
response.end("Not Found");
|
|
174
|
+
}
|
|
175
|
+
function isAddressInfo(value) {
|
|
176
|
+
return value !== null && typeof value === "object";
|
|
177
|
+
}
|
|
178
|
+
function formatUrlHost(host) {
|
|
179
|
+
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
180
|
+
}
|
|
181
|
+
function closeNodeServer(server) {
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
server.close((error) => {
|
|
184
|
+
if (error)
|
|
185
|
+
reject(error);
|
|
186
|
+
else
|
|
187
|
+
resolve();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function closeWebSocketServer(server) {
|
|
192
|
+
return new Promise((resolve, reject) => {
|
|
193
|
+
server.close((error) => {
|
|
194
|
+
if (error)
|
|
195
|
+
reject(error);
|
|
196
|
+
else
|
|
197
|
+
resolve();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { serveAcpServer } from "./server.js";
|
|
2
2
|
export type { ServeAcpServerOptions } from "./server.js";
|
|
3
|
+
export { DEFAULT_ACP_HTTP_HOST, DEFAULT_ACP_HTTP_PATH, DEFAULT_ACP_HTTP_PORT, listenAcpHttpServer, } from "./http-server.js";
|
|
4
|
+
export type { AcpHttpServerHandle, ListenAcpHttpServerOptions } from "./http-server.js";
|
|
3
5
|
export { resolveBackendTargets } from "./backends.js";
|
|
4
6
|
export type { BackendTarget, ResolveBackendTargetsOptions } from "./backends.js";
|
|
5
7
|
export { ACP_BACKENDS_PROBE_METHOD, ACP_ROUTER_META_NAMESPACE, ACP_ROUTER_VERSION, assertSessionBackend, discoveryInitializeResponse, mergeBackendInitializeResponse, parseProbeBackendsParams, parseRouterInitialize, } from "./protocol.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAEjF,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACpB,2BAA2B,EAC3B,8BAA8B,EAC9B,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAChB,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAExF,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAEjF,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACpB,2BAA2B,EAC3B,8BAA8B,EAC9B,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAChB,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { serveAcpServer } from "./server.js";
|
|
2
|
+
export { DEFAULT_ACP_HTTP_HOST, DEFAULT_ACP_HTTP_PATH, DEFAULT_ACP_HTTP_PORT, listenAcpHttpServer, } from "./http-server.js";
|
|
2
3
|
export { resolveBackendTargets } from "./backends.js";
|
|
3
4
|
export { ACP_BACKENDS_PROBE_METHOD, ACP_ROUTER_META_NAMESPACE, ACP_ROUTER_VERSION, assertSessionBackend, discoveryInitializeResponse, mergeBackendInitializeResponse, parseProbeBackendsParams, parseRouterInitialize, } from "./protocol.js";
|
package/dist/server.d.ts
CHANGED
|
@@ -13,6 +13,6 @@ export interface ServeAcpServerOptions {
|
|
|
13
13
|
/** Closes the active outer and downstream connections when aborted. */
|
|
14
14
|
signal?: AbortSignal;
|
|
15
15
|
}
|
|
16
|
-
/** Serve one connection-pinned AgentPrism ACP
|
|
16
|
+
/** Serve one connection-pinned AgentPrism ACP V1 stream until either side closes. */
|
|
17
17
|
export declare function serveAcpServer(options?: ServeAcpServerOptions): Promise<void>;
|
|
18
18
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAYL,KAAK,MAAM,EACZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,mBAAmB,EAAwB,MAAM,0BAA0B,CAAC;AAC1F,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAgB1E,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC/C,8EAA8E;IAC9E,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAYL,KAAK,MAAM,EACZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,mBAAmB,EAAwB,MAAM,0BAA0B,CAAC;AAC1F,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAgB1E,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC/C,8EAA8E;IAC9E,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,qFAAqF;AACrF,wBAAsB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAiEvF"}
|
package/dist/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Readable, Writable } from "node:stream";
|
|
|
4
4
|
import { resolveBackendTargets } from "./backends.js";
|
|
5
5
|
import { ACP_BACKENDS_PROBE_METHOD, assertSessionBackend, discoveryInitializeResponse, isRecord, mergeBackendInitializeResponse, parseProbeBackendsParams, parseRouterInitialize, } from "./protocol.js";
|
|
6
6
|
import { RawRpcPeer, errorResponse } from "./raw-rpc.js";
|
|
7
|
-
/** Serve one connection-pinned AgentPrism ACP
|
|
7
|
+
/** Serve one connection-pinned AgentPrism ACP V1 stream until either side closes. */
|
|
8
8
|
export async function serveAcpServer(options = {}) {
|
|
9
9
|
const stream = options.stream ?? stdioStream();
|
|
10
10
|
const targets = options.targets ? [...options.targets] : resolveBackendTargets({ backends: options.backends });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automatalabs/acp-server",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22"
|
|
@@ -31,7 +31,11 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@agentclientprotocol/sdk": "^1.4.0",
|
|
34
|
-
"
|
|
34
|
+
"ws": "^8.21.3",
|
|
35
|
+
"@automatalabs/acp-agents": "1.1.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/ws": "^8.18.1"
|
|
35
39
|
},
|
|
36
40
|
"scripts": {
|
|
37
41
|
"build": "tsc -b",
|