@agentxv2/sdk 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/dist/core.d.mts +64 -0
- package/dist/core.d.ts +64 -0
- package/dist/core.js +275 -0
- package/dist/core.js.map +1 -0
- package/dist/core.mjs +240 -0
- package/dist/core.mjs.map +1 -0
- package/dist/{agent/agent-runner.d.ts → index-DhXVi_ub.d.ts} +94 -10
- package/dist/index-eQO4iaOr.d.mts +208 -0
- package/dist/index.d.mts +269 -0
- package/dist/index.d.ts +269 -10
- package/dist/index.js +2398 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2345 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +3 -0
- package/dist/react.d.ts +3 -0
- package/dist/react.js +737 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +717 -0
- package/dist/react.mjs.map +1 -0
- package/dist/{core/types.d.ts → types-2aHUvS4M.d.mts} +34 -33
- package/dist/types-2aHUvS4M.d.ts +297 -0
- package/package.json +12 -11
- package/dist/a2a/a2a.d.ts +0 -37
- package/dist/a2a/a2a.d.ts.map +0 -1
- package/dist/a2a/a2a.js +0 -252
- package/dist/a2a/a2a.js.map +0 -1
- package/dist/a2a/index.d.ts +0 -4
- package/dist/a2a/index.d.ts.map +0 -1
- package/dist/a2a/index.js +0 -4
- package/dist/a2a/index.js.map +0 -1
- package/dist/agent/agent-runner.d.ts.map +0 -1
- package/dist/agent/agent-runner.js +0 -249
- package/dist/agent/agent-runner.js.map +0 -1
- package/dist/agent/index.d.ts +0 -3
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/agent/index.js +0 -3
- package/dist/agent/index.js.map +0 -1
- package/dist/config/config.d.ts +0 -31
- package/dist/config/config.d.ts.map +0 -1
- package/dist/config/config.js +0 -95
- package/dist/config/config.js.map +0 -1
- package/dist/config/index.d.ts +0 -4
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js +0 -4
- package/dist/config/index.js.map +0 -1
- package/dist/core/crypto.d.ts +0 -62
- package/dist/core/crypto.d.ts.map +0 -1
- package/dist/core/crypto.js +0 -291
- package/dist/core/crypto.js.map +0 -1
- package/dist/core/index.d.ts +0 -3
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -4
- package/dist/core/index.js.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -30
- package/dist/core/types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/mcp/connector.d.ts +0 -43
- package/dist/mcp/connector.d.ts.map +0 -1
- package/dist/mcp/connector.js +0 -79
- package/dist/mcp/connector.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -4
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -4
- package/dist/mcp/index.js.map +0 -1
- package/dist/react/index.d.ts +0 -4
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/index.js +0 -3
- package/dist/react/index.js.map +0 -1
- package/dist/react/useAgentRunner.d.ts +0 -16
- package/dist/react/useAgentRunner.d.ts.map +0 -1
- package/dist/react/useAgentRunner.js +0 -204
- package/dist/react/useAgentRunner.js.map +0 -1
- package/dist/registry/agent-registry.d.ts +0 -52
- package/dist/registry/agent-registry.d.ts.map +0 -1
- package/dist/registry/agent-registry.js +0 -221
- package/dist/registry/agent-registry.js.map +0 -1
- package/dist/registry/index.d.ts +0 -7
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/index.js +0 -6
- package/dist/registry/index.js.map +0 -1
- package/dist/registry/ipfs-fetcher.d.ts +0 -39
- package/dist/registry/ipfs-fetcher.d.ts.map +0 -1
- package/dist/registry/ipfs-fetcher.js +0 -139
- package/dist/registry/ipfs-fetcher.js.map +0 -1
- package/dist/reputation/index.d.ts +0 -4
- package/dist/reputation/index.d.ts.map +0 -1
- package/dist/reputation/index.js +0 -4
- package/dist/reputation/index.js.map +0 -1
- package/dist/reputation/reputation.d.ts +0 -26
- package/dist/reputation/reputation.d.ts.map +0 -1
- package/dist/reputation/reputation.js +0 -113
- package/dist/reputation/reputation.js.map +0 -1
- package/dist/subscription/agent-x402.d.ts +0 -33
- package/dist/subscription/agent-x402.d.ts.map +0 -1
- package/dist/subscription/agent-x402.js +0 -144
- package/dist/subscription/agent-x402.js.map +0 -1
- package/dist/subscription/index.d.ts +0 -6
- package/dist/subscription/index.d.ts.map +0 -1
- package/dist/subscription/index.js +0 -5
- package/dist/subscription/index.js.map +0 -1
- package/dist/subscription/subscription.d.ts +0 -67
- package/dist/subscription/subscription.d.ts.map +0 -1
- package/dist/subscription/subscription.js +0 -346
- package/dist/subscription/subscription.js.map +0 -1
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/react/useAgentRunner.ts
|
|
9
|
+
import { useState, useEffect, useRef } from "react";
|
|
10
|
+
import { usePublicClient, useWalletClient } from "wagmi";
|
|
11
|
+
|
|
12
|
+
// src/core/crypto.ts
|
|
13
|
+
import { gcm } from "@noble/ciphers/aes.js";
|
|
14
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
15
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
16
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
17
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
18
|
+
import { bytesToHex, hexToBytes } from "@noble/ciphers/utils.js";
|
|
19
|
+
function randomBytes(length) {
|
|
20
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
21
|
+
const buf = new Uint8Array(length);
|
|
22
|
+
crypto.getRandomValues(buf);
|
|
23
|
+
return buf;
|
|
24
|
+
}
|
|
25
|
+
const nodeCrypto = __require("crypto");
|
|
26
|
+
return new Uint8Array(nodeCrypto.randomBytes(length));
|
|
27
|
+
}
|
|
28
|
+
var AES_KEY_SIZE = 32;
|
|
29
|
+
var IV_SIZE = 12;
|
|
30
|
+
var TAG_SIZE = 16;
|
|
31
|
+
function fromBase64(b64) {
|
|
32
|
+
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(b64, "base64"));
|
|
33
|
+
const binary = atob(b64);
|
|
34
|
+
const bytes = new Uint8Array(binary.length);
|
|
35
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
36
|
+
return bytes;
|
|
37
|
+
}
|
|
38
|
+
function aesDecrypt(encryptedBase64, keyHex) {
|
|
39
|
+
const key = hexToBytes(keyHex);
|
|
40
|
+
const combined = fromBase64(encryptedBase64);
|
|
41
|
+
const iv = combined.subarray(0, IV_SIZE);
|
|
42
|
+
const ciphertext = combined.subarray(IV_SIZE, -TAG_SIZE);
|
|
43
|
+
const authTag = combined.subarray(-TAG_SIZE);
|
|
44
|
+
const cipher = gcm(key, iv);
|
|
45
|
+
const ciphertextWithTag = new Uint8Array(ciphertext.length + TAG_SIZE);
|
|
46
|
+
ciphertextWithTag.set(ciphertext, 0);
|
|
47
|
+
ciphertextWithTag.set(authTag, ciphertext.length);
|
|
48
|
+
const decrypted = cipher.decrypt(ciphertextWithTag);
|
|
49
|
+
return new TextDecoder().decode(decrypted);
|
|
50
|
+
}
|
|
51
|
+
function generateAesKey() {
|
|
52
|
+
return bytesToHex(randomBytes(AES_KEY_SIZE));
|
|
53
|
+
}
|
|
54
|
+
function eciesEncode(ephemeralPub, iv, ciphertext, mac) {
|
|
55
|
+
const out = new Uint8Array(33 + 16 + ciphertext.length + 32);
|
|
56
|
+
out.set(ephemeralPub, 0);
|
|
57
|
+
out.set(iv, 33);
|
|
58
|
+
out.set(ciphertext, 33 + 16);
|
|
59
|
+
out.set(mac, 33 + 16 + ciphertext.length);
|
|
60
|
+
return bytesToHex(out);
|
|
61
|
+
}
|
|
62
|
+
function eciesDecode(dataHex) {
|
|
63
|
+
const d = hexToBytes(dataHex);
|
|
64
|
+
return {
|
|
65
|
+
ephemeralPub: d.subarray(0, 33),
|
|
66
|
+
iv: d.subarray(33, 49),
|
|
67
|
+
ciphertext: d.subarray(49, -32),
|
|
68
|
+
mac: d.subarray(-32)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function aesCtrEncrypt(key, ctrBytes, data) {
|
|
72
|
+
const blockSize = 16;
|
|
73
|
+
const cipher = gcm(key, ctrBytes);
|
|
74
|
+
const result = new Uint8Array(data.length);
|
|
75
|
+
const counter = new Uint8Array(blockSize);
|
|
76
|
+
counter.set(ctrBytes);
|
|
77
|
+
for (let i = 0; i < data.length; i += blockSize) {
|
|
78
|
+
const keystream = gcm(key, counter).encrypt(new Uint8Array(blockSize));
|
|
79
|
+
for (let j = 0; j < blockSize && i + j < data.length; j++) {
|
|
80
|
+
result[i + j] = keystream[j] ^ data[i + j];
|
|
81
|
+
}
|
|
82
|
+
for (let j = blockSize - 1; j >= 0; j--) {
|
|
83
|
+
const val = counter[j];
|
|
84
|
+
if (val !== void 0) {
|
|
85
|
+
counter[j] = val + 1 & 255;
|
|
86
|
+
if (counter[j] !== 0) break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
function eciesEncrypt(dataHex, publicKey) {
|
|
93
|
+
const ephPriv = randomBytes(32);
|
|
94
|
+
const ephPub = secp256k1.getPublicKey(ephPriv, true);
|
|
95
|
+
let recipientPub;
|
|
96
|
+
if (publicKey.startsWith("04") && publicKey.length === 130) {
|
|
97
|
+
recipientPub = hexToBytes(publicKey);
|
|
98
|
+
} else if (publicKey.startsWith("02") || publicKey.startsWith("03")) {
|
|
99
|
+
recipientPub = hexToBytes(publicKey);
|
|
100
|
+
} else {
|
|
101
|
+
throw new Error("Invalid public key format: expected hex with 02/03/04 prefix");
|
|
102
|
+
}
|
|
103
|
+
const shared = secp256k1.getSharedSecret(ephPriv, recipientPub);
|
|
104
|
+
const sharedX = shared.subarray(1, 33);
|
|
105
|
+
const sharedKey = sha256(sharedX);
|
|
106
|
+
const hkdfOut = hkdf(sha256, sharedKey, void 0, void 0, 64);
|
|
107
|
+
const encKey = hkdfOut.subarray(0, 32);
|
|
108
|
+
const macKey = hkdfOut.subarray(32, 64);
|
|
109
|
+
const iv = randomBytes(16);
|
|
110
|
+
const plaintext = hexToBytes(dataHex);
|
|
111
|
+
const ciphertext = aesCtrEncrypt(encKey, iv, plaintext);
|
|
112
|
+
const macInput = new Uint8Array(33 + 16 + ciphertext.length);
|
|
113
|
+
macInput.set(ephPub, 0);
|
|
114
|
+
macInput.set(iv, 33);
|
|
115
|
+
macInput.set(ciphertext, 33 + 16);
|
|
116
|
+
const mac = hmac(sha256, macKey, macInput);
|
|
117
|
+
return eciesEncode(ephPub, iv, ciphertext, mac);
|
|
118
|
+
}
|
|
119
|
+
function eciesDecrypt(dataHex, privateKey) {
|
|
120
|
+
const { ephemeralPub, iv, ciphertext, mac } = eciesDecode(dataHex);
|
|
121
|
+
const privBytes = hexToBytes(privateKey);
|
|
122
|
+
const shared = secp256k1.getSharedSecret(privBytes, ephemeralPub);
|
|
123
|
+
const sharedX = shared.subarray(1, 33);
|
|
124
|
+
const sharedKey = sha256(sharedX);
|
|
125
|
+
const hkdfOut = hkdf(sha256, sharedKey, void 0, void 0, 64);
|
|
126
|
+
const encKey = hkdfOut.subarray(0, 32);
|
|
127
|
+
const macKey = hkdfOut.subarray(32, 64);
|
|
128
|
+
const macInput = new Uint8Array(33 + 16 + ciphertext.length);
|
|
129
|
+
macInput.set(ephemeralPub, 0);
|
|
130
|
+
macInput.set(iv, 33);
|
|
131
|
+
macInput.set(ciphertext, 33 + 16);
|
|
132
|
+
const expectedMac = hmac(sha256, macKey, macInput);
|
|
133
|
+
if (!constantTimeEqual(mac, expectedMac)) {
|
|
134
|
+
throw new Error("ECIES decryption failed: MAC mismatch");
|
|
135
|
+
}
|
|
136
|
+
const plaintext = aesCtrEncrypt(encKey, iv, ciphertext);
|
|
137
|
+
return bytesToHex(plaintext);
|
|
138
|
+
}
|
|
139
|
+
function constantTimeEqual(a, b) {
|
|
140
|
+
if (a.length !== b.length) return false;
|
|
141
|
+
let diff = 0;
|
|
142
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
143
|
+
return diff === 0;
|
|
144
|
+
}
|
|
145
|
+
function decryptPayload(encrypted, keyHex) {
|
|
146
|
+
if (encrypted.algorithm !== "AES-256-GCM") {
|
|
147
|
+
throw new Error(`Unsupported algorithm: ${encrypted.algorithm}`);
|
|
148
|
+
}
|
|
149
|
+
return JSON.parse(aesDecrypt(encrypted.data, keyHex));
|
|
150
|
+
}
|
|
151
|
+
function unpackAgent(encryptedPayload, eciesEncryptedKey, privateKey) {
|
|
152
|
+
const aesKeyHex = eciesDecrypt(eciesEncryptedKey, privateKey);
|
|
153
|
+
return decryptPayload(encryptedPayload, aesKeyHex);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/registry/ipfs-fetcher.ts
|
|
157
|
+
var IPFSFetcher = class {
|
|
158
|
+
gateway;
|
|
159
|
+
fallbackGateways;
|
|
160
|
+
timeoutMs;
|
|
161
|
+
cache = /* @__PURE__ */ new Map();
|
|
162
|
+
maxCache;
|
|
163
|
+
pending = /* @__PURE__ */ new Map();
|
|
164
|
+
failed = /* @__PURE__ */ new Set();
|
|
165
|
+
constructor(config = {}) {
|
|
166
|
+
this.gateway = config.gateway ?? "ipfs.io";
|
|
167
|
+
this.fallbackGateways = config.fallbackGateways ?? [
|
|
168
|
+
"gateway.pinata.cloud",
|
|
169
|
+
"dweb.link",
|
|
170
|
+
"cf-ipfs.com"
|
|
171
|
+
];
|
|
172
|
+
this.timeoutMs = config.timeoutMs ?? 1e4;
|
|
173
|
+
this.maxCache = config.maxCache ?? 200;
|
|
174
|
+
}
|
|
175
|
+
// ── Public API ──────────────────────────────────────────────────────────
|
|
176
|
+
/** Fetch JSON from a single IPFS CID. */
|
|
177
|
+
async fetchJSON(cid) {
|
|
178
|
+
const cached = this.cache.get(cid);
|
|
179
|
+
if (cached) return cached.data;
|
|
180
|
+
if (this.failed.has(cid)) throw new Error(`CID ${cid} previously failed`);
|
|
181
|
+
const pending = this.pending.get(cid);
|
|
182
|
+
if (pending) return pending;
|
|
183
|
+
const promise = this._doFetch(cid);
|
|
184
|
+
this.pending.set(cid, promise);
|
|
185
|
+
try {
|
|
186
|
+
const data = await promise;
|
|
187
|
+
this._cacheSet(cid, data);
|
|
188
|
+
return data;
|
|
189
|
+
} catch (e) {
|
|
190
|
+
this.failed.add(cid);
|
|
191
|
+
throw e;
|
|
192
|
+
} finally {
|
|
193
|
+
this.pending.delete(cid);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Fetch encrypted agent payload (validates algorithm). */
|
|
197
|
+
async fetchEncryptedPayload(cid) {
|
|
198
|
+
const raw = await this.fetchJSON(cid);
|
|
199
|
+
if (!raw.encrypted || raw.algorithm !== "AES-256-GCM" || typeof raw.data !== "string") {
|
|
200
|
+
throw new Error(`Invalid EncryptedPayload at CID ${cid}`);
|
|
201
|
+
}
|
|
202
|
+
return raw;
|
|
203
|
+
}
|
|
204
|
+
/** Batch fetch multiple CIDs with concurrency control. */
|
|
205
|
+
async fetchBatch(cids, concurrency = 5) {
|
|
206
|
+
const results = /* @__PURE__ */ new Map();
|
|
207
|
+
const unique = [...new Set(cids)].filter((c) => this.isValidCID(c));
|
|
208
|
+
for (let i = 0; i < unique.length; i += concurrency) {
|
|
209
|
+
const batch = unique.slice(i, i + concurrency);
|
|
210
|
+
const settled = await Promise.allSettled(
|
|
211
|
+
batch.map((cid) => this.fetchJSON(cid))
|
|
212
|
+
);
|
|
213
|
+
settled.forEach((r, j) => {
|
|
214
|
+
if (r.status === "fulfilled") results.set(batch[j], r.value);
|
|
215
|
+
});
|
|
216
|
+
if (i + concurrency < unique.length) {
|
|
217
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return results;
|
|
221
|
+
}
|
|
222
|
+
/** Check if a string looks like a valid IPFS CID. */
|
|
223
|
+
isValidCID(cid) {
|
|
224
|
+
return /^(Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[a-z2-7]{58,}|[A-Za-z0-9+/]{46,})$/.test(cid);
|
|
225
|
+
}
|
|
226
|
+
/** Clear cache (optionally for a specific CID). */
|
|
227
|
+
clearCache(cid) {
|
|
228
|
+
if (cid) {
|
|
229
|
+
this.cache.delete(cid);
|
|
230
|
+
} else {
|
|
231
|
+
this.cache.clear();
|
|
232
|
+
}
|
|
233
|
+
this.failed.clear();
|
|
234
|
+
}
|
|
235
|
+
/** Number of cached entries. */
|
|
236
|
+
get cacheSize() {
|
|
237
|
+
return this.cache.size;
|
|
238
|
+
}
|
|
239
|
+
// ── Internal ─────────────────────────────────────────────────────────────
|
|
240
|
+
async _doFetch(cid) {
|
|
241
|
+
if (!this.isValidCID(cid)) throw new Error(`Invalid CID: ${cid}`);
|
|
242
|
+
try {
|
|
243
|
+
return await this._fetchFrom(cid, this.gateway, this.timeoutMs);
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
for (const gw of this.fallbackGateways) {
|
|
247
|
+
try {
|
|
248
|
+
return await this._fetchFrom(cid, gw, this.timeoutMs);
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
throw new Error(`All IPFS gateways failed for CID ${cid}`);
|
|
253
|
+
}
|
|
254
|
+
async _fetchFrom(cid, gateway, timeoutMs) {
|
|
255
|
+
const url = `https://${gateway}/ipfs/${cid}`;
|
|
256
|
+
const res = await fetch(url, {
|
|
257
|
+
headers: { Accept: "application/json" },
|
|
258
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
259
|
+
});
|
|
260
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
261
|
+
return await res.json();
|
|
262
|
+
}
|
|
263
|
+
_cacheSet(cid, data) {
|
|
264
|
+
this.cache.set(cid, { data, timestamp: Date.now() });
|
|
265
|
+
if (this.cache.size > this.maxCache) {
|
|
266
|
+
const oldest = [...this.cache.entries()].sort(
|
|
267
|
+
(a, b) => a[1].timestamp - b[1].timestamp
|
|
268
|
+
)[0];
|
|
269
|
+
if (oldest) this.cache.delete(oldest[0]);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
var defaultIPFSFetcher = new IPFSFetcher();
|
|
274
|
+
|
|
275
|
+
// src/core/types.ts
|
|
276
|
+
var AgentXError = class extends Error {
|
|
277
|
+
code;
|
|
278
|
+
/** If NOT_SUBSCRIBED, carry enough info for wallet/X402 auto-payment */
|
|
279
|
+
paymentInfo;
|
|
280
|
+
constructor(code, message) {
|
|
281
|
+
super(message);
|
|
282
|
+
this.code = code;
|
|
283
|
+
this.name = "AgentXError";
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// src/agent/agent-runner.ts
|
|
288
|
+
var AgentRunner = class {
|
|
289
|
+
reader;
|
|
290
|
+
wallet;
|
|
291
|
+
ipfs;
|
|
292
|
+
constructor(config) {
|
|
293
|
+
this.reader = config.reader;
|
|
294
|
+
this.wallet = config.wallet;
|
|
295
|
+
this.ipfs = config.ipfsFetcher ?? new IPFSFetcher({
|
|
296
|
+
fallbackGateways: config.ipfsGateways ?? [
|
|
297
|
+
"gateway.pinata.cloud",
|
|
298
|
+
"dweb.link",
|
|
299
|
+
"cf-ipfs.com"
|
|
300
|
+
]
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
// ── Primary API: useAgent ────────────────────────────────────────────────
|
|
304
|
+
/**
|
|
305
|
+
* Load and decrypt an Agent, returning a run context ready to inject
|
|
306
|
+
* into any LLM conversation.
|
|
307
|
+
*
|
|
308
|
+
* Steps:
|
|
309
|
+
* 1. Verify on-chain subscription (frontend check)
|
|
310
|
+
* 2. Fetch metadata → get encryptedPayloadCid + eciesEncryptedKey
|
|
311
|
+
* 3. IPFS fetch encrypted payload
|
|
312
|
+
* 4. ECIES decrypt AES key (using wallet private key)
|
|
313
|
+
* 5. AES-256-GCM decrypt payload → { prompt, skills, mcp }
|
|
314
|
+
* 6. Build RunnableSkill wrappers (Open: local stub, Closed: MCP remote)
|
|
315
|
+
*/
|
|
316
|
+
async useAgent(agentId) {
|
|
317
|
+
const address = await this.wallet.getAddress();
|
|
318
|
+
const isActive = await this.reader.hasActiveSubscription(address, agentId);
|
|
319
|
+
if (!isActive) {
|
|
320
|
+
const err = new AgentXError(
|
|
321
|
+
"NOT_SUBSCRIBED" /* NOT_SUBSCRIBED */,
|
|
322
|
+
`No active subscription for Agent #${agentId}. Check error.paymentInfo for auto-subscribe via wallet/X402.`
|
|
323
|
+
);
|
|
324
|
+
err.paymentInfo = {
|
|
325
|
+
agentId
|
|
326
|
+
};
|
|
327
|
+
throw err;
|
|
328
|
+
}
|
|
329
|
+
const attrs = await this.reader.getAttributes(agentId);
|
|
330
|
+
const encryptedPayloadCid = attrs.encryptedPayloadCid;
|
|
331
|
+
const eciesEncryptedKey = attrs.eciesEncryptedKey;
|
|
332
|
+
if (!encryptedPayloadCid || !eciesEncryptedKey) {
|
|
333
|
+
throw new AgentXError(
|
|
334
|
+
"AGENT_NOT_FOUND" /* AGENT_NOT_FOUND */,
|
|
335
|
+
`Agent #${agentId} metadata incomplete \u2014 missing encryptedPayloadCid or eciesEncryptedKey`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
let encryptedPayload;
|
|
339
|
+
try {
|
|
340
|
+
encryptedPayload = await this.ipfs.fetchEncryptedPayload(encryptedPayloadCid);
|
|
341
|
+
} catch (e) {
|
|
342
|
+
throw new AgentXError(
|
|
343
|
+
"IPFS_FETCH_FAILED" /* IPFS_FETCH_FAILED */,
|
|
344
|
+
`Failed to fetch encrypted payload for agent #${agentId}: ${e}`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
let privatePayload;
|
|
348
|
+
try {
|
|
349
|
+
const privKey = await this._getPrivateKey();
|
|
350
|
+
privatePayload = unpackAgent(encryptedPayload, eciesEncryptedKey, privKey);
|
|
351
|
+
} catch (e) {
|
|
352
|
+
throw new AgentXError(
|
|
353
|
+
"DECRYPTION_FAILED" /* DECRYPTION_FAILED */,
|
|
354
|
+
`Failed to decrypt agent #${agentId}: ${e}`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const skills = privatePayload.skills.map((s) => this._wrapSkill(s));
|
|
358
|
+
return {
|
|
359
|
+
agentId,
|
|
360
|
+
prompt: privatePayload.prompt,
|
|
361
|
+
skills,
|
|
362
|
+
mcp: {
|
|
363
|
+
type: privatePayload.mcp.type,
|
|
364
|
+
url: privatePayload.mcp.url,
|
|
365
|
+
toolFilter: privatePayload.mcp.toolFilter
|
|
366
|
+
},
|
|
367
|
+
subscriptionExpiry: 0
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
// ── Publishing ───────────────────────────────────────────────────────────
|
|
371
|
+
/**
|
|
372
|
+
* Pack an AgentPayload for publishing (encryption only, no IPFS upload).
|
|
373
|
+
* Caller is responsible for IPFS upload and on-chain registration.
|
|
374
|
+
*/
|
|
375
|
+
packForPublish(payload, publicKey) {
|
|
376
|
+
const key = generateAesKey();
|
|
377
|
+
return {
|
|
378
|
+
encryptedCid: "",
|
|
379
|
+
publicCid: "",
|
|
380
|
+
aesKeyHex: key,
|
|
381
|
+
eciesEncryptedKeyHex: eciesEncrypt(key, publicKey)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
// ── Internals ────────────────────────────────────────────────────────────
|
|
385
|
+
/** Wrap a SkillDef into a RunnableSkill with execute(). */
|
|
386
|
+
_wrapSkill(skill) {
|
|
387
|
+
let mode = "open";
|
|
388
|
+
let executeFn;
|
|
389
|
+
if (skill.execution) {
|
|
390
|
+
if (skill.execution.type === "mcp") {
|
|
391
|
+
mode = "mcp";
|
|
392
|
+
const endpoint = skill.execution.endpoint ?? "";
|
|
393
|
+
const toolName = skill.execution.toolName ?? skill.name;
|
|
394
|
+
executeFn = async (input) => {
|
|
395
|
+
return this._executeMCPTool(endpoint, toolName, input);
|
|
396
|
+
};
|
|
397
|
+
} else if (skill.execution.type === "a2a") {
|
|
398
|
+
mode = "a2a";
|
|
399
|
+
executeFn = async (input) => {
|
|
400
|
+
return this._executeA2ASkill(skill, input);
|
|
401
|
+
};
|
|
402
|
+
} else {
|
|
403
|
+
throw new AgentXError(
|
|
404
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */,
|
|
405
|
+
`Unknown execution type "${skill.execution.type}" for skill "${skill.name}"`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
executeFn = async () => {
|
|
410
|
+
throw new AgentXError(
|
|
411
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */,
|
|
412
|
+
`Open skill "${skill.name}" has no local executor. Implement execute() or switch to execution.type = "mcp" or "a2a".`
|
|
413
|
+
);
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
name: skill.name,
|
|
418
|
+
description: skill.description,
|
|
419
|
+
inputSchema: skill.inputSchema,
|
|
420
|
+
outputSchema: skill.outputSchema,
|
|
421
|
+
mode,
|
|
422
|
+
execute: executeFn,
|
|
423
|
+
/** If A2A, carry delegation metadata so the LLM can see it */
|
|
424
|
+
a2aTargetAgentId: skill.execution?.type === "a2a" ? skill.execution.targetAgentId : void 0
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/** Call a tool on the publisher's MCP server (Closed skill). */
|
|
428
|
+
async _executeMCPTool(endpoint, toolName, params) {
|
|
429
|
+
const address = await this.wallet.getAddress();
|
|
430
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
431
|
+
const message = `agentx:mcp:${toolName}:${timestamp}`;
|
|
432
|
+
const signature = await this.wallet.signMessage(message);
|
|
433
|
+
const res = await fetch(endpoint, {
|
|
434
|
+
method: "POST",
|
|
435
|
+
headers: {
|
|
436
|
+
"Content-Type": "application/json",
|
|
437
|
+
"X-Subscriber-Address": address,
|
|
438
|
+
"X-Signature": signature,
|
|
439
|
+
"X-Timestamp": String(timestamp)
|
|
440
|
+
},
|
|
441
|
+
body: JSON.stringify({
|
|
442
|
+
method: "tools/call",
|
|
443
|
+
params: {
|
|
444
|
+
name: toolName,
|
|
445
|
+
arguments: params
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
});
|
|
449
|
+
if (!res.ok) {
|
|
450
|
+
const text = await res.text();
|
|
451
|
+
if (res.status === 403) {
|
|
452
|
+
throw new AgentXError(
|
|
453
|
+
"SUBSCRIPTION_EXPIRED" /* SUBSCRIPTION_EXPIRED */,
|
|
454
|
+
`MCP server rejected request: subscription may have expired. ${text}`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
throw new AgentXError(
|
|
458
|
+
"TX_FAILED" /* TX_FAILED */,
|
|
459
|
+
`MCP tool "${toolName}" failed (HTTP ${res.status}): ${text}`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
const data = await res.json();
|
|
463
|
+
const content = data.content?.[0];
|
|
464
|
+
if (content?.type === "text" && content.text) {
|
|
465
|
+
try {
|
|
466
|
+
return JSON.parse(content.text);
|
|
467
|
+
} catch {
|
|
468
|
+
return content.text;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return data;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Execute an A2A skill — delegate to another AgentX Agent.
|
|
475
|
+
*
|
|
476
|
+
* Standard Interface:
|
|
477
|
+
* Input: { task, ...taskSpecificParams }
|
|
478
|
+
* Output: { agentId, prompt, skills[] }
|
|
479
|
+
*
|
|
480
|
+
* The caller (LLM) receives the sub-Agent's prompt + skill list.
|
|
481
|
+
* The LLM then decides how to use the sub-Agent — typically by
|
|
482
|
+
* injecting the sub-Agent's system prompt and calling its skills.
|
|
483
|
+
*/
|
|
484
|
+
async _executeA2ASkill(skill, input) {
|
|
485
|
+
const exec = skill.execution;
|
|
486
|
+
if (!exec || exec.type !== "a2a") {
|
|
487
|
+
throw new AgentXError(
|
|
488
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */,
|
|
489
|
+
`Skill "${skill.name}" is not an A2A delegation skill`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
const targetAgentId = exec.targetAgentId;
|
|
493
|
+
let subContext;
|
|
494
|
+
try {
|
|
495
|
+
subContext = await this.useAgent(targetAgentId);
|
|
496
|
+
} catch (e) {
|
|
497
|
+
throw new AgentXError(
|
|
498
|
+
"AGENT_NOT_FOUND" /* AGENT_NOT_FOUND */,
|
|
499
|
+
`A2A delegation failed: cannot load Agent #${targetAgentId}. ${e}`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if (exec.skillFilter && exec.skillFilter.length > 0) {
|
|
503
|
+
const filterSet = new Set(exec.skillFilter);
|
|
504
|
+
subContext = {
|
|
505
|
+
...subContext,
|
|
506
|
+
skills: subContext.skills.filter((s) => filterSet.has(s.name))
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (exec.promptOverride) {
|
|
510
|
+
subContext = { ...subContext, prompt: exec.promptOverride };
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
agentId: targetAgentId,
|
|
514
|
+
prompt: subContext.prompt,
|
|
515
|
+
skills: subContext.skills.map((s) => ({
|
|
516
|
+
name: s.name,
|
|
517
|
+
description: s.description,
|
|
518
|
+
inputSchema: s.inputSchema
|
|
519
|
+
})),
|
|
520
|
+
// Pass the caller's input to the sub-agent's context
|
|
521
|
+
callerInput: input
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
async _getPrivateKey() {
|
|
525
|
+
if (this.wallet.getPrivateKey) return this.wallet.getPrivateKey();
|
|
526
|
+
throw new AgentXError(
|
|
527
|
+
"WALLET_NOT_CONNECTED" /* WALLET_NOT_CONNECTED */,
|
|
528
|
+
"Wallet must support getPrivateKey() for ECIES decryption."
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// src/config/config.ts
|
|
534
|
+
var KNOWN_CHAINS = {
|
|
535
|
+
// Sepolia
|
|
536
|
+
// Sepolia v2 (deployed 2026-07-13): platformFee=500bps(5%), multi-currency, trial escrow
|
|
537
|
+
11155111: {
|
|
538
|
+
chainId: 11155111,
|
|
539
|
+
contracts: {
|
|
540
|
+
identityRegistry: "0xe94ad380d3F8d08a7590eda0C84f354a93F96e5F",
|
|
541
|
+
subscriptionManager: "0xC15fE80b9d800abb72121F353a6ae6d6E9077E63",
|
|
542
|
+
paymentGateway: "0x0000000000000000000000000000000000000000",
|
|
543
|
+
a2aProtocolRegistry: "0x0000000000000000000000000000000000000000",
|
|
544
|
+
reputationRegistry: "0x0000000000000000000000000000000000000000",
|
|
545
|
+
configurationRegistry: "0x0000000000000000000000000000000000000000"
|
|
546
|
+
},
|
|
547
|
+
ipfsGateways: ["ipfs.io", "gateway.pinata.cloud", "dweb.link", "cf-ipfs.com"]
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// src/react/useAgentRunner.ts
|
|
552
|
+
var IDENTITY_REGISTRY_ABI = [
|
|
553
|
+
// getAgentMetadata returns MetadataEntry[] with key+value strings
|
|
554
|
+
{
|
|
555
|
+
name: "getAgentMetadata",
|
|
556
|
+
type: "function",
|
|
557
|
+
stateMutability: "view",
|
|
558
|
+
inputs: [{ name: "agentId", type: "uint256" }],
|
|
559
|
+
outputs: [
|
|
560
|
+
{
|
|
561
|
+
name: "",
|
|
562
|
+
type: "tuple[]",
|
|
563
|
+
components: [
|
|
564
|
+
{ name: "key", type: "string" },
|
|
565
|
+
{ name: "value", type: "bytes" }
|
|
566
|
+
]
|
|
567
|
+
}
|
|
568
|
+
]
|
|
569
|
+
}
|
|
570
|
+
];
|
|
571
|
+
var SUBSCRIPTION_MANAGER_ABI = [
|
|
572
|
+
{
|
|
573
|
+
name: "hasActiveSubscription",
|
|
574
|
+
type: "function",
|
|
575
|
+
stateMutability: "view",
|
|
576
|
+
inputs: [
|
|
577
|
+
{ name: "subscriber", type: "address" },
|
|
578
|
+
{ name: "agentId", type: "uint256" }
|
|
579
|
+
],
|
|
580
|
+
outputs: [{ name: "", type: "bool" }]
|
|
581
|
+
}
|
|
582
|
+
];
|
|
583
|
+
var ViemOnChainReader = class {
|
|
584
|
+
constructor(publicClient, chainConfig) {
|
|
585
|
+
this.publicClient = publicClient;
|
|
586
|
+
this.chainConfig = chainConfig;
|
|
587
|
+
}
|
|
588
|
+
publicClient;
|
|
589
|
+
chainConfig;
|
|
590
|
+
async getTokenURI(_agentId) {
|
|
591
|
+
return "";
|
|
592
|
+
}
|
|
593
|
+
async getAttributes(agentId) {
|
|
594
|
+
if (!this.publicClient) throw new Error("Public client not available");
|
|
595
|
+
const entries = await this.publicClient.readContract({
|
|
596
|
+
address: this.chainConfig.contracts.identityRegistry,
|
|
597
|
+
abi: IDENTITY_REGISTRY_ABI,
|
|
598
|
+
functionName: "getAgentMetadata",
|
|
599
|
+
args: [BigInt(agentId)]
|
|
600
|
+
});
|
|
601
|
+
const attrs = {};
|
|
602
|
+
for (const entry of entries) {
|
|
603
|
+
const hexStr = entry.value;
|
|
604
|
+
if (hexStr && hexStr !== "0x") {
|
|
605
|
+
attrs[entry.key] = hexToStringUTF8(hexStr);
|
|
606
|
+
} else {
|
|
607
|
+
attrs[entry.key] = "";
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return attrs;
|
|
611
|
+
}
|
|
612
|
+
async hasActiveSubscription(address, agentId) {
|
|
613
|
+
if (!this.publicClient) return false;
|
|
614
|
+
try {
|
|
615
|
+
return await this.publicClient.readContract({
|
|
616
|
+
address: this.chainConfig.contracts.subscriptionManager,
|
|
617
|
+
abi: SUBSCRIPTION_MANAGER_ABI,
|
|
618
|
+
functionName: "hasActiveSubscription",
|
|
619
|
+
args: [address, BigInt(agentId)]
|
|
620
|
+
});
|
|
621
|
+
} catch {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
function hexToStringUTF8(hex) {
|
|
627
|
+
if (!hex.startsWith("0x")) return hex;
|
|
628
|
+
const hexClean = hex.slice(2);
|
|
629
|
+
if (hexClean.length === 0) return "";
|
|
630
|
+
try {
|
|
631
|
+
const bytes = new Uint8Array(hexClean.length / 2);
|
|
632
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
633
|
+
bytes[i] = parseInt(hexClean.substring(i * 2, i * 2 + 2), 16);
|
|
634
|
+
}
|
|
635
|
+
return new TextDecoder().decode(bytes);
|
|
636
|
+
} catch {
|
|
637
|
+
return hex;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function useAgentRunner(config) {
|
|
641
|
+
const { agentId, chainConfig: chainConfigOverride, ipfsGateways } = config;
|
|
642
|
+
const publicClient = usePublicClient();
|
|
643
|
+
const { data: walletClient } = useWalletClient();
|
|
644
|
+
const [ctx, setCtx] = useState(null);
|
|
645
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
646
|
+
const [error, setError] = useState(null);
|
|
647
|
+
const [refetchKey, setRefetchKey] = useState(0);
|
|
648
|
+
const runnerRef = useRef(null);
|
|
649
|
+
const mountedRef = useRef(true);
|
|
650
|
+
useEffect(() => {
|
|
651
|
+
mountedRef.current = true;
|
|
652
|
+
return () => {
|
|
653
|
+
mountedRef.current = false;
|
|
654
|
+
};
|
|
655
|
+
}, []);
|
|
656
|
+
useEffect(() => {
|
|
657
|
+
if (!publicClient || !walletClient) {
|
|
658
|
+
setError(new Error("Wallet not connected"));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const chainConfig = chainConfigOverride ?? (publicClient.chain?.id ? KNOWN_CHAINS[publicClient.chain.id] : void 0);
|
|
662
|
+
if (!chainConfig) {
|
|
663
|
+
setError(new Error(`Chain ${publicClient.chain?.id} not supported`));
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const reader = new ViemOnChainReader(publicClient, chainConfig);
|
|
667
|
+
const signer = {
|
|
668
|
+
async signMessage(message) {
|
|
669
|
+
if (!walletClient.account) throw new Error("Wallet not connected");
|
|
670
|
+
return walletClient.signMessage({ account: walletClient.account, message });
|
|
671
|
+
},
|
|
672
|
+
async getAddress() {
|
|
673
|
+
if (!walletClient.account) throw new Error("Wallet not connected");
|
|
674
|
+
return walletClient.account.address;
|
|
675
|
+
},
|
|
676
|
+
async getPrivateKey() {
|
|
677
|
+
throw new Error(
|
|
678
|
+
'Private key not available via wagmi. Use window.ethereum.request({ method: "eth_private_key" }) or inject getPrivateKey via custom WalletSigner.'
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
const ipfsFetcher = new IPFSFetcher({
|
|
683
|
+
fallbackGateways: ipfsGateways ?? chainConfig.ipfsGateways ?? [
|
|
684
|
+
"gateway.pinata.cloud",
|
|
685
|
+
"dweb.link",
|
|
686
|
+
"cf-ipfs.com"
|
|
687
|
+
]
|
|
688
|
+
});
|
|
689
|
+
runnerRef.current = new AgentRunner({
|
|
690
|
+
reader,
|
|
691
|
+
wallet: signer,
|
|
692
|
+
ipfsFetcher
|
|
693
|
+
});
|
|
694
|
+
setIsLoading(true);
|
|
695
|
+
setError(null);
|
|
696
|
+
runnerRef.current.useAgent(agentId).then((result) => {
|
|
697
|
+
if (mountedRef.current) {
|
|
698
|
+
setCtx(result);
|
|
699
|
+
setIsLoading(false);
|
|
700
|
+
}
|
|
701
|
+
}).catch((err) => {
|
|
702
|
+
if (mountedRef.current) {
|
|
703
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
704
|
+
setIsLoading(false);
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
return () => {
|
|
708
|
+
mountedRef.current = false;
|
|
709
|
+
};
|
|
710
|
+
}, [agentId, publicClient?.chain?.id, publicClient, walletClient, refetchKey]);
|
|
711
|
+
const refetch = () => setRefetchKey((k) => k + 1);
|
|
712
|
+
return { ctx, isLoading, error, refetch };
|
|
713
|
+
}
|
|
714
|
+
export {
|
|
715
|
+
useAgentRunner
|
|
716
|
+
};
|
|
717
|
+
//# sourceMappingURL=index.mjs.map
|