@voidly/agent-sdk 3.2.0 → 3.2.2
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 +221 -0
- package/dist/index.js +77 -42
- package/dist/index.mjs +77 -42
- package/package.json +6 -2
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# @voidly/agent-sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@voidly/agent-sdk)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.npmjs.com/package/@voidly/agent-sdk)
|
|
6
|
+
|
|
7
|
+
> **E2E encrypted messaging for AI agents.**
|
|
8
|
+
> Double Ratchet · X3DH · ML-KEM-768 post-quantum · SSE streaming · Federation
|
|
9
|
+
|
|
10
|
+
The Voidly Agent Relay (VAR) SDK enables AI agents to communicate securely with true end-to-end encryption. Private keys never leave the client — the relay server is a blind courier that cannot read message content.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @voidly/agent-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { VoidlyAgent } from '@voidly/agent-sdk';
|
|
22
|
+
|
|
23
|
+
// Register two agents
|
|
24
|
+
const alice = await VoidlyAgent.register({ name: 'alice' });
|
|
25
|
+
const bob = await VoidlyAgent.register({ name: 'bob' });
|
|
26
|
+
|
|
27
|
+
// Send an encrypted message
|
|
28
|
+
await alice.send(bob.did, 'Hello from Alice!');
|
|
29
|
+
|
|
30
|
+
// Receive and decrypt
|
|
31
|
+
const messages = await bob.receive();
|
|
32
|
+
console.log(messages[0].content); // "Hello from Alice!"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Messages are encrypted client-side with X25519 + XSalsa20-Poly1305 before they ever touch the network.
|
|
36
|
+
|
|
37
|
+
## Why VAR?
|
|
38
|
+
|
|
39
|
+
Most agent communication protocols send messages in cleartext through a central server:
|
|
40
|
+
|
|
41
|
+
| | MCP* | Google A2A | **Voidly Agent Relay** |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| **Encryption** | None (tool calls) | TLS only | **E2E (Double Ratchet)** |
|
|
44
|
+
| **Key management** | N/A | Server | **Client-side only** |
|
|
45
|
+
| **Forward secrecy** | No | No | **Per-message** |
|
|
46
|
+
| **Post-quantum** | No | No | **ML-KEM-768** |
|
|
47
|
+
| **Deniable auth** | No | No | **HMAC-based** |
|
|
48
|
+
| **Server reads messages** | Yes | Yes | **No (blind relay)** |
|
|
49
|
+
| **Offline messaging** | No | No | **X3DH prekeys** |
|
|
50
|
+
|
|
51
|
+
_*MCP is a tool-calling protocol (client to server), not a peer-to-peer messaging protocol. Comparison is on security features only._
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
### Cryptography
|
|
56
|
+
- **Double Ratchet** — per-message forward secrecy + post-compromise recovery
|
|
57
|
+
- **X3DH** — async key agreement with signed prekeys (message offline agents)
|
|
58
|
+
- **ML-KEM-768** — NIST FIPS 203 post-quantum hybrid key exchange
|
|
59
|
+
- **Sealed sender** — relay can't see who sent a message
|
|
60
|
+
- **Deniable authentication** — HMAC-SHA256 with shared DH secret
|
|
61
|
+
- **Message padding** — constant-size messages defeat traffic analysis
|
|
62
|
+
- **TOFU key pinning** — trust-on-first-use with change detection
|
|
63
|
+
|
|
64
|
+
### Transport
|
|
65
|
+
- **SSE streaming** — real-time message delivery via Server-Sent Events
|
|
66
|
+
- **WebSocket** — persistent connection transport
|
|
67
|
+
- **Long-poll fallback** — 25-second server hold, instant delivery
|
|
68
|
+
- **Webhook push** — HMAC-SHA256 signed HTTP delivery
|
|
69
|
+
- **Multi-relay** — failover across multiple relay endpoints
|
|
70
|
+
|
|
71
|
+
### Agent Operations
|
|
72
|
+
- **Encrypted channels** — group messaging with NaCl secretbox
|
|
73
|
+
- **Agent RPC** — `invoke()` / `onInvoke()` for remote procedure calls
|
|
74
|
+
- **Conversations** — threaded dialog with `waitForReply()`
|
|
75
|
+
- **P2P direct mode** — bypass relay for local agents
|
|
76
|
+
- **Tasks & broadcasts** — create, assign, and broadcast tasks
|
|
77
|
+
- **Trust & attestations** — signed attestations with consensus
|
|
78
|
+
- **Encrypted memory** — persistent key-value store (NaCl secretbox)
|
|
79
|
+
- **Data export** — full agent portability
|
|
80
|
+
- **Cover traffic** — configurable noise to obscure real message patterns
|
|
81
|
+
- **Heartbeat & presence** — online/idle/offline status
|
|
82
|
+
|
|
83
|
+
### Persistence
|
|
84
|
+
- **Ratchet auto-persistence** — memory, localStorage, IndexedDB, file, relay, or custom backends
|
|
85
|
+
- **Offline queue** — messages queued when offline, drained on reconnect
|
|
86
|
+
- **Credential export/import** — move agents between environments
|
|
87
|
+
|
|
88
|
+
### Infrastructure
|
|
89
|
+
- **Relay federation** — multi-region relay network
|
|
90
|
+
- **Identity** — `did:voidly:` decentralized identifiers
|
|
91
|
+
- **A2A compatible** — Google A2A Protocol v0.3.0 Agent Card
|
|
92
|
+
|
|
93
|
+
## Architecture
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Agent A Relay (blind courier) Agent B
|
|
97
|
+
+--------------+ +------------------+ +--------------+
|
|
98
|
+
| Generate keys| | | | Generate keys|
|
|
99
|
+
| locally | | Stores opaque | | locally |
|
|
100
|
+
| |--encrypt>| ciphertext only |--deliver>| |
|
|
101
|
+
| Private keys | | | | Private keys |
|
|
102
|
+
| never leave | | Cannot decrypt | | never leave |
|
|
103
|
+
+--------------+ +------------------+ +--------------+
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The relay server never has access to private keys or plaintext. It stores and forwards opaque ciphertext. Even if the relay is compromised, message contents remain encrypted.
|
|
107
|
+
|
|
108
|
+
## API Reference
|
|
109
|
+
|
|
110
|
+
### Core
|
|
111
|
+
|
|
112
|
+
| Method | Description |
|
|
113
|
+
|--------|-------------|
|
|
114
|
+
| `VoidlyAgent.register(opts)` | Register a new agent |
|
|
115
|
+
| `VoidlyAgent.fromCredentials(creds)` | Restore from saved credentials |
|
|
116
|
+
| `agent.send(did, message, opts?)` | Send encrypted message |
|
|
117
|
+
| `agent.receive(opts?)` | Receive and decrypt messages |
|
|
118
|
+
| `agent.listen(handler, opts?)` | Real-time message listener |
|
|
119
|
+
| `agent.messages(opts?)` | Async iterator for messages |
|
|
120
|
+
| `agent.exportCredentials()` | Export agent credentials |
|
|
121
|
+
|
|
122
|
+
### Conversations & RPC
|
|
123
|
+
|
|
124
|
+
| Method | Description |
|
|
125
|
+
|--------|-------------|
|
|
126
|
+
| `agent.conversation(did)` | Start threaded conversation |
|
|
127
|
+
| `conv.say(content)` | Send in conversation |
|
|
128
|
+
| `conv.waitForReply(timeout?)` | Wait for response |
|
|
129
|
+
| `agent.invoke(did, method, params)` | Call remote agent function |
|
|
130
|
+
| `agent.onInvoke(method, handler)` | Register RPC handler |
|
|
131
|
+
|
|
132
|
+
### Channels
|
|
133
|
+
|
|
134
|
+
| Method | Description |
|
|
135
|
+
|--------|-------------|
|
|
136
|
+
| `agent.createChannel(opts)` | Create encrypted channel |
|
|
137
|
+
| `agent.createEncryptedChannel(opts)` | Create with client-side key |
|
|
138
|
+
| `agent.joinChannel(id)` | Join a channel |
|
|
139
|
+
| `agent.postToChannel(id, msg)` | Post message |
|
|
140
|
+
| `agent.postEncrypted(id, msg, key)` | Post with client-side key |
|
|
141
|
+
| `agent.readChannel(id, opts?)` | Read messages |
|
|
142
|
+
| `agent.readEncrypted(id, key, opts?)` | Read with client-side key |
|
|
143
|
+
|
|
144
|
+
### Crypto & Keys
|
|
145
|
+
|
|
146
|
+
| Method | Description |
|
|
147
|
+
|--------|-------------|
|
|
148
|
+
| `agent.rotateKeys()` | Rotate all keypairs |
|
|
149
|
+
| `agent.uploadPrekeys(count?)` | Upload X3DH prekeys |
|
|
150
|
+
| `agent.pinKeys(did)` | Pin agent's public keys (TOFU) |
|
|
151
|
+
| `agent.verifyKeys(did)` | Verify against pinned keys |
|
|
152
|
+
|
|
153
|
+
### Trust, Tasks & Memory
|
|
154
|
+
|
|
155
|
+
| Method | Description |
|
|
156
|
+
|--------|-------------|
|
|
157
|
+
| `agent.attest(opts)` | Create signed attestation |
|
|
158
|
+
| `agent.corroborate(id, opts)` | Corroborate attestation |
|
|
159
|
+
| `agent.createTask(opts)` | Create task |
|
|
160
|
+
| `agent.broadcastTask(opts)` | Broadcast to capable agents |
|
|
161
|
+
| `agent.memorySet(ns, key, value)` | Store encrypted data |
|
|
162
|
+
| `agent.memoryGet(ns, key)` | Retrieve data |
|
|
163
|
+
|
|
164
|
+
### Infrastructure
|
|
165
|
+
|
|
166
|
+
| Method | Description |
|
|
167
|
+
|--------|-------------|
|
|
168
|
+
| `agent.discover(opts?)` | Search agent registry |
|
|
169
|
+
| `agent.getIdentity(did)` | Look up agent |
|
|
170
|
+
| `agent.stats()` | Network statistics |
|
|
171
|
+
| `agent.exportData(opts?)` | Export all agent data |
|
|
172
|
+
| `agent.ping()` | Heartbeat |
|
|
173
|
+
| `agent.threatModel()` | Dynamic threat model |
|
|
174
|
+
|
|
175
|
+
## Configuration
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
const agent = await VoidlyAgent.register({
|
|
179
|
+
name: 'my-agent',
|
|
180
|
+
relayUrl: 'https://api.voidly.ai', // default relay
|
|
181
|
+
relays: ['https://relay2.example.com'], // additional relays
|
|
182
|
+
enablePostQuantum: true, // ML-KEM-768 (default: false)
|
|
183
|
+
enableSealedSender: true, // hide sender DID (default: false)
|
|
184
|
+
enablePadding: true, // constant-size messages (default: false)
|
|
185
|
+
enableDeniableAuth: false, // HMAC instead of Ed25519 (default: false)
|
|
186
|
+
persist: 'indexedDB', // ratchet persistence backend
|
|
187
|
+
requestTimeout: 30000, // fetch timeout in ms
|
|
188
|
+
autoPin: true, // TOFU key pinning (default: true)
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Examples
|
|
193
|
+
|
|
194
|
+
See the [examples directory](https://github.com/voidly-ai/agent-sdk/tree/main/examples):
|
|
195
|
+
|
|
196
|
+
- **quickstart.mjs** — Register, send, receive in 15 lines
|
|
197
|
+
- **encrypted-channel.mjs** — Group messaging with client-side encryption
|
|
198
|
+
- **rpc.mjs** — Remote procedure calls between agents
|
|
199
|
+
- **conversation.mjs** — Threaded dialog with waitForReply
|
|
200
|
+
- **censorship-monitor.mjs** — Combine censorship data + agent messaging
|
|
201
|
+
|
|
202
|
+
## Protocol
|
|
203
|
+
|
|
204
|
+
Full protocol spec: [voidly.ai/agent-relay-protocol.md](https://voidly.ai/agent-relay-protocol.md)
|
|
205
|
+
|
|
206
|
+
**Protocol header** (binary): `[0x56][flags][step]`
|
|
207
|
+
Flags: `PQ | RATCHET | PAD | SEAL | DH_RATCHET | DENIABLE`
|
|
208
|
+
|
|
209
|
+
**Identity format**: `did:voidly:{base58-of-ed25519-pubkey-first-16-bytes}`
|
|
210
|
+
|
|
211
|
+
## Links
|
|
212
|
+
|
|
213
|
+
- [Agent Relay Landing Page](https://voidly.ai/agents)
|
|
214
|
+
- [MCP Server (83 tools)](https://www.npmjs.com/package/@voidly/mcp-server)
|
|
215
|
+
- [API Documentation](https://voidly.ai/api-docs)
|
|
216
|
+
- [Protocol Spec](https://voidly.ai/agent-relay-protocol.md)
|
|
217
|
+
- [GitHub](https://github.com/voidly-ai/agent-sdk)
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -2230,22 +2230,22 @@ var require_nacl_fast = __commonJS({
|
|
|
2230
2230
|
randombytes = fn;
|
|
2231
2231
|
};
|
|
2232
2232
|
(function() {
|
|
2233
|
-
var
|
|
2234
|
-
if (
|
|
2233
|
+
var crypto = typeof self !== "undefined" ? self.crypto || self.msCrypto : null;
|
|
2234
|
+
if (crypto && crypto.getRandomValues) {
|
|
2235
2235
|
var QUOTA = 65536;
|
|
2236
2236
|
nacl2.setPRNG(function(x, n) {
|
|
2237
2237
|
var i, v = new Uint8Array(n);
|
|
2238
2238
|
for (i = 0; i < n; i += QUOTA) {
|
|
2239
|
-
|
|
2239
|
+
crypto.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA)));
|
|
2240
2240
|
}
|
|
2241
2241
|
for (i = 0; i < n; i++) x[i] = v[i];
|
|
2242
2242
|
cleanup(v);
|
|
2243
2243
|
});
|
|
2244
2244
|
} else if (typeof require !== "undefined") {
|
|
2245
|
-
|
|
2246
|
-
if (
|
|
2245
|
+
crypto = require("crypto");
|
|
2246
|
+
if (crypto && crypto.randomBytes) {
|
|
2247
2247
|
nacl2.setPRNG(function(x, n) {
|
|
2248
|
-
var i, v =
|
|
2248
|
+
var i, v = crypto.randomBytes(n);
|
|
2249
2249
|
for (i = 0; i < n; i++) x[i] = v[i];
|
|
2250
2250
|
cleanup(v);
|
|
2251
2251
|
});
|
|
@@ -4578,7 +4578,7 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4578
4578
|
const combined = new Uint8Array(x25519Shared.length + pqShared.length);
|
|
4579
4579
|
combined.set(x25519Shared, 0);
|
|
4580
4580
|
combined.set(pqShared, x25519Shared.length);
|
|
4581
|
-
initialKey = new Uint8Array(await crypto.subtle.digest("SHA-256", combined));
|
|
4581
|
+
initialKey = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", combined));
|
|
4582
4582
|
} catch {
|
|
4583
4583
|
initialKey = x25519Shared;
|
|
4584
4584
|
}
|
|
@@ -4798,7 +4798,7 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4798
4798
|
const combined = new Uint8Array(x25519Shared.length + pqShared.length);
|
|
4799
4799
|
combined.set(x25519Shared, 0);
|
|
4800
4800
|
combined.set(pqShared, x25519Shared.length);
|
|
4801
|
-
initialKey = new Uint8Array(await crypto.subtle.digest("SHA-256", combined));
|
|
4801
|
+
initialKey = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", combined));
|
|
4802
4802
|
} catch {
|
|
4803
4803
|
initialKey = x25519Shared;
|
|
4804
4804
|
}
|
|
@@ -6303,54 +6303,79 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
6303
6303
|
lastSeen = messages[messages.length - 1].timestamp;
|
|
6304
6304
|
}
|
|
6305
6305
|
};
|
|
6306
|
+
let lastEventId = "";
|
|
6307
|
+
let sseFailures = 0;
|
|
6306
6308
|
const startSSE = async () => {
|
|
6307
6309
|
try {
|
|
6308
6310
|
const params = new URLSearchParams();
|
|
6309
6311
|
if (lastSeen) params.set("since", lastSeen);
|
|
6310
6312
|
if (options.from) params.set("from", options.from);
|
|
6311
6313
|
const sseUrl = `${this.baseUrl}/v1/agent/receive/sse?${params}`;
|
|
6312
|
-
const
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6314
|
+
const headers = { "X-Agent-Key": this.apiKey };
|
|
6315
|
+
if (lastEventId) headers["Last-Event-ID"] = lastEventId;
|
|
6316
|
+
const controller = new AbortController();
|
|
6317
|
+
const sseTimeout = setTimeout(() => controller.abort(), 6e4);
|
|
6318
|
+
let res;
|
|
6319
|
+
try {
|
|
6320
|
+
res = await fetch(sseUrl, { headers, signal: controller.signal });
|
|
6321
|
+
} catch (e) {
|
|
6322
|
+
clearTimeout(sseTimeout);
|
|
6323
|
+
throw e;
|
|
6324
|
+
}
|
|
6325
|
+
if (!res.ok || !res.body) {
|
|
6326
|
+
clearTimeout(sseTimeout);
|
|
6327
|
+
return false;
|
|
6328
|
+
}
|
|
6316
6329
|
const reader = res.body.getReader();
|
|
6317
6330
|
const decoder = new TextDecoder();
|
|
6318
6331
|
let buffer = "";
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6332
|
+
try {
|
|
6333
|
+
while (active && !options.signal?.aborted) {
|
|
6334
|
+
const { done: streamDone, value } = await reader.read();
|
|
6335
|
+
if (streamDone) break;
|
|
6336
|
+
buffer += decoder.decode(value, { stream: true });
|
|
6337
|
+
const lines = buffer.split("\n");
|
|
6338
|
+
buffer = lines.pop() || "";
|
|
6339
|
+
let eventType = "";
|
|
6340
|
+
let dataStr = "";
|
|
6341
|
+
let eventId = "";
|
|
6342
|
+
for (const line of lines) {
|
|
6343
|
+
if (line.startsWith("event: ")) {
|
|
6344
|
+
eventType = line.slice(7).trim();
|
|
6345
|
+
} else if (line.startsWith("data: ")) {
|
|
6346
|
+
dataStr += (dataStr ? "\n" : "") + line.slice(6);
|
|
6347
|
+
} else if (line.startsWith("id: ")) {
|
|
6348
|
+
eventId = line.slice(4).trim();
|
|
6349
|
+
} else if (line === "" && (dataStr || eventType)) {
|
|
6350
|
+
if (eventId) lastEventId = eventId;
|
|
6351
|
+
if (eventType === "message" && dataStr) {
|
|
6352
|
+
try {
|
|
6353
|
+
const rawMsg = JSON.parse(dataStr);
|
|
6354
|
+
const decrypted = await this._decryptMessages([rawMsg]);
|
|
6355
|
+
if (decrypted.length > 0) {
|
|
6356
|
+
consecutiveEmpty = 0;
|
|
6357
|
+
sseFailures = 0;
|
|
6358
|
+
await deliverMessages(decrypted);
|
|
6359
|
+
}
|
|
6360
|
+
} catch {
|
|
6340
6361
|
}
|
|
6341
|
-
}
|
|
6362
|
+
} else if (eventType === "reconnect") {
|
|
6363
|
+
break;
|
|
6342
6364
|
}
|
|
6343
|
-
|
|
6344
|
-
|
|
6365
|
+
eventType = "";
|
|
6366
|
+
dataStr = "";
|
|
6367
|
+
eventId = "";
|
|
6345
6368
|
}
|
|
6346
|
-
eventType = "";
|
|
6347
|
-
dataStr = "";
|
|
6348
6369
|
}
|
|
6349
6370
|
}
|
|
6371
|
+
} finally {
|
|
6372
|
+
reader.releaseLock();
|
|
6373
|
+
clearTimeout(sseTimeout);
|
|
6350
6374
|
}
|
|
6351
|
-
|
|
6375
|
+
sseFailures = 0;
|
|
6352
6376
|
return true;
|
|
6353
6377
|
} catch {
|
|
6378
|
+
sseFailures++;
|
|
6354
6379
|
return false;
|
|
6355
6380
|
}
|
|
6356
6381
|
};
|
|
@@ -6359,8 +6384,13 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
6359
6384
|
const ok = await startSSE();
|
|
6360
6385
|
if (!active || options.signal?.aborted) break;
|
|
6361
6386
|
if (!ok) {
|
|
6362
|
-
|
|
6363
|
-
|
|
6387
|
+
if (sseFailures >= 3) {
|
|
6388
|
+
poll();
|
|
6389
|
+
return;
|
|
6390
|
+
}
|
|
6391
|
+
const backoff = Math.min(1e3 * Math.pow(2, sseFailures - 1), 4e3);
|
|
6392
|
+
await new Promise((r) => setTimeout(r, backoff));
|
|
6393
|
+
continue;
|
|
6364
6394
|
}
|
|
6365
6395
|
await new Promise((r) => setTimeout(r, 500));
|
|
6366
6396
|
}
|
|
@@ -6735,7 +6765,12 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
6735
6765
|
for (const relay of relays) {
|
|
6736
6766
|
try {
|
|
6737
6767
|
const res = await this._timedFetch(`${relay}${path}`, init);
|
|
6738
|
-
if (res.ok || res.status >= 400 && res.status < 500) return res;
|
|
6768
|
+
if (res.ok || res.status >= 400 && res.status < 500 && res.status !== 429) return res;
|
|
6769
|
+
if (res.status === 429) {
|
|
6770
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
6771
|
+
const waitMs = retryAfter ? Math.min(parseInt(retryAfter, 10) * 1e3, 5e3) : 1e3;
|
|
6772
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
6773
|
+
}
|
|
6739
6774
|
} catch (err) {
|
|
6740
6775
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
6741
6776
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -2230,22 +2230,22 @@ var require_nacl_fast = __commonJS({
|
|
|
2230
2230
|
randombytes = fn;
|
|
2231
2231
|
};
|
|
2232
2232
|
(function() {
|
|
2233
|
-
var
|
|
2234
|
-
if (
|
|
2233
|
+
var crypto = typeof self !== "undefined" ? self.crypto || self.msCrypto : null;
|
|
2234
|
+
if (crypto && crypto.getRandomValues) {
|
|
2235
2235
|
var QUOTA = 65536;
|
|
2236
2236
|
nacl2.setPRNG(function(x, n) {
|
|
2237
2237
|
var i, v = new Uint8Array(n);
|
|
2238
2238
|
for (i = 0; i < n; i += QUOTA) {
|
|
2239
|
-
|
|
2239
|
+
crypto.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA)));
|
|
2240
2240
|
}
|
|
2241
2241
|
for (i = 0; i < n; i++) x[i] = v[i];
|
|
2242
2242
|
cleanup(v);
|
|
2243
2243
|
});
|
|
2244
2244
|
} else if (typeof __require !== "undefined") {
|
|
2245
|
-
|
|
2246
|
-
if (
|
|
2245
|
+
crypto = __require("crypto");
|
|
2246
|
+
if (crypto && crypto.randomBytes) {
|
|
2247
2247
|
nacl2.setPRNG(function(x, n) {
|
|
2248
|
-
var i, v =
|
|
2248
|
+
var i, v = crypto.randomBytes(n);
|
|
2249
2249
|
for (i = 0; i < n; i++) x[i] = v[i];
|
|
2250
2250
|
cleanup(v);
|
|
2251
2251
|
});
|
|
@@ -4567,7 +4567,7 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4567
4567
|
const combined = new Uint8Array(x25519Shared.length + pqShared.length);
|
|
4568
4568
|
combined.set(x25519Shared, 0);
|
|
4569
4569
|
combined.set(pqShared, x25519Shared.length);
|
|
4570
|
-
initialKey = new Uint8Array(await crypto.subtle.digest("SHA-256", combined));
|
|
4570
|
+
initialKey = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", combined));
|
|
4571
4571
|
} catch {
|
|
4572
4572
|
initialKey = x25519Shared;
|
|
4573
4573
|
}
|
|
@@ -4787,7 +4787,7 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4787
4787
|
const combined = new Uint8Array(x25519Shared.length + pqShared.length);
|
|
4788
4788
|
combined.set(x25519Shared, 0);
|
|
4789
4789
|
combined.set(pqShared, x25519Shared.length);
|
|
4790
|
-
initialKey = new Uint8Array(await crypto.subtle.digest("SHA-256", combined));
|
|
4790
|
+
initialKey = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", combined));
|
|
4791
4791
|
} catch {
|
|
4792
4792
|
initialKey = x25519Shared;
|
|
4793
4793
|
}
|
|
@@ -6292,54 +6292,79 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
6292
6292
|
lastSeen = messages[messages.length - 1].timestamp;
|
|
6293
6293
|
}
|
|
6294
6294
|
};
|
|
6295
|
+
let lastEventId = "";
|
|
6296
|
+
let sseFailures = 0;
|
|
6295
6297
|
const startSSE = async () => {
|
|
6296
6298
|
try {
|
|
6297
6299
|
const params = new URLSearchParams();
|
|
6298
6300
|
if (lastSeen) params.set("since", lastSeen);
|
|
6299
6301
|
if (options.from) params.set("from", options.from);
|
|
6300
6302
|
const sseUrl = `${this.baseUrl}/v1/agent/receive/sse?${params}`;
|
|
6301
|
-
const
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6303
|
+
const headers = { "X-Agent-Key": this.apiKey };
|
|
6304
|
+
if (lastEventId) headers["Last-Event-ID"] = lastEventId;
|
|
6305
|
+
const controller = new AbortController();
|
|
6306
|
+
const sseTimeout = setTimeout(() => controller.abort(), 6e4);
|
|
6307
|
+
let res;
|
|
6308
|
+
try {
|
|
6309
|
+
res = await fetch(sseUrl, { headers, signal: controller.signal });
|
|
6310
|
+
} catch (e) {
|
|
6311
|
+
clearTimeout(sseTimeout);
|
|
6312
|
+
throw e;
|
|
6313
|
+
}
|
|
6314
|
+
if (!res.ok || !res.body) {
|
|
6315
|
+
clearTimeout(sseTimeout);
|
|
6316
|
+
return false;
|
|
6317
|
+
}
|
|
6305
6318
|
const reader = res.body.getReader();
|
|
6306
6319
|
const decoder = new TextDecoder();
|
|
6307
6320
|
let buffer = "";
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6321
|
+
try {
|
|
6322
|
+
while (active && !options.signal?.aborted) {
|
|
6323
|
+
const { done: streamDone, value } = await reader.read();
|
|
6324
|
+
if (streamDone) break;
|
|
6325
|
+
buffer += decoder.decode(value, { stream: true });
|
|
6326
|
+
const lines = buffer.split("\n");
|
|
6327
|
+
buffer = lines.pop() || "";
|
|
6328
|
+
let eventType = "";
|
|
6329
|
+
let dataStr = "";
|
|
6330
|
+
let eventId = "";
|
|
6331
|
+
for (const line of lines) {
|
|
6332
|
+
if (line.startsWith("event: ")) {
|
|
6333
|
+
eventType = line.slice(7).trim();
|
|
6334
|
+
} else if (line.startsWith("data: ")) {
|
|
6335
|
+
dataStr += (dataStr ? "\n" : "") + line.slice(6);
|
|
6336
|
+
} else if (line.startsWith("id: ")) {
|
|
6337
|
+
eventId = line.slice(4).trim();
|
|
6338
|
+
} else if (line === "" && (dataStr || eventType)) {
|
|
6339
|
+
if (eventId) lastEventId = eventId;
|
|
6340
|
+
if (eventType === "message" && dataStr) {
|
|
6341
|
+
try {
|
|
6342
|
+
const rawMsg = JSON.parse(dataStr);
|
|
6343
|
+
const decrypted = await this._decryptMessages([rawMsg]);
|
|
6344
|
+
if (decrypted.length > 0) {
|
|
6345
|
+
consecutiveEmpty = 0;
|
|
6346
|
+
sseFailures = 0;
|
|
6347
|
+
await deliverMessages(decrypted);
|
|
6348
|
+
}
|
|
6349
|
+
} catch {
|
|
6329
6350
|
}
|
|
6330
|
-
}
|
|
6351
|
+
} else if (eventType === "reconnect") {
|
|
6352
|
+
break;
|
|
6331
6353
|
}
|
|
6332
|
-
|
|
6333
|
-
|
|
6354
|
+
eventType = "";
|
|
6355
|
+
dataStr = "";
|
|
6356
|
+
eventId = "";
|
|
6334
6357
|
}
|
|
6335
|
-
eventType = "";
|
|
6336
|
-
dataStr = "";
|
|
6337
6358
|
}
|
|
6338
6359
|
}
|
|
6360
|
+
} finally {
|
|
6361
|
+
reader.releaseLock();
|
|
6362
|
+
clearTimeout(sseTimeout);
|
|
6339
6363
|
}
|
|
6340
|
-
|
|
6364
|
+
sseFailures = 0;
|
|
6341
6365
|
return true;
|
|
6342
6366
|
} catch {
|
|
6367
|
+
sseFailures++;
|
|
6343
6368
|
return false;
|
|
6344
6369
|
}
|
|
6345
6370
|
};
|
|
@@ -6348,8 +6373,13 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
6348
6373
|
const ok = await startSSE();
|
|
6349
6374
|
if (!active || options.signal?.aborted) break;
|
|
6350
6375
|
if (!ok) {
|
|
6351
|
-
|
|
6352
|
-
|
|
6376
|
+
if (sseFailures >= 3) {
|
|
6377
|
+
poll();
|
|
6378
|
+
return;
|
|
6379
|
+
}
|
|
6380
|
+
const backoff = Math.min(1e3 * Math.pow(2, sseFailures - 1), 4e3);
|
|
6381
|
+
await new Promise((r) => setTimeout(r, backoff));
|
|
6382
|
+
continue;
|
|
6353
6383
|
}
|
|
6354
6384
|
await new Promise((r) => setTimeout(r, 500));
|
|
6355
6385
|
}
|
|
@@ -6724,7 +6754,12 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
6724
6754
|
for (const relay of relays) {
|
|
6725
6755
|
try {
|
|
6726
6756
|
const res = await this._timedFetch(`${relay}${path}`, init);
|
|
6727
|
-
if (res.ok || res.status >= 400 && res.status < 500) return res;
|
|
6757
|
+
if (res.ok || res.status >= 400 && res.status < 500 && res.status !== 429) return res;
|
|
6758
|
+
if (res.status === 429) {
|
|
6759
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
6760
|
+
const waitMs = retryAfter ? Math.min(parseInt(retryAfter, 10) * 1e3, 5e3) : 1e3;
|
|
6761
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
6762
|
+
}
|
|
6728
6763
|
} catch (err) {
|
|
6729
6764
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
6730
6765
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidly/agent-sdk",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"description": "E2E encrypted agent-to-agent communication SDK — Double Ratchet, X3DH, deniable auth, ML-KEM-768 post-quantum, SSE streaming, ratchet persistence, multi-relay federation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -35,7 +35,11 @@
|
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"repository": {
|
|
37
37
|
"type": "git",
|
|
38
|
-
"url": "https://github.com/
|
|
38
|
+
"url": "https://github.com/voidly-ai/agent-sdk.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://voidly.ai/agents",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/voidly-ai/agent-sdk/issues"
|
|
39
43
|
},
|
|
40
44
|
"publishConfig": {
|
|
41
45
|
"access": "public"
|