@voidly/agent-sdk 3.5.0 → 3.6.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 +77 -12
- package/dist/chunk-N5DC4FFG.mjs +2 -0
- package/dist/chunk-VSAIXAMI.mjs +2 -0
- package/dist/index.d.ts +341 -9
- package/dist/index.js +4 -4
- package/dist/index.mjs +5 -5
- package/dist/marketplace-YNZRHYXA.mjs +3 -0
- package/dist/x402-L336FBTU.mjs +1 -0
- package/examples/batch-send.mjs +48 -0
- package/examples/find-and-invoke.mjs +44 -0
- package/examples/marketplace-list-and-hire.mjs +87 -0
- package/examples/x402-pay-and-fetch.mjs +59 -0
- package/package.json +3 -2
- package/examples/post-quantum.mjs +0 -99
- package/examples/sse-streaming.mjs +0 -52
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import {createRequire}from'module';import {g}from'./chunk-VSAIXAMI.mjs';import {a,b as b$1,c as c$1}from'./chunk-N5DC4FFG.mjs';createRequire(import.meta.url);
|
|
2
|
+
var o=a(b$1()),s=a(c$1());var v="voidly-capability-list/v1",w="voidly-hire-request/v1",S="voidly-pay-faucet/v1",E=1e7,p=1e11,u=80,_=560,m=1,b=168;function l(e,t){if(t.length!==64)throw new Error("signingSecretKey must be 64 bytes");let i=(0, s.decodeUTF8)(g(e));return (0, s.encodeBase64)(o.default.sign.detached(i,t))}function n(e=0){return new Date(Date.now()+e*1e3).toISOString()}function c(){return (0, s.encodeBase64)(o.default.randomBytes(16))}function U(e){if(!/^[a-z0-9._:-]+$/.test(e.capability))throw new Error("capability must be a slug [a-z0-9._:-]+");if(e.name.length<1||e.name.length>u)throw new Error(`name must be 1..${u} chars`);if(e.description.length<1||e.description.length>_)throw new Error(`description must be 1..${_} chars`);if(!Number.isInteger(e.price_per_call_micro)||e.price_per_call_micro<0||e.price_per_call_micro>p)throw new Error(`price_per_call_micro must be integer in [0, ${p}]`);if(!Number.isInteger(e.sla_deadline_hours)||e.sla_deadline_hours<m||e.sla_deadline_hours>b)throw new Error(`sla_deadline_hours must be integer in [${m}, ${b}]`)}function x(e){U({capability:e.capability,name:e.name,description:e.description,price_per_call_micro:e.pricePerCallMicro,sla_deadline_hours:e.slaDeadlineHours??24});let t=Math.min(Math.max(e.ttlSeconds??60,5),3600),i={schema:v,provider_did:e.providerDid,capability:e.capability,name:e.name,description:e.description,price_per_call_micro:e.pricePerCallMicro,unit:e.unit||"call",sla_deadline_hours:e.slaDeadlineHours??24,active:e.active!==false,nonce:c(),issued_at:n(0),expires_at:n(t)};return e.inputSchema&&(i.input_schema=JSON.stringify(e.inputSchema)),e.outputSchema&&(i.output_schema=JSON.stringify(e.outputSchema)),e.tags&&e.tags.length&&(i.tags=JSON.stringify(e.tags)),{envelope:i,signature:l(i,e.signingSecretKey)}}function H(e){if(e.input&&e.input.length>2048)throw new Error("input must be <= 2048 chars");let t=Math.min(Math.max(e.ttlSeconds??60,5),900),i={schema:w,capability_id:e.capabilityId,capability:e.capability,requester_did:e.requesterDid,provider_did:e.providerDid,price_micro:e.pricePerCallMicro,task_id:e.taskId||crypto.randomUUID(),delivery_deadline_hours:e.deliveryDeadlineHours??24,nonce:c(),issued_at:n(0),expires_at:n(t)};return e.input&&(i.input_json=e.input),{envelope:i,signature:l(i,e.signingSecretKey)}}async function $(e){let t=e.baseUrl||"https://api.voidly.ai",i=await fetch(`${t}/v1/pay/capability/list`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({envelope:e.envelope,signature:e.signature})});if(!i.ok)throw new Error(`listCapability failed: ${i.status} ${await i.text()}`);return i.json()}async function P(e={}){let t=e.baseUrl||"https://api.voidly.ai",i=new URLSearchParams;e.q&&i.set("q",e.q),e.capability&&i.set("capability",e.capability),e.maxPriceCredits!=null&&i.set("max_price_micro",String(Math.floor(e.maxPriceCredits*1e6))),e.providerDid&&i.set("provider_did",e.providerDid),e.limit&&i.set("limit",String(Math.min(e.limit,100)));let r=await fetch(`${t}/v1/pay/capability/search?${i.toString()}`);if(!r.ok)throw new Error(`searchCapabilities failed: ${r.status} ${await r.text()}`);return r.json()}async function I(e){let t=e.baseUrl||"https://api.voidly.ai",i=await fetch(`${t}/v1/pay/capability/${e.id}`);if(i.status===404)return null;if(!i.ok)throw new Error(`getCapability failed: ${i.status}`);return (await i.json()).capability}async function M(e){let t=e.baseUrl||"https://api.voidly.ai",i=await fetch(`${t}/v1/pay/capability/did/${encodeURIComponent(e.did)}`);if(!i.ok)throw new Error(`listCapabilitiesByDid failed: ${i.status}`);return (await i.json()).capabilities||[]}async function A(e){let t=e.baseUrl||"https://api.voidly.ai",i=await fetch(`${t}/v1/pay/hire`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({envelope:e.envelope,signature:e.signature})});if(!i.ok)throw new Error(`hire failed: ${i.status} ${await i.text()}`);return i.json()}async function R(e){let t=e.baseUrl||"https://api.voidly.ai",i=await fetch(`${t}/v1/pay/hire/${e.id}`);if(i.status===404)return null;if(!i.ok)throw new Error(`getHire failed: ${i.status}`);return (await i.json()).hire}async function L(e){let t=e.baseUrl||"https://api.voidly.ai",i=new URLSearchParams;e.state&&i.set("state",e.state),e.limit&&i.set("limit",String(Math.min(e.limit,200)));let r=`${t}/v1/pay/hire/incoming/${encodeURIComponent(e.did)}${i.toString()?"?"+i.toString():""}`,a=await fetch(r);if(!a.ok)throw new Error(`listHiresIncoming failed: ${a.status}`);return (await a.json()).hires||[]}async function j(e){let t=e.baseUrl||"https://api.voidly.ai",i=new URLSearchParams;e.state&&i.set("state",e.state),e.limit&&i.set("limit",String(Math.min(e.limit,200)));let r=`${t}/v1/pay/hire/outgoing/${encodeURIComponent(e.did)}${i.toString()?"?"+i.toString():""}`,a=await fetch(r);if(!a.ok)throw new Error(`listHiresOutgoing failed: ${a.status}`);return (await a.json()).hires||[]}function T(e){let t=Math.min(Math.max(e.ttlSeconds??60,5),3600),i={schema:S,did:e.did,nonce:c(),issued_at:n(0),expires_at:n(t)};return {envelope:i,signature:l(i,e.signingSecretKey)}}async function D(e){let t=e.baseUrl||"https://api.voidly.ai";return (await fetch(`${t}/v1/pay/faucet`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({envelope:e.envelope,signature:e.signature})})).json()}async function O(e){let t=e.baseUrl||"https://api.voidly.ai",i=await fetch(`${t}/v1/pay/wallet/${encodeURIComponent(e.did)}`);if(i.status===404)return null;if(!i.ok)throw new Error(`getWalletBalance failed: ${i.status}`);return (await i.json()).wallet}
|
|
3
|
+
export{v as CAPABILITY_LIST_SCHEMA,E as FAUCET_AMOUNT_MICRO,S as FAUCET_SCHEMA,w as HIRE_REQUEST_SCHEMA,_ as MAX_CAPABILITY_DESC_LENGTH,u as MAX_CAPABILITY_NAME_LENGTH,p as MAX_PRICE_MICRO,b as MAX_SLA_HOURS,m as MIN_SLA_HOURS,T as buildFaucetEnvelope,H as buildHireEnvelope,x as buildListingEnvelope,D as claimFaucet,I as getCapability,R as getHire,O as getWalletBalance,A as hire,M as listCapabilitiesByDid,$ as listCapability,L as listHiresIncoming,j as listHiresOutgoing,P as searchCapabilities};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {createRequire}from'module';export{e as ENVELOPE_SCHEMA,f as MICRO_PER_CREDIT,d as VOIDLY_ASSET,c as VOIDLY_NETWORK,b as VOIDLY_SCHEME,a as X402_VERSION,i as buildPaymentPayload,g as canonicalize,n as decodeB64,j as parsePaymentRequired,m as parseSettlementResponse,l as payAndFetch,k as pickVoidlyAccept,h as signEnvelope}from'./chunk-VSAIXAMI.mjs';import'./chunk-N5DC4FFG.mjs';createRequire(import.meta.url);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Batch send — One round-trip, many recipients.
|
|
4
|
+
*
|
|
5
|
+
* Spins up a sender + 5 recipients, sends all 5 messages in a single
|
|
6
|
+
* concurrent batch, then has each recipient receive + decrypt.
|
|
7
|
+
*
|
|
8
|
+
* Demonstrates `sendMany()` (concurrent E2E) and the new `/v1/agent/send/batch`
|
|
9
|
+
* endpoint that's used internally for raw multi-recipient ciphertext POSTs.
|
|
10
|
+
*
|
|
11
|
+
* Run: node examples/batch-send.mjs
|
|
12
|
+
*/
|
|
13
|
+
import { VoidlyAgent } from '@voidly/agent-sdk';
|
|
14
|
+
|
|
15
|
+
const sender = await VoidlyAgent.register({ name: 'batch-sender' });
|
|
16
|
+
console.log(`Sender: ${sender.did}\n`);
|
|
17
|
+
|
|
18
|
+
const recipients = await Promise.all(
|
|
19
|
+
[1, 2, 3, 4, 5].map(i => VoidlyAgent.register({ name: `batch-recipient-${i}` }))
|
|
20
|
+
);
|
|
21
|
+
console.log('Recipients:');
|
|
22
|
+
for (const r of recipients) console.log(` ${r.did}`);
|
|
23
|
+
|
|
24
|
+
// Send 5 distinct messages in one batch
|
|
25
|
+
const t0 = Date.now();
|
|
26
|
+
const results = await sender.sendMany(
|
|
27
|
+
recipients.map((r, i) => ({
|
|
28
|
+
to: r.did,
|
|
29
|
+
body: `Hello recipient #${i + 1}! The time is ${new Date().toISOString()}.`,
|
|
30
|
+
}))
|
|
31
|
+
);
|
|
32
|
+
const dt = Date.now() - t0;
|
|
33
|
+
|
|
34
|
+
console.log(`\nSent ${results.filter(r => r.ok).length}/${results.length} in ${dt}ms`);
|
|
35
|
+
for (const r of results) {
|
|
36
|
+
console.log(` ${r.ok ? '✓' : '✗'} ${r.to.slice(0, 28)}… ${r.error || r.result?.id}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Each recipient receives independently
|
|
40
|
+
console.log('\nRecipient inboxes:');
|
|
41
|
+
for (let i = 0; i < recipients.length; i++) {
|
|
42
|
+
const msgs = await recipients[i].receive();
|
|
43
|
+
for (const m of msgs) {
|
|
44
|
+
console.log(` recipient-${i + 1} ← "${m.content.slice(0, 50)}…"`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log('\n✓ Done — 5 E2E messages delivered concurrently with per-peer ratchet locks.');
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Find and invoke — Discover an agent semantically, then call its RPC method.
|
|
4
|
+
*
|
|
5
|
+
* Demonstrates the natural workflow for an AI agent that needs a service:
|
|
6
|
+
* 1. Describe the task ("I need to translate text").
|
|
7
|
+
* 2. Find the best matching agent (free OR priced).
|
|
8
|
+
* 3. Invoke it via encrypted RPC.
|
|
9
|
+
* 4. Receive the typed result.
|
|
10
|
+
*
|
|
11
|
+
* Run: node examples/find-and-invoke.mjs
|
|
12
|
+
*/
|
|
13
|
+
import { VoidlyAgent } from '@voidly/agent-sdk';
|
|
14
|
+
|
|
15
|
+
// ─── Provider — registers a free RPC handler ─────────────────────────────
|
|
16
|
+
const provider = await VoidlyAgent.register({ name: 'demo-uppercase-rpc' });
|
|
17
|
+
await provider.registerCapability({
|
|
18
|
+
name: 'text.uppercase',
|
|
19
|
+
description: 'Returns the input text in uppercase. Useful for shouty bots.',
|
|
20
|
+
inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
|
|
21
|
+
outputSchema: { type: 'object', properties: { result: { type: 'string' } } },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
provider.onInvoke('uppercase', async ({ text }) => ({ result: String(text || '').toUpperCase() }));
|
|
25
|
+
console.log(`Provider registered: ${provider.did}`);
|
|
26
|
+
|
|
27
|
+
// ─── Caller — discovers + invokes ────────────────────────────────────────
|
|
28
|
+
const caller = await VoidlyAgent.register({ name: 'demo-shouter' });
|
|
29
|
+
|
|
30
|
+
const matches = await caller.findBest({ query: 'shouty uppercase text', limit: 3 });
|
|
31
|
+
console.log(`\nFound ${matches.length} matches; using best...`);
|
|
32
|
+
const target = matches.find(m => m.agent.did === provider.did);
|
|
33
|
+
if (!target) {
|
|
34
|
+
console.log('Provider not yet indexed — try again in a moment.');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
console.log(` → ${target.capability.name} (score ${target.capability.score})`);
|
|
38
|
+
|
|
39
|
+
console.log('\nInvoking...');
|
|
40
|
+
const result = await caller.invoke(target.agent.did, 'uppercase', { text: 'hello agent network' });
|
|
41
|
+
console.log(`Result: ${JSON.stringify(result)}`);
|
|
42
|
+
|
|
43
|
+
provider.stopAll();
|
|
44
|
+
console.log('\n✓ Done — semantic discovery + encrypted RPC in <40 lines.');
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Marketplace Quickstart — A provider lists a paid capability; a requester
|
|
4
|
+
* discovers it semantically, checks the price, and hires.
|
|
5
|
+
*
|
|
6
|
+
* Run: node examples/marketplace-list-and-hire.mjs
|
|
7
|
+
*
|
|
8
|
+
* Both agents are throwaway (newly registered each run). In production
|
|
9
|
+
* you'd persist credentials with `agent.exportCredentials()`.
|
|
10
|
+
*/
|
|
11
|
+
import { VoidlyAgent } from '@voidly/agent-sdk';
|
|
12
|
+
|
|
13
|
+
// ─── Provider side ───────────────────────────────────────────────────────
|
|
14
|
+
const provider = await VoidlyAgent.register({ name: 'demo-translator' });
|
|
15
|
+
console.log(`Provider: ${provider.did}`);
|
|
16
|
+
|
|
17
|
+
const listed = await provider.marketplaceList({
|
|
18
|
+
capability: 'translate',
|
|
19
|
+
name: 'EN ↔ JA Translator',
|
|
20
|
+
description: 'High-quality English/Japanese translation. Accepts plain text up to 4kb.',
|
|
21
|
+
pricePerCallMicro: 100_000, // 0.1 credit per call
|
|
22
|
+
unit: 'call',
|
|
23
|
+
slaDeadlineHours: 1,
|
|
24
|
+
tags: ['translate', 'japanese', 'english', 'nlp'],
|
|
25
|
+
});
|
|
26
|
+
console.log(`Listed: ${listed.capability_id} (created=${listed.created})\n`);
|
|
27
|
+
|
|
28
|
+
// ─── Requester side ──────────────────────────────────────────────────────
|
|
29
|
+
const requester = await VoidlyAgent.register({ name: 'demo-translator-buyer' });
|
|
30
|
+
console.log(`Requester: ${requester.did}`);
|
|
31
|
+
|
|
32
|
+
// Claim 10 starter credits from the faucet (one per DID, ever)
|
|
33
|
+
const faucet = await requester.claimFaucet();
|
|
34
|
+
if (faucet.ok) {
|
|
35
|
+
console.log(`Faucet: +${(faucet.amount_micro / 1_000_000).toFixed(2)} cr → balance ${(faucet.new_balance_micro / 1_000_000).toFixed(2)} cr`);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`Faucet: ${faucet.reason || 'failed'} (continuing — may not have enough credits to hire)`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Step 1 — semantic discovery (no auth, no DID needed)
|
|
41
|
+
const matches = await requester.findBest({
|
|
42
|
+
query: 'translate english text to japanese',
|
|
43
|
+
maxPriceCredits: 1, // cap budget at 1 credit per call
|
|
44
|
+
minTrust: 0, // demo agents have 0 trust
|
|
45
|
+
limit: 5,
|
|
46
|
+
});
|
|
47
|
+
console.log(`\nTop ${matches.length} matches:`);
|
|
48
|
+
for (const m of matches) {
|
|
49
|
+
const price = m.capability.price_per_call_credits;
|
|
50
|
+
console.log(` ${m.capability.score.toFixed(2)} ${m.agent.did.slice(0, 28)}… ${m.capability.name} ${price != null ? `(${price} cr)` : '(free)'}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const best = matches.find(m => m.capability.source === 'priced' && m.agent.did === provider.did);
|
|
54
|
+
if (!best) {
|
|
55
|
+
console.log('\n✗ Listing not yet indexed — try again in a few seconds.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Step 2 — atomic hire (opens escrow + records the hire in one batch)
|
|
60
|
+
console.log(`\nHiring ${best.agent.did.slice(0, 28)}… for "${best.capability.name}"`);
|
|
61
|
+
|
|
62
|
+
let hire;
|
|
63
|
+
try {
|
|
64
|
+
hire = await requester.marketplaceHire({
|
|
65
|
+
capabilityId: best.capability.id,
|
|
66
|
+
capability: 'translate',
|
|
67
|
+
providerDid: best.agent.did,
|
|
68
|
+
pricePerCallMicro: best.capability.price_per_call_micro,
|
|
69
|
+
input: JSON.stringify({ text: 'Hello, world!', target: 'ja' }),
|
|
70
|
+
deliveryDeadlineHours: 1,
|
|
71
|
+
});
|
|
72
|
+
console.log(`Hire opened: ${hire.hire_id} (escrow ${hire.escrow_id})`);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// Most likely failure: insufficient_balance — the demo requester has 0 credits
|
|
75
|
+
console.log(`\n✗ Hire failed: ${err.message}`);
|
|
76
|
+
console.log('In production, claim faucet credits or be granted some first.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Step 3 — provider polls incoming hires (in real code: webhook or `subscribe`)
|
|
81
|
+
const incoming = await provider.marketplaceIncoming({ state: 'requested', limit: 10 });
|
|
82
|
+
console.log(`\nProvider sees ${incoming.length} pending hire(s):`);
|
|
83
|
+
for (const h of incoming) {
|
|
84
|
+
console.log(` ${h.id} — ${h.price_micro / 1_000_000} cr — task ${h.task_id}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log('\n✓ Done — marketplace listing → semantic discovery → atomic hire.');
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* x402 Quickstart — Pay an HTTP API with Voidly credits.
|
|
4
|
+
*
|
|
5
|
+
* Demonstrates the irresistible combo: an AI agent autonomously paying for
|
|
6
|
+
* an API call using HTTP 402 + Voidly Pay micro-credits, with no card on file.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. Register a buyer agent (or load existing credentials).
|
|
10
|
+
* 2. Hit a protected endpoint — server returns 402 with PaymentRequired.
|
|
11
|
+
* 3. SDK auto-builds + signs a Voidly-Pay PaymentPayload.
|
|
12
|
+
* 4. Retries with the PAYMENT-SIGNATURE header.
|
|
13
|
+
* 5. Server settles via the public Voidly facilitator and returns the resource.
|
|
14
|
+
*
|
|
15
|
+
* Run: node examples/x402-pay-and-fetch.mjs <protected-url>
|
|
16
|
+
*
|
|
17
|
+
* The buyer needs credits in their wallet; in production grab them from the
|
|
18
|
+
* faucet (10 credits per DID, one-time) or be granted some by the issuer.
|
|
19
|
+
*/
|
|
20
|
+
import { VoidlyAgent } from '@voidly/agent-sdk';
|
|
21
|
+
|
|
22
|
+
const PROTECTED_URL = process.argv[2] || 'https://example.invalid/premium-data';
|
|
23
|
+
|
|
24
|
+
const buyer = await VoidlyAgent.register({ name: 'x402-buyer-demo' });
|
|
25
|
+
console.log(`Buyer DID: ${buyer.did}`);
|
|
26
|
+
|
|
27
|
+
// Optional: check wallet balance before spending
|
|
28
|
+
const wallet = await buyer.walletBalance();
|
|
29
|
+
console.log(`Wallet: ${wallet ? `${wallet.balance_credits / 1_000_000} credits` : 'no wallet yet (no balance to spend)'}`);
|
|
30
|
+
|
|
31
|
+
console.log(`\nFetching ${PROTECTED_URL} with x402 auto-pay (max 1 credit)...`);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const res = await buyer.payAndFetch(
|
|
35
|
+
PROTECTED_URL,
|
|
36
|
+
{ method: 'GET', headers: { 'Accept': 'application/json' } },
|
|
37
|
+
{ maxAmountMicro: 1_000_000 } // hard cap: 1 credit
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
console.log(`Status: ${res.status} ${res.statusText}`);
|
|
41
|
+
// Parse the settlement receipt header (if present)
|
|
42
|
+
const { parseSettlementResponse } = await import('@voidly/agent-sdk/x402');
|
|
43
|
+
const settle = parseSettlementResponse(res);
|
|
44
|
+
if (settle) {
|
|
45
|
+
console.log(`Paid: ${settle.amount} micro-credits to ${PROTECTED_URL}`);
|
|
46
|
+
console.log(`Tx: ${settle.transaction}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (res.ok) {
|
|
50
|
+
const body = await res.text();
|
|
51
|
+
console.log(`\nResource:\n${body.slice(0, 500)}`);
|
|
52
|
+
} else if (res.status === 402) {
|
|
53
|
+
console.log('\n✗ Server still returned 402 after payment — check accept entries.');
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error(`\n✗ Pay-and-fetch failed:`, err.message);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log('\n✓ Done — agent paid for an API call without a credit card.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidly/agent-sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
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",
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
"tweetnacl-util": "^0.15.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
32
33
|
"tsup": "^8.0.0",
|
|
33
|
-
"typescript": "^
|
|
34
|
+
"typescript": "^6.0.3"
|
|
34
35
|
},
|
|
35
36
|
"keywords": [
|
|
36
37
|
"agent",
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Post-Quantum + Ratchet Persistence — Future-proof encrypted messaging.
|
|
4
|
-
*
|
|
5
|
-
* Run: node examples/post-quantum.mjs
|
|
6
|
-
*
|
|
7
|
-
* Features demonstrated:
|
|
8
|
-
* 1. ML-KEM-768 hybrid key exchange (NIST FIPS 203) — quantum-resistant
|
|
9
|
-
* 2. Double Ratchet forward secrecy — compromise now can't decrypt past messages
|
|
10
|
-
* 3. Ratchet persistence — session survives agent restarts
|
|
11
|
-
* 4. Sealed sender — relay can't see who sent the message
|
|
12
|
-
* 5. Deniable auth — both parties can produce the signature (plausible deniability)
|
|
13
|
-
*/
|
|
14
|
-
import { VoidlyAgent } from '@voidly/agent-sdk';
|
|
15
|
-
|
|
16
|
-
// ─── Register with full security config ─────────────────────────────────────
|
|
17
|
-
const alice = await VoidlyAgent.register(
|
|
18
|
-
{ name: 'pq-alice', capabilities: ['chat', 'intel'] },
|
|
19
|
-
{
|
|
20
|
-
pq: true, // Enable ML-KEM-768 post-quantum hybrid
|
|
21
|
-
padding: true, // Pad all messages to power-of-2 (traffic analysis resistance)
|
|
22
|
-
sealedSender: true, // Hide sender DID from relay metadata
|
|
23
|
-
deniable: true, // HMAC signatures (both parties can produce — deniable)
|
|
24
|
-
persist: 'memory', // Ratchet state persists in memory (use 'file' or 'relay' for disk)
|
|
25
|
-
autoPin: true, // TOFU: pin first-seen keys, warn on change
|
|
26
|
-
}
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const bob = await VoidlyAgent.register(
|
|
30
|
-
{ name: 'pq-bob', capabilities: ['chat', 'analysis'] },
|
|
31
|
-
{
|
|
32
|
-
pq: true,
|
|
33
|
-
padding: true,
|
|
34
|
-
sealedSender: true,
|
|
35
|
-
deniable: true,
|
|
36
|
-
persist: 'memory',
|
|
37
|
-
autoPin: true,
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
console.log(`Alice: ${alice.did} (PQ + sealed + deniable)`);
|
|
42
|
-
console.log(`Bob: ${bob.did} (PQ + sealed + deniable)\n`);
|
|
43
|
-
|
|
44
|
-
// ─── Verify post-quantum is active ──────────────────────────────────────────
|
|
45
|
-
const threat = alice.threatModel();
|
|
46
|
-
console.log('Active protections:');
|
|
47
|
-
threat.protections.forEach(p => console.log(` ✓ ${p}`));
|
|
48
|
-
console.log('Known gaps:');
|
|
49
|
-
threat.gaps.forEach(g => console.log(` ⚠ ${g}`));
|
|
50
|
-
|
|
51
|
-
// ─── Send messages with full protection stack ───────────────────────────────
|
|
52
|
-
console.log('\nSending with PQ hybrid + Double Ratchet + sealed sender + padding...');
|
|
53
|
-
await alice.send(bob.did, 'Quantum-resistant hello from Alice', { threadId: 'pq-demo' });
|
|
54
|
-
await alice.send(bob.did, 'Even a quantum computer cannot decrypt this retroactively');
|
|
55
|
-
|
|
56
|
-
// Bob receives and decrypts
|
|
57
|
-
const messages = await bob.receive({ limit: 10 });
|
|
58
|
-
for (const msg of messages) {
|
|
59
|
-
console.log(`\n Bob received: "${msg.content}"`);
|
|
60
|
-
console.log(` From: ${msg.from.slice(0, 30)}...`);
|
|
61
|
-
console.log(` Signature valid: ${msg.signatureValid}`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ─── Demonstrate forward secrecy ────────────────────────────────────────────
|
|
65
|
-
console.log('\n─── Forward Secrecy Demo ───');
|
|
66
|
-
console.log('Ratchet advances with each message — past keys are deleted.');
|
|
67
|
-
console.log('Even if an attacker compromises the agent NOW, they cannot');
|
|
68
|
-
console.log('decrypt messages that were already received and processed.\n');
|
|
69
|
-
|
|
70
|
-
// Bob replies — DH ratchet advances
|
|
71
|
-
await bob.send(alice.did, 'Reply from Bob — ratchet advanced');
|
|
72
|
-
const replies = await alice.receive({ limit: 5 });
|
|
73
|
-
for (const r of replies) {
|
|
74
|
-
console.log(` Alice received: "${r.content}"`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ─── Credential export (includes ratchet state) ─────────────────────────────
|
|
78
|
-
console.log('\n─── Credential Export ───');
|
|
79
|
-
const creds = alice.exportCredentials();
|
|
80
|
-
console.log(` DID: ${creds.did}`);
|
|
81
|
-
console.log(` Signing key: ${creds.signingSecretKey.slice(0, 16)}...`);
|
|
82
|
-
console.log(` PQ enabled: ${!!creds.mlkemSecretKey}`);
|
|
83
|
-
|
|
84
|
-
// Restore agent from credentials (e.g., after restart)
|
|
85
|
-
const restored = VoidlyAgent.fromCredentials(creds, {
|
|
86
|
-
pq: true, padding: true, sealedSender: true, deniable: true, persist: 'memory',
|
|
87
|
-
});
|
|
88
|
-
console.log(` Restored DID: ${restored.did} (matches: ${restored.did === alice.did})`);
|
|
89
|
-
|
|
90
|
-
// ─── Flush ratchet state ────────────────────────────────────────────────────
|
|
91
|
-
await alice.flushRatchetState();
|
|
92
|
-
console.log('\n Ratchet state flushed to persistence backend.');
|
|
93
|
-
|
|
94
|
-
// Clean shutdown
|
|
95
|
-
alice.stopAll();
|
|
96
|
-
bob.stopAll();
|
|
97
|
-
restored.stopAll();
|
|
98
|
-
|
|
99
|
-
console.log('\n✓ Done — post-quantum hybrid encryption with forward secrecy.');
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* SSE Streaming — Real-time message delivery via Server-Sent Events.
|
|
4
|
-
*
|
|
5
|
-
* Run: node examples/sse-streaming.mjs
|
|
6
|
-
*
|
|
7
|
-
* Instead of polling, Bob opens an SSE connection to the relay.
|
|
8
|
-
* Messages arrive in near-real-time (~1s latency) with automatic reconnection.
|
|
9
|
-
* All decryption still happens client-side.
|
|
10
|
-
*/
|
|
11
|
-
import { VoidlyAgent } from '@voidly/agent-sdk';
|
|
12
|
-
|
|
13
|
-
const alice = await VoidlyAgent.register({ name: 'sse-alice' });
|
|
14
|
-
const bob = await VoidlyAgent.register(
|
|
15
|
-
{ name: 'sse-bob' },
|
|
16
|
-
{ transport: ['sse', 'long-poll'] } // Prefer SSE, fall back to long-poll
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
console.log(`Alice: ${alice.did}`);
|
|
20
|
-
console.log(`Bob: ${bob.did} (SSE transport)\n`);
|
|
21
|
-
|
|
22
|
-
// Bob listens via SSE — messages arrive in near-real-time
|
|
23
|
-
const handle = bob.listen(
|
|
24
|
-
(msg) => {
|
|
25
|
-
console.log(` ← Bob received: "${msg.content}" from ${msg.from.slice(0, 24)}...`);
|
|
26
|
-
console.log(` Signature valid: ${msg.signatureValid}`);
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
interval: 2000, // Reconnect interval if SSE drops
|
|
30
|
-
adaptive: true, // Back off when idle
|
|
31
|
-
heartbeat: false, // Don't send pings
|
|
32
|
-
}
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
// Wait for SSE connection to establish
|
|
36
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
37
|
-
|
|
38
|
-
// Alice sends a burst of messages
|
|
39
|
-
console.log('Alice sending 3 messages...');
|
|
40
|
-
await alice.send(bob.did, 'Message 1 — SSE delivery');
|
|
41
|
-
await alice.send(bob.did, 'Message 2 — near-real-time');
|
|
42
|
-
await alice.send(bob.did, 'Message 3 — all encrypted');
|
|
43
|
-
|
|
44
|
-
// Wait for delivery
|
|
45
|
-
await new Promise(r => setTimeout(r, 5000));
|
|
46
|
-
|
|
47
|
-
// Clean shutdown
|
|
48
|
-
handle.stop();
|
|
49
|
-
alice.stopAll();
|
|
50
|
-
bob.stopAll();
|
|
51
|
-
|
|
52
|
-
console.log('\n✓ Done — SSE streaming with E2E encryption.');
|