agentbnb 2.2.0 → 3.1.0

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.
@@ -0,0 +1,12 @@
1
+ import {
2
+ findPeer,
3
+ loadPeers,
4
+ removePeer,
5
+ savePeer
6
+ } from "./chunk-BEI5MTNZ.js";
7
+ export {
8
+ findPeer,
9
+ loadPeers,
10
+ removePeer,
11
+ savePeer
12
+ };
@@ -0,0 +1,275 @@
1
+ import {
2
+ RelayMessageSchema
3
+ } from "./chunk-3Y36WQDV.js";
4
+
5
+ // src/relay/websocket-client.ts
6
+ import WebSocket from "ws";
7
+ import { randomUUID } from "crypto";
8
+ var RelayClient = class {
9
+ ws = null;
10
+ opts;
11
+ pendingRequests = /* @__PURE__ */ new Map();
12
+ reconnectAttempts = 0;
13
+ reconnectTimer = null;
14
+ intentionalClose = false;
15
+ registered = false;
16
+ pongTimeout = null;
17
+ pingInterval = null;
18
+ constructor(opts) {
19
+ this.opts = opts;
20
+ }
21
+ /**
22
+ * Connect to the registry relay and register.
23
+ * Resolves when registration is acknowledged.
24
+ */
25
+ async connect() {
26
+ return new Promise((resolve, reject) => {
27
+ this.intentionalClose = false;
28
+ this.registered = false;
29
+ const wsUrl = this.buildWsUrl();
30
+ this.ws = new WebSocket(wsUrl);
31
+ let resolved = false;
32
+ this.ws.on("open", () => {
33
+ this.reconnectAttempts = 0;
34
+ this.startPingInterval();
35
+ this.send({
36
+ type: "register",
37
+ owner: this.opts.owner,
38
+ token: this.opts.token,
39
+ card: this.opts.card
40
+ });
41
+ });
42
+ this.ws.on("message", (raw) => {
43
+ this.handleMessage(raw, (err) => {
44
+ if (!resolved) {
45
+ resolved = true;
46
+ if (err) reject(err);
47
+ else resolve();
48
+ }
49
+ });
50
+ });
51
+ this.ws.on("close", () => {
52
+ this.cleanup();
53
+ if (!this.intentionalClose) {
54
+ if (!resolved) {
55
+ resolved = true;
56
+ reject(new Error("WebSocket closed before registration"));
57
+ }
58
+ this.scheduleReconnect();
59
+ }
60
+ });
61
+ this.ws.on("error", (err) => {
62
+ if (!resolved) {
63
+ resolved = true;
64
+ reject(err);
65
+ }
66
+ });
67
+ setTimeout(() => {
68
+ if (!resolved) {
69
+ resolved = true;
70
+ reject(new Error("Connection timeout"));
71
+ this.ws?.close();
72
+ }
73
+ }, 1e4);
74
+ });
75
+ }
76
+ /**
77
+ * Disconnect from the registry relay.
78
+ */
79
+ disconnect() {
80
+ this.intentionalClose = true;
81
+ this.cleanup();
82
+ if (this.ws) {
83
+ try {
84
+ this.ws.close(1e3, "Client disconnect");
85
+ } catch {
86
+ }
87
+ this.ws = null;
88
+ }
89
+ for (const [id, pending] of this.pendingRequests) {
90
+ clearTimeout(pending.timeout);
91
+ pending.reject(new Error("Client disconnected"));
92
+ this.pendingRequests.delete(id);
93
+ }
94
+ }
95
+ /**
96
+ * Send a relay request to another agent via the registry.
97
+ * @returns The result from the target agent.
98
+ */
99
+ async request(opts) {
100
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.registered) {
101
+ throw new Error("Not connected to registry relay");
102
+ }
103
+ const id = randomUUID();
104
+ const timeoutMs = opts.timeoutMs ?? 3e4;
105
+ return new Promise((resolve, reject) => {
106
+ const timeout = setTimeout(() => {
107
+ this.pendingRequests.delete(id);
108
+ reject(new Error("Relay request timeout"));
109
+ }, timeoutMs);
110
+ this.pendingRequests.set(id, { resolve, reject, timeout });
111
+ this.send({
112
+ type: "relay_request",
113
+ id,
114
+ target_owner: opts.targetOwner,
115
+ card_id: opts.cardId,
116
+ skill_id: opts.skillId,
117
+ params: opts.params,
118
+ requester: opts.requester ?? this.opts.owner,
119
+ escrow_receipt: opts.escrowReceipt
120
+ });
121
+ });
122
+ }
123
+ /** Whether the client is connected and registered */
124
+ get isConnected() {
125
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
126
+ }
127
+ // ── Private methods ─────────────────────────────────────────────────────────
128
+ buildWsUrl() {
129
+ let url = this.opts.registryUrl;
130
+ if (url.startsWith("http://")) {
131
+ url = "ws://" + url.slice(7);
132
+ } else if (url.startsWith("https://")) {
133
+ url = "wss://" + url.slice(8);
134
+ } else if (!url.startsWith("ws://") && !url.startsWith("wss://")) {
135
+ url = "wss://" + url;
136
+ }
137
+ if (!url.endsWith("/ws")) {
138
+ url = url.replace(/\/$/, "") + "/ws";
139
+ }
140
+ return url;
141
+ }
142
+ handleMessage(raw, onRegistered) {
143
+ let data;
144
+ try {
145
+ data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
146
+ } catch {
147
+ return;
148
+ }
149
+ const parsed = RelayMessageSchema.safeParse(data);
150
+ if (!parsed.success) return;
151
+ const msg = parsed.data;
152
+ switch (msg.type) {
153
+ case "registered":
154
+ this.registered = true;
155
+ if (!this.opts.silent) {
156
+ console.log(` \u2713 Registered with registry (agent_id: ${msg.agent_id})`);
157
+ }
158
+ onRegistered?.();
159
+ break;
160
+ case "incoming_request":
161
+ this.handleIncomingRequest(msg);
162
+ break;
163
+ case "response":
164
+ this.handleResponse(msg);
165
+ break;
166
+ case "error":
167
+ this.handleError(msg);
168
+ break;
169
+ default:
170
+ break;
171
+ }
172
+ }
173
+ async handleIncomingRequest(msg) {
174
+ try {
175
+ const result = await this.opts.onRequest(msg);
176
+ this.send({
177
+ type: "relay_response",
178
+ id: msg.id,
179
+ result: result.result,
180
+ error: result.error
181
+ });
182
+ } catch (err) {
183
+ this.send({
184
+ type: "relay_response",
185
+ id: msg.id,
186
+ error: {
187
+ code: -32603,
188
+ message: err instanceof Error ? err.message : "Internal error"
189
+ }
190
+ });
191
+ }
192
+ }
193
+ handleResponse(msg) {
194
+ const pending = this.pendingRequests.get(msg.id);
195
+ if (!pending) return;
196
+ clearTimeout(pending.timeout);
197
+ this.pendingRequests.delete(msg.id);
198
+ if (msg.error) {
199
+ pending.reject(new Error(msg.error.message));
200
+ } else {
201
+ pending.resolve(msg.result);
202
+ }
203
+ }
204
+ handleError(msg) {
205
+ if (msg.request_id) {
206
+ const pending = this.pendingRequests.get(msg.request_id);
207
+ if (pending) {
208
+ clearTimeout(pending.timeout);
209
+ this.pendingRequests.delete(msg.request_id);
210
+ pending.reject(new Error(`${msg.code}: ${msg.message}`));
211
+ }
212
+ }
213
+ }
214
+ send(msg) {
215
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
216
+ this.ws.send(JSON.stringify(msg));
217
+ }
218
+ }
219
+ startPingInterval() {
220
+ this.stopPingInterval();
221
+ this.pingInterval = setInterval(() => {
222
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
223
+ this.ws.ping();
224
+ this.pongTimeout = setTimeout(() => {
225
+ if (!this.opts.silent) {
226
+ console.log(" \u26A0 Registry pong timeout, reconnecting...");
227
+ }
228
+ this.ws?.terminate();
229
+ }, 15e3);
230
+ }
231
+ }, 3e4);
232
+ this.ws?.on("pong", () => {
233
+ if (this.pongTimeout) {
234
+ clearTimeout(this.pongTimeout);
235
+ this.pongTimeout = null;
236
+ }
237
+ });
238
+ }
239
+ stopPingInterval() {
240
+ if (this.pingInterval) {
241
+ clearInterval(this.pingInterval);
242
+ this.pingInterval = null;
243
+ }
244
+ if (this.pongTimeout) {
245
+ clearTimeout(this.pongTimeout);
246
+ this.pongTimeout = null;
247
+ }
248
+ }
249
+ cleanup() {
250
+ this.stopPingInterval();
251
+ this.registered = false;
252
+ }
253
+ scheduleReconnect() {
254
+ if (this.intentionalClose) return;
255
+ if (this.reconnectTimer) return;
256
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
257
+ this.reconnectAttempts++;
258
+ if (!this.opts.silent) {
259
+ console.log(` \u21BB Reconnecting to registry in ${delay / 1e3}s...`);
260
+ }
261
+ this.reconnectTimer = setTimeout(async () => {
262
+ this.reconnectTimer = null;
263
+ try {
264
+ await this.connect();
265
+ if (!this.opts.silent) {
266
+ console.log(" \u2713 Reconnected to registry");
267
+ }
268
+ } catch {
269
+ }
270
+ }, delay);
271
+ }
272
+ };
273
+ export {
274
+ RelayClient
275
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentbnb",
3
- "version": "2.2.0",
3
+ "version": "3.1.0",
4
4
  "description": "P2P Agent Capability Sharing Protocol — Airbnb for AI agent pipelines",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,12 +39,13 @@
39
39
  "protocol",
40
40
  "airbnb"
41
41
  ],
42
- "author": "Cheng Wen <樂洋集團>",
42
+ "author": "Cheng Wen Chen",
43
43
  "license": "MIT",
44
44
  "devDependencies": {
45
45
  "@types/better-sqlite3": "^7.6.12",
46
46
  "@types/js-yaml": "^4.0.9",
47
47
  "@types/node": "^22.0.0",
48
+ "@types/ws": "^8.18.1",
48
49
  "eslint": "^9.0.0",
49
50
  "prettier": "^3.4.0",
50
51
  "tsup": "^8.3.0",
@@ -55,6 +56,7 @@
55
56
  "dependencies": {
56
57
  "@fastify/cors": "^11.2.0",
57
58
  "@fastify/static": "^9.0.0",
59
+ "@fastify/websocket": "^11.2.0",
58
60
  "better-sqlite3": "^11.6.0",
59
61
  "bonjour-service": "^1.3.0",
60
62
  "commander": "^12.1.0",
@@ -62,6 +64,7 @@
62
64
  "fastify": "^5.1.0",
63
65
  "js-yaml": "^4.1.1",
64
66
  "typed-emitter": "^2.1.0",
67
+ "ws": "^8.19.0",
65
68
  "zod": "^3.24.0"
66
69
  },
67
70
  "engines": {