@dominusnode/ai-tools 1.1.1 → 1.3.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 +19 -17
- package/dist/create-tools.d.ts +100 -18
- package/dist/create-tools.d.ts.map +1 -1
- package/dist/create-tools.js +88 -36
- package/dist/create-tools.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tools.d.ts +162 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +1565 -76
- package/dist/tools.js.map +1 -1
- package/package.json +3 -1
package/dist/tools.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { tool } from "ai";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import * as crypto from "node:crypto";
|
|
3
4
|
import dns from "dns/promises";
|
|
4
5
|
import * as http from "node:http";
|
|
5
6
|
import * as tls from "node:tls";
|
|
@@ -73,11 +74,66 @@ function isPrivateIp(hostname) {
|
|
|
73
74
|
return true;
|
|
74
75
|
return false;
|
|
75
76
|
}
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// SHA-256 Proof-of-Work solver
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
function countLeadingZeroBits(buf) {
|
|
81
|
+
let count = 0;
|
|
82
|
+
for (const byte of buf) {
|
|
83
|
+
if (byte === 0) {
|
|
84
|
+
count += 8;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
let mask = 0x80;
|
|
88
|
+
while (mask && !(byte & mask)) {
|
|
89
|
+
count++;
|
|
90
|
+
mask >>= 1;
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
return count;
|
|
95
|
+
}
|
|
96
|
+
async function solvePoW(powBaseUrl) {
|
|
97
|
+
try {
|
|
98
|
+
const resp = await fetch(`${powBaseUrl}/api/auth/pow/challenge`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { "Content-Type": "application/json" },
|
|
101
|
+
redirect: "error",
|
|
102
|
+
});
|
|
103
|
+
if (!resp.ok)
|
|
104
|
+
return null;
|
|
105
|
+
const text = await resp.text();
|
|
106
|
+
if (text.length > 10_485_760)
|
|
107
|
+
return null;
|
|
108
|
+
const challenge = JSON.parse(text);
|
|
109
|
+
const prefix = challenge.prefix ?? "";
|
|
110
|
+
const difficulty = challenge.difficulty ?? 20;
|
|
111
|
+
const challengeId = challenge.challengeId ?? "";
|
|
112
|
+
if (!prefix || !challengeId)
|
|
113
|
+
return null;
|
|
114
|
+
for (let nonce = 0; nonce < 100_000_000; nonce++) {
|
|
115
|
+
const hash = crypto
|
|
116
|
+
.createHash("sha256")
|
|
117
|
+
.update(prefix + nonce.toString())
|
|
118
|
+
.digest();
|
|
119
|
+
if (countLeadingZeroBits(hash) >= difficulty) {
|
|
120
|
+
return { challengeId, nonce: nonce.toString() };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
76
129
|
/** Validate a URL is safe to fetch through the proxy. */
|
|
77
130
|
function validateUrl(urlStr) {
|
|
78
131
|
// Length limit
|
|
79
132
|
if (urlStr.length > 2048) {
|
|
80
|
-
return {
|
|
133
|
+
return {
|
|
134
|
+
valid: false,
|
|
135
|
+
error: "URL exceeds maximum length of 2048 characters",
|
|
136
|
+
};
|
|
81
137
|
}
|
|
82
138
|
let url;
|
|
83
139
|
try {
|
|
@@ -88,7 +144,10 @@ function validateUrl(urlStr) {
|
|
|
88
144
|
}
|
|
89
145
|
// Protocol check — only http(s)
|
|
90
146
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
91
|
-
return {
|
|
147
|
+
return {
|
|
148
|
+
valid: false,
|
|
149
|
+
error: `Unsupported protocol: ${url.protocol} — only http and https are allowed`,
|
|
150
|
+
};
|
|
92
151
|
}
|
|
93
152
|
// Hostname must be present
|
|
94
153
|
if (!url.hostname) {
|
|
@@ -103,16 +162,27 @@ function validateUrl(urlStr) {
|
|
|
103
162
|
return { valid: false, error: "Requests to localhost are not allowed" };
|
|
104
163
|
}
|
|
105
164
|
// Block internal network TLDs
|
|
106
|
-
if (lowerHost.endsWith(".local") ||
|
|
107
|
-
|
|
165
|
+
if (lowerHost.endsWith(".local") ||
|
|
166
|
+
lowerHost.endsWith(".internal") ||
|
|
167
|
+
lowerHost.endsWith(".arpa")) {
|
|
168
|
+
return {
|
|
169
|
+
valid: false,
|
|
170
|
+
error: "Requests to internal network hostnames are not allowed",
|
|
171
|
+
};
|
|
108
172
|
}
|
|
109
173
|
// Block private/reserved IPs
|
|
110
174
|
if (isPrivateIp(url.hostname)) {
|
|
111
|
-
return {
|
|
175
|
+
return {
|
|
176
|
+
valid: false,
|
|
177
|
+
error: "Requests to private/reserved IP addresses are not allowed",
|
|
178
|
+
};
|
|
112
179
|
}
|
|
113
180
|
// Block credentials in URL (user:pass@host)
|
|
114
181
|
if (url.username || url.password) {
|
|
115
|
-
return {
|
|
182
|
+
return {
|
|
183
|
+
valid: false,
|
|
184
|
+
error: "URLs with embedded credentials are not allowed",
|
|
185
|
+
};
|
|
116
186
|
}
|
|
117
187
|
return { valid: true, url };
|
|
118
188
|
}
|
|
@@ -122,7 +192,8 @@ const MAX_BODY_LENGTH = 4000;
|
|
|
122
192
|
function truncateBody(body) {
|
|
123
193
|
if (body.length <= MAX_BODY_LENGTH)
|
|
124
194
|
return body;
|
|
125
|
-
return body.slice(0, MAX_BODY_LENGTH) +
|
|
195
|
+
return (body.slice(0, MAX_BODY_LENGTH) +
|
|
196
|
+
`\n...[truncated, ${body.length - MAX_BODY_LENGTH} chars omitted]`);
|
|
126
197
|
}
|
|
127
198
|
// ---------------------------------------------------------------------------
|
|
128
199
|
// Safe header subset — never forward sensitive headers to the AI
|
|
@@ -197,7 +268,7 @@ async function checkDnsRebinding(hostname) {
|
|
|
197
268
|
// ---------------------------------------------------------------------------
|
|
198
269
|
// Credential sanitization for error messages
|
|
199
270
|
// ---------------------------------------------------------------------------
|
|
200
|
-
const CREDENTIAL_RE = /dn_(live|test)_[a-zA-Z0-9]+|eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g;
|
|
271
|
+
const CREDENTIAL_RE = /dn_(live|test|proxy)_[a-zA-Z0-9]+|eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g;
|
|
201
272
|
function sanitizeError(message) {
|
|
202
273
|
return message.replace(CREDENTIAL_RE, "***");
|
|
203
274
|
}
|
|
@@ -245,7 +316,11 @@ export function createProxiedFetchTool(client, apiKey) {
|
|
|
245
316
|
"Supports geo-targeting by country and choice of datacenter (dc) or residential proxy. " +
|
|
246
317
|
"Use this to fetch web pages, APIs, or any HTTP resource through a proxy IP.",
|
|
247
318
|
parameters: z.object({
|
|
248
|
-
url: z
|
|
319
|
+
url: z
|
|
320
|
+
.string()
|
|
321
|
+
.max(2048)
|
|
322
|
+
.url()
|
|
323
|
+
.describe("The URL to fetch through the proxy"),
|
|
249
324
|
method: z
|
|
250
325
|
.enum(["GET", "HEAD", "OPTIONS"])
|
|
251
326
|
.default("GET")
|
|
@@ -264,7 +339,7 @@ export function createProxiedFetchTool(client, apiKey) {
|
|
|
264
339
|
.optional()
|
|
265
340
|
.describe("Optional HTTP headers to include in the request"),
|
|
266
341
|
}),
|
|
267
|
-
execute: async ({ url, method, country, proxyType, headers }) => {
|
|
342
|
+
execute: async ({ url, method, country, proxyType, headers, }) => {
|
|
268
343
|
// SSRF validation
|
|
269
344
|
const validation = validateUrl(url);
|
|
270
345
|
if (!validation.valid) {
|
|
@@ -272,14 +347,18 @@ export function createProxiedFetchTool(client, apiKey) {
|
|
|
272
347
|
}
|
|
273
348
|
// OFAC sanctioned country check
|
|
274
349
|
if (country && SANCTIONED_COUNTRIES.has(country.toUpperCase())) {
|
|
275
|
-
return {
|
|
350
|
+
return {
|
|
351
|
+
error: `Country '${country.toUpperCase()}' is blocked (OFAC sanctioned)`,
|
|
352
|
+
};
|
|
276
353
|
}
|
|
277
354
|
// DNS rebinding protection
|
|
278
355
|
try {
|
|
279
356
|
await checkDnsRebinding(validation.url.hostname);
|
|
280
357
|
}
|
|
281
358
|
catch (err) {
|
|
282
|
-
return {
|
|
359
|
+
return {
|
|
360
|
+
error: err instanceof Error ? err.message : "DNS validation failed",
|
|
361
|
+
};
|
|
283
362
|
}
|
|
284
363
|
// Build the proxy URL using the SDK
|
|
285
364
|
const proxyUrl = client.proxy.buildUrl(apiKey, {
|
|
@@ -293,12 +372,22 @@ export function createProxiedFetchTool(client, apiKey) {
|
|
|
293
372
|
const proxyAuth = "Basic " +
|
|
294
373
|
Buffer.from(`${proxyUrlObj.username}:${proxyUrlObj.password}`).toString("base64");
|
|
295
374
|
// Validate custom headers for CRLF injection
|
|
296
|
-
const STRIPPED_HEADERS = new Set([
|
|
375
|
+
const STRIPPED_HEADERS = new Set([
|
|
376
|
+
"host",
|
|
377
|
+
"connection",
|
|
378
|
+
"content-length",
|
|
379
|
+
"transfer-encoding",
|
|
380
|
+
"proxy-authorization",
|
|
381
|
+
"authorization",
|
|
382
|
+
"user-agent",
|
|
383
|
+
]);
|
|
297
384
|
const safeHeaders = {};
|
|
298
385
|
if (headers) {
|
|
299
386
|
for (const [k, v] of Object.entries(headers)) {
|
|
300
387
|
if (/[\r\n\0]/.test(k) || /[\r\n\0]/.test(v)) {
|
|
301
|
-
return {
|
|
388
|
+
return {
|
|
389
|
+
error: `Header "${k.replace(/[\r\n\0]/g, "")}" contains invalid characters`,
|
|
390
|
+
};
|
|
302
391
|
}
|
|
303
392
|
if (!STRIPPED_HEADERS.has(k.toLowerCase())) {
|
|
304
393
|
safeHeaders[k] = v;
|
|
@@ -309,14 +398,23 @@ export function createProxiedFetchTool(client, apiKey) {
|
|
|
309
398
|
const MAX_RESP = 1_048_576; // 1MB
|
|
310
399
|
const result = await new Promise((resolve, reject) => {
|
|
311
400
|
const timer = setTimeout(() => reject(new Error("Proxy request timed out")), 30_000);
|
|
312
|
-
const customHeaderLines = Object.entries(safeHeaders)
|
|
401
|
+
const customHeaderLines = Object.entries(safeHeaders)
|
|
402
|
+
.map(([k, v]) => `${k}: ${v}\r\n`)
|
|
403
|
+
.join("");
|
|
313
404
|
if (targetUrl.protocol === "https:") {
|
|
314
405
|
// HTTPS: use CONNECT tunnel through proxy
|
|
315
|
-
const connectHost = targetUrl.hostname.includes(":")
|
|
406
|
+
const connectHost = targetUrl.hostname.includes(":")
|
|
407
|
+
? `[${targetUrl.hostname}]`
|
|
408
|
+
: targetUrl.hostname;
|
|
316
409
|
const connectReq = http.request({
|
|
317
|
-
hostname: proxyHost,
|
|
410
|
+
hostname: proxyHost,
|
|
411
|
+
port: proxyPort,
|
|
412
|
+
method: "CONNECT",
|
|
318
413
|
path: `${connectHost}:${targetUrl.port || 443}`,
|
|
319
|
-
headers: {
|
|
414
|
+
headers: {
|
|
415
|
+
"Proxy-Authorization": proxyAuth,
|
|
416
|
+
Host: `${connectHost}:${targetUrl.port || 443}`,
|
|
417
|
+
},
|
|
320
418
|
});
|
|
321
419
|
connectReq.on("connect", (_res, sock) => {
|
|
322
420
|
if (_res.statusCode !== 200) {
|
|
@@ -325,13 +423,21 @@ export function createProxiedFetchTool(client, apiKey) {
|
|
|
325
423
|
reject(new Error(`CONNECT failed: ${_res.statusCode}`));
|
|
326
424
|
return;
|
|
327
425
|
}
|
|
328
|
-
const tlsSock = tls.connect({
|
|
426
|
+
const tlsSock = tls.connect({
|
|
427
|
+
host: targetUrl.hostname,
|
|
428
|
+
socket: sock,
|
|
429
|
+
servername: targetUrl.hostname,
|
|
430
|
+
minVersion: "TLSv1.2",
|
|
431
|
+
}, () => {
|
|
329
432
|
const reqLine = `${method} ${targetUrl.pathname + targetUrl.search} HTTP/1.1\r\nHost: ${targetUrl.host}\r\nUser-Agent: dominusnode-vercel-ai/1.0.0\r\n${customHeaderLines}Connection: close\r\n\r\n`;
|
|
330
433
|
tlsSock.write(reqLine);
|
|
331
434
|
const chunks = [];
|
|
332
435
|
let bytes = 0;
|
|
333
|
-
tlsSock.on("data", (c) => {
|
|
334
|
-
|
|
436
|
+
tlsSock.on("data", (c) => {
|
|
437
|
+
bytes += c.length;
|
|
438
|
+
if (bytes <= MAX_RESP + 16384)
|
|
439
|
+
chunks.push(c);
|
|
440
|
+
});
|
|
335
441
|
let done = false;
|
|
336
442
|
const fin = () => {
|
|
337
443
|
if (done)
|
|
@@ -346,55 +452,96 @@ export function createProxiedFetchTool(client, apiKey) {
|
|
|
346
452
|
}
|
|
347
453
|
const hdr = raw.substring(0, hEnd);
|
|
348
454
|
const body = raw.substring(hEnd + 4).substring(0, MAX_RESP);
|
|
349
|
-
const sm = hdr
|
|
455
|
+
const sm = hdr
|
|
456
|
+
.split("\r\n")[0]
|
|
457
|
+
.match(/^HTTP\/\d\.\d\s+(\d+)\s*(.*)/);
|
|
350
458
|
const hdrs = {};
|
|
351
459
|
for (const l of hdr.split("\r\n").slice(1)) {
|
|
352
460
|
const ci = l.indexOf(":");
|
|
353
461
|
if (ci > 0)
|
|
354
|
-
hdrs[l.substring(0, ci).trim().toLowerCase()] = l
|
|
462
|
+
hdrs[l.substring(0, ci).trim().toLowerCase()] = l
|
|
463
|
+
.substring(ci + 1)
|
|
464
|
+
.trim();
|
|
355
465
|
}
|
|
356
466
|
stripDangerousKeys(hdrs);
|
|
357
|
-
resolve({
|
|
467
|
+
resolve({
|
|
468
|
+
status: sm ? parseInt(sm[1], 10) : 0,
|
|
469
|
+
statusText: sm ? (sm[2] ?? "") : "",
|
|
470
|
+
headers: hdrs,
|
|
471
|
+
body,
|
|
472
|
+
});
|
|
358
473
|
};
|
|
359
474
|
tlsSock.on("end", fin);
|
|
360
475
|
tlsSock.on("close", fin);
|
|
361
|
-
tlsSock.on("error", (e) => {
|
|
476
|
+
tlsSock.on("error", (e) => {
|
|
477
|
+
clearTimeout(timer);
|
|
478
|
+
reject(e);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
tlsSock.on("error", (e) => {
|
|
482
|
+
clearTimeout(timer);
|
|
483
|
+
reject(e);
|
|
362
484
|
});
|
|
363
|
-
tlsSock.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
364
485
|
});
|
|
365
|
-
connectReq.on("error", (e) => {
|
|
486
|
+
connectReq.on("error", (e) => {
|
|
487
|
+
clearTimeout(timer);
|
|
488
|
+
reject(e);
|
|
489
|
+
});
|
|
366
490
|
connectReq.end();
|
|
367
491
|
}
|
|
368
492
|
else {
|
|
369
493
|
// HTTP: route through proxy with full URL as request path
|
|
370
494
|
const req = http.request({
|
|
371
|
-
hostname: proxyHost,
|
|
372
|
-
|
|
495
|
+
hostname: proxyHost,
|
|
496
|
+
port: proxyPort,
|
|
497
|
+
method,
|
|
498
|
+
path: targetUrl.toString(),
|
|
499
|
+
headers: {
|
|
500
|
+
"Proxy-Authorization": proxyAuth,
|
|
501
|
+
Host: targetUrl.host ?? "",
|
|
502
|
+
...safeHeaders,
|
|
503
|
+
},
|
|
373
504
|
}, (res) => {
|
|
374
505
|
const chunks = [];
|
|
375
506
|
let bytes = 0;
|
|
376
|
-
res.on("data", (c) => {
|
|
377
|
-
|
|
507
|
+
res.on("data", (c) => {
|
|
508
|
+
bytes += c.length;
|
|
509
|
+
if (bytes <= MAX_RESP)
|
|
510
|
+
chunks.push(c);
|
|
511
|
+
});
|
|
378
512
|
let done = false;
|
|
379
513
|
const fin = () => {
|
|
380
514
|
if (done)
|
|
381
515
|
return;
|
|
382
516
|
done = true;
|
|
383
517
|
clearTimeout(timer);
|
|
384
|
-
const body = Buffer.concat(chunks)
|
|
518
|
+
const body = Buffer.concat(chunks)
|
|
519
|
+
.toString("utf-8")
|
|
520
|
+
.substring(0, MAX_RESP);
|
|
385
521
|
const hdrs = {};
|
|
386
522
|
for (const [k, v] of Object.entries(res.headers)) {
|
|
387
523
|
if (v)
|
|
388
524
|
hdrs[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
389
525
|
}
|
|
390
526
|
stripDangerousKeys(hdrs);
|
|
391
|
-
resolve({
|
|
527
|
+
resolve({
|
|
528
|
+
status: res.statusCode ?? 0,
|
|
529
|
+
statusText: res.statusMessage ?? "",
|
|
530
|
+
headers: hdrs,
|
|
531
|
+
body,
|
|
532
|
+
});
|
|
392
533
|
};
|
|
393
534
|
res.on("end", fin);
|
|
394
535
|
res.on("close", fin);
|
|
395
|
-
res.on("error", (e) => {
|
|
536
|
+
res.on("error", (e) => {
|
|
537
|
+
clearTimeout(timer);
|
|
538
|
+
reject(e);
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
req.on("error", (e) => {
|
|
542
|
+
clearTimeout(timer);
|
|
543
|
+
reject(e);
|
|
396
544
|
});
|
|
397
|
-
req.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
398
545
|
req.end();
|
|
399
546
|
}
|
|
400
547
|
});
|
|
@@ -535,7 +682,9 @@ export function createGetProxyConfigTool(client) {
|
|
|
535
682
|
}
|
|
536
683
|
catch (err) {
|
|
537
684
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
538
|
-
return {
|
|
685
|
+
return {
|
|
686
|
+
error: `Failed to get proxy config: ${sanitizeError(message)}`,
|
|
687
|
+
};
|
|
539
688
|
}
|
|
540
689
|
},
|
|
541
690
|
});
|
|
@@ -585,7 +734,9 @@ export function createTopupPaypalTool(client) {
|
|
|
585
734
|
}),
|
|
586
735
|
execute: async ({ amount_cents }) => {
|
|
587
736
|
try {
|
|
588
|
-
const result = await client.wallet.topupPaypal({
|
|
737
|
+
const result = await client.wallet.topupPaypal({
|
|
738
|
+
amountCents: amount_cents,
|
|
739
|
+
});
|
|
589
740
|
return {
|
|
590
741
|
orderId: result.orderId,
|
|
591
742
|
approvalUrl: result.approvalUrl,
|
|
@@ -594,7 +745,9 @@ export function createTopupPaypalTool(client) {
|
|
|
594
745
|
}
|
|
595
746
|
catch (err) {
|
|
596
747
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
597
|
-
return {
|
|
748
|
+
return {
|
|
749
|
+
error: `Failed to create PayPal top-up: ${sanitizeError(message)}`,
|
|
750
|
+
};
|
|
598
751
|
}
|
|
599
752
|
},
|
|
600
753
|
});
|
|
@@ -617,7 +770,9 @@ export function createTopupStripeTool(client) {
|
|
|
617
770
|
}),
|
|
618
771
|
execute: async ({ amount_cents }) => {
|
|
619
772
|
try {
|
|
620
|
-
const result = await client.wallet.topupStripe({
|
|
773
|
+
const result = await client.wallet.topupStripe({
|
|
774
|
+
amountCents: amount_cents,
|
|
775
|
+
});
|
|
621
776
|
return {
|
|
622
777
|
sessionId: result.sessionId,
|
|
623
778
|
url: result.url,
|
|
@@ -626,7 +781,9 @@ export function createTopupStripeTool(client) {
|
|
|
626
781
|
}
|
|
627
782
|
catch (err) {
|
|
628
783
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
629
|
-
return {
|
|
784
|
+
return {
|
|
785
|
+
error: `Failed to create Stripe checkout: ${sanitizeError(message)}`,
|
|
786
|
+
};
|
|
630
787
|
}
|
|
631
788
|
},
|
|
632
789
|
});
|
|
@@ -645,12 +802,27 @@ export function createTopupCryptoTool(client) {
|
|
|
645
802
|
.max(1000)
|
|
646
803
|
.describe("Amount in USD to top up (min $5, max $1,000)"),
|
|
647
804
|
currency: z
|
|
648
|
-
.enum([
|
|
805
|
+
.enum([
|
|
806
|
+
"BTC",
|
|
807
|
+
"ETH",
|
|
808
|
+
"LTC",
|
|
809
|
+
"XMR",
|
|
810
|
+
"ZEC",
|
|
811
|
+
"USDC",
|
|
812
|
+
"SOL",
|
|
813
|
+
"USDT",
|
|
814
|
+
"DAI",
|
|
815
|
+
"BNB",
|
|
816
|
+
"LINK",
|
|
817
|
+
])
|
|
649
818
|
.describe("Cryptocurrency to pay with"),
|
|
650
819
|
}),
|
|
651
|
-
execute: async ({ amount_usd, currency }) => {
|
|
820
|
+
execute: async ({ amount_usd, currency, }) => {
|
|
652
821
|
try {
|
|
653
|
-
const result = await client.wallet.topupCrypto({
|
|
822
|
+
const result = await client.wallet.topupCrypto({
|
|
823
|
+
amountUsd: amount_usd,
|
|
824
|
+
currency: currency.toLowerCase(),
|
|
825
|
+
});
|
|
654
826
|
return {
|
|
655
827
|
invoiceId: result.invoiceId,
|
|
656
828
|
invoiceUrl: result.invoiceUrl,
|
|
@@ -660,7 +832,9 @@ export function createTopupCryptoTool(client) {
|
|
|
660
832
|
}
|
|
661
833
|
catch (err) {
|
|
662
834
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
663
|
-
return {
|
|
835
|
+
return {
|
|
836
|
+
error: `Failed to create crypto invoice: ${sanitizeError(message)}`,
|
|
837
|
+
};
|
|
664
838
|
}
|
|
665
839
|
},
|
|
666
840
|
});
|
|
@@ -698,7 +872,7 @@ async function apiRequest(client, method, path, body, agentSecret) {
|
|
|
698
872
|
const apiKey = client.apiKey || "";
|
|
699
873
|
const url = `${baseUrl}${path}`;
|
|
700
874
|
const headers = {
|
|
701
|
-
|
|
875
|
+
Authorization: `Bearer ${apiKey}`,
|
|
702
876
|
"Content-Type": "application/json",
|
|
703
877
|
};
|
|
704
878
|
if (agentSecret) {
|
|
@@ -733,18 +907,40 @@ export function createCreateAgenticWalletTool(client, agentSecret) {
|
|
|
733
907
|
description: "Create a new agentic sub-wallet with a spending limit. " +
|
|
734
908
|
"Agentic wallets are custodial sub-wallets for AI agents with per-transaction spending caps.",
|
|
735
909
|
parameters: z.object({
|
|
736
|
-
label: z
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
910
|
+
label: z
|
|
911
|
+
.string()
|
|
912
|
+
.min(1)
|
|
913
|
+
.max(100)
|
|
914
|
+
.describe("Human-readable label for the wallet"),
|
|
915
|
+
spending_limit_cents: z
|
|
916
|
+
.number()
|
|
917
|
+
.int()
|
|
918
|
+
.min(1)
|
|
919
|
+
.max(2147483647)
|
|
920
|
+
.describe("Per-transaction spending limit in cents"),
|
|
921
|
+
daily_limit_cents: z
|
|
922
|
+
.number()
|
|
923
|
+
.int()
|
|
924
|
+
.min(1)
|
|
925
|
+
.max(1000000)
|
|
926
|
+
.optional()
|
|
927
|
+
.describe("Optional daily spending limit in cents"),
|
|
928
|
+
allowed_domains: z
|
|
929
|
+
.array(z.string().max(253).regex(DOMAIN_RE))
|
|
930
|
+
.max(100)
|
|
931
|
+
.optional()
|
|
932
|
+
.describe("Optional list of allowed domains"),
|
|
740
933
|
}),
|
|
741
|
-
execute: async ({ label, spending_limit_cents, daily_limit_cents, allowed_domains }) => {
|
|
934
|
+
execute: async ({ label, spending_limit_cents, daily_limit_cents, allowed_domains, }) => {
|
|
742
935
|
// Validate no control chars in label
|
|
743
936
|
if (/[\x00-\x1F\x7F]/.test(label)) {
|
|
744
937
|
return { error: "label contains invalid control characters" };
|
|
745
938
|
}
|
|
746
939
|
try {
|
|
747
|
-
const body = {
|
|
940
|
+
const body = {
|
|
941
|
+
label,
|
|
942
|
+
spendingLimitCents: spending_limit_cents,
|
|
943
|
+
};
|
|
748
944
|
if (daily_limit_cents !== undefined)
|
|
749
945
|
body.dailyLimitCents = daily_limit_cents;
|
|
750
946
|
if (allowed_domains !== undefined)
|
|
@@ -753,7 +949,9 @@ export function createCreateAgenticWalletTool(client, agentSecret) {
|
|
|
753
949
|
return result;
|
|
754
950
|
}
|
|
755
951
|
catch (err) {
|
|
756
|
-
return {
|
|
952
|
+
return {
|
|
953
|
+
error: `Failed to create agentic wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
954
|
+
};
|
|
757
955
|
}
|
|
758
956
|
},
|
|
759
957
|
});
|
|
@@ -765,15 +963,25 @@ export function createFundAgenticWalletTool(client, agentSecret) {
|
|
|
765
963
|
return tool({
|
|
766
964
|
description: "Transfer funds from the main wallet to an agentic sub-wallet.",
|
|
767
965
|
parameters: z.object({
|
|
768
|
-
wallet_id: z
|
|
769
|
-
|
|
966
|
+
wallet_id: z
|
|
967
|
+
.string()
|
|
968
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
969
|
+
.describe("UUID of the agentic wallet"),
|
|
970
|
+
amount_cents: z
|
|
971
|
+
.number()
|
|
972
|
+
.int()
|
|
973
|
+
.min(1)
|
|
974
|
+
.max(2147483647)
|
|
975
|
+
.describe("Amount in cents to transfer"),
|
|
770
976
|
}),
|
|
771
|
-
execute: async ({ wallet_id, amount_cents }) => {
|
|
977
|
+
execute: async ({ wallet_id, amount_cents, }) => {
|
|
772
978
|
try {
|
|
773
979
|
return await apiRequest(client, "POST", `/api/agent-wallet/${encodeURIComponent(wallet_id)}/fund`, { amountCents: amount_cents }, agentSecret);
|
|
774
980
|
}
|
|
775
981
|
catch (err) {
|
|
776
|
-
return {
|
|
982
|
+
return {
|
|
983
|
+
error: `Failed to fund wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
984
|
+
};
|
|
777
985
|
}
|
|
778
986
|
},
|
|
779
987
|
});
|
|
@@ -785,14 +993,19 @@ export function createAgenticWalletBalanceTool(client, agentSecret) {
|
|
|
785
993
|
return tool({
|
|
786
994
|
description: "Check the balance and details of an agentic sub-wallet.",
|
|
787
995
|
parameters: z.object({
|
|
788
|
-
wallet_id: z
|
|
996
|
+
wallet_id: z
|
|
997
|
+
.string()
|
|
998
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
999
|
+
.describe("UUID of the agentic wallet"),
|
|
789
1000
|
}),
|
|
790
1001
|
execute: async ({ wallet_id }) => {
|
|
791
1002
|
try {
|
|
792
1003
|
return await apiRequest(client, "GET", `/api/agent-wallet/${encodeURIComponent(wallet_id)}`, undefined, agentSecret);
|
|
793
1004
|
}
|
|
794
1005
|
catch (err) {
|
|
795
|
-
return {
|
|
1006
|
+
return {
|
|
1007
|
+
error: `Failed to get wallet balance: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1008
|
+
};
|
|
796
1009
|
}
|
|
797
1010
|
},
|
|
798
1011
|
});
|
|
@@ -809,7 +1022,9 @@ export function createListAgenticWalletsTool(client, agentSecret) {
|
|
|
809
1022
|
return await apiRequest(client, "GET", "/api/agent-wallet", undefined, agentSecret);
|
|
810
1023
|
}
|
|
811
1024
|
catch (err) {
|
|
812
|
-
return {
|
|
1025
|
+
return {
|
|
1026
|
+
error: `Failed to list wallets: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1027
|
+
};
|
|
813
1028
|
}
|
|
814
1029
|
},
|
|
815
1030
|
});
|
|
@@ -821,16 +1036,27 @@ export function createAgenticTransactionsTool(client, agentSecret) {
|
|
|
821
1036
|
return tool({
|
|
822
1037
|
description: "List recent transactions for an agentic sub-wallet.",
|
|
823
1038
|
parameters: z.object({
|
|
824
|
-
wallet_id: z
|
|
825
|
-
|
|
1039
|
+
wallet_id: z
|
|
1040
|
+
.string()
|
|
1041
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1042
|
+
.describe("UUID of the agentic wallet"),
|
|
1043
|
+
limit: z
|
|
1044
|
+
.number()
|
|
1045
|
+
.int()
|
|
1046
|
+
.min(1)
|
|
1047
|
+
.max(100)
|
|
1048
|
+
.optional()
|
|
1049
|
+
.describe("Maximum transactions to return (1-100)"),
|
|
826
1050
|
}),
|
|
827
|
-
execute: async ({ wallet_id, limit }) => {
|
|
1051
|
+
execute: async ({ wallet_id, limit, }) => {
|
|
828
1052
|
try {
|
|
829
1053
|
const qs = limit ? `?limit=${limit}` : "";
|
|
830
1054
|
return await apiRequest(client, "GET", `/api/agent-wallet/${encodeURIComponent(wallet_id)}/transactions${qs}`, undefined, agentSecret);
|
|
831
1055
|
}
|
|
832
1056
|
catch (err) {
|
|
833
|
-
return {
|
|
1057
|
+
return {
|
|
1058
|
+
error: `Failed to get transactions: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1059
|
+
};
|
|
834
1060
|
}
|
|
835
1061
|
},
|
|
836
1062
|
});
|
|
@@ -842,14 +1068,19 @@ export function createFreezeAgenticWalletTool(client, agentSecret) {
|
|
|
842
1068
|
return tool({
|
|
843
1069
|
description: "Freeze an agentic sub-wallet to prevent further spending.",
|
|
844
1070
|
parameters: z.object({
|
|
845
|
-
wallet_id: z
|
|
1071
|
+
wallet_id: z
|
|
1072
|
+
.string()
|
|
1073
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1074
|
+
.describe("UUID of the agentic wallet"),
|
|
846
1075
|
}),
|
|
847
1076
|
execute: async ({ wallet_id }) => {
|
|
848
1077
|
try {
|
|
849
1078
|
return await apiRequest(client, "POST", `/api/agent-wallet/${encodeURIComponent(wallet_id)}/freeze`, undefined, agentSecret);
|
|
850
1079
|
}
|
|
851
1080
|
catch (err) {
|
|
852
|
-
return {
|
|
1081
|
+
return {
|
|
1082
|
+
error: `Failed to freeze wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1083
|
+
};
|
|
853
1084
|
}
|
|
854
1085
|
},
|
|
855
1086
|
});
|
|
@@ -861,14 +1092,19 @@ export function createUnfreezeAgenticWalletTool(client, agentSecret) {
|
|
|
861
1092
|
return tool({
|
|
862
1093
|
description: "Unfreeze a previously frozen agentic sub-wallet to re-enable spending.",
|
|
863
1094
|
parameters: z.object({
|
|
864
|
-
wallet_id: z
|
|
1095
|
+
wallet_id: z
|
|
1096
|
+
.string()
|
|
1097
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1098
|
+
.describe("UUID of the agentic wallet"),
|
|
865
1099
|
}),
|
|
866
1100
|
execute: async ({ wallet_id }) => {
|
|
867
1101
|
try {
|
|
868
1102
|
return await apiRequest(client, "POST", `/api/agent-wallet/${encodeURIComponent(wallet_id)}/unfreeze`, undefined, agentSecret);
|
|
869
1103
|
}
|
|
870
1104
|
catch (err) {
|
|
871
|
-
return {
|
|
1105
|
+
return {
|
|
1106
|
+
error: `Failed to unfreeze wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1107
|
+
};
|
|
872
1108
|
}
|
|
873
1109
|
},
|
|
874
1110
|
});
|
|
@@ -881,14 +1117,19 @@ export function createDeleteAgenticWalletTool(client, agentSecret) {
|
|
|
881
1117
|
return tool({
|
|
882
1118
|
description: "Delete an agentic sub-wallet. Must be active (not frozen). Remaining balance returns to main wallet.",
|
|
883
1119
|
parameters: z.object({
|
|
884
|
-
wallet_id: z
|
|
1120
|
+
wallet_id: z
|
|
1121
|
+
.string()
|
|
1122
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1123
|
+
.describe("UUID of the agentic wallet"),
|
|
885
1124
|
}),
|
|
886
1125
|
execute: async ({ wallet_id }) => {
|
|
887
1126
|
try {
|
|
888
1127
|
return await apiRequest(client, "DELETE", `/api/agent-wallet/${encodeURIComponent(wallet_id)}`, undefined, agentSecret);
|
|
889
1128
|
}
|
|
890
1129
|
catch (err) {
|
|
891
|
-
return {
|
|
1130
|
+
return {
|
|
1131
|
+
error: `Failed to delete wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1132
|
+
};
|
|
892
1133
|
}
|
|
893
1134
|
},
|
|
894
1135
|
});
|
|
@@ -900,28 +1141,1276 @@ export function createUpdateWalletPolicyTool(client, agentSecret) {
|
|
|
900
1141
|
return tool({
|
|
901
1142
|
description: "Update the policy (daily limit, allowed domains) of an agentic sub-wallet.",
|
|
902
1143
|
parameters: z.object({
|
|
903
|
-
wallet_id: z
|
|
904
|
-
|
|
905
|
-
|
|
1144
|
+
wallet_id: z
|
|
1145
|
+
.string()
|
|
1146
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1147
|
+
.describe("UUID of the agentic wallet"),
|
|
1148
|
+
daily_limit_cents: z
|
|
1149
|
+
.number()
|
|
1150
|
+
.int()
|
|
1151
|
+
.min(1)
|
|
1152
|
+
.max(1000000)
|
|
1153
|
+
.optional()
|
|
1154
|
+
.describe("New daily spending limit in cents"),
|
|
1155
|
+
allowed_domains: z
|
|
1156
|
+
.array(z.string().max(253).regex(DOMAIN_RE))
|
|
1157
|
+
.max(100)
|
|
1158
|
+
.optional()
|
|
1159
|
+
.describe("New allowed domains list"),
|
|
906
1160
|
}),
|
|
907
|
-
execute: async ({ wallet_id, daily_limit_cents, allowed_domains }) => {
|
|
1161
|
+
execute: async ({ wallet_id, daily_limit_cents, allowed_domains, }) => {
|
|
908
1162
|
const body = {};
|
|
909
1163
|
if (daily_limit_cents !== undefined)
|
|
910
1164
|
body.dailyLimitCents = daily_limit_cents;
|
|
911
1165
|
if (allowed_domains !== undefined)
|
|
912
1166
|
body.allowedDomains = allowed_domains;
|
|
913
1167
|
if (Object.keys(body).length === 0) {
|
|
914
|
-
return {
|
|
1168
|
+
return {
|
|
1169
|
+
error: "At least one of daily_limit_cents or allowed_domains must be provided",
|
|
1170
|
+
};
|
|
915
1171
|
}
|
|
916
1172
|
try {
|
|
917
1173
|
return await apiRequest(client, "PATCH", `/api/agent-wallet/${encodeURIComponent(wallet_id)}/policy`, body, agentSecret);
|
|
918
1174
|
}
|
|
919
1175
|
catch (err) {
|
|
920
|
-
return {
|
|
1176
|
+
return {
|
|
1177
|
+
error: `Failed to update policy: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
},
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
// ---------------------------------------------------------------------------
|
|
1184
|
+
// Account lifecycle tools
|
|
1185
|
+
// ---------------------------------------------------------------------------
|
|
1186
|
+
/**
|
|
1187
|
+
* Creates a tool to register a new DomiNode account.
|
|
1188
|
+
*/
|
|
1189
|
+
export function createRegisterTool(agentSecret) {
|
|
1190
|
+
return tool({
|
|
1191
|
+
description: "Register a new Dominus Node account. Returns user info and JWT tokens. " +
|
|
1192
|
+
"After registering, use verifyEmail or rely on MCP agent auto-verification.",
|
|
1193
|
+
parameters: z.object({
|
|
1194
|
+
email: z.string().email().describe("Email address for the new account"),
|
|
1195
|
+
password: z
|
|
1196
|
+
.string()
|
|
1197
|
+
.min(8)
|
|
1198
|
+
.max(128)
|
|
1199
|
+
.describe("Password (min 8 characters)"),
|
|
1200
|
+
}),
|
|
1201
|
+
execute: async ({ email, password, }) => {
|
|
1202
|
+
try {
|
|
1203
|
+
const baseUrl = process.env.DOMINUSNODE_BASE_URL || "https://api.dominusnode.com";
|
|
1204
|
+
const headers = {
|
|
1205
|
+
"Content-Type": "application/json",
|
|
1206
|
+
};
|
|
1207
|
+
if (agentSecret) {
|
|
1208
|
+
headers["X-DominusNode-Agent"] = "mcp";
|
|
1209
|
+
headers["X-DominusNode-Agent-Secret"] = agentSecret;
|
|
1210
|
+
}
|
|
1211
|
+
// Solve PoW for CAPTCHA-free registration
|
|
1212
|
+
const pow = await solvePoW(baseUrl);
|
|
1213
|
+
const regBody = { email, password };
|
|
1214
|
+
if (pow)
|
|
1215
|
+
regBody.pow = pow;
|
|
1216
|
+
const resp = await fetch(`${baseUrl}/api/auth/register`, {
|
|
1217
|
+
method: "POST",
|
|
1218
|
+
headers,
|
|
1219
|
+
body: JSON.stringify(regBody),
|
|
1220
|
+
redirect: "error",
|
|
1221
|
+
});
|
|
1222
|
+
if (!resp.ok) {
|
|
1223
|
+
const text = await resp.text().catch(() => "");
|
|
1224
|
+
throw new Error(`Registration failed (${resp.status}): ${text.slice(0, 200)}`);
|
|
1225
|
+
}
|
|
1226
|
+
const data = JSON.parse(await resp.text());
|
|
1227
|
+
stripDangerousKeys(data);
|
|
1228
|
+
return {
|
|
1229
|
+
userId: data.user?.id,
|
|
1230
|
+
email: data.user?.email,
|
|
1231
|
+
message: "Account created. Verify email to unlock financial features.",
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
catch (err) {
|
|
1235
|
+
return {
|
|
1236
|
+
error: `Failed to register: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
},
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Creates a tool to log into an existing DomiNode account.
|
|
1244
|
+
*/
|
|
1245
|
+
export function createLoginTool(agentSecret) {
|
|
1246
|
+
return tool({
|
|
1247
|
+
description: "Log into an existing Dominus Node account. Returns JWT access and refresh tokens.",
|
|
1248
|
+
parameters: z.object({
|
|
1249
|
+
email: z.string().email().describe("Account email address"),
|
|
1250
|
+
password: z.string().min(1).describe("Account password"),
|
|
1251
|
+
}),
|
|
1252
|
+
execute: async ({ email, password, }) => {
|
|
1253
|
+
try {
|
|
1254
|
+
const baseUrl = process.env.DOMINUSNODE_BASE_URL || "https://api.dominusnode.com";
|
|
1255
|
+
const headers = {
|
|
1256
|
+
"Content-Type": "application/json",
|
|
1257
|
+
};
|
|
1258
|
+
if (agentSecret) {
|
|
1259
|
+
headers["X-DominusNode-Agent"] = "mcp";
|
|
1260
|
+
headers["X-DominusNode-Agent-Secret"] = agentSecret;
|
|
1261
|
+
}
|
|
1262
|
+
const resp = await fetch(`${baseUrl}/api/auth/login`, {
|
|
1263
|
+
method: "POST",
|
|
1264
|
+
headers,
|
|
1265
|
+
body: JSON.stringify({ email, password }),
|
|
1266
|
+
redirect: "error",
|
|
1267
|
+
});
|
|
1268
|
+
if (!resp.ok) {
|
|
1269
|
+
const text = await resp.text().catch(() => "");
|
|
1270
|
+
throw new Error(`Login failed (${resp.status}): ${sanitizeError(text.slice(0, 200))}`);
|
|
1271
|
+
}
|
|
1272
|
+
const data = JSON.parse(await resp.text());
|
|
1273
|
+
stripDangerousKeys(data);
|
|
1274
|
+
if (data.mfaRequired) {
|
|
1275
|
+
return {
|
|
1276
|
+
mfaRequired: true,
|
|
1277
|
+
challengeToken: data.challengeToken,
|
|
1278
|
+
message: "MFA verification required",
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
return {
|
|
1282
|
+
accessToken: data.accessToken ? "[REDACTED]" : undefined,
|
|
1283
|
+
message: "Login successful",
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
catch (err) {
|
|
1287
|
+
return {
|
|
1288
|
+
error: `Failed to login: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
},
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Creates a tool to get the current authenticated account information.
|
|
1296
|
+
*/
|
|
1297
|
+
export function createGetAccountInfoTool(client, agentSecret) {
|
|
1298
|
+
return tool({
|
|
1299
|
+
description: "Get the current authenticated Dominus Node account information including email, plan, and verification status.",
|
|
1300
|
+
parameters: z.object({}),
|
|
1301
|
+
execute: async () => {
|
|
1302
|
+
try {
|
|
1303
|
+
const result = await apiRequest(client, "GET", "/api/auth/me", undefined, agentSecret);
|
|
1304
|
+
const user = result.user;
|
|
1305
|
+
return {
|
|
1306
|
+
id: user?.id,
|
|
1307
|
+
email: user?.email,
|
|
1308
|
+
plan: user?.plan,
|
|
1309
|
+
emailVerified: user?.emailVerified,
|
|
1310
|
+
status: user?.status,
|
|
1311
|
+
createdAt: user?.createdAt,
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
catch (err) {
|
|
1315
|
+
return {
|
|
1316
|
+
error: `Failed to get account info: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Creates a tool to verify an email address with a token.
|
|
1324
|
+
*/
|
|
1325
|
+
export function createVerifyEmailTool(agentSecret) {
|
|
1326
|
+
return tool({
|
|
1327
|
+
description: "Verify email address using the verification token sent to email. MCP agents are auto-verified.",
|
|
1328
|
+
parameters: z.object({
|
|
1329
|
+
token: z
|
|
1330
|
+
.string()
|
|
1331
|
+
.min(1)
|
|
1332
|
+
.describe("Email verification token from the verification email"),
|
|
1333
|
+
}),
|
|
1334
|
+
execute: async ({ token }) => {
|
|
1335
|
+
try {
|
|
1336
|
+
const baseUrl = process.env.DOMINUSNODE_BASE_URL || "https://api.dominusnode.com";
|
|
1337
|
+
const headers = {
|
|
1338
|
+
"Content-Type": "application/json",
|
|
1339
|
+
};
|
|
1340
|
+
if (agentSecret) {
|
|
1341
|
+
headers["X-DominusNode-Agent"] = "mcp";
|
|
1342
|
+
headers["X-DominusNode-Agent-Secret"] = agentSecret;
|
|
1343
|
+
}
|
|
1344
|
+
const resp = await fetch(`${baseUrl}/api/auth/verify-email`, {
|
|
1345
|
+
method: "POST",
|
|
1346
|
+
headers,
|
|
1347
|
+
body: JSON.stringify({ token }),
|
|
1348
|
+
redirect: "error",
|
|
1349
|
+
});
|
|
1350
|
+
if (!resp.ok) {
|
|
1351
|
+
const text = await resp.text().catch(() => "");
|
|
1352
|
+
throw new Error(`Verification failed (${resp.status}): ${text.slice(0, 200)}`);
|
|
1353
|
+
}
|
|
1354
|
+
return { success: true, message: "Email verified successfully" };
|
|
1355
|
+
}
|
|
1356
|
+
catch (err) {
|
|
1357
|
+
return {
|
|
1358
|
+
error: `Failed to verify email: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Creates a tool to resend the email verification token.
|
|
1366
|
+
*/
|
|
1367
|
+
export function createResendVerificationTool(client, agentSecret) {
|
|
1368
|
+
return tool({
|
|
1369
|
+
description: "Resend the email verification token to the account's email address.",
|
|
1370
|
+
parameters: z.object({}),
|
|
1371
|
+
execute: async () => {
|
|
1372
|
+
try {
|
|
1373
|
+
await apiRequest(client, "POST", "/api/auth/resend-verification", undefined, agentSecret);
|
|
1374
|
+
return { success: true, message: "Verification email sent" };
|
|
1375
|
+
}
|
|
1376
|
+
catch (err) {
|
|
1377
|
+
return {
|
|
1378
|
+
error: `Failed to resend verification: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
},
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Creates a tool to change the account password.
|
|
1386
|
+
*/
|
|
1387
|
+
export function createUpdatePasswordTool(client, agentSecret) {
|
|
1388
|
+
return tool({
|
|
1389
|
+
description: "Change the password for the current Dominus Node account.",
|
|
1390
|
+
parameters: z.object({
|
|
1391
|
+
current_password: z.string().min(1).describe("Current password"),
|
|
1392
|
+
new_password: z
|
|
1393
|
+
.string()
|
|
1394
|
+
.min(8)
|
|
1395
|
+
.max(128)
|
|
1396
|
+
.describe("New password (min 8 characters)"),
|
|
1397
|
+
}),
|
|
1398
|
+
execute: async ({ current_password, new_password, }) => {
|
|
1399
|
+
try {
|
|
1400
|
+
await apiRequest(client, "POST", "/api/auth/change-password", {
|
|
1401
|
+
currentPassword: current_password,
|
|
1402
|
+
newPassword: new_password,
|
|
1403
|
+
}, agentSecret);
|
|
1404
|
+
return { success: true, message: "Password updated" };
|
|
1405
|
+
}
|
|
1406
|
+
catch (err) {
|
|
1407
|
+
return {
|
|
1408
|
+
error: `Failed to update password: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
},
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
// ---------------------------------------------------------------------------
|
|
1415
|
+
// API key management tools
|
|
1416
|
+
// ---------------------------------------------------------------------------
|
|
1417
|
+
/**
|
|
1418
|
+
* Creates a tool to list all API keys for the current account.
|
|
1419
|
+
*/
|
|
1420
|
+
export function createListKeysTool(client, agentSecret) {
|
|
1421
|
+
return tool({
|
|
1422
|
+
description: "List all API keys for the current Dominus Node account. Shows key ID, label, prefix, and creation date.",
|
|
1423
|
+
parameters: z.object({}),
|
|
1424
|
+
execute: async () => {
|
|
1425
|
+
try {
|
|
1426
|
+
return await apiRequest(client, "GET", "/api/keys", undefined, agentSecret);
|
|
1427
|
+
}
|
|
1428
|
+
catch (err) {
|
|
1429
|
+
return {
|
|
1430
|
+
error: `Failed to list keys: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
},
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Creates a tool to create a new API key.
|
|
1438
|
+
*/
|
|
1439
|
+
export function createCreateKeyTool(client, agentSecret) {
|
|
1440
|
+
return tool({
|
|
1441
|
+
description: "Create a new Dominus Node API key. The full key is shown only once — store it securely. " +
|
|
1442
|
+
"WARNING: API keys are secret credentials. Never log or share them.",
|
|
1443
|
+
parameters: z.object({
|
|
1444
|
+
label: z
|
|
1445
|
+
.string()
|
|
1446
|
+
.min(1)
|
|
1447
|
+
.max(100)
|
|
1448
|
+
.optional()
|
|
1449
|
+
.describe("Human-readable label for the key"),
|
|
1450
|
+
}),
|
|
1451
|
+
execute: async ({ label }) => {
|
|
1452
|
+
if (label && /[\x00-\x1F\x7F]/.test(label)) {
|
|
1453
|
+
return { error: "label contains invalid control characters" };
|
|
1454
|
+
}
|
|
1455
|
+
try {
|
|
1456
|
+
const body = {};
|
|
1457
|
+
if (label)
|
|
1458
|
+
body.label = label;
|
|
1459
|
+
return await apiRequest(client, "POST", "/api/keys", body, agentSecret);
|
|
1460
|
+
}
|
|
1461
|
+
catch (err) {
|
|
1462
|
+
return {
|
|
1463
|
+
error: `Failed to create key: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
},
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Creates a tool to revoke (delete) an API key.
|
|
1471
|
+
*/
|
|
1472
|
+
export function createRevokeKeyTool(client, agentSecret) {
|
|
1473
|
+
return tool({
|
|
1474
|
+
description: "Revoke (permanently delete) a Dominus Node API key. This cannot be undone.",
|
|
1475
|
+
parameters: z.object({
|
|
1476
|
+
key_id: z
|
|
1477
|
+
.string()
|
|
1478
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1479
|
+
.describe("UUID of the API key to revoke"),
|
|
1480
|
+
}),
|
|
1481
|
+
execute: async ({ key_id }) => {
|
|
1482
|
+
try {
|
|
1483
|
+
await apiRequest(client, "DELETE", `/api/keys/${encodeURIComponent(key_id)}`, undefined, agentSecret);
|
|
1484
|
+
return { success: true, message: "API key revoked" };
|
|
1485
|
+
}
|
|
1486
|
+
catch (err) {
|
|
1487
|
+
return {
|
|
1488
|
+
error: `Failed to revoke key: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
// ---------------------------------------------------------------------------
|
|
1495
|
+
// Wallet extended tools
|
|
1496
|
+
// ---------------------------------------------------------------------------
|
|
1497
|
+
/**
|
|
1498
|
+
* Creates a tool to get wallet transaction history.
|
|
1499
|
+
*/
|
|
1500
|
+
export function createGetTransactionsTool(client, agentSecret) {
|
|
1501
|
+
return tool({
|
|
1502
|
+
description: "Get wallet transaction history showing top-ups, usage charges, and transfers.",
|
|
1503
|
+
parameters: z.object({
|
|
1504
|
+
limit: z
|
|
1505
|
+
.number()
|
|
1506
|
+
.int()
|
|
1507
|
+
.min(1)
|
|
1508
|
+
.max(100)
|
|
1509
|
+
.optional()
|
|
1510
|
+
.describe("Maximum transactions to return (default 50)"),
|
|
1511
|
+
offset: z
|
|
1512
|
+
.number()
|
|
1513
|
+
.int()
|
|
1514
|
+
.min(0)
|
|
1515
|
+
.optional()
|
|
1516
|
+
.describe("Offset for pagination"),
|
|
1517
|
+
}),
|
|
1518
|
+
execute: async ({ limit, offset }) => {
|
|
1519
|
+
try {
|
|
1520
|
+
const params = new URLSearchParams();
|
|
1521
|
+
if (limit !== undefined)
|
|
1522
|
+
params.set("limit", String(limit));
|
|
1523
|
+
if (offset !== undefined)
|
|
1524
|
+
params.set("offset", String(offset));
|
|
1525
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1526
|
+
return await apiRequest(client, "GET", `/api/wallet/transactions${qs}`, undefined, agentSecret);
|
|
1527
|
+
}
|
|
1528
|
+
catch (err) {
|
|
1529
|
+
return {
|
|
1530
|
+
error: `Failed to get transactions: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
},
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Creates a tool to get wallet balance forecast.
|
|
1538
|
+
*/
|
|
1539
|
+
export function createGetForecastTool(client, agentSecret) {
|
|
1540
|
+
return tool({
|
|
1541
|
+
description: "Get a wallet balance forecast based on recent usage patterns. Shows estimated days until balance depletion.",
|
|
1542
|
+
parameters: z.object({}),
|
|
1543
|
+
execute: async () => {
|
|
1544
|
+
try {
|
|
1545
|
+
return await apiRequest(client, "GET", "/api/wallet/forecast", undefined, agentSecret);
|
|
1546
|
+
}
|
|
1547
|
+
catch (err) {
|
|
1548
|
+
return {
|
|
1549
|
+
error: `Failed to get forecast: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
},
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Creates a tool to check the status of a crypto payment invoice.
|
|
1557
|
+
*/
|
|
1558
|
+
export function createCheckPaymentTool(client, agentSecret) {
|
|
1559
|
+
return tool({
|
|
1560
|
+
description: "Check the status of a cryptocurrency payment invoice. Use after creating a crypto top-up.",
|
|
1561
|
+
parameters: z.object({
|
|
1562
|
+
invoice_id: z
|
|
1563
|
+
.string()
|
|
1564
|
+
.min(1)
|
|
1565
|
+
.describe("The invoice ID from the crypto top-up creation"),
|
|
1566
|
+
}),
|
|
1567
|
+
execute: async ({ invoice_id }) => {
|
|
1568
|
+
try {
|
|
1569
|
+
return await apiRequest(client, "GET", `/api/wallet/crypto/status/${encodeURIComponent(invoice_id)}`, undefined, agentSecret);
|
|
1570
|
+
}
|
|
1571
|
+
catch (err) {
|
|
1572
|
+
return {
|
|
1573
|
+
error: `Failed to check payment: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
// ---------------------------------------------------------------------------
|
|
1580
|
+
// Usage extended tools
|
|
1581
|
+
// ---------------------------------------------------------------------------
|
|
1582
|
+
/**
|
|
1583
|
+
* Creates a tool to get daily usage breakdown.
|
|
1584
|
+
*/
|
|
1585
|
+
export function createGetDailyUsageTool(client, agentSecret) {
|
|
1586
|
+
return tool({
|
|
1587
|
+
description: "Get daily proxy usage breakdown showing bytes, cost, and request count per day.",
|
|
1588
|
+
parameters: z.object({
|
|
1589
|
+
since: z
|
|
1590
|
+
.string()
|
|
1591
|
+
.optional()
|
|
1592
|
+
.describe("Start date (ISO 8601). Defaults to 30 days ago."),
|
|
1593
|
+
until: z
|
|
1594
|
+
.string()
|
|
1595
|
+
.optional()
|
|
1596
|
+
.describe("End date (ISO 8601). Defaults to now."),
|
|
1597
|
+
}),
|
|
1598
|
+
execute: async ({ since, until }) => {
|
|
1599
|
+
try {
|
|
1600
|
+
const params = new URLSearchParams();
|
|
1601
|
+
if (since)
|
|
1602
|
+
params.set("since", since);
|
|
1603
|
+
if (until)
|
|
1604
|
+
params.set("until", until);
|
|
1605
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1606
|
+
return await apiRequest(client, "GET", `/api/usage/daily${qs}`, undefined, agentSecret);
|
|
1607
|
+
}
|
|
1608
|
+
catch (err) {
|
|
1609
|
+
return {
|
|
1610
|
+
error: `Failed to get daily usage: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
},
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Creates a tool to get the top accessed hosts through the proxy.
|
|
1618
|
+
*/
|
|
1619
|
+
export function createGetTopHostsTool(client, agentSecret) {
|
|
1620
|
+
return tool({
|
|
1621
|
+
description: "Get the top accessed hosts (domains) through the proxy, ranked by total bytes transferred.",
|
|
1622
|
+
parameters: z.object({
|
|
1623
|
+
since: z
|
|
1624
|
+
.string()
|
|
1625
|
+
.optional()
|
|
1626
|
+
.describe("Start date (ISO 8601). Defaults to 30 days ago."),
|
|
1627
|
+
until: z
|
|
1628
|
+
.string()
|
|
1629
|
+
.optional()
|
|
1630
|
+
.describe("End date (ISO 8601). Defaults to now."),
|
|
1631
|
+
limit: z
|
|
1632
|
+
.number()
|
|
1633
|
+
.int()
|
|
1634
|
+
.min(1)
|
|
1635
|
+
.max(100)
|
|
1636
|
+
.optional()
|
|
1637
|
+
.describe("Max hosts to return (default 10)"),
|
|
1638
|
+
}),
|
|
1639
|
+
execute: async ({ since, until, limit, }) => {
|
|
1640
|
+
try {
|
|
1641
|
+
const params = new URLSearchParams();
|
|
1642
|
+
if (since)
|
|
1643
|
+
params.set("since", since);
|
|
1644
|
+
if (until)
|
|
1645
|
+
params.set("until", until);
|
|
1646
|
+
if (limit !== undefined)
|
|
1647
|
+
params.set("limit", String(limit));
|
|
1648
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1649
|
+
return await apiRequest(client, "GET", `/api/usage/top-hosts${qs}`, undefined, agentSecret);
|
|
1650
|
+
}
|
|
1651
|
+
catch (err) {
|
|
1652
|
+
return {
|
|
1653
|
+
error: `Failed to get top hosts: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
},
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
// ---------------------------------------------------------------------------
|
|
1660
|
+
// Plan management tools
|
|
1661
|
+
// ---------------------------------------------------------------------------
|
|
1662
|
+
/**
|
|
1663
|
+
* Creates a tool to get the current user's plan.
|
|
1664
|
+
*/
|
|
1665
|
+
export function createGetPlanTool(client, agentSecret) {
|
|
1666
|
+
return tool({
|
|
1667
|
+
description: "Get the current user's plan details including bandwidth limits, pricing tier, and features.",
|
|
1668
|
+
parameters: z.object({}),
|
|
1669
|
+
execute: async () => {
|
|
1670
|
+
try {
|
|
1671
|
+
return await apiRequest(client, "GET", "/api/plans/user/plan", undefined, agentSecret);
|
|
1672
|
+
}
|
|
1673
|
+
catch (err) {
|
|
1674
|
+
return {
|
|
1675
|
+
error: `Failed to get plan: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Creates a tool to list all available plans.
|
|
1683
|
+
*/
|
|
1684
|
+
export function createListPlansTool(client, agentSecret) {
|
|
1685
|
+
return tool({
|
|
1686
|
+
description: "List all available Dominus Node plans with pricing, bandwidth limits, and features.",
|
|
1687
|
+
parameters: z.object({}),
|
|
1688
|
+
execute: async () => {
|
|
1689
|
+
try {
|
|
1690
|
+
return await apiRequest(client, "GET", "/api/plans", undefined, agentSecret);
|
|
1691
|
+
}
|
|
1692
|
+
catch (err) {
|
|
1693
|
+
return {
|
|
1694
|
+
error: `Failed to list plans: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
},
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Creates a tool to change the current user's plan.
|
|
1702
|
+
*/
|
|
1703
|
+
export function createChangePlanTool(client, agentSecret) {
|
|
1704
|
+
return tool({
|
|
1705
|
+
description: "Change the current user's plan. Requires email verification.",
|
|
1706
|
+
parameters: z.object({
|
|
1707
|
+
plan_id: z.string().min(1).describe("ID of the plan to switch to"),
|
|
1708
|
+
}),
|
|
1709
|
+
execute: async ({ plan_id }) => {
|
|
1710
|
+
try {
|
|
1711
|
+
return await apiRequest(client, "PUT", "/api/plans/user/plan", { planId: plan_id }, agentSecret);
|
|
1712
|
+
}
|
|
1713
|
+
catch (err) {
|
|
1714
|
+
return {
|
|
1715
|
+
error: `Failed to change plan: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
},
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
// ---------------------------------------------------------------------------
|
|
1722
|
+
// Proxy extended tools
|
|
1723
|
+
// ---------------------------------------------------------------------------
|
|
1724
|
+
/**
|
|
1725
|
+
* Creates a tool to get proxy health/status information.
|
|
1726
|
+
*/
|
|
1727
|
+
export function createGetProxyStatusTool(client, agentSecret) {
|
|
1728
|
+
return tool({
|
|
1729
|
+
description: "Get proxy health and status information including uptime, active connections, and provider status.",
|
|
1730
|
+
parameters: z.object({}),
|
|
1731
|
+
execute: async () => {
|
|
1732
|
+
try {
|
|
1733
|
+
return await apiRequest(client, "GET", "/api/proxy/status", undefined, agentSecret);
|
|
1734
|
+
}
|
|
1735
|
+
catch (err) {
|
|
1736
|
+
return {
|
|
1737
|
+
error: `Failed to get proxy status: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
},
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
// ---------------------------------------------------------------------------
|
|
1744
|
+
// Team management tools (full suite — 17 tools)
|
|
1745
|
+
// ---------------------------------------------------------------------------
|
|
1746
|
+
/**
|
|
1747
|
+
* Creates a tool to create a new team.
|
|
1748
|
+
*/
|
|
1749
|
+
export function createCreateTeamTool(client, agentSecret) {
|
|
1750
|
+
return tool({
|
|
1751
|
+
description: "Create a new Dominus Node team with a shared wallet for collaborative proxy usage.",
|
|
1752
|
+
parameters: z.object({
|
|
1753
|
+
name: z.string().min(1).max(100).describe("Team name"),
|
|
1754
|
+
max_members: z
|
|
1755
|
+
.number()
|
|
1756
|
+
.int()
|
|
1757
|
+
.min(2)
|
|
1758
|
+
.max(100)
|
|
1759
|
+
.optional()
|
|
1760
|
+
.describe("Maximum team members (default 10)"),
|
|
1761
|
+
}),
|
|
1762
|
+
execute: async ({ name, max_members, }) => {
|
|
1763
|
+
if (/[\x00-\x1F\x7F]/.test(name)) {
|
|
1764
|
+
return { error: "name contains invalid control characters" };
|
|
1765
|
+
}
|
|
1766
|
+
try {
|
|
1767
|
+
const body = { name };
|
|
1768
|
+
if (max_members !== undefined)
|
|
1769
|
+
body.maxMembers = max_members;
|
|
1770
|
+
return await apiRequest(client, "POST", "/api/teams", body, agentSecret);
|
|
1771
|
+
}
|
|
1772
|
+
catch (err) {
|
|
1773
|
+
return {
|
|
1774
|
+
error: `Failed to create team: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
},
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Creates a tool to list all teams the user belongs to.
|
|
1782
|
+
*/
|
|
1783
|
+
export function createListTeamsTool(client, agentSecret) {
|
|
1784
|
+
return tool({
|
|
1785
|
+
description: "List all teams the current user belongs to (as owner, admin, or member).",
|
|
1786
|
+
parameters: z.object({}),
|
|
1787
|
+
execute: async () => {
|
|
1788
|
+
try {
|
|
1789
|
+
return await apiRequest(client, "GET", "/api/teams", undefined, agentSecret);
|
|
1790
|
+
}
|
|
1791
|
+
catch (err) {
|
|
1792
|
+
return {
|
|
1793
|
+
error: `Failed to list teams: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
},
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Creates a tool to get detailed information about a team.
|
|
1801
|
+
*/
|
|
1802
|
+
export function createTeamDetailsTool(client, agentSecret) {
|
|
1803
|
+
return tool({
|
|
1804
|
+
description: "Get detailed information about a team including wallet balance, members count, and settings.",
|
|
1805
|
+
parameters: z.object({
|
|
1806
|
+
team_id: z
|
|
1807
|
+
.string()
|
|
1808
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1809
|
+
.describe("UUID of the team"),
|
|
1810
|
+
}),
|
|
1811
|
+
execute: async ({ team_id }) => {
|
|
1812
|
+
try {
|
|
1813
|
+
return await apiRequest(client, "GET", `/api/teams/${encodeURIComponent(team_id)}`, undefined, agentSecret);
|
|
1814
|
+
}
|
|
1815
|
+
catch (err) {
|
|
1816
|
+
return {
|
|
1817
|
+
error: `Failed to get team details: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
},
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Creates a tool to update team settings.
|
|
1825
|
+
*/
|
|
1826
|
+
export function createUpdateTeamTool(client, agentSecret) {
|
|
1827
|
+
return tool({
|
|
1828
|
+
description: "Update team settings such as name or max members. Requires owner or admin role.",
|
|
1829
|
+
parameters: z.object({
|
|
1830
|
+
team_id: z
|
|
1831
|
+
.string()
|
|
1832
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1833
|
+
.describe("UUID of the team"),
|
|
1834
|
+
name: z.string().min(1).max(100).optional().describe("New team name"),
|
|
1835
|
+
max_members: z
|
|
1836
|
+
.number()
|
|
1837
|
+
.int()
|
|
1838
|
+
.min(2)
|
|
1839
|
+
.max(100)
|
|
1840
|
+
.optional()
|
|
1841
|
+
.describe("New max members limit"),
|
|
1842
|
+
}),
|
|
1843
|
+
execute: async ({ team_id, name, max_members, }) => {
|
|
1844
|
+
if (name && /[\x00-\x1F\x7F]/.test(name)) {
|
|
1845
|
+
return { error: "name contains invalid control characters" };
|
|
1846
|
+
}
|
|
1847
|
+
const body = {};
|
|
1848
|
+
if (name !== undefined)
|
|
1849
|
+
body.name = name;
|
|
1850
|
+
if (max_members !== undefined)
|
|
1851
|
+
body.maxMembers = max_members;
|
|
1852
|
+
if (Object.keys(body).length === 0) {
|
|
1853
|
+
return {
|
|
1854
|
+
error: "At least one field (name or max_members) must be provided",
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
try {
|
|
1858
|
+
return await apiRequest(client, "PATCH", `/api/teams/${encodeURIComponent(team_id)}`, body, agentSecret);
|
|
1859
|
+
}
|
|
1860
|
+
catch (err) {
|
|
1861
|
+
return {
|
|
1862
|
+
error: `Failed to update team: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
},
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Creates a tool to delete a team. Requires owner role.
|
|
1870
|
+
*/
|
|
1871
|
+
export function createTeamDeleteTool(client, agentSecret) {
|
|
1872
|
+
return tool({
|
|
1873
|
+
description: "Delete a team permanently. Requires owner role. Remaining team wallet balance is NOT refunded.",
|
|
1874
|
+
parameters: z.object({
|
|
1875
|
+
team_id: z
|
|
1876
|
+
.string()
|
|
1877
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1878
|
+
.describe("UUID of the team to delete"),
|
|
1879
|
+
}),
|
|
1880
|
+
execute: async ({ team_id }) => {
|
|
1881
|
+
try {
|
|
1882
|
+
await apiRequest(client, "DELETE", `/api/teams/${encodeURIComponent(team_id)}`, undefined, agentSecret);
|
|
1883
|
+
return { success: true, message: "Team deleted" };
|
|
1884
|
+
}
|
|
1885
|
+
catch (err) {
|
|
1886
|
+
return {
|
|
1887
|
+
error: `Failed to delete team: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
},
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Creates a tool to fund a team wallet from the user's personal wallet.
|
|
1895
|
+
*/
|
|
1896
|
+
export function createTeamFundTool(client, agentSecret) {
|
|
1897
|
+
return tool({
|
|
1898
|
+
description: "Fund a team wallet by transferring from your personal wallet. Requires owner or admin role.",
|
|
1899
|
+
parameters: z.object({
|
|
1900
|
+
team_id: z
|
|
1901
|
+
.string()
|
|
1902
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1903
|
+
.describe("UUID of the team"),
|
|
1904
|
+
amount_cents: z
|
|
1905
|
+
.number()
|
|
1906
|
+
.int()
|
|
1907
|
+
.min(1)
|
|
1908
|
+
.max(2147483647)
|
|
1909
|
+
.describe("Amount in cents to transfer"),
|
|
1910
|
+
}),
|
|
1911
|
+
execute: async ({ team_id, amount_cents, }) => {
|
|
1912
|
+
try {
|
|
1913
|
+
return await apiRequest(client, "POST", `/api/teams/${encodeURIComponent(team_id)}/wallet/fund`, { amountCents: amount_cents }, agentSecret);
|
|
1914
|
+
}
|
|
1915
|
+
catch (err) {
|
|
1916
|
+
return {
|
|
1917
|
+
error: `Failed to fund team: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
},
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Creates a tool to create a team API key.
|
|
1925
|
+
*/
|
|
1926
|
+
export function createTeamCreateKeyTool(client, agentSecret) {
|
|
1927
|
+
return tool({
|
|
1928
|
+
description: "Create an API key for a team. Team keys bill to the team wallet. Requires owner or admin role.",
|
|
1929
|
+
parameters: z.object({
|
|
1930
|
+
team_id: z
|
|
1931
|
+
.string()
|
|
1932
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1933
|
+
.describe("UUID of the team"),
|
|
1934
|
+
label: z
|
|
1935
|
+
.string()
|
|
1936
|
+
.min(1)
|
|
1937
|
+
.max(100)
|
|
1938
|
+
.optional()
|
|
1939
|
+
.describe("Label for the team API key"),
|
|
1940
|
+
}),
|
|
1941
|
+
execute: async ({ team_id, label, }) => {
|
|
1942
|
+
if (label && /[\x00-\x1F\x7F]/.test(label)) {
|
|
1943
|
+
return { error: "label contains invalid control characters" };
|
|
1944
|
+
}
|
|
1945
|
+
try {
|
|
1946
|
+
const body = {};
|
|
1947
|
+
if (label)
|
|
1948
|
+
body.label = label;
|
|
1949
|
+
return await apiRequest(client, "POST", `/api/teams/${encodeURIComponent(team_id)}/keys`, body, agentSecret);
|
|
1950
|
+
}
|
|
1951
|
+
catch (err) {
|
|
1952
|
+
return {
|
|
1953
|
+
error: `Failed to create team key: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
},
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Creates a tool to revoke a team API key.
|
|
1961
|
+
*/
|
|
1962
|
+
export function createTeamRevokeKeyTool(client, agentSecret) {
|
|
1963
|
+
return tool({
|
|
1964
|
+
description: "Revoke (delete) a team API key. Requires owner or admin role.",
|
|
1965
|
+
parameters: z.object({
|
|
1966
|
+
team_id: z
|
|
1967
|
+
.string()
|
|
1968
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1969
|
+
.describe("UUID of the team"),
|
|
1970
|
+
key_id: z
|
|
1971
|
+
.string()
|
|
1972
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1973
|
+
.describe("UUID of the team key to revoke"),
|
|
1974
|
+
}),
|
|
1975
|
+
execute: async ({ team_id, key_id, }) => {
|
|
1976
|
+
try {
|
|
1977
|
+
await apiRequest(client, "DELETE", `/api/teams/${encodeURIComponent(team_id)}/keys/${encodeURIComponent(key_id)}`, undefined, agentSecret);
|
|
1978
|
+
return { success: true, message: "Team key revoked" };
|
|
1979
|
+
}
|
|
1980
|
+
catch (err) {
|
|
1981
|
+
return {
|
|
1982
|
+
error: `Failed to revoke team key: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
},
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Creates a tool to list all API keys for a team.
|
|
1990
|
+
*/
|
|
1991
|
+
export function createTeamListKeysTool(client, agentSecret) {
|
|
1992
|
+
return tool({
|
|
1993
|
+
description: "List all API keys for a team. Shows key ID, label, prefix, and creation date.",
|
|
1994
|
+
parameters: z.object({
|
|
1995
|
+
team_id: z
|
|
1996
|
+
.string()
|
|
1997
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
1998
|
+
.describe("UUID of the team"),
|
|
1999
|
+
}),
|
|
2000
|
+
execute: async ({ team_id }) => {
|
|
2001
|
+
try {
|
|
2002
|
+
return await apiRequest(client, "GET", `/api/teams/${encodeURIComponent(team_id)}/keys`, undefined, agentSecret);
|
|
2003
|
+
}
|
|
2004
|
+
catch (err) {
|
|
2005
|
+
return {
|
|
2006
|
+
error: `Failed to list team keys: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
},
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
/**
|
|
2013
|
+
* Creates a tool to get team usage statistics.
|
|
2014
|
+
*/
|
|
2015
|
+
export function createTeamUsageTool(client, agentSecret) {
|
|
2016
|
+
return tool({
|
|
2017
|
+
description: "Get proxy usage statistics for a team including total bytes, cost, and per-member breakdown.",
|
|
2018
|
+
parameters: z.object({
|
|
2019
|
+
team_id: z
|
|
2020
|
+
.string()
|
|
2021
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2022
|
+
.describe("UUID of the team"),
|
|
2023
|
+
}),
|
|
2024
|
+
execute: async ({ team_id }) => {
|
|
2025
|
+
try {
|
|
2026
|
+
return await apiRequest(client, "GET", `/api/teams/${encodeURIComponent(team_id)}/usage`, undefined, agentSecret);
|
|
2027
|
+
}
|
|
2028
|
+
catch (err) {
|
|
2029
|
+
return {
|
|
2030
|
+
error: `Failed to get team usage: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
},
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Creates a tool to list all members of a team.
|
|
2038
|
+
*/
|
|
2039
|
+
export function createTeamListMembersTool(client, agentSecret) {
|
|
2040
|
+
return tool({
|
|
2041
|
+
description: "List all members of a team with their roles (owner/admin/member) and join dates.",
|
|
2042
|
+
parameters: z.object({
|
|
2043
|
+
team_id: z
|
|
2044
|
+
.string()
|
|
2045
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2046
|
+
.describe("UUID of the team"),
|
|
2047
|
+
}),
|
|
2048
|
+
execute: async ({ team_id }) => {
|
|
2049
|
+
try {
|
|
2050
|
+
return await apiRequest(client, "GET", `/api/teams/${encodeURIComponent(team_id)}/members`, undefined, agentSecret);
|
|
2051
|
+
}
|
|
2052
|
+
catch (err) {
|
|
2053
|
+
return {
|
|
2054
|
+
error: `Failed to list team members: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
},
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Creates a tool to add a member directly to a team (by user ID or email).
|
|
2062
|
+
*/
|
|
2063
|
+
export function createTeamAddMemberTool(client, agentSecret) {
|
|
2064
|
+
return tool({
|
|
2065
|
+
description: "Add a member directly to a team by email. Requires owner or admin role.",
|
|
2066
|
+
parameters: z.object({
|
|
2067
|
+
team_id: z
|
|
2068
|
+
.string()
|
|
2069
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2070
|
+
.describe("UUID of the team"),
|
|
2071
|
+
email: z.string().email().describe("Email of the user to add"),
|
|
2072
|
+
role: z
|
|
2073
|
+
.enum(["admin", "member"])
|
|
2074
|
+
.optional()
|
|
2075
|
+
.describe("Role to assign (default: member)"),
|
|
2076
|
+
}),
|
|
2077
|
+
execute: async ({ team_id, email, role, }) => {
|
|
2078
|
+
try {
|
|
2079
|
+
const body = { email };
|
|
2080
|
+
if (role)
|
|
2081
|
+
body.role = role;
|
|
2082
|
+
return await apiRequest(client, "POST", `/api/teams/${encodeURIComponent(team_id)}/members`, body, agentSecret);
|
|
2083
|
+
}
|
|
2084
|
+
catch (err) {
|
|
2085
|
+
return {
|
|
2086
|
+
error: `Failed to add member: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
},
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Creates a tool to remove a member from a team.
|
|
2094
|
+
*/
|
|
2095
|
+
export function createTeamRemoveMemberTool(client, agentSecret) {
|
|
2096
|
+
return tool({
|
|
2097
|
+
description: "Remove a member from a team. Requires owner or admin role. Cannot remove the owner.",
|
|
2098
|
+
parameters: z.object({
|
|
2099
|
+
team_id: z
|
|
2100
|
+
.string()
|
|
2101
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2102
|
+
.describe("UUID of the team"),
|
|
2103
|
+
user_id: z
|
|
2104
|
+
.string()
|
|
2105
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2106
|
+
.describe("UUID of the member to remove"),
|
|
2107
|
+
}),
|
|
2108
|
+
execute: async ({ team_id, user_id, }) => {
|
|
2109
|
+
try {
|
|
2110
|
+
await apiRequest(client, "DELETE", `/api/teams/${encodeURIComponent(team_id)}/members/${encodeURIComponent(user_id)}`, undefined, agentSecret);
|
|
2111
|
+
return { success: true, message: "Member removed" };
|
|
2112
|
+
}
|
|
2113
|
+
catch (err) {
|
|
2114
|
+
return {
|
|
2115
|
+
error: `Failed to remove member: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
},
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
/**
|
|
2122
|
+
* Creates a tool to update a team member's role.
|
|
2123
|
+
*/
|
|
2124
|
+
export function createUpdateTeamMemberRoleTool(client, agentSecret) {
|
|
2125
|
+
return tool({
|
|
2126
|
+
description: "Update a team member's role (admin or member). Requires owner role.",
|
|
2127
|
+
parameters: z.object({
|
|
2128
|
+
team_id: z
|
|
2129
|
+
.string()
|
|
2130
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2131
|
+
.describe("UUID of the team"),
|
|
2132
|
+
user_id: z
|
|
2133
|
+
.string()
|
|
2134
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2135
|
+
.describe("UUID of the member"),
|
|
2136
|
+
role: z.enum(["admin", "member"]).describe("New role for the member"),
|
|
2137
|
+
}),
|
|
2138
|
+
execute: async ({ team_id, user_id, role, }) => {
|
|
2139
|
+
try {
|
|
2140
|
+
return await apiRequest(client, "PATCH", `/api/teams/${encodeURIComponent(team_id)}/members/${encodeURIComponent(user_id)}`, { role }, agentSecret);
|
|
2141
|
+
}
|
|
2142
|
+
catch (err) {
|
|
2143
|
+
return {
|
|
2144
|
+
error: `Failed to update member role: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
},
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Creates a tool to invite a member to a team via email.
|
|
2152
|
+
*/
|
|
2153
|
+
export function createTeamInviteMemberTool(client, agentSecret) {
|
|
2154
|
+
return tool({
|
|
2155
|
+
description: "Invite a user to join a team via email. They receive an invitation link. Requires owner or admin role.",
|
|
2156
|
+
parameters: z.object({
|
|
2157
|
+
team_id: z
|
|
2158
|
+
.string()
|
|
2159
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2160
|
+
.describe("UUID of the team"),
|
|
2161
|
+
email: z.string().email().describe("Email address to invite"),
|
|
2162
|
+
role: z
|
|
2163
|
+
.enum(["admin", "member"])
|
|
2164
|
+
.optional()
|
|
2165
|
+
.describe("Role to assign when they accept (default: member)"),
|
|
2166
|
+
}),
|
|
2167
|
+
execute: async ({ team_id, email, role, }) => {
|
|
2168
|
+
try {
|
|
2169
|
+
const body = { email };
|
|
2170
|
+
if (role)
|
|
2171
|
+
body.role = role;
|
|
2172
|
+
return await apiRequest(client, "POST", `/api/teams/${encodeURIComponent(team_id)}/invites`, body, agentSecret);
|
|
2173
|
+
}
|
|
2174
|
+
catch (err) {
|
|
2175
|
+
return {
|
|
2176
|
+
error: `Failed to invite member: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
},
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* Creates a tool to list pending team invitations.
|
|
2184
|
+
*/
|
|
2185
|
+
export function createTeamListInvitesTool(client, agentSecret) {
|
|
2186
|
+
return tool({
|
|
2187
|
+
description: "List all pending invitations for a team.",
|
|
2188
|
+
parameters: z.object({
|
|
2189
|
+
team_id: z
|
|
2190
|
+
.string()
|
|
2191
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2192
|
+
.describe("UUID of the team"),
|
|
2193
|
+
}),
|
|
2194
|
+
execute: async ({ team_id }) => {
|
|
2195
|
+
try {
|
|
2196
|
+
return await apiRequest(client, "GET", `/api/teams/${encodeURIComponent(team_id)}/invites`, undefined, agentSecret);
|
|
2197
|
+
}
|
|
2198
|
+
catch (err) {
|
|
2199
|
+
return {
|
|
2200
|
+
error: `Failed to list invites: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
},
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Creates a tool to cancel a pending team invitation.
|
|
2208
|
+
*/
|
|
2209
|
+
export function createTeamCancelInviteTool(client, agentSecret) {
|
|
2210
|
+
return tool({
|
|
2211
|
+
description: "Cancel a pending team invitation. Requires owner or admin role.",
|
|
2212
|
+
parameters: z.object({
|
|
2213
|
+
team_id: z
|
|
2214
|
+
.string()
|
|
2215
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2216
|
+
.describe("UUID of the team"),
|
|
2217
|
+
invite_id: z
|
|
2218
|
+
.string()
|
|
2219
|
+
.regex(UUID_RE, "Must be a valid UUID")
|
|
2220
|
+
.describe("UUID of the invitation to cancel"),
|
|
2221
|
+
}),
|
|
2222
|
+
execute: async ({ team_id, invite_id, }) => {
|
|
2223
|
+
try {
|
|
2224
|
+
await apiRequest(client, "DELETE", `/api/teams/${encodeURIComponent(team_id)}/invites/${encodeURIComponent(invite_id)}`, undefined, agentSecret);
|
|
2225
|
+
return { success: true, message: "Invitation cancelled" };
|
|
2226
|
+
}
|
|
2227
|
+
catch (err) {
|
|
2228
|
+
return {
|
|
2229
|
+
error: `Failed to cancel invite: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}`,
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
},
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
// ---------------------------------------------------------------------------
|
|
2236
|
+
// MPP (Machine Payment Protocol) tools
|
|
2237
|
+
// ---------------------------------------------------------------------------
|
|
2238
|
+
/**
|
|
2239
|
+
* Creates a tool to get MPP protocol info (no auth required).
|
|
2240
|
+
*/
|
|
2241
|
+
export function createMppInfoTool(client, agentSecret) {
|
|
2242
|
+
return tool({
|
|
2243
|
+
description: "Get Machine Payment Protocol (MPP) information including enabled status, " +
|
|
2244
|
+
"supported payment methods, pricing, and session limits. No authentication required. " +
|
|
2245
|
+
"MPP enables keyless proxy access -- AI agents can use the proxy WITHOUT any API key " +
|
|
2246
|
+
"by calling mpp_challenge first, then using the returned credential as proxy auth.",
|
|
2247
|
+
parameters: z.object({}),
|
|
2248
|
+
execute: async () => {
|
|
2249
|
+
try {
|
|
2250
|
+
const result = await apiRequest(client, "GET", "/api/mpp/info", undefined, agentSecret);
|
|
2251
|
+
return result;
|
|
2252
|
+
}
|
|
2253
|
+
catch (err) {
|
|
2254
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
2255
|
+
return { error: `Failed to get MPP info: ${sanitizeError(message)}` };
|
|
2256
|
+
}
|
|
2257
|
+
},
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Creates a tool to get an MPP challenge for keyless proxy access (no auth required).
|
|
2262
|
+
*/
|
|
2263
|
+
export function createMppChallengeTool(client, agentSecret) {
|
|
2264
|
+
return tool({
|
|
2265
|
+
description: "Get an MPP challenge for keyless proxy access. No authentication required. " +
|
|
2266
|
+
"Returns a credential that can be used to access the proxy WITHOUT any API key -- " +
|
|
2267
|
+
"just pay per request via MPP. Specify 'dc' for datacenter ($3/GB) or " +
|
|
2268
|
+
"'residential' ($5/GB) proxy pool.",
|
|
2269
|
+
parameters: z.object({
|
|
2270
|
+
pool_type: z
|
|
2271
|
+
.enum(["dc", "residential"])
|
|
2272
|
+
.describe("Proxy pool type: 'dc' for datacenter ($3/GB) or 'residential' ($5/GB)"),
|
|
2273
|
+
}),
|
|
2274
|
+
execute: async ({ pool_type }) => {
|
|
2275
|
+
try {
|
|
2276
|
+
const baseUrl = client.baseUrl ||
|
|
2277
|
+
process.env.DOMINUSNODE_BASE_URL ||
|
|
2278
|
+
"https://api.dominusnode.com";
|
|
2279
|
+
const headers = {
|
|
2280
|
+
"Content-Type": "application/json",
|
|
2281
|
+
};
|
|
2282
|
+
if (agentSecret) {
|
|
2283
|
+
headers["X-DominusNode-Agent"] = "mcp";
|
|
2284
|
+
headers["X-DominusNode-Agent-Secret"] = agentSecret;
|
|
2285
|
+
}
|
|
2286
|
+
const resp = await fetch(`${baseUrl}/api/mpp/challenge`, {
|
|
2287
|
+
method: "POST",
|
|
2288
|
+
headers,
|
|
2289
|
+
body: JSON.stringify({ poolType: pool_type }),
|
|
2290
|
+
redirect: "error",
|
|
2291
|
+
});
|
|
2292
|
+
if (!resp.ok) {
|
|
2293
|
+
const text = await resp.text().catch(() => "");
|
|
2294
|
+
throw new Error(`MPP challenge failed (${resp.status}): ${text.slice(0, 200)}`);
|
|
2295
|
+
}
|
|
2296
|
+
const text = await resp.text();
|
|
2297
|
+
if (text.length > MAX_RESPONSE_BODY_BYTES) {
|
|
2298
|
+
throw new Error("Response body exceeds size limit");
|
|
2299
|
+
}
|
|
2300
|
+
const data = JSON.parse(text);
|
|
2301
|
+
stripDangerousKeys(data);
|
|
2302
|
+
return data;
|
|
2303
|
+
}
|
|
2304
|
+
catch (err) {
|
|
2305
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
2306
|
+
return {
|
|
2307
|
+
error: `Failed to get MPP challenge: ${sanitizeError(message)}`,
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
},
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Creates a tool to top up wallet via MPP.
|
|
2315
|
+
*/
|
|
2316
|
+
export function createPayMppTool(client, agentSecret) {
|
|
2317
|
+
return tool({
|
|
2318
|
+
description: "Top up your Dominus Node wallet via Machine Payment Protocol (MPP). " +
|
|
2319
|
+
"Supports tempo, stripe_spt, and lightning payment methods.",
|
|
2320
|
+
parameters: z.object({
|
|
2321
|
+
amount_cents: z
|
|
2322
|
+
.number()
|
|
2323
|
+
.int()
|
|
2324
|
+
.min(500)
|
|
2325
|
+
.max(100000)
|
|
2326
|
+
.describe("Amount in cents to top up (min 500 = $5, max 100000 = $1,000)"),
|
|
2327
|
+
method: z
|
|
2328
|
+
.enum(["tempo", "stripe_spt", "lightning"])
|
|
2329
|
+
.describe("MPP payment method: tempo, stripe_spt, or lightning"),
|
|
2330
|
+
}),
|
|
2331
|
+
execute: async ({ amount_cents, method, }) => {
|
|
2332
|
+
try {
|
|
2333
|
+
const result = await apiRequest(client, "POST", "/api/mpp/topup", {
|
|
2334
|
+
amountCents: amount_cents,
|
|
2335
|
+
method,
|
|
2336
|
+
}, agentSecret);
|
|
2337
|
+
return result;
|
|
2338
|
+
}
|
|
2339
|
+
catch (err) {
|
|
2340
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
2341
|
+
return { error: `Failed to top up via MPP: ${sanitizeError(message)}` };
|
|
2342
|
+
}
|
|
2343
|
+
},
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Creates a tool to open an MPP pay-as-you-go session.
|
|
2348
|
+
*/
|
|
2349
|
+
export function createMppSessionOpenTool(client, agentSecret) {
|
|
2350
|
+
return tool({
|
|
2351
|
+
description: "Open a pay-as-you-go MPP session. Returns a channelId for metered proxy usage. " +
|
|
2352
|
+
"Deposit is held and refunded on close minus usage.",
|
|
2353
|
+
parameters: z.object({
|
|
2354
|
+
max_deposit_cents: z
|
|
2355
|
+
.number()
|
|
2356
|
+
.int()
|
|
2357
|
+
.min(500)
|
|
2358
|
+
.max(100000)
|
|
2359
|
+
.describe("Maximum deposit in cents for the session (min 500, max 100000)"),
|
|
2360
|
+
method: z
|
|
2361
|
+
.enum(["tempo", "stripe_spt", "lightning"])
|
|
2362
|
+
.describe("MPP payment method: tempo, stripe_spt, or lightning"),
|
|
2363
|
+
pool_type: z
|
|
2364
|
+
.enum(["dc", "residential"])
|
|
2365
|
+
.default("dc")
|
|
2366
|
+
.describe("Proxy pool type: dc ($3/GB) or residential ($5/GB)"),
|
|
2367
|
+
}),
|
|
2368
|
+
execute: async ({ max_deposit_cents, method, pool_type, }) => {
|
|
2369
|
+
try {
|
|
2370
|
+
const result = await apiRequest(client, "POST", "/api/mpp/session/open", {
|
|
2371
|
+
maxDepositCents: max_deposit_cents,
|
|
2372
|
+
method,
|
|
2373
|
+
poolType: pool_type,
|
|
2374
|
+
}, agentSecret);
|
|
2375
|
+
return result;
|
|
2376
|
+
}
|
|
2377
|
+
catch (err) {
|
|
2378
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
2379
|
+
return {
|
|
2380
|
+
error: `Failed to open MPP session: ${sanitizeError(message)}`,
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
},
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
/**
|
|
2387
|
+
* Creates a tool to close an MPP pay-as-you-go session.
|
|
2388
|
+
*/
|
|
2389
|
+
export function createMppSessionCloseTool(client, agentSecret) {
|
|
2390
|
+
return tool({
|
|
2391
|
+
description: "Close an MPP pay-as-you-go session. Returns the amount spent and refunded.",
|
|
2392
|
+
parameters: z.object({
|
|
2393
|
+
channel_id: z
|
|
2394
|
+
.string()
|
|
2395
|
+
.min(1)
|
|
2396
|
+
.describe("The channelId returned from mpp_session_open"),
|
|
2397
|
+
}),
|
|
2398
|
+
execute: async ({ channel_id }) => {
|
|
2399
|
+
try {
|
|
2400
|
+
const result = await apiRequest(client, "POST", "/api/mpp/session/close", {
|
|
2401
|
+
channelId: channel_id,
|
|
2402
|
+
}, agentSecret);
|
|
2403
|
+
return result;
|
|
2404
|
+
}
|
|
2405
|
+
catch (err) {
|
|
2406
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
2407
|
+
return {
|
|
2408
|
+
error: `Failed to close MPP session: ${sanitizeError(message)}`,
|
|
2409
|
+
};
|
|
921
2410
|
}
|
|
922
2411
|
},
|
|
923
2412
|
});
|
|
924
2413
|
}
|
|
925
2414
|
// Re-export validation helpers for testing
|
|
926
|
-
export { validateUrl, isPrivateIp, truncateBody, filterHeaders, apiRequest, UUID_RE, DOMAIN_RE };
|
|
2415
|
+
export { validateUrl, isPrivateIp, truncateBody, filterHeaders, apiRequest, UUID_RE, DOMAIN_RE, };
|
|
927
2416
|
//# sourceMappingURL=tools.js.map
|