@anycast/agent 0.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.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # @anycast/agent
2
+
3
+ TypeScript SDK for [Anycast Agents](https://agents.anycast.com) P2P connectivity.
4
+
5
+ Connect agents to the Anycast network for peer-to-peer communication with relay fallback, powered by the global Anycast edge infrastructure.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @anycast/agent
11
+ # or
12
+ pnpm add @anycast/agent
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { AnycastAgent } from '@anycast/agent';
19
+
20
+ const agent = new AnycastAgent({
21
+ rendezvousUrl: 'wss://agents.anycast.com/rendezvous',
22
+ token: 'agt_your_token_here',
23
+ });
24
+
25
+ agent.on('connect', ({ agentId }) => {
26
+ console.log(`Connected as ${agentId}`);
27
+ });
28
+
29
+ agent.on('peer', (peerId, status) => {
30
+ console.log(`Peer ${peerId} is ${status}`);
31
+ });
32
+
33
+ agent.on('error', (err) => {
34
+ console.error(`Error: ${err.message} (${err.code})`);
35
+ });
36
+
37
+ await agent.connect();
38
+
39
+ // Request a connection to another agent
40
+ const sessionId = agent.requestConnection('target-agent-id');
41
+
42
+ // Handle incoming WebRTC signals
43
+ agent.on('signal', (sessionId, fromAgentId, signal) => {
44
+ // Forward to your WebRTC implementation
45
+ });
46
+
47
+ // Send WebRTC signals
48
+ agent.sendSignal(sessionId, 'target-agent-id', {
49
+ type: 'offer',
50
+ data: JSON.stringify(sdpOffer),
51
+ });
52
+
53
+ // Disconnect when done
54
+ await agent.disconnect();
55
+ ```
56
+
57
+ ## API Reference
58
+
59
+ ### `new AnycastAgent(options)`
60
+
61
+ Create a new agent instance.
62
+
63
+ | Option | Type | Default | Description |
64
+ |--------|------|---------|-------------|
65
+ | `rendezvousUrl` | `string` | required | WebSocket URL of the rendezvous server |
66
+ | `token` | `string` | required | Agent auth token from the Anycast portal |
67
+ | `machineId` | `string` | auto-generated | Unique machine identifier |
68
+ | `publicKey` | `string` | auto-generated | Public key for E2E encryption |
69
+ | `version` | `string` | — | Agent version string |
70
+ | `reconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
71
+ | `reconnectInterval` | `number` | `3000` | Base reconnect interval (ms) |
72
+ | `timeout` | `number` | `30000` | Connection timeout (ms) |
73
+ | `heartbeatInterval` | `number` | `25000` | Heartbeat interval (ms) |
74
+
75
+ ### Properties
76
+
77
+ | Property | Type | Description |
78
+ |----------|------|-------------|
79
+ | `agentId` | `string \| null` | Assigned agent ID (set after connect) |
80
+ | `tenantId` | `string \| null` | Tenant ID (set after connect) |
81
+ | `connected` | `boolean` | Whether the agent is connected |
82
+ | `connectionState` | `ConnectionState` | Current connection state |
83
+
84
+ ### Methods
85
+
86
+ #### `connect(): Promise<void>`
87
+ Connect to the rendezvous server. Resolves when registered.
88
+
89
+ #### `disconnect(): Promise<void>`
90
+ Disconnect from the server.
91
+
92
+ #### `requestConnection(targetAgentId: string): string`
93
+ Request a P2P connection to another agent. Returns a session ID.
94
+
95
+ #### `sendSignal(sessionId, targetAgentId, signal)`
96
+ Send a WebRTC signal (offer/answer/ICE candidate) via the rendezvous server.
97
+
98
+ #### `disconnectSession(sessionId: string)`
99
+ Close a specific session.
100
+
101
+ ### Events
102
+
103
+ | Event | Payload | Description |
104
+ |-------|---------|-------------|
105
+ | `connect` | `{ agentId, tenantId, name }` | Connected and registered |
106
+ | `disconnect` | `reason: string` | Disconnected |
107
+ | `reconnecting` | `attempt: number` | Attempting reconnection |
108
+ | `peer` | `agentId, status` | Peer came online/offline |
109
+ | `connect_incoming` | `sessionId, fromAgentId, fromAgentName` | Incoming connection request |
110
+ | `connect_accepted` | `sessionId, agentId, publicKey` | Connection accepted by peer |
111
+ | `connect_rejected` | `sessionId, reason` | Connection rejected |
112
+ | `signal` | `sessionId, fromAgentId, signal` | WebRTC signal from peer |
113
+ | `relay_assigned` | `sessionId, host, port, token` | Relay server assigned |
114
+ | `error` | `AnycastError` | Error occurred |
115
+ | `stateChange` | `ConnectionState` | Connection state changed |
116
+
117
+ ### ConnectionState
118
+
119
+ ```typescript
120
+ enum ConnectionState {
121
+ DISCONNECTED = 'DISCONNECTED',
122
+ CONNECTING = 'CONNECTING',
123
+ CONNECTED = 'CONNECTED',
124
+ RECONNECTING = 'RECONNECTING',
125
+ }
126
+ ```
127
+
128
+ ### AnycastError
129
+
130
+ ```typescript
131
+ class AnycastError extends Error {
132
+ code: string; // Error code (e.g., 'AUTH_FAILED', 'TIMEOUT')
133
+ fatal: boolean; // If true, reconnection won't help
134
+ }
135
+ ```
136
+
137
+ ## Auto-Reconnection
138
+
139
+ By default, the agent automatically reconnects with exponential backoff (3s, 6s, 12s, ..., max 30s). Disable with `reconnect: false`.
140
+
141
+ ## License
142
+
143
+ MIT
@@ -0,0 +1,135 @@
1
+ import EventEmitter from 'eventemitter3';
2
+
3
+ /**
4
+ * Agent SDK types — mirrors the rendezvous server protocol.
5
+ */
6
+ interface AnycastAgentOptions {
7
+ /** WebSocket URL of the rendezvous server (e.g., wss://agents.anycast.com/rendezvous) */
8
+ rendezvousUrl: string;
9
+ /** Agent auth token from the Anycast portal */
10
+ token: string;
11
+ /** Unique machine identifier (auto-generated if not provided) */
12
+ machineId?: string;
13
+ /** Public key for end-to-end encryption */
14
+ publicKey?: string;
15
+ /** Agent version string */
16
+ version?: string;
17
+ /** Auto-reconnect on disconnect (default: true) */
18
+ reconnect?: boolean;
19
+ /** Reconnect interval in ms (default: 3000) */
20
+ reconnectInterval?: number;
21
+ /** Connection timeout in ms (default: 30000) */
22
+ timeout?: number;
23
+ /** Heartbeat interval in ms (default: 25000) */
24
+ heartbeatInterval?: number;
25
+ }
26
+ declare enum ConnectionState {
27
+ DISCONNECTED = "DISCONNECTED",
28
+ CONNECTING = "CONNECTING",
29
+ CONNECTED = "CONNECTED",
30
+ RECONNECTING = "RECONNECTING"
31
+ }
32
+ interface AnycastAgentEvents {
33
+ connect: (info: {
34
+ agentId: string;
35
+ tenantId: string;
36
+ name: string;
37
+ }) => void;
38
+ disconnect: (reason: string) => void;
39
+ reconnecting: (attempt: number) => void;
40
+ message: (fromAgentId: string, data: string) => void;
41
+ peer: (agentId: string, status: 'online' | 'offline') => void;
42
+ 'connect_incoming': (sessionId: string, fromAgentId: string, fromAgentName: string) => void;
43
+ 'connect_accepted': (sessionId: string, agentId: string, publicKey: string) => void;
44
+ 'connect_rejected': (sessionId: string, reason: string) => void;
45
+ signal: (sessionId: string, fromAgentId: string, signal: {
46
+ type: string;
47
+ data: string;
48
+ }) => void;
49
+ 'relay_assigned': (sessionId: string, relayHost: string, relayPort: number, relayToken: string) => void;
50
+ error: (error: AnycastError) => void;
51
+ stateChange: (state: ConnectionState) => void;
52
+ }
53
+ declare class AnycastError extends Error {
54
+ code: string;
55
+ fatal: boolean;
56
+ constructor(message: string, code: string, fatal?: boolean);
57
+ }
58
+
59
+ /**
60
+ * AnycastAgent — connects to the Anycast Agents rendezvous server
61
+ * for P2P connectivity, signaling, and relay fallback.
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const agent = new AnycastAgent({
66
+ * rendezvousUrl: 'wss://agents.anycast.com/rendezvous',
67
+ * token: 'agt_...',
68
+ * });
69
+ *
70
+ * agent.on('connect', ({ agentId }) => {
71
+ * console.log(`Connected as ${agentId}`);
72
+ * });
73
+ *
74
+ * agent.on('peer', (peerId, status) => {
75
+ * console.log(`Peer ${peerId} is ${status}`);
76
+ * });
77
+ *
78
+ * await agent.connect();
79
+ * ```
80
+ */
81
+ declare class AnycastAgent extends EventEmitter<AnycastAgentEvents> {
82
+ private ws;
83
+ private state;
84
+ private heartbeatTimer;
85
+ private reconnectTimer;
86
+ private reconnectAttempt;
87
+ private intentionalClose;
88
+ /** Agent ID assigned by the server after registration */
89
+ agentId: string | null;
90
+ /** Tenant ID from the server */
91
+ tenantId: string | null;
92
+ /** Agent name from the server */
93
+ agentName: string | null;
94
+ private readonly opts;
95
+ constructor(options: AnycastAgentOptions);
96
+ /** Current connection state */
97
+ get connectionState(): ConnectionState;
98
+ /** Whether the agent is connected */
99
+ get connected(): boolean;
100
+ /**
101
+ * Connect to the rendezvous server.
102
+ * Resolves when registered, rejects on timeout or fatal error.
103
+ */
104
+ connect(): Promise<void>;
105
+ /**
106
+ * Disconnect from the rendezvous server.
107
+ */
108
+ disconnect(): Promise<void>;
109
+ /**
110
+ * Request a connection to a peer agent.
111
+ * @returns The session ID for this connection
112
+ */
113
+ requestConnection(targetAgentId: string): string;
114
+ /**
115
+ * Send a WebRTC signal to a peer via the rendezvous server.
116
+ */
117
+ sendSignal(sessionId: string, targetAgentId: string, signal: {
118
+ type: 'offer' | 'answer' | 'ice';
119
+ data: string;
120
+ }): void;
121
+ /**
122
+ * Disconnect a session.
123
+ */
124
+ disconnectSession(sessionId: string): void;
125
+ private handleMessage;
126
+ private send;
127
+ private startHeartbeat;
128
+ private stopHeartbeat;
129
+ private scheduleReconnect;
130
+ private cleanup;
131
+ private setState;
132
+ private assertConnected;
133
+ }
134
+
135
+ export { AnycastAgent, type AnycastAgentEvents, type AnycastAgentOptions, AnycastError, ConnectionState };
@@ -0,0 +1,135 @@
1
+ import EventEmitter from 'eventemitter3';
2
+
3
+ /**
4
+ * Agent SDK types — mirrors the rendezvous server protocol.
5
+ */
6
+ interface AnycastAgentOptions {
7
+ /** WebSocket URL of the rendezvous server (e.g., wss://agents.anycast.com/rendezvous) */
8
+ rendezvousUrl: string;
9
+ /** Agent auth token from the Anycast portal */
10
+ token: string;
11
+ /** Unique machine identifier (auto-generated if not provided) */
12
+ machineId?: string;
13
+ /** Public key for end-to-end encryption */
14
+ publicKey?: string;
15
+ /** Agent version string */
16
+ version?: string;
17
+ /** Auto-reconnect on disconnect (default: true) */
18
+ reconnect?: boolean;
19
+ /** Reconnect interval in ms (default: 3000) */
20
+ reconnectInterval?: number;
21
+ /** Connection timeout in ms (default: 30000) */
22
+ timeout?: number;
23
+ /** Heartbeat interval in ms (default: 25000) */
24
+ heartbeatInterval?: number;
25
+ }
26
+ declare enum ConnectionState {
27
+ DISCONNECTED = "DISCONNECTED",
28
+ CONNECTING = "CONNECTING",
29
+ CONNECTED = "CONNECTED",
30
+ RECONNECTING = "RECONNECTING"
31
+ }
32
+ interface AnycastAgentEvents {
33
+ connect: (info: {
34
+ agentId: string;
35
+ tenantId: string;
36
+ name: string;
37
+ }) => void;
38
+ disconnect: (reason: string) => void;
39
+ reconnecting: (attempt: number) => void;
40
+ message: (fromAgentId: string, data: string) => void;
41
+ peer: (agentId: string, status: 'online' | 'offline') => void;
42
+ 'connect_incoming': (sessionId: string, fromAgentId: string, fromAgentName: string) => void;
43
+ 'connect_accepted': (sessionId: string, agentId: string, publicKey: string) => void;
44
+ 'connect_rejected': (sessionId: string, reason: string) => void;
45
+ signal: (sessionId: string, fromAgentId: string, signal: {
46
+ type: string;
47
+ data: string;
48
+ }) => void;
49
+ 'relay_assigned': (sessionId: string, relayHost: string, relayPort: number, relayToken: string) => void;
50
+ error: (error: AnycastError) => void;
51
+ stateChange: (state: ConnectionState) => void;
52
+ }
53
+ declare class AnycastError extends Error {
54
+ code: string;
55
+ fatal: boolean;
56
+ constructor(message: string, code: string, fatal?: boolean);
57
+ }
58
+
59
+ /**
60
+ * AnycastAgent — connects to the Anycast Agents rendezvous server
61
+ * for P2P connectivity, signaling, and relay fallback.
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const agent = new AnycastAgent({
66
+ * rendezvousUrl: 'wss://agents.anycast.com/rendezvous',
67
+ * token: 'agt_...',
68
+ * });
69
+ *
70
+ * agent.on('connect', ({ agentId }) => {
71
+ * console.log(`Connected as ${agentId}`);
72
+ * });
73
+ *
74
+ * agent.on('peer', (peerId, status) => {
75
+ * console.log(`Peer ${peerId} is ${status}`);
76
+ * });
77
+ *
78
+ * await agent.connect();
79
+ * ```
80
+ */
81
+ declare class AnycastAgent extends EventEmitter<AnycastAgentEvents> {
82
+ private ws;
83
+ private state;
84
+ private heartbeatTimer;
85
+ private reconnectTimer;
86
+ private reconnectAttempt;
87
+ private intentionalClose;
88
+ /** Agent ID assigned by the server after registration */
89
+ agentId: string | null;
90
+ /** Tenant ID from the server */
91
+ tenantId: string | null;
92
+ /** Agent name from the server */
93
+ agentName: string | null;
94
+ private readonly opts;
95
+ constructor(options: AnycastAgentOptions);
96
+ /** Current connection state */
97
+ get connectionState(): ConnectionState;
98
+ /** Whether the agent is connected */
99
+ get connected(): boolean;
100
+ /**
101
+ * Connect to the rendezvous server.
102
+ * Resolves when registered, rejects on timeout or fatal error.
103
+ */
104
+ connect(): Promise<void>;
105
+ /**
106
+ * Disconnect from the rendezvous server.
107
+ */
108
+ disconnect(): Promise<void>;
109
+ /**
110
+ * Request a connection to a peer agent.
111
+ * @returns The session ID for this connection
112
+ */
113
+ requestConnection(targetAgentId: string): string;
114
+ /**
115
+ * Send a WebRTC signal to a peer via the rendezvous server.
116
+ */
117
+ sendSignal(sessionId: string, targetAgentId: string, signal: {
118
+ type: 'offer' | 'answer' | 'ice';
119
+ data: string;
120
+ }): void;
121
+ /**
122
+ * Disconnect a session.
123
+ */
124
+ disconnectSession(sessionId: string): void;
125
+ private handleMessage;
126
+ private send;
127
+ private startHeartbeat;
128
+ private stopHeartbeat;
129
+ private scheduleReconnect;
130
+ private cleanup;
131
+ private setState;
132
+ private assertConnected;
133
+ }
134
+
135
+ export { AnycastAgent, type AnycastAgentEvents, type AnycastAgentOptions, AnycastError, ConnectionState };
package/dist/index.js ADDED
@@ -0,0 +1,303 @@
1
+ 'use strict';
2
+
3
+ var WebSocket = require('ws');
4
+ var EventEmitter = require('eventemitter3');
5
+ var crypto = require('crypto');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var WebSocket__default = /*#__PURE__*/_interopDefault(WebSocket);
10
+ var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
11
+
12
+ // src/agent.ts
13
+
14
+ // src/types.ts
15
+ var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
16
+ ConnectionState2["DISCONNECTED"] = "DISCONNECTED";
17
+ ConnectionState2["CONNECTING"] = "CONNECTING";
18
+ ConnectionState2["CONNECTED"] = "CONNECTED";
19
+ ConnectionState2["RECONNECTING"] = "RECONNECTING";
20
+ return ConnectionState2;
21
+ })(ConnectionState || {});
22
+ var AnycastError = class extends Error {
23
+ constructor(message, code, fatal = false) {
24
+ super(message);
25
+ this.code = code;
26
+ this.fatal = fatal;
27
+ this.name = "AnycastError";
28
+ }
29
+ };
30
+
31
+ // src/agent.ts
32
+ var DEFAULT_OPTIONS = {
33
+ reconnect: true,
34
+ reconnectInterval: 3e3,
35
+ timeout: 3e4,
36
+ heartbeatInterval: 25e3
37
+ };
38
+ var AnycastAgent = class extends EventEmitter__default.default {
39
+ constructor(options) {
40
+ super();
41
+ this.ws = null;
42
+ this.state = "DISCONNECTED" /* DISCONNECTED */;
43
+ this.heartbeatTimer = null;
44
+ this.reconnectTimer = null;
45
+ this.reconnectAttempt = 0;
46
+ this.intentionalClose = false;
47
+ /** Agent ID assigned by the server after registration */
48
+ this.agentId = null;
49
+ /** Tenant ID from the server */
50
+ this.tenantId = null;
51
+ /** Agent name from the server */
52
+ this.agentName = null;
53
+ this.opts = {
54
+ rendezvousUrl: options.rendezvousUrl,
55
+ token: options.token,
56
+ machineId: options.machineId || crypto.randomBytes(16).toString("hex"),
57
+ publicKey: options.publicKey || crypto.randomBytes(32).toString("base64"),
58
+ version: options.version,
59
+ reconnect: options.reconnect ?? DEFAULT_OPTIONS.reconnect,
60
+ reconnectInterval: options.reconnectInterval ?? DEFAULT_OPTIONS.reconnectInterval,
61
+ timeout: options.timeout ?? DEFAULT_OPTIONS.timeout,
62
+ heartbeatInterval: options.heartbeatInterval ?? DEFAULT_OPTIONS.heartbeatInterval
63
+ };
64
+ }
65
+ /** Current connection state */
66
+ get connectionState() {
67
+ return this.state;
68
+ }
69
+ /** Whether the agent is connected */
70
+ get connected() {
71
+ return this.state === "CONNECTED" /* CONNECTED */;
72
+ }
73
+ /**
74
+ * Connect to the rendezvous server.
75
+ * Resolves when registered, rejects on timeout or fatal error.
76
+ */
77
+ async connect() {
78
+ if (this.state === "CONNECTED" /* CONNECTED */ || this.state === "CONNECTING" /* CONNECTING */) {
79
+ return;
80
+ }
81
+ this.intentionalClose = false;
82
+ this.setState("CONNECTING" /* CONNECTING */);
83
+ return new Promise((resolve, reject) => {
84
+ const timeout = setTimeout(() => {
85
+ this.cleanup();
86
+ const err = new AnycastError("Connection timeout", "TIMEOUT", false);
87
+ this.emit("error", err);
88
+ reject(err);
89
+ }, this.opts.timeout);
90
+ try {
91
+ this.ws = new WebSocket__default.default(this.opts.rendezvousUrl);
92
+ } catch (err) {
93
+ clearTimeout(timeout);
94
+ const error = new AnycastError(
95
+ `Failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`,
96
+ "CONNECTION_FAILED",
97
+ false
98
+ );
99
+ this.setState("DISCONNECTED" /* DISCONNECTED */);
100
+ reject(error);
101
+ return;
102
+ }
103
+ this.ws.on("open", () => {
104
+ this.send({
105
+ type: "register",
106
+ token: this.opts.token,
107
+ machineId: this.opts.machineId,
108
+ publicKey: this.opts.publicKey,
109
+ version: this.opts.version
110
+ });
111
+ });
112
+ this.ws.on("message", (data) => {
113
+ let msg;
114
+ try {
115
+ msg = JSON.parse(data.toString());
116
+ } catch {
117
+ return;
118
+ }
119
+ if (msg.type === "registered") {
120
+ clearTimeout(timeout);
121
+ this.agentId = msg.agentId;
122
+ this.tenantId = msg.tenantId;
123
+ this.agentName = msg.name;
124
+ this.reconnectAttempt = 0;
125
+ this.setState("CONNECTED" /* CONNECTED */);
126
+ this.startHeartbeat();
127
+ this.emit("connect", { agentId: msg.agentId, tenantId: msg.tenantId, name: msg.name });
128
+ resolve();
129
+ return;
130
+ }
131
+ if (msg.type === "error" && msg.fatal) {
132
+ clearTimeout(timeout);
133
+ const err = new AnycastError(msg.message, msg.code, true);
134
+ this.emit("error", err);
135
+ this.cleanup();
136
+ reject(err);
137
+ return;
138
+ }
139
+ this.handleMessage(msg);
140
+ });
141
+ this.ws.on("close", (code, reason) => {
142
+ clearTimeout(timeout);
143
+ this.stopHeartbeat();
144
+ const wasConnected = this.state === "CONNECTED" /* CONNECTED */;
145
+ this.setState("DISCONNECTED" /* DISCONNECTED */);
146
+ if (wasConnected) {
147
+ this.emit("disconnect", reason?.toString() || `Code ${code}`);
148
+ }
149
+ if (!this.intentionalClose && this.opts.reconnect) {
150
+ this.scheduleReconnect();
151
+ } else if (this.state !== "CONNECTED" /* CONNECTED */) {
152
+ reject(new AnycastError("Connection closed", "CLOSED", false));
153
+ }
154
+ });
155
+ this.ws.on("error", (err) => {
156
+ const error = new AnycastError(err.message, "WS_ERROR", false);
157
+ this.emit("error", error);
158
+ });
159
+ });
160
+ }
161
+ /**
162
+ * Disconnect from the rendezvous server.
163
+ */
164
+ async disconnect() {
165
+ this.intentionalClose = true;
166
+ this.cleanup();
167
+ this.agentId = null;
168
+ this.tenantId = null;
169
+ this.agentName = null;
170
+ }
171
+ /**
172
+ * Request a connection to a peer agent.
173
+ * @returns The session ID for this connection
174
+ */
175
+ requestConnection(targetAgentId) {
176
+ this.assertConnected();
177
+ const sessionId = crypto.randomBytes(16).toString("hex");
178
+ this.send({
179
+ type: "connect_request",
180
+ targetAgentId,
181
+ sessionId
182
+ });
183
+ return sessionId;
184
+ }
185
+ /**
186
+ * Send a WebRTC signal to a peer via the rendezvous server.
187
+ */
188
+ sendSignal(sessionId, targetAgentId, signal) {
189
+ this.assertConnected();
190
+ this.send({
191
+ type: "signal",
192
+ sessionId,
193
+ targetAgentId,
194
+ signal
195
+ });
196
+ }
197
+ /**
198
+ * Disconnect a session.
199
+ */
200
+ disconnectSession(sessionId) {
201
+ this.assertConnected();
202
+ this.send({ type: "disconnect", sessionId });
203
+ }
204
+ // ---------------------------------------------------------------------------
205
+ // Private
206
+ // ---------------------------------------------------------------------------
207
+ handleMessage(msg) {
208
+ switch (msg.type) {
209
+ case "heartbeat_ack":
210
+ break;
211
+ case "agent_status":
212
+ this.emit("peer", msg.agentId, msg.status);
213
+ break;
214
+ case "connect_incoming":
215
+ this.emit("connect_incoming", msg.sessionId, msg.fromAgentId, msg.fromAgentName);
216
+ break;
217
+ case "connect_accepted":
218
+ this.emit("connect_accepted", msg.sessionId, msg.agentId, msg.publicKey);
219
+ break;
220
+ case "connect_rejected":
221
+ this.emit("connect_rejected", msg.sessionId, msg.reason);
222
+ break;
223
+ case "signal_forward":
224
+ this.emit("signal", msg.sessionId, msg.fromAgentId, msg.signal);
225
+ break;
226
+ case "relay_assigned":
227
+ this.emit("relay_assigned", msg.sessionId, msg.relayHost, msg.relayPort, msg.relayToken);
228
+ break;
229
+ case "error":
230
+ this.emit("error", new AnycastError(msg.message, msg.code, msg.fatal || false));
231
+ if (msg.fatal) {
232
+ this.cleanup();
233
+ }
234
+ break;
235
+ }
236
+ }
237
+ send(msg) {
238
+ if (this.ws?.readyState === WebSocket__default.default.OPEN) {
239
+ this.ws.send(JSON.stringify(msg));
240
+ }
241
+ }
242
+ startHeartbeat() {
243
+ this.stopHeartbeat();
244
+ this.heartbeatTimer = setInterval(() => {
245
+ this.send({ type: "heartbeat" });
246
+ }, this.opts.heartbeatInterval);
247
+ }
248
+ stopHeartbeat() {
249
+ if (this.heartbeatTimer) {
250
+ clearInterval(this.heartbeatTimer);
251
+ this.heartbeatTimer = null;
252
+ }
253
+ }
254
+ scheduleReconnect() {
255
+ if (this.reconnectTimer) return;
256
+ this.reconnectAttempt++;
257
+ const delay = Math.min(
258
+ this.opts.reconnectInterval * Math.pow(2, this.reconnectAttempt - 1),
259
+ 3e4
260
+ );
261
+ this.setState("RECONNECTING" /* RECONNECTING */);
262
+ this.emit("reconnecting", this.reconnectAttempt);
263
+ this.reconnectTimer = setTimeout(async () => {
264
+ this.reconnectTimer = null;
265
+ try {
266
+ await this.connect();
267
+ } catch {
268
+ }
269
+ }, delay);
270
+ }
271
+ cleanup() {
272
+ this.stopHeartbeat();
273
+ if (this.reconnectTimer) {
274
+ clearTimeout(this.reconnectTimer);
275
+ this.reconnectTimer = null;
276
+ }
277
+ if (this.ws) {
278
+ this.ws.removeAllListeners();
279
+ if (this.ws.readyState === WebSocket__default.default.OPEN || this.ws.readyState === WebSocket__default.default.CONNECTING) {
280
+ this.ws.close(1e3, "Client disconnect");
281
+ }
282
+ this.ws = null;
283
+ }
284
+ this.setState("DISCONNECTED" /* DISCONNECTED */);
285
+ }
286
+ setState(state) {
287
+ if (this.state !== state) {
288
+ this.state = state;
289
+ this.emit("stateChange", state);
290
+ }
291
+ }
292
+ assertConnected() {
293
+ if (this.state !== "CONNECTED" /* CONNECTED */ || !this.ws) {
294
+ throw new AnycastError("Not connected", "NOT_CONNECTED", false);
295
+ }
296
+ }
297
+ };
298
+
299
+ exports.AnycastAgent = AnycastAgent;
300
+ exports.AnycastError = AnycastError;
301
+ exports.ConnectionState = ConnectionState;
302
+ //# sourceMappingURL=index.js.map
303
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/agent.ts"],"names":["ConnectionState","EventEmitter","randomBytes","WebSocket"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Agent SDK types — mirrors the rendezvous server protocol.\n */\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface AnycastAgentOptions {\n /** WebSocket URL of the rendezvous server (e.g., wss://agents.anycast.com/rendezvous) */\n rendezvousUrl: string;\n /** Agent auth token from the Anycast portal */\n token: string;\n /** Unique machine identifier (auto-generated if not provided) */\n machineId?: string;\n /** Public key for end-to-end encryption */\n publicKey?: string;\n /** Agent version string */\n version?: string;\n /** Auto-reconnect on disconnect (default: true) */\n reconnect?: boolean;\n /** Reconnect interval in ms (default: 3000) */\n reconnectInterval?: number;\n /** Connection timeout in ms (default: 30000) */\n timeout?: number;\n /** Heartbeat interval in ms (default: 25000) */\n heartbeatInterval?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Connection state\n// ---------------------------------------------------------------------------\n\nexport enum ConnectionState {\n DISCONNECTED = 'DISCONNECTED',\n CONNECTING = 'CONNECTING',\n CONNECTED = 'CONNECTED',\n RECONNECTING = 'RECONNECTING',\n}\n\n// ---------------------------------------------------------------------------\n// Client → Server messages\n// ---------------------------------------------------------------------------\n\nexport interface RegisterMessage {\n type: 'register';\n token: string;\n machineId: string;\n publicKey: string;\n version?: string;\n os?: string;\n arch?: string;\n}\n\nexport interface HeartbeatMessage {\n type: 'heartbeat';\n}\n\nexport interface ConnectRequestMessage {\n type: 'connect_request';\n targetAgentId: string;\n sessionId: string;\n}\n\nexport interface SignalMessage {\n type: 'signal';\n sessionId: string;\n targetAgentId: string;\n signal: {\n type: 'offer' | 'answer' | 'ice';\n data: string;\n };\n}\n\nexport interface DisconnectMessage {\n type: 'disconnect';\n sessionId: string;\n}\n\nexport type ClientMessage =\n | RegisterMessage\n | HeartbeatMessage\n | ConnectRequestMessage\n | SignalMessage\n | DisconnectMessage;\n\n// ---------------------------------------------------------------------------\n// Server → Client messages\n// ---------------------------------------------------------------------------\n\nexport interface RegisteredMessage {\n type: 'registered';\n agentId: string;\n tenantId: string;\n name: string;\n}\n\nexport interface HeartbeatAckMessage {\n type: 'heartbeat_ack';\n timestamp: number;\n}\n\nexport interface ConnectIncomingMessage {\n type: 'connect_incoming';\n sessionId: string;\n fromAgentId: string;\n fromAgentName: string;\n}\n\nexport interface ConnectAcceptedMessage {\n type: 'connect_accepted';\n sessionId: string;\n agentId: string;\n agentName: string;\n publicKey: string;\n}\n\nexport interface ConnectRejectedMessage {\n type: 'connect_rejected';\n sessionId: string;\n reason: string;\n}\n\nexport interface SignalForwardMessage {\n type: 'signal_forward';\n sessionId: string;\n fromAgentId: string;\n signal: {\n type: 'offer' | 'answer' | 'ice';\n data: string;\n };\n}\n\nexport interface RelayAssignedMessage {\n type: 'relay_assigned';\n sessionId: string;\n relayHost: string;\n relayPort: number;\n relayToken: string;\n}\n\nexport interface AgentStatusMessage {\n type: 'agent_status';\n agentId: string;\n status: 'online' | 'offline';\n}\n\nexport interface ErrorMessage {\n type: 'error';\n code: string;\n message: string;\n fatal?: boolean;\n}\n\nexport type ServerMessage =\n | RegisteredMessage\n | HeartbeatAckMessage\n | ConnectIncomingMessage\n | ConnectAcceptedMessage\n | ConnectRejectedMessage\n | SignalForwardMessage\n | RelayAssignedMessage\n | AgentStatusMessage\n | ErrorMessage;\n\n// ---------------------------------------------------------------------------\n// Event map for typed event emitter\n// ---------------------------------------------------------------------------\n\nexport interface AnycastAgentEvents {\n connect: (info: { agentId: string; tenantId: string; name: string }) => void;\n disconnect: (reason: string) => void;\n reconnecting: (attempt: number) => void;\n message: (fromAgentId: string, data: string) => void;\n peer: (agentId: string, status: 'online' | 'offline') => void;\n 'connect_incoming': (sessionId: string, fromAgentId: string, fromAgentName: string) => void;\n 'connect_accepted': (sessionId: string, agentId: string, publicKey: string) => void;\n 'connect_rejected': (sessionId: string, reason: string) => void;\n signal: (sessionId: string, fromAgentId: string, signal: { type: string; data: string }) => void;\n 'relay_assigned': (sessionId: string, relayHost: string, relayPort: number, relayToken: string) => void;\n error: (error: AnycastError) => void;\n stateChange: (state: ConnectionState) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Error\n// ---------------------------------------------------------------------------\n\nexport class AnycastError extends Error {\n constructor(\n message: string,\n public code: string,\n public fatal: boolean = false\n ) {\n super(message);\n this.name = 'AnycastError';\n }\n}\n","import WebSocket from 'ws';\nimport EventEmitter from 'eventemitter3';\nimport { randomBytes } from 'crypto';\nimport {\n type AnycastAgentOptions,\n type AnycastAgentEvents,\n type ServerMessage,\n type ClientMessage,\n ConnectionState,\n AnycastError,\n} from './types.js';\n\nconst DEFAULT_OPTIONS = {\n reconnect: true,\n reconnectInterval: 3000,\n timeout: 30000,\n heartbeatInterval: 25000,\n};\n\n/**\n * AnycastAgent — connects to the Anycast Agents rendezvous server\n * for P2P connectivity, signaling, and relay fallback.\n *\n * @example\n * ```typescript\n * const agent = new AnycastAgent({\n * rendezvousUrl: 'wss://agents.anycast.com/rendezvous',\n * token: 'agt_...',\n * });\n *\n * agent.on('connect', ({ agentId }) => {\n * console.log(`Connected as ${agentId}`);\n * });\n *\n * agent.on('peer', (peerId, status) => {\n * console.log(`Peer ${peerId} is ${status}`);\n * });\n *\n * await agent.connect();\n * ```\n */\nexport class AnycastAgent extends EventEmitter<AnycastAgentEvents> {\n private ws: WebSocket | null = null;\n private state: ConnectionState = ConnectionState.DISCONNECTED;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private reconnectAttempt = 0;\n private intentionalClose = false;\n\n /** Agent ID assigned by the server after registration */\n public agentId: string | null = null;\n /** Tenant ID from the server */\n public tenantId: string | null = null;\n /** Agent name from the server */\n public agentName: string | null = null;\n\n private readonly opts: Required<\n Pick<AnycastAgentOptions, 'rendezvousUrl' | 'token' | 'machineId' | 'publicKey' | 'reconnect' | 'reconnectInterval' | 'timeout' | 'heartbeatInterval'>\n > & Pick<AnycastAgentOptions, 'version'>;\n\n constructor(options: AnycastAgentOptions) {\n super();\n\n this.opts = {\n rendezvousUrl: options.rendezvousUrl,\n token: options.token,\n machineId: options.machineId || randomBytes(16).toString('hex'),\n publicKey: options.publicKey || randomBytes(32).toString('base64'),\n version: options.version,\n reconnect: options.reconnect ?? DEFAULT_OPTIONS.reconnect,\n reconnectInterval: options.reconnectInterval ?? DEFAULT_OPTIONS.reconnectInterval,\n timeout: options.timeout ?? DEFAULT_OPTIONS.timeout,\n heartbeatInterval: options.heartbeatInterval ?? DEFAULT_OPTIONS.heartbeatInterval,\n };\n }\n\n /** Current connection state */\n get connectionState(): ConnectionState {\n return this.state;\n }\n\n /** Whether the agent is connected */\n get connected(): boolean {\n return this.state === ConnectionState.CONNECTED;\n }\n\n /**\n * Connect to the rendezvous server.\n * Resolves when registered, rejects on timeout or fatal error.\n */\n async connect(): Promise<void> {\n if (this.state === ConnectionState.CONNECTED || this.state === ConnectionState.CONNECTING) {\n return;\n }\n\n this.intentionalClose = false;\n this.setState(ConnectionState.CONNECTING);\n\n return new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.cleanup();\n const err = new AnycastError('Connection timeout', 'TIMEOUT', false);\n this.emit('error', err);\n reject(err);\n }, this.opts.timeout);\n\n try {\n this.ws = new WebSocket(this.opts.rendezvousUrl);\n } catch (err) {\n clearTimeout(timeout);\n const error = new AnycastError(\n `Failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`,\n 'CONNECTION_FAILED',\n false\n );\n this.setState(ConnectionState.DISCONNECTED);\n reject(error);\n return;\n }\n\n this.ws.on('open', () => {\n this.send({\n type: 'register',\n token: this.opts.token,\n machineId: this.opts.machineId,\n publicKey: this.opts.publicKey,\n version: this.opts.version,\n });\n });\n\n this.ws.on('message', (data: WebSocket.Data) => {\n let msg: ServerMessage;\n try {\n msg = JSON.parse(data.toString());\n } catch {\n return;\n }\n\n if (msg.type === 'registered') {\n clearTimeout(timeout);\n this.agentId = msg.agentId;\n this.tenantId = msg.tenantId;\n this.agentName = msg.name;\n this.reconnectAttempt = 0;\n this.setState(ConnectionState.CONNECTED);\n this.startHeartbeat();\n this.emit('connect', { agentId: msg.agentId, tenantId: msg.tenantId, name: msg.name });\n resolve();\n return;\n }\n\n if (msg.type === 'error' && msg.fatal) {\n clearTimeout(timeout);\n const err = new AnycastError(msg.message, msg.code, true);\n this.emit('error', err);\n this.cleanup();\n reject(err);\n return;\n }\n\n // Route other messages after registration\n this.handleMessage(msg);\n });\n\n this.ws.on('close', (code, reason) => {\n clearTimeout(timeout);\n this.stopHeartbeat();\n const wasConnected = this.state === ConnectionState.CONNECTED;\n this.setState(ConnectionState.DISCONNECTED);\n\n if (wasConnected) {\n this.emit('disconnect', reason?.toString() || `Code ${code}`);\n }\n\n if (!this.intentionalClose && this.opts.reconnect) {\n this.scheduleReconnect();\n } else if (this.state !== ConnectionState.CONNECTED) {\n reject(new AnycastError('Connection closed', 'CLOSED', false));\n }\n });\n\n this.ws.on('error', (err) => {\n const error = new AnycastError(err.message, 'WS_ERROR', false);\n this.emit('error', error);\n });\n });\n }\n\n /**\n * Disconnect from the rendezvous server.\n */\n async disconnect(): Promise<void> {\n this.intentionalClose = true;\n this.cleanup();\n this.agentId = null;\n this.tenantId = null;\n this.agentName = null;\n }\n\n /**\n * Request a connection to a peer agent.\n * @returns The session ID for this connection\n */\n requestConnection(targetAgentId: string): string {\n this.assertConnected();\n const sessionId = randomBytes(16).toString('hex');\n this.send({\n type: 'connect_request',\n targetAgentId,\n sessionId,\n });\n return sessionId;\n }\n\n /**\n * Send a WebRTC signal to a peer via the rendezvous server.\n */\n sendSignal(\n sessionId: string,\n targetAgentId: string,\n signal: { type: 'offer' | 'answer' | 'ice'; data: string }\n ): void {\n this.assertConnected();\n this.send({\n type: 'signal',\n sessionId,\n targetAgentId,\n signal,\n });\n }\n\n /**\n * Disconnect a session.\n */\n disconnectSession(sessionId: string): void {\n this.assertConnected();\n this.send({ type: 'disconnect', sessionId });\n }\n\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n\n private handleMessage(msg: ServerMessage): void {\n switch (msg.type) {\n case 'heartbeat_ack':\n // No action needed\n break;\n\n case 'agent_status':\n this.emit('peer', msg.agentId, msg.status);\n break;\n\n case 'connect_incoming':\n this.emit('connect_incoming', msg.sessionId, msg.fromAgentId, msg.fromAgentName);\n break;\n\n case 'connect_accepted':\n this.emit('connect_accepted', msg.sessionId, msg.agentId, msg.publicKey);\n break;\n\n case 'connect_rejected':\n this.emit('connect_rejected', msg.sessionId, msg.reason);\n break;\n\n case 'signal_forward':\n this.emit('signal', msg.sessionId, msg.fromAgentId, msg.signal);\n break;\n\n case 'relay_assigned':\n this.emit('relay_assigned', msg.sessionId, msg.relayHost, msg.relayPort, msg.relayToken);\n break;\n\n case 'error':\n this.emit('error', new AnycastError(msg.message, msg.code, msg.fatal || false));\n if (msg.fatal) {\n this.cleanup();\n }\n break;\n }\n }\n\n private send(msg: ClientMessage): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n this.send({ type: 'heartbeat' });\n }, this.opts.heartbeatInterval);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectTimer) return;\n\n this.reconnectAttempt++;\n // Exponential backoff: base * 2^attempt, capped at 30s\n const delay = Math.min(\n this.opts.reconnectInterval * Math.pow(2, this.reconnectAttempt - 1),\n 30000\n );\n\n this.setState(ConnectionState.RECONNECTING);\n this.emit('reconnecting', this.reconnectAttempt);\n\n this.reconnectTimer = setTimeout(async () => {\n this.reconnectTimer = null;\n try {\n await this.connect();\n } catch {\n // connect() will schedule another reconnect if needed\n }\n }, delay);\n }\n\n private cleanup(): void {\n this.stopHeartbeat();\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.removeAllListeners();\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n this.ws.close(1000, 'Client disconnect');\n }\n this.ws = null;\n }\n this.setState(ConnectionState.DISCONNECTED);\n }\n\n private setState(state: ConnectionState): void {\n if (this.state !== state) {\n this.state = state;\n this.emit('stateChange', state);\n }\n }\n\n private assertConnected(): void {\n if (this.state !== ConnectionState.CONNECTED || !this.ws) {\n throw new AnycastError('Not connected', 'NOT_CONNECTED', false);\n }\n }\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,294 @@
1
+ import WebSocket from 'ws';
2
+ import EventEmitter from 'eventemitter3';
3
+ import { randomBytes } from 'crypto';
4
+
5
+ // src/agent.ts
6
+
7
+ // src/types.ts
8
+ var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
9
+ ConnectionState2["DISCONNECTED"] = "DISCONNECTED";
10
+ ConnectionState2["CONNECTING"] = "CONNECTING";
11
+ ConnectionState2["CONNECTED"] = "CONNECTED";
12
+ ConnectionState2["RECONNECTING"] = "RECONNECTING";
13
+ return ConnectionState2;
14
+ })(ConnectionState || {});
15
+ var AnycastError = class extends Error {
16
+ constructor(message, code, fatal = false) {
17
+ super(message);
18
+ this.code = code;
19
+ this.fatal = fatal;
20
+ this.name = "AnycastError";
21
+ }
22
+ };
23
+
24
+ // src/agent.ts
25
+ var DEFAULT_OPTIONS = {
26
+ reconnect: true,
27
+ reconnectInterval: 3e3,
28
+ timeout: 3e4,
29
+ heartbeatInterval: 25e3
30
+ };
31
+ var AnycastAgent = class extends EventEmitter {
32
+ constructor(options) {
33
+ super();
34
+ this.ws = null;
35
+ this.state = "DISCONNECTED" /* DISCONNECTED */;
36
+ this.heartbeatTimer = null;
37
+ this.reconnectTimer = null;
38
+ this.reconnectAttempt = 0;
39
+ this.intentionalClose = false;
40
+ /** Agent ID assigned by the server after registration */
41
+ this.agentId = null;
42
+ /** Tenant ID from the server */
43
+ this.tenantId = null;
44
+ /** Agent name from the server */
45
+ this.agentName = null;
46
+ this.opts = {
47
+ rendezvousUrl: options.rendezvousUrl,
48
+ token: options.token,
49
+ machineId: options.machineId || randomBytes(16).toString("hex"),
50
+ publicKey: options.publicKey || randomBytes(32).toString("base64"),
51
+ version: options.version,
52
+ reconnect: options.reconnect ?? DEFAULT_OPTIONS.reconnect,
53
+ reconnectInterval: options.reconnectInterval ?? DEFAULT_OPTIONS.reconnectInterval,
54
+ timeout: options.timeout ?? DEFAULT_OPTIONS.timeout,
55
+ heartbeatInterval: options.heartbeatInterval ?? DEFAULT_OPTIONS.heartbeatInterval
56
+ };
57
+ }
58
+ /** Current connection state */
59
+ get connectionState() {
60
+ return this.state;
61
+ }
62
+ /** Whether the agent is connected */
63
+ get connected() {
64
+ return this.state === "CONNECTED" /* CONNECTED */;
65
+ }
66
+ /**
67
+ * Connect to the rendezvous server.
68
+ * Resolves when registered, rejects on timeout or fatal error.
69
+ */
70
+ async connect() {
71
+ if (this.state === "CONNECTED" /* CONNECTED */ || this.state === "CONNECTING" /* CONNECTING */) {
72
+ return;
73
+ }
74
+ this.intentionalClose = false;
75
+ this.setState("CONNECTING" /* CONNECTING */);
76
+ return new Promise((resolve, reject) => {
77
+ const timeout = setTimeout(() => {
78
+ this.cleanup();
79
+ const err = new AnycastError("Connection timeout", "TIMEOUT", false);
80
+ this.emit("error", err);
81
+ reject(err);
82
+ }, this.opts.timeout);
83
+ try {
84
+ this.ws = new WebSocket(this.opts.rendezvousUrl);
85
+ } catch (err) {
86
+ clearTimeout(timeout);
87
+ const error = new AnycastError(
88
+ `Failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`,
89
+ "CONNECTION_FAILED",
90
+ false
91
+ );
92
+ this.setState("DISCONNECTED" /* DISCONNECTED */);
93
+ reject(error);
94
+ return;
95
+ }
96
+ this.ws.on("open", () => {
97
+ this.send({
98
+ type: "register",
99
+ token: this.opts.token,
100
+ machineId: this.opts.machineId,
101
+ publicKey: this.opts.publicKey,
102
+ version: this.opts.version
103
+ });
104
+ });
105
+ this.ws.on("message", (data) => {
106
+ let msg;
107
+ try {
108
+ msg = JSON.parse(data.toString());
109
+ } catch {
110
+ return;
111
+ }
112
+ if (msg.type === "registered") {
113
+ clearTimeout(timeout);
114
+ this.agentId = msg.agentId;
115
+ this.tenantId = msg.tenantId;
116
+ this.agentName = msg.name;
117
+ this.reconnectAttempt = 0;
118
+ this.setState("CONNECTED" /* CONNECTED */);
119
+ this.startHeartbeat();
120
+ this.emit("connect", { agentId: msg.agentId, tenantId: msg.tenantId, name: msg.name });
121
+ resolve();
122
+ return;
123
+ }
124
+ if (msg.type === "error" && msg.fatal) {
125
+ clearTimeout(timeout);
126
+ const err = new AnycastError(msg.message, msg.code, true);
127
+ this.emit("error", err);
128
+ this.cleanup();
129
+ reject(err);
130
+ return;
131
+ }
132
+ this.handleMessage(msg);
133
+ });
134
+ this.ws.on("close", (code, reason) => {
135
+ clearTimeout(timeout);
136
+ this.stopHeartbeat();
137
+ const wasConnected = this.state === "CONNECTED" /* CONNECTED */;
138
+ this.setState("DISCONNECTED" /* DISCONNECTED */);
139
+ if (wasConnected) {
140
+ this.emit("disconnect", reason?.toString() || `Code ${code}`);
141
+ }
142
+ if (!this.intentionalClose && this.opts.reconnect) {
143
+ this.scheduleReconnect();
144
+ } else if (this.state !== "CONNECTED" /* CONNECTED */) {
145
+ reject(new AnycastError("Connection closed", "CLOSED", false));
146
+ }
147
+ });
148
+ this.ws.on("error", (err) => {
149
+ const error = new AnycastError(err.message, "WS_ERROR", false);
150
+ this.emit("error", error);
151
+ });
152
+ });
153
+ }
154
+ /**
155
+ * Disconnect from the rendezvous server.
156
+ */
157
+ async disconnect() {
158
+ this.intentionalClose = true;
159
+ this.cleanup();
160
+ this.agentId = null;
161
+ this.tenantId = null;
162
+ this.agentName = null;
163
+ }
164
+ /**
165
+ * Request a connection to a peer agent.
166
+ * @returns The session ID for this connection
167
+ */
168
+ requestConnection(targetAgentId) {
169
+ this.assertConnected();
170
+ const sessionId = randomBytes(16).toString("hex");
171
+ this.send({
172
+ type: "connect_request",
173
+ targetAgentId,
174
+ sessionId
175
+ });
176
+ return sessionId;
177
+ }
178
+ /**
179
+ * Send a WebRTC signal to a peer via the rendezvous server.
180
+ */
181
+ sendSignal(sessionId, targetAgentId, signal) {
182
+ this.assertConnected();
183
+ this.send({
184
+ type: "signal",
185
+ sessionId,
186
+ targetAgentId,
187
+ signal
188
+ });
189
+ }
190
+ /**
191
+ * Disconnect a session.
192
+ */
193
+ disconnectSession(sessionId) {
194
+ this.assertConnected();
195
+ this.send({ type: "disconnect", sessionId });
196
+ }
197
+ // ---------------------------------------------------------------------------
198
+ // Private
199
+ // ---------------------------------------------------------------------------
200
+ handleMessage(msg) {
201
+ switch (msg.type) {
202
+ case "heartbeat_ack":
203
+ break;
204
+ case "agent_status":
205
+ this.emit("peer", msg.agentId, msg.status);
206
+ break;
207
+ case "connect_incoming":
208
+ this.emit("connect_incoming", msg.sessionId, msg.fromAgentId, msg.fromAgentName);
209
+ break;
210
+ case "connect_accepted":
211
+ this.emit("connect_accepted", msg.sessionId, msg.agentId, msg.publicKey);
212
+ break;
213
+ case "connect_rejected":
214
+ this.emit("connect_rejected", msg.sessionId, msg.reason);
215
+ break;
216
+ case "signal_forward":
217
+ this.emit("signal", msg.sessionId, msg.fromAgentId, msg.signal);
218
+ break;
219
+ case "relay_assigned":
220
+ this.emit("relay_assigned", msg.sessionId, msg.relayHost, msg.relayPort, msg.relayToken);
221
+ break;
222
+ case "error":
223
+ this.emit("error", new AnycastError(msg.message, msg.code, msg.fatal || false));
224
+ if (msg.fatal) {
225
+ this.cleanup();
226
+ }
227
+ break;
228
+ }
229
+ }
230
+ send(msg) {
231
+ if (this.ws?.readyState === WebSocket.OPEN) {
232
+ this.ws.send(JSON.stringify(msg));
233
+ }
234
+ }
235
+ startHeartbeat() {
236
+ this.stopHeartbeat();
237
+ this.heartbeatTimer = setInterval(() => {
238
+ this.send({ type: "heartbeat" });
239
+ }, this.opts.heartbeatInterval);
240
+ }
241
+ stopHeartbeat() {
242
+ if (this.heartbeatTimer) {
243
+ clearInterval(this.heartbeatTimer);
244
+ this.heartbeatTimer = null;
245
+ }
246
+ }
247
+ scheduleReconnect() {
248
+ if (this.reconnectTimer) return;
249
+ this.reconnectAttempt++;
250
+ const delay = Math.min(
251
+ this.opts.reconnectInterval * Math.pow(2, this.reconnectAttempt - 1),
252
+ 3e4
253
+ );
254
+ this.setState("RECONNECTING" /* RECONNECTING */);
255
+ this.emit("reconnecting", this.reconnectAttempt);
256
+ this.reconnectTimer = setTimeout(async () => {
257
+ this.reconnectTimer = null;
258
+ try {
259
+ await this.connect();
260
+ } catch {
261
+ }
262
+ }, delay);
263
+ }
264
+ cleanup() {
265
+ this.stopHeartbeat();
266
+ if (this.reconnectTimer) {
267
+ clearTimeout(this.reconnectTimer);
268
+ this.reconnectTimer = null;
269
+ }
270
+ if (this.ws) {
271
+ this.ws.removeAllListeners();
272
+ if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
273
+ this.ws.close(1e3, "Client disconnect");
274
+ }
275
+ this.ws = null;
276
+ }
277
+ this.setState("DISCONNECTED" /* DISCONNECTED */);
278
+ }
279
+ setState(state) {
280
+ if (this.state !== state) {
281
+ this.state = state;
282
+ this.emit("stateChange", state);
283
+ }
284
+ }
285
+ assertConnected() {
286
+ if (this.state !== "CONNECTED" /* CONNECTED */ || !this.ws) {
287
+ throw new AnycastError("Not connected", "NOT_CONNECTED", false);
288
+ }
289
+ }
290
+ };
291
+
292
+ export { AnycastAgent, AnycastError, ConnectionState };
293
+ //# sourceMappingURL=index.mjs.map
294
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/agent.ts"],"names":["ConnectionState"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Agent SDK types — mirrors the rendezvous server protocol.\n */\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface AnycastAgentOptions {\n /** WebSocket URL of the rendezvous server (e.g., wss://agents.anycast.com/rendezvous) */\n rendezvousUrl: string;\n /** Agent auth token from the Anycast portal */\n token: string;\n /** Unique machine identifier (auto-generated if not provided) */\n machineId?: string;\n /** Public key for end-to-end encryption */\n publicKey?: string;\n /** Agent version string */\n version?: string;\n /** Auto-reconnect on disconnect (default: true) */\n reconnect?: boolean;\n /** Reconnect interval in ms (default: 3000) */\n reconnectInterval?: number;\n /** Connection timeout in ms (default: 30000) */\n timeout?: number;\n /** Heartbeat interval in ms (default: 25000) */\n heartbeatInterval?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Connection state\n// ---------------------------------------------------------------------------\n\nexport enum ConnectionState {\n DISCONNECTED = 'DISCONNECTED',\n CONNECTING = 'CONNECTING',\n CONNECTED = 'CONNECTED',\n RECONNECTING = 'RECONNECTING',\n}\n\n// ---------------------------------------------------------------------------\n// Client → Server messages\n// ---------------------------------------------------------------------------\n\nexport interface RegisterMessage {\n type: 'register';\n token: string;\n machineId: string;\n publicKey: string;\n version?: string;\n os?: string;\n arch?: string;\n}\n\nexport interface HeartbeatMessage {\n type: 'heartbeat';\n}\n\nexport interface ConnectRequestMessage {\n type: 'connect_request';\n targetAgentId: string;\n sessionId: string;\n}\n\nexport interface SignalMessage {\n type: 'signal';\n sessionId: string;\n targetAgentId: string;\n signal: {\n type: 'offer' | 'answer' | 'ice';\n data: string;\n };\n}\n\nexport interface DisconnectMessage {\n type: 'disconnect';\n sessionId: string;\n}\n\nexport type ClientMessage =\n | RegisterMessage\n | HeartbeatMessage\n | ConnectRequestMessage\n | SignalMessage\n | DisconnectMessage;\n\n// ---------------------------------------------------------------------------\n// Server → Client messages\n// ---------------------------------------------------------------------------\n\nexport interface RegisteredMessage {\n type: 'registered';\n agentId: string;\n tenantId: string;\n name: string;\n}\n\nexport interface HeartbeatAckMessage {\n type: 'heartbeat_ack';\n timestamp: number;\n}\n\nexport interface ConnectIncomingMessage {\n type: 'connect_incoming';\n sessionId: string;\n fromAgentId: string;\n fromAgentName: string;\n}\n\nexport interface ConnectAcceptedMessage {\n type: 'connect_accepted';\n sessionId: string;\n agentId: string;\n agentName: string;\n publicKey: string;\n}\n\nexport interface ConnectRejectedMessage {\n type: 'connect_rejected';\n sessionId: string;\n reason: string;\n}\n\nexport interface SignalForwardMessage {\n type: 'signal_forward';\n sessionId: string;\n fromAgentId: string;\n signal: {\n type: 'offer' | 'answer' | 'ice';\n data: string;\n };\n}\n\nexport interface RelayAssignedMessage {\n type: 'relay_assigned';\n sessionId: string;\n relayHost: string;\n relayPort: number;\n relayToken: string;\n}\n\nexport interface AgentStatusMessage {\n type: 'agent_status';\n agentId: string;\n status: 'online' | 'offline';\n}\n\nexport interface ErrorMessage {\n type: 'error';\n code: string;\n message: string;\n fatal?: boolean;\n}\n\nexport type ServerMessage =\n | RegisteredMessage\n | HeartbeatAckMessage\n | ConnectIncomingMessage\n | ConnectAcceptedMessage\n | ConnectRejectedMessage\n | SignalForwardMessage\n | RelayAssignedMessage\n | AgentStatusMessage\n | ErrorMessage;\n\n// ---------------------------------------------------------------------------\n// Event map for typed event emitter\n// ---------------------------------------------------------------------------\n\nexport interface AnycastAgentEvents {\n connect: (info: { agentId: string; tenantId: string; name: string }) => void;\n disconnect: (reason: string) => void;\n reconnecting: (attempt: number) => void;\n message: (fromAgentId: string, data: string) => void;\n peer: (agentId: string, status: 'online' | 'offline') => void;\n 'connect_incoming': (sessionId: string, fromAgentId: string, fromAgentName: string) => void;\n 'connect_accepted': (sessionId: string, agentId: string, publicKey: string) => void;\n 'connect_rejected': (sessionId: string, reason: string) => void;\n signal: (sessionId: string, fromAgentId: string, signal: { type: string; data: string }) => void;\n 'relay_assigned': (sessionId: string, relayHost: string, relayPort: number, relayToken: string) => void;\n error: (error: AnycastError) => void;\n stateChange: (state: ConnectionState) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Error\n// ---------------------------------------------------------------------------\n\nexport class AnycastError extends Error {\n constructor(\n message: string,\n public code: string,\n public fatal: boolean = false\n ) {\n super(message);\n this.name = 'AnycastError';\n }\n}\n","import WebSocket from 'ws';\nimport EventEmitter from 'eventemitter3';\nimport { randomBytes } from 'crypto';\nimport {\n type AnycastAgentOptions,\n type AnycastAgentEvents,\n type ServerMessage,\n type ClientMessage,\n ConnectionState,\n AnycastError,\n} from './types.js';\n\nconst DEFAULT_OPTIONS = {\n reconnect: true,\n reconnectInterval: 3000,\n timeout: 30000,\n heartbeatInterval: 25000,\n};\n\n/**\n * AnycastAgent — connects to the Anycast Agents rendezvous server\n * for P2P connectivity, signaling, and relay fallback.\n *\n * @example\n * ```typescript\n * const agent = new AnycastAgent({\n * rendezvousUrl: 'wss://agents.anycast.com/rendezvous',\n * token: 'agt_...',\n * });\n *\n * agent.on('connect', ({ agentId }) => {\n * console.log(`Connected as ${agentId}`);\n * });\n *\n * agent.on('peer', (peerId, status) => {\n * console.log(`Peer ${peerId} is ${status}`);\n * });\n *\n * await agent.connect();\n * ```\n */\nexport class AnycastAgent extends EventEmitter<AnycastAgentEvents> {\n private ws: WebSocket | null = null;\n private state: ConnectionState = ConnectionState.DISCONNECTED;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private reconnectAttempt = 0;\n private intentionalClose = false;\n\n /** Agent ID assigned by the server after registration */\n public agentId: string | null = null;\n /** Tenant ID from the server */\n public tenantId: string | null = null;\n /** Agent name from the server */\n public agentName: string | null = null;\n\n private readonly opts: Required<\n Pick<AnycastAgentOptions, 'rendezvousUrl' | 'token' | 'machineId' | 'publicKey' | 'reconnect' | 'reconnectInterval' | 'timeout' | 'heartbeatInterval'>\n > & Pick<AnycastAgentOptions, 'version'>;\n\n constructor(options: AnycastAgentOptions) {\n super();\n\n this.opts = {\n rendezvousUrl: options.rendezvousUrl,\n token: options.token,\n machineId: options.machineId || randomBytes(16).toString('hex'),\n publicKey: options.publicKey || randomBytes(32).toString('base64'),\n version: options.version,\n reconnect: options.reconnect ?? DEFAULT_OPTIONS.reconnect,\n reconnectInterval: options.reconnectInterval ?? DEFAULT_OPTIONS.reconnectInterval,\n timeout: options.timeout ?? DEFAULT_OPTIONS.timeout,\n heartbeatInterval: options.heartbeatInterval ?? DEFAULT_OPTIONS.heartbeatInterval,\n };\n }\n\n /** Current connection state */\n get connectionState(): ConnectionState {\n return this.state;\n }\n\n /** Whether the agent is connected */\n get connected(): boolean {\n return this.state === ConnectionState.CONNECTED;\n }\n\n /**\n * Connect to the rendezvous server.\n * Resolves when registered, rejects on timeout or fatal error.\n */\n async connect(): Promise<void> {\n if (this.state === ConnectionState.CONNECTED || this.state === ConnectionState.CONNECTING) {\n return;\n }\n\n this.intentionalClose = false;\n this.setState(ConnectionState.CONNECTING);\n\n return new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.cleanup();\n const err = new AnycastError('Connection timeout', 'TIMEOUT', false);\n this.emit('error', err);\n reject(err);\n }, this.opts.timeout);\n\n try {\n this.ws = new WebSocket(this.opts.rendezvousUrl);\n } catch (err) {\n clearTimeout(timeout);\n const error = new AnycastError(\n `Failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`,\n 'CONNECTION_FAILED',\n false\n );\n this.setState(ConnectionState.DISCONNECTED);\n reject(error);\n return;\n }\n\n this.ws.on('open', () => {\n this.send({\n type: 'register',\n token: this.opts.token,\n machineId: this.opts.machineId,\n publicKey: this.opts.publicKey,\n version: this.opts.version,\n });\n });\n\n this.ws.on('message', (data: WebSocket.Data) => {\n let msg: ServerMessage;\n try {\n msg = JSON.parse(data.toString());\n } catch {\n return;\n }\n\n if (msg.type === 'registered') {\n clearTimeout(timeout);\n this.agentId = msg.agentId;\n this.tenantId = msg.tenantId;\n this.agentName = msg.name;\n this.reconnectAttempt = 0;\n this.setState(ConnectionState.CONNECTED);\n this.startHeartbeat();\n this.emit('connect', { agentId: msg.agentId, tenantId: msg.tenantId, name: msg.name });\n resolve();\n return;\n }\n\n if (msg.type === 'error' && msg.fatal) {\n clearTimeout(timeout);\n const err = new AnycastError(msg.message, msg.code, true);\n this.emit('error', err);\n this.cleanup();\n reject(err);\n return;\n }\n\n // Route other messages after registration\n this.handleMessage(msg);\n });\n\n this.ws.on('close', (code, reason) => {\n clearTimeout(timeout);\n this.stopHeartbeat();\n const wasConnected = this.state === ConnectionState.CONNECTED;\n this.setState(ConnectionState.DISCONNECTED);\n\n if (wasConnected) {\n this.emit('disconnect', reason?.toString() || `Code ${code}`);\n }\n\n if (!this.intentionalClose && this.opts.reconnect) {\n this.scheduleReconnect();\n } else if (this.state !== ConnectionState.CONNECTED) {\n reject(new AnycastError('Connection closed', 'CLOSED', false));\n }\n });\n\n this.ws.on('error', (err) => {\n const error = new AnycastError(err.message, 'WS_ERROR', false);\n this.emit('error', error);\n });\n });\n }\n\n /**\n * Disconnect from the rendezvous server.\n */\n async disconnect(): Promise<void> {\n this.intentionalClose = true;\n this.cleanup();\n this.agentId = null;\n this.tenantId = null;\n this.agentName = null;\n }\n\n /**\n * Request a connection to a peer agent.\n * @returns The session ID for this connection\n */\n requestConnection(targetAgentId: string): string {\n this.assertConnected();\n const sessionId = randomBytes(16).toString('hex');\n this.send({\n type: 'connect_request',\n targetAgentId,\n sessionId,\n });\n return sessionId;\n }\n\n /**\n * Send a WebRTC signal to a peer via the rendezvous server.\n */\n sendSignal(\n sessionId: string,\n targetAgentId: string,\n signal: { type: 'offer' | 'answer' | 'ice'; data: string }\n ): void {\n this.assertConnected();\n this.send({\n type: 'signal',\n sessionId,\n targetAgentId,\n signal,\n });\n }\n\n /**\n * Disconnect a session.\n */\n disconnectSession(sessionId: string): void {\n this.assertConnected();\n this.send({ type: 'disconnect', sessionId });\n }\n\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n\n private handleMessage(msg: ServerMessage): void {\n switch (msg.type) {\n case 'heartbeat_ack':\n // No action needed\n break;\n\n case 'agent_status':\n this.emit('peer', msg.agentId, msg.status);\n break;\n\n case 'connect_incoming':\n this.emit('connect_incoming', msg.sessionId, msg.fromAgentId, msg.fromAgentName);\n break;\n\n case 'connect_accepted':\n this.emit('connect_accepted', msg.sessionId, msg.agentId, msg.publicKey);\n break;\n\n case 'connect_rejected':\n this.emit('connect_rejected', msg.sessionId, msg.reason);\n break;\n\n case 'signal_forward':\n this.emit('signal', msg.sessionId, msg.fromAgentId, msg.signal);\n break;\n\n case 'relay_assigned':\n this.emit('relay_assigned', msg.sessionId, msg.relayHost, msg.relayPort, msg.relayToken);\n break;\n\n case 'error':\n this.emit('error', new AnycastError(msg.message, msg.code, msg.fatal || false));\n if (msg.fatal) {\n this.cleanup();\n }\n break;\n }\n }\n\n private send(msg: ClientMessage): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n this.send({ type: 'heartbeat' });\n }, this.opts.heartbeatInterval);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectTimer) return;\n\n this.reconnectAttempt++;\n // Exponential backoff: base * 2^attempt, capped at 30s\n const delay = Math.min(\n this.opts.reconnectInterval * Math.pow(2, this.reconnectAttempt - 1),\n 30000\n );\n\n this.setState(ConnectionState.RECONNECTING);\n this.emit('reconnecting', this.reconnectAttempt);\n\n this.reconnectTimer = setTimeout(async () => {\n this.reconnectTimer = null;\n try {\n await this.connect();\n } catch {\n // connect() will schedule another reconnect if needed\n }\n }, delay);\n }\n\n private cleanup(): void {\n this.stopHeartbeat();\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.removeAllListeners();\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n this.ws.close(1000, 'Client disconnect');\n }\n this.ws = null;\n }\n this.setState(ConnectionState.DISCONNECTED);\n }\n\n private setState(state: ConnectionState): void {\n if (this.state !== state) {\n this.state = state;\n this.emit('stateChange', state);\n }\n }\n\n private assertConnected(): void {\n if (this.state !== ConnectionState.CONNECTED || !this.ws) {\n throw new AnycastError('Not connected', 'NOT_CONNECTED', false);\n }\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@anycast/agent",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for Anycast Agents P2P connectivity",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "lint": "tsc --noEmit",
25
+ "clean": "rm -rf dist"
26
+ },
27
+ "dependencies": {
28
+ "eventemitter3": "^5.0.1",
29
+ "ws": "^8.16.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.11.0",
33
+ "@types/ws": "^8.5.10",
34
+ "tsup": "^8.0.1",
35
+ "typescript": "^5.3.3",
36
+ "vitest": "^4.0.18"
37
+ },
38
+ "keywords": [
39
+ "anycast",
40
+ "p2p",
41
+ "agents",
42
+ "webrtc",
43
+ "relay",
44
+ "connectivity"
45
+ ],
46
+ "license": "MIT"
47
+ }