@agentick/gateway 0.0.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/LICENSE +21 -0
- package/README.md +477 -0
- package/dist/agent-registry.d.ts +51 -0
- package/dist/agent-registry.d.ts.map +1 -0
- package/dist/agent-registry.js +78 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/app-registry.d.ts +51 -0
- package/dist/app-registry.d.ts.map +1 -0
- package/dist/app-registry.js +78 -0
- package/dist/app-registry.js.map +1 -0
- package/dist/bin.d.ts +8 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +37 -0
- package/dist/bin.js.map +1 -0
- package/dist/gateway.d.ts +165 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +1339 -0
- package/dist/gateway.js.map +1 -0
- package/dist/http-transport.d.ts +65 -0
- package/dist/http-transport.d.ts.map +1 -0
- package/dist/http-transport.js +517 -0
- package/dist/http-transport.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +162 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +16 -0
- package/dist/protocol.js.map +1 -0
- package/dist/session-manager.d.ts +101 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +208 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/testing.d.ts +92 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +129 -0
- package/dist/testing.js.map +1 -0
- package/dist/transport-protocol.d.ts +162 -0
- package/dist/transport-protocol.d.ts.map +1 -0
- package/dist/transport-protocol.js +16 -0
- package/dist/transport-protocol.js.map +1 -0
- package/dist/transport.d.ts +115 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +56 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +314 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +37 -0
- package/dist/types.js.map +1 -0
- package/dist/websocket-server.d.ts +87 -0
- package/dist/websocket-server.d.ts.map +1 -0
- package/dist/websocket-server.js +245 -0
- package/dist/websocket-server.js.map +1 -0
- package/dist/ws-transport.d.ts +17 -0
- package/dist/ws-transport.d.ts.map +1 -0
- package/dist/ws-transport.js +174 -0
- package/dist/ws-transport.js.map +1 -0
- package/package.json +51 -0
- package/src/__tests__/custom-methods.spec.ts +220 -0
- package/src/__tests__/gateway-methods.spec.ts +262 -0
- package/src/__tests__/gateway.spec.ts +404 -0
- package/src/__tests__/guards.spec.ts +235 -0
- package/src/__tests__/protocol.spec.ts +58 -0
- package/src/__tests__/session-manager.spec.ts +220 -0
- package/src/__tests__/ws-transport.spec.ts +246 -0
- package/src/app-registry.ts +103 -0
- package/src/bin.ts +38 -0
- package/src/gateway.ts +1712 -0
- package/src/http-transport.ts +623 -0
- package/src/index.ts +94 -0
- package/src/session-manager.ts +272 -0
- package/src/testing.ts +236 -0
- package/src/transport-protocol.ts +249 -0
- package/src/transport.ts +191 -0
- package/src/types.ts +392 -0
- package/src/websocket-server.ts +303 -0
- package/src/ws-transport.ts +205 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Transport Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
6
|
+
import { WSTransport } from "../ws-transport.js";
|
|
7
|
+
import WebSocket from "ws";
|
|
8
|
+
|
|
9
|
+
describe("WSTransport", () => {
|
|
10
|
+
let server: WSTransport;
|
|
11
|
+
let client: WebSocket;
|
|
12
|
+
const TEST_PORT = 19999;
|
|
13
|
+
const TEST_HOST = "127.0.0.1";
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
server = new WSTransport({
|
|
17
|
+
port: TEST_PORT,
|
|
18
|
+
host: TEST_HOST,
|
|
19
|
+
});
|
|
20
|
+
await server.start();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
if (client && client.readyState === WebSocket.OPEN) {
|
|
25
|
+
client.close();
|
|
26
|
+
}
|
|
27
|
+
await server.stop();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("start/stop", () => {
|
|
31
|
+
it("starts and stops server", async () => {
|
|
32
|
+
const anotherServer = new WSTransport({
|
|
33
|
+
port: TEST_PORT + 1,
|
|
34
|
+
host: TEST_HOST,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await anotherServer.start();
|
|
38
|
+
expect(anotherServer.clientCount).toBe(0);
|
|
39
|
+
|
|
40
|
+
await anotherServer.stop();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("connections", () => {
|
|
45
|
+
it("accepts client connection", async () => {
|
|
46
|
+
const connectionPromise = new Promise<void>((resolve) => {
|
|
47
|
+
server.on("connection", () => resolve());
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
|
|
51
|
+
await connectionPromise;
|
|
52
|
+
|
|
53
|
+
expect(server.clientCount).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("handles client disconnect", async () => {
|
|
57
|
+
const disconnectPromise = new Promise<string>((resolve) => {
|
|
58
|
+
server.on("disconnect", (clientId) => resolve(clientId));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
|
|
62
|
+
await new Promise<void>((resolve) => {
|
|
63
|
+
client.on("open", () => resolve());
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
client.close();
|
|
67
|
+
const disconnectedId = await disconnectPromise;
|
|
68
|
+
|
|
69
|
+
expect(disconnectedId).toBeDefined();
|
|
70
|
+
expect(server.clientCount).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("authentication", () => {
|
|
75
|
+
it("authenticates with valid token", async () => {
|
|
76
|
+
await server.stop();
|
|
77
|
+
|
|
78
|
+
server = new WSTransport({
|
|
79
|
+
port: TEST_PORT,
|
|
80
|
+
host: TEST_HOST,
|
|
81
|
+
auth: { type: "token", token: "secret123" },
|
|
82
|
+
});
|
|
83
|
+
await server.start();
|
|
84
|
+
|
|
85
|
+
client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
|
|
86
|
+
await new Promise<void>((resolve) => {
|
|
87
|
+
client.on("open", () => resolve());
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Track any error responses
|
|
91
|
+
let gotError = false;
|
|
92
|
+
client.on("message", (data) => {
|
|
93
|
+
const msg = JSON.parse(data.toString());
|
|
94
|
+
if (msg.type === "error") gotError = true;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
client.send(
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
type: "connect",
|
|
100
|
+
clientId: "test-client",
|
|
101
|
+
token: "secret123",
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Wait a bit and verify no error was received
|
|
106
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
107
|
+
expect(gotError).toBe(false);
|
|
108
|
+
expect(server.clientCount).toBe(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("rejects invalid token", async () => {
|
|
112
|
+
await server.stop();
|
|
113
|
+
|
|
114
|
+
server = new WSTransport({
|
|
115
|
+
port: TEST_PORT,
|
|
116
|
+
host: TEST_HOST,
|
|
117
|
+
auth: { type: "token", token: "secret123" },
|
|
118
|
+
});
|
|
119
|
+
await server.start();
|
|
120
|
+
|
|
121
|
+
client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
|
|
122
|
+
await new Promise<void>((resolve) => {
|
|
123
|
+
client.on("open", () => resolve());
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const responsePromise = new Promise<{ type: string; code?: string }>((resolve) => {
|
|
127
|
+
client.on("message", (data) => {
|
|
128
|
+
resolve(JSON.parse(data.toString()));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
client.send(
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
type: "connect",
|
|
135
|
+
clientId: "test-client",
|
|
136
|
+
token: "wrong-token",
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const response = await responsePromise;
|
|
141
|
+
expect(response.type).toBe("error");
|
|
142
|
+
expect(response.code).toBe("AUTH_FAILED");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("allows connection without auth when not configured", async () => {
|
|
146
|
+
client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
|
|
147
|
+
await new Promise<void>((resolve) => {
|
|
148
|
+
client.on("open", () => resolve());
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const responsePromise = new Promise<{ type: string }>((resolve) => {
|
|
152
|
+
client.on("message", (data) => {
|
|
153
|
+
resolve(JSON.parse(data.toString()));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
client.send(
|
|
158
|
+
JSON.stringify({
|
|
159
|
+
type: "connect",
|
|
160
|
+
clientId: "test-client",
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Should not get auth error
|
|
165
|
+
// Since no auth configured, any connect should work
|
|
166
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
167
|
+
expect(server.clientCount).toBe(1);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("ping/pong", () => {
|
|
172
|
+
it("responds to ping with pong", async () => {
|
|
173
|
+
client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
|
|
174
|
+
await new Promise<void>((resolve) => {
|
|
175
|
+
client.on("open", () => resolve());
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Connect first
|
|
179
|
+
client.send(
|
|
180
|
+
JSON.stringify({
|
|
181
|
+
type: "connect",
|
|
182
|
+
clientId: "test-client",
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const pongPromise = new Promise<{ type: string; timestamp: number }>((resolve) => {
|
|
187
|
+
client.on("message", (data) => {
|
|
188
|
+
const msg = JSON.parse(data.toString());
|
|
189
|
+
if (msg.type === "pong") {
|
|
190
|
+
resolve(msg);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const timestamp = Date.now();
|
|
196
|
+
client.send(
|
|
197
|
+
JSON.stringify({
|
|
198
|
+
type: "ping",
|
|
199
|
+
timestamp,
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const pong = await pongPromise;
|
|
204
|
+
expect(pong.type).toBe("pong");
|
|
205
|
+
expect(pong.timestamp).toBe(timestamp);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("broadcast", () => {
|
|
210
|
+
it("broadcasts to authenticated clients", async () => {
|
|
211
|
+
// Connect two clients
|
|
212
|
+
const client1 = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
|
|
213
|
+
const client2 = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
|
|
214
|
+
|
|
215
|
+
await Promise.all([
|
|
216
|
+
new Promise<void>((r) => client1.on("open", () => r())),
|
|
217
|
+
new Promise<void>((r) => client2.on("open", () => r())),
|
|
218
|
+
]);
|
|
219
|
+
|
|
220
|
+
// Authenticate both
|
|
221
|
+
client1.send(JSON.stringify({ type: "connect", clientId: "c1" }));
|
|
222
|
+
client2.send(JSON.stringify({ type: "connect", clientId: "c2" }));
|
|
223
|
+
|
|
224
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
225
|
+
|
|
226
|
+
const messages: unknown[] = [];
|
|
227
|
+
client1.on("message", (data) => messages.push(JSON.parse(data.toString())));
|
|
228
|
+
client2.on("message", (data) => messages.push(JSON.parse(data.toString())));
|
|
229
|
+
|
|
230
|
+
server.broadcast({
|
|
231
|
+
type: "event",
|
|
232
|
+
event: "test_event",
|
|
233
|
+
sessionId: "test",
|
|
234
|
+
data: { foo: "bar" },
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
238
|
+
|
|
239
|
+
const events = messages.filter((m: any) => m.type === "event");
|
|
240
|
+
expect(events.length).toBeGreaterThanOrEqual(2);
|
|
241
|
+
|
|
242
|
+
client1.close();
|
|
243
|
+
client2.close();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Registry
|
|
3
|
+
*
|
|
4
|
+
* Manages available apps and their configurations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { App } from "@agentick/core";
|
|
8
|
+
|
|
9
|
+
export interface AppInfo {
|
|
10
|
+
id: string;
|
|
11
|
+
app: App;
|
|
12
|
+
name?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
isDefault: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class AppRegistry {
|
|
18
|
+
private apps = new Map<string, AppInfo>();
|
|
19
|
+
private defaultAppId: string;
|
|
20
|
+
|
|
21
|
+
constructor(apps: Record<string, App>, defaultApp: string) {
|
|
22
|
+
if (!apps[defaultApp]) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Default app "${defaultApp}" not found in apps: ${Object.keys(apps).join(", ")}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.defaultAppId = defaultApp;
|
|
29
|
+
|
|
30
|
+
for (const [id, app] of Object.entries(apps)) {
|
|
31
|
+
this.apps.set(id, {
|
|
32
|
+
id,
|
|
33
|
+
app,
|
|
34
|
+
isDefault: id === defaultApp,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get an app by ID
|
|
41
|
+
*/
|
|
42
|
+
get(id: string): AppInfo | undefined {
|
|
43
|
+
return this.apps.get(id);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the default app
|
|
48
|
+
*/
|
|
49
|
+
getDefault(): AppInfo {
|
|
50
|
+
return this.apps.get(this.defaultAppId)!;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get the default app ID
|
|
55
|
+
*/
|
|
56
|
+
get defaultId(): string {
|
|
57
|
+
return this.defaultAppId;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if an app exists
|
|
62
|
+
*/
|
|
63
|
+
has(id: string): boolean {
|
|
64
|
+
return this.apps.has(id);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get all app IDs
|
|
69
|
+
*/
|
|
70
|
+
ids(): string[] {
|
|
71
|
+
return Array.from(this.apps.keys());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get all apps
|
|
76
|
+
*/
|
|
77
|
+
all(): AppInfo[] {
|
|
78
|
+
return Array.from(this.apps.values());
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get app count
|
|
83
|
+
*/
|
|
84
|
+
get size(): number {
|
|
85
|
+
return this.apps.size;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolve an app ID, falling back to default
|
|
90
|
+
*/
|
|
91
|
+
resolve(id?: string): AppInfo {
|
|
92
|
+
if (!id) {
|
|
93
|
+
return this.getDefault();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const app = this.apps.get(id);
|
|
97
|
+
if (!app) {
|
|
98
|
+
throw new Error(`Unknown app "${id}". Available: ${this.ids().join(", ")}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return app;
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gateway CLI
|
|
4
|
+
*
|
|
5
|
+
* Run the gateway daemon from the command line.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createGateway } from "./gateway.js";
|
|
9
|
+
|
|
10
|
+
// For now, just a placeholder that shows usage
|
|
11
|
+
console.log("@agentick/gateway");
|
|
12
|
+
console.log("");
|
|
13
|
+
console.log("Usage:");
|
|
14
|
+
console.log(" The gateway is typically started programmatically:");
|
|
15
|
+
console.log("");
|
|
16
|
+
console.log(" ```typescript");
|
|
17
|
+
console.log(" import { createGateway } from '@agentick/gateway';");
|
|
18
|
+
console.log(" import { createApp } from '@agentick/core';");
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log(" const Agent = () => (");
|
|
21
|
+
console.log(" <>");
|
|
22
|
+
console.log(" <Model model={gpt4} />");
|
|
23
|
+
console.log(" <System>You are a helpful assistant.</System>");
|
|
24
|
+
console.log(" <Timeline />");
|
|
25
|
+
console.log(" </>");
|
|
26
|
+
console.log(" );");
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log(" const gateway = createGateway({");
|
|
29
|
+
console.log(" agents: { chat: createApp(Agent) },");
|
|
30
|
+
console.log(" defaultAgent: 'chat',");
|
|
31
|
+
console.log(" port: 18789,");
|
|
32
|
+
console.log(" });");
|
|
33
|
+
console.log("");
|
|
34
|
+
console.log(" await gateway.start();");
|
|
35
|
+
console.log(" ```");
|
|
36
|
+
console.log("");
|
|
37
|
+
console.log("Future versions will support a config file:");
|
|
38
|
+
console.log(" agentick-gateway --config ./gateway.config.ts");
|