acn-client 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/dist/index.js ADDED
@@ -0,0 +1,627 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ACNClient: () => ACNClient,
34
+ ACNError: () => ACNError,
35
+ ACNRealtime: () => ACNRealtime,
36
+ subscribeToACN: () => subscribeToACN
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/client.ts
41
+ var ACNClient = class {
42
+ baseUrl;
43
+ timeout;
44
+ headers;
45
+ constructor(options) {
46
+ if (typeof options === "string") {
47
+ this.baseUrl = options.replace(/\/$/, "");
48
+ this.timeout = 3e4;
49
+ this.headers = {};
50
+ } else {
51
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
52
+ this.timeout = options.timeout ?? 3e4;
53
+ this.headers = options.headers ?? {};
54
+ if (options.apiKey) {
55
+ this.headers["X-API-Key"] = options.apiKey;
56
+ }
57
+ }
58
+ }
59
+ // ============================================
60
+ // Internal HTTP Methods
61
+ // ============================================
62
+ async request(method, path, options) {
63
+ const url = new URL(`${this.baseUrl}${path}`);
64
+ if (options?.params) {
65
+ Object.entries(options.params).forEach(([key, value]) => {
66
+ if (value !== void 0) {
67
+ url.searchParams.append(key, String(value));
68
+ }
69
+ });
70
+ }
71
+ const controller = new AbortController();
72
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
73
+ try {
74
+ const response = await fetch(url.toString(), {
75
+ method,
76
+ headers: {
77
+ "Content-Type": "application/json",
78
+ ...this.headers
79
+ },
80
+ body: options?.body ? JSON.stringify(options.body) : void 0,
81
+ signal: controller.signal
82
+ });
83
+ if (!response.ok) {
84
+ const error = await response.json().catch(() => ({ message: response.statusText }));
85
+ throw new ACNError(response.status, error.detail || error.message || "Request failed");
86
+ }
87
+ if (response.status === 204) {
88
+ return void 0;
89
+ }
90
+ return response.json();
91
+ } finally {
92
+ clearTimeout(timeoutId);
93
+ }
94
+ }
95
+ get(path, params) {
96
+ return this.request("GET", path, { params });
97
+ }
98
+ post(path, body) {
99
+ return this.request("POST", path, { body });
100
+ }
101
+ delete(path) {
102
+ return this.request("DELETE", path);
103
+ }
104
+ // ============================================
105
+ // Health & Status
106
+ // ============================================
107
+ /** Check if ACN server is healthy */
108
+ async health() {
109
+ return this.get("/health");
110
+ }
111
+ /** Get server statistics */
112
+ async getStats() {
113
+ return this.get("/api/v1/stats");
114
+ }
115
+ // ============================================
116
+ // Agent Management
117
+ // ============================================
118
+ /** Register a new agent */
119
+ async registerAgent(agent) {
120
+ return this.post("/api/v1/agents/register", agent);
121
+ }
122
+ /** Get agent by ID */
123
+ async getAgent(agentId) {
124
+ return this.get(`/api/v1/agents/${agentId}`);
125
+ }
126
+ /** Search agents (status: online | offline | all; public list does not include verification_code) */
127
+ async searchAgents(options) {
128
+ return this.get("/api/v1/agents", {
129
+ skill: options?.skills,
130
+ status: options?.status
131
+ });
132
+ }
133
+ /** Unregister an agent */
134
+ async unregisterAgent(agentId) {
135
+ return this.delete(`/api/v1/agents/${agentId}`);
136
+ }
137
+ /** Send agent heartbeat */
138
+ async heartbeat(agentId) {
139
+ return this.post(`/api/v1/agents/${agentId}/heartbeat`);
140
+ }
141
+ /** Get agent endpoint */
142
+ async getAgentEndpoint(agentId) {
143
+ return this.get(`/api/v1/agents/${agentId}/endpoint`);
144
+ }
145
+ /** List all available skills */
146
+ async getSkills() {
147
+ return this.get("/api/v1/skills");
148
+ }
149
+ // ============================================
150
+ // Subnet Management
151
+ // ============================================
152
+ /** Create a new subnet */
153
+ async createSubnet(request) {
154
+ return this.post("/api/v1/subnets", request);
155
+ }
156
+ /** List all subnets */
157
+ async listSubnets() {
158
+ return this.get("/api/v1/subnets");
159
+ }
160
+ /** Get subnet by ID */
161
+ async getSubnet(subnetId) {
162
+ return this.get(`/api/v1/subnets/${subnetId}`);
163
+ }
164
+ /** Delete a subnet */
165
+ async deleteSubnet(subnetId, force = false) {
166
+ return this.request("DELETE", `/api/v1/subnets/${subnetId}`, {
167
+ params: { force }
168
+ });
169
+ }
170
+ /** Get agents in a subnet */
171
+ async getSubnetAgents(subnetId) {
172
+ return this.get(`/api/v1/subnets/${subnetId}/agents`);
173
+ }
174
+ /** Join agent to subnet */
175
+ async joinSubnet(agentId, subnetId) {
176
+ return this.post(`/api/v1/agents/${agentId}/subnets/${subnetId}`);
177
+ }
178
+ /** Remove agent from subnet */
179
+ async leaveSubnet(agentId, subnetId) {
180
+ return this.delete(`/api/v1/agents/${agentId}/subnets/${subnetId}`);
181
+ }
182
+ /** Get agent's subnets */
183
+ async getAgentSubnets(agentId) {
184
+ return this.get(`/api/v1/agents/${agentId}/subnets`);
185
+ }
186
+ // ============================================
187
+ // Communication
188
+ // ============================================
189
+ /** Send message to an agent */
190
+ async sendMessage(request) {
191
+ return this.post("/api/v1/communication/send", request);
192
+ }
193
+ /** Broadcast message to multiple agents */
194
+ async broadcast(request) {
195
+ return this.post("/api/v1/communication/broadcast", request);
196
+ }
197
+ /** Broadcast message to agents with specific skill */
198
+ async broadcastBySkill(request) {
199
+ return this.post("/api/v1/communication/broadcast-by-skill", request);
200
+ }
201
+ /** Get message history for an agent */
202
+ async getMessageHistory(agentId, options) {
203
+ return this.get(`/api/v1/communication/history/${agentId}`, options);
204
+ }
205
+ // ============================================
206
+ // Payment Discovery
207
+ // ============================================
208
+ /** Set agent's payment capability */
209
+ async setPaymentCapability(agentId, capability) {
210
+ return this.post(`/api/v1/agents/${agentId}/payment-capability`, capability);
211
+ }
212
+ /** Get agent's payment capability */
213
+ async getPaymentCapability(agentId) {
214
+ return this.get(`/api/v1/agents/${agentId}/payment-capability`);
215
+ }
216
+ /** Discover agents that accept payments */
217
+ async discoverPaymentAgents(options) {
218
+ return this.get("/api/v1/payments/discover", {
219
+ method: options?.method,
220
+ network: options?.network,
221
+ min_amount: options?.min_amount,
222
+ max_amount: options?.max_amount
223
+ });
224
+ }
225
+ /** Get payment task by ID */
226
+ async getPaymentTask(taskId) {
227
+ return this.get(`/api/v1/payments/tasks/${taskId}`);
228
+ }
229
+ /** Get agent's payment tasks */
230
+ async getAgentPaymentTasks(agentId, options) {
231
+ return this.get(`/api/v1/payments/tasks/agent/${agentId}`, options);
232
+ }
233
+ /** Get agent's payment statistics */
234
+ async getPaymentStats(agentId) {
235
+ return this.get(`/api/v1/payments/stats/${agentId}`);
236
+ }
237
+ // ============================================
238
+ // Monitoring & Analytics
239
+ // ============================================
240
+ /** Get Prometheus metrics (text format) */
241
+ async getPrometheusMetrics() {
242
+ const response = await fetch(`${this.baseUrl}/metrics`);
243
+ return response.text();
244
+ }
245
+ /** Get all metrics */
246
+ async getMetrics() {
247
+ return this.get("/api/v1/monitoring/metrics");
248
+ }
249
+ /** Get system health */
250
+ async getSystemHealth() {
251
+ return this.get("/api/v1/monitoring/health");
252
+ }
253
+ /** Get dashboard data */
254
+ async getDashboard() {
255
+ return this.get("/api/v1/monitoring/dashboard");
256
+ }
257
+ /** Get agent analytics */
258
+ async getAgentAnalytics() {
259
+ return this.get("/api/v1/analytics/agents");
260
+ }
261
+ /** Get specific agent's activity */
262
+ async getAgentActivity(agentId, options) {
263
+ return this.get(`/api/v1/analytics/agents/${agentId}`, options);
264
+ }
265
+ /** Get message analytics */
266
+ async getMessageAnalytics() {
267
+ return this.get("/api/v1/analytics/messages");
268
+ }
269
+ /** Get latency analytics */
270
+ async getLatencyAnalytics() {
271
+ return this.get("/api/v1/analytics/latency");
272
+ }
273
+ /** Get subnet analytics */
274
+ async getSubnetAnalytics() {
275
+ return this.get("/api/v1/analytics/subnets");
276
+ }
277
+ // ============================================
278
+ // Audit
279
+ // ============================================
280
+ /** Get audit events */
281
+ async getAuditEvents(options) {
282
+ return this.get("/api/v1/audit/events", options);
283
+ }
284
+ /** Get recent audit events */
285
+ async getRecentAuditEvents(limit = 100) {
286
+ return this.get("/api/v1/audit/events/recent", { limit });
287
+ }
288
+ // ============================================
289
+ // ERC-8004 On-Chain Identity
290
+ // ============================================
291
+ /**
292
+ * Register the agent on ERC-8004 Identity Registry and bind to ACN.
293
+ *
294
+ * Full flow:
295
+ * 1. Generate wallet if privateKey is undefined (saved to saveWalletPath).
296
+ * 2. Construct agentURI → agent-registration.json endpoint.
297
+ * 3. Sign and broadcast register(agentURI) transaction via viem.
298
+ * 4. Extract token ID from Registered event.
299
+ * 5. POST /api/v1/onchain/agents/{agentId}/bind to inform ACN.
300
+ *
301
+ * @param agentId - ACN agent ID (from join response).
302
+ * @param options - Chain, RPC, private key, wallet save path.
303
+ */
304
+ async registerOnchain(agentId, options = {}) {
305
+ const {
306
+ chain = "base",
307
+ rpcUrl,
308
+ saveWalletPath = ".env"
309
+ } = options;
310
+ const { createWalletClient, createPublicClient, http } = await import("viem");
311
+ const { generatePrivateKey, privateKeyToAccount } = await import("viem/accounts");
312
+ const { base, baseSepolia } = await import("viem/chains");
313
+ const chainConfigs = {
314
+ base: {
315
+ viemChain: base,
316
+ identityContract: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
317
+ namespace: "eip155:8453"
318
+ },
319
+ "base-sepolia": {
320
+ viemChain: baseSepolia,
321
+ identityContract: "0x8004A818BFB912233c491871b3d84c89A494BD9e",
322
+ namespace: "eip155:84532"
323
+ }
324
+ };
325
+ const cfg = chainConfigs[chain];
326
+ let walletGenerated = false;
327
+ let privateKey;
328
+ if (!options.privateKey) {
329
+ privateKey = generatePrivateKey();
330
+ walletGenerated = true;
331
+ if (saveWalletPath) {
332
+ await this._saveWalletToEnv(saveWalletPath, privateKey);
333
+ }
334
+ } else {
335
+ privateKey = options.privateKey;
336
+ }
337
+ const account = privateKeyToAccount(privateKey);
338
+ const agentRegistrationUrl = `${this.baseUrl}/api/v1/agents/${agentId}/.well-known/agent-registration.json`;
339
+ const abi = [
340
+ {
341
+ name: "register",
342
+ type: "function",
343
+ stateMutability: "nonpayable",
344
+ inputs: [{ type: "string", name: "agentURI" }],
345
+ outputs: [{ type: "uint256", name: "agentId" }]
346
+ },
347
+ {
348
+ name: "Registered",
349
+ type: "event",
350
+ inputs: [
351
+ { type: "uint256", name: "agentId", indexed: true },
352
+ { type: "string", name: "agentURI", indexed: false },
353
+ { type: "address", name: "owner", indexed: true }
354
+ ]
355
+ }
356
+ ];
357
+ const transport = http(rpcUrl ?? void 0);
358
+ const walletClient = createWalletClient({ account, chain: cfg.viemChain, transport });
359
+ const publicClient = createPublicClient({ chain: cfg.viemChain, transport });
360
+ const txHash = await walletClient.writeContract({
361
+ address: cfg.identityContract,
362
+ abi,
363
+ functionName: "register",
364
+ args: [agentRegistrationUrl]
365
+ });
366
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
367
+ const { decodeEventLog } = await import("viem");
368
+ let tokenId;
369
+ for (const log of receipt.logs) {
370
+ try {
371
+ const decoded = decodeEventLog({ abi, data: log.data, topics: log.topics });
372
+ if (decoded.eventName === "Registered") {
373
+ tokenId = decoded.args.agentId;
374
+ break;
375
+ }
376
+ } catch {
377
+ }
378
+ }
379
+ if (tokenId === void 0) {
380
+ throw new Error("Registered event not found in transaction receipt");
381
+ }
382
+ await this.post(`/api/v1/onchain/agents/${agentId}/bind`, {
383
+ token_id: Number(tokenId),
384
+ chain: cfg.namespace,
385
+ tx_hash: txHash
386
+ });
387
+ return {
388
+ tokenId,
389
+ txHash,
390
+ chain: cfg.namespace,
391
+ agentRegistrationUrl,
392
+ walletAddress: account.address,
393
+ walletGenerated
394
+ };
395
+ }
396
+ /** @internal Save generated wallet credentials to a .env file. */
397
+ async _saveWalletToEnv(path, privateKey) {
398
+ if (typeof window !== "undefined") return;
399
+ try {
400
+ const fs = await import("fs/promises");
401
+ let content = "";
402
+ try {
403
+ content = await fs.readFile(path, "utf8");
404
+ } catch {
405
+ }
406
+ const existing = new Set(content.split("\n").map((l) => l.split("=")[0].trim()));
407
+ const toAdd = [];
408
+ if (!existing.has("WALLET_PRIVATE_KEY")) toAdd.push(`WALLET_PRIVATE_KEY=${privateKey}`);
409
+ if (toAdd.length) await fs.appendFile(path, "\n" + toAdd.join("\n") + "\n");
410
+ } catch {
411
+ }
412
+ }
413
+ /** Get audit statistics */
414
+ async getAuditStats(options) {
415
+ return this.get("/api/v1/audit/stats", options);
416
+ }
417
+ };
418
+ var ACNError = class extends Error {
419
+ constructor(status, message) {
420
+ super(message);
421
+ this.status = status;
422
+ this.name = "ACNError";
423
+ }
424
+ };
425
+
426
+ // src/realtime.ts
427
+ var ACNRealtime = class {
428
+ baseUrl;
429
+ options;
430
+ ws = null;
431
+ state = "disconnected";
432
+ reconnectAttempts = 0;
433
+ heartbeatTimer = null;
434
+ reconnectTimer = null;
435
+ channels = /* @__PURE__ */ new Map();
436
+ globalHandlers = /* @__PURE__ */ new Set();
437
+ stateHandlers = /* @__PURE__ */ new Set();
438
+ constructor(baseUrl, options) {
439
+ this.baseUrl = baseUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:").replace(/\/$/, "");
440
+ this.options = {
441
+ autoReconnect: options?.autoReconnect ?? true,
442
+ reconnectInterval: options?.reconnectInterval ?? 3e3,
443
+ maxReconnectAttempts: options?.maxReconnectAttempts ?? 10,
444
+ heartbeatInterval: options?.heartbeatInterval ?? 3e4
445
+ };
446
+ }
447
+ /** Current connection state */
448
+ get connectionState() {
449
+ return this.state;
450
+ }
451
+ /** Whether currently connected */
452
+ get isConnected() {
453
+ return this.state === "connected";
454
+ }
455
+ /**
456
+ * Connect to a channel
457
+ */
458
+ async connect(channel = "default") {
459
+ if (this.ws && this.state === "connected") {
460
+ return;
461
+ }
462
+ return new Promise((resolve, reject) => {
463
+ this.setState("connecting");
464
+ try {
465
+ this.ws = new WebSocket(`${this.baseUrl}/ws/${channel}`);
466
+ this.ws.onopen = () => {
467
+ this.setState("connected");
468
+ this.reconnectAttempts = 0;
469
+ this.startHeartbeat();
470
+ resolve();
471
+ };
472
+ this.ws.onclose = (event) => {
473
+ this.stopHeartbeat();
474
+ if (event.wasClean) {
475
+ this.setState("disconnected");
476
+ } else if (this.options.autoReconnect && this.reconnectAttempts < this.options.maxReconnectAttempts) {
477
+ this.scheduleReconnect(channel);
478
+ } else {
479
+ this.setState("disconnected");
480
+ }
481
+ };
482
+ this.ws.onerror = (error) => {
483
+ if (this.state === "connecting") {
484
+ reject(new Error("WebSocket connection failed"));
485
+ }
486
+ this.handleError(error);
487
+ };
488
+ this.ws.onmessage = (event) => {
489
+ this.handleMessage(event.data);
490
+ };
491
+ } catch (error) {
492
+ this.setState("disconnected");
493
+ reject(error);
494
+ }
495
+ });
496
+ }
497
+ /**
498
+ * Disconnect from server
499
+ */
500
+ disconnect() {
501
+ this.stopHeartbeat();
502
+ this.clearReconnectTimer();
503
+ if (this.ws) {
504
+ this.ws.close(1e3, "Client disconnect");
505
+ this.ws = null;
506
+ }
507
+ this.setState("disconnected");
508
+ }
509
+ /**
510
+ * Subscribe to a channel
511
+ */
512
+ subscribe(channel, handler) {
513
+ let handlers = this.channels.get(channel);
514
+ if (!handlers) {
515
+ handlers = /* @__PURE__ */ new Set();
516
+ this.channels.set(channel, handlers);
517
+ }
518
+ handlers.add(handler);
519
+ return () => {
520
+ handlers?.delete(handler);
521
+ if (handlers?.size === 0) {
522
+ this.channels.delete(channel);
523
+ }
524
+ };
525
+ }
526
+ /**
527
+ * Subscribe to all messages
528
+ */
529
+ onMessage(handler) {
530
+ this.globalHandlers.add(handler);
531
+ return () => {
532
+ this.globalHandlers.delete(handler);
533
+ };
534
+ }
535
+ /**
536
+ * Subscribe to state changes
537
+ */
538
+ onStateChange(handler) {
539
+ this.stateHandlers.add(handler);
540
+ return () => {
541
+ this.stateHandlers.delete(handler);
542
+ };
543
+ }
544
+ /**
545
+ * Send a message
546
+ */
547
+ send(message) {
548
+ if (!this.ws || this.state !== "connected") {
549
+ throw new Error("WebSocket not connected");
550
+ }
551
+ this.ws.send(JSON.stringify(message));
552
+ }
553
+ // ============================================
554
+ // Private Methods
555
+ // ============================================
556
+ setState(state) {
557
+ this.state = state;
558
+ this.stateHandlers.forEach((handler) => handler(state));
559
+ }
560
+ handleMessage(data) {
561
+ try {
562
+ const message = JSON.parse(data);
563
+ this.globalHandlers.forEach((handler) => handler(message));
564
+ const channelHandlers = this.channels.get(message.channel);
565
+ if (channelHandlers) {
566
+ channelHandlers.forEach((handler) => handler(message));
567
+ }
568
+ const typeHandlers = this.channels.get(message.type);
569
+ if (typeHandlers) {
570
+ typeHandlers.forEach((handler) => handler(message));
571
+ }
572
+ } catch (error) {
573
+ console.error("Failed to parse WebSocket message:", error);
574
+ }
575
+ }
576
+ handleError(error) {
577
+ console.error("WebSocket error:", error);
578
+ }
579
+ startHeartbeat() {
580
+ this.stopHeartbeat();
581
+ this.heartbeatTimer = setInterval(() => {
582
+ if (this.ws && this.state === "connected") {
583
+ this.send({ type: "ping", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
584
+ }
585
+ }, this.options.heartbeatInterval);
586
+ }
587
+ stopHeartbeat() {
588
+ if (this.heartbeatTimer) {
589
+ clearInterval(this.heartbeatTimer);
590
+ this.heartbeatTimer = null;
591
+ }
592
+ }
593
+ scheduleReconnect(channel) {
594
+ this.clearReconnectTimer();
595
+ this.setState("reconnecting");
596
+ this.reconnectAttempts++;
597
+ const delay = this.options.reconnectInterval * Math.min(this.reconnectAttempts, 5);
598
+ this.reconnectTimer = setTimeout(() => {
599
+ this.connect(channel).catch(() => {
600
+ });
601
+ }, delay);
602
+ }
603
+ clearReconnectTimer() {
604
+ if (this.reconnectTimer) {
605
+ clearTimeout(this.reconnectTimer);
606
+ this.reconnectTimer = null;
607
+ }
608
+ }
609
+ };
610
+ function subscribeToACN(baseUrl, channel, handler) {
611
+ const realtime = new ACNRealtime(baseUrl);
612
+ const unsubscribe = realtime.subscribe(channel, handler);
613
+ realtime.connect(channel).catch((error) => {
614
+ console.error("Failed to connect to ACN:", error);
615
+ });
616
+ return () => {
617
+ unsubscribe();
618
+ realtime.disconnect();
619
+ };
620
+ }
621
+ // Annotate the CommonJS export names for ESM import in node:
622
+ 0 && (module.exports = {
623
+ ACNClient,
624
+ ACNError,
625
+ ACNRealtime,
626
+ subscribeToACN
627
+ });