@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/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 { valid: false, error: "URL exceeds maximum length of 2048 characters" };
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 { valid: false, error: `Unsupported protocol: ${url.protocol} — only http and https are allowed` };
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") || lowerHost.endsWith(".internal") || lowerHost.endsWith(".arpa")) {
107
- return { valid: false, error: "Requests to internal network hostnames are not allowed" };
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 { valid: false, error: "Requests to private/reserved IP addresses are not allowed" };
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 { valid: false, error: "URLs with embedded credentials are not allowed" };
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) + `\n...[truncated, ${body.length - MAX_BODY_LENGTH} chars omitted]`;
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.string().max(2048).url().describe("The URL to fetch through the proxy"),
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 { error: `Country '${country.toUpperCase()}' is blocked (OFAC sanctioned)` };
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 { error: err instanceof Error ? err.message : "DNS validation failed" };
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(["host", "connection", "content-length", "transfer-encoding", "proxy-authorization", "authorization", "user-agent"]);
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 { error: `Header "${k.replace(/[\r\n\0]/g, "")}" contains invalid characters` };
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).map(([k, v]) => `${k}: ${v}\r\n`).join("");
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(":") ? `[${targetUrl.hostname}]` : targetUrl.hostname;
406
+ const connectHost = targetUrl.hostname.includes(":")
407
+ ? `[${targetUrl.hostname}]`
408
+ : targetUrl.hostname;
316
409
  const connectReq = http.request({
317
- hostname: proxyHost, port: proxyPort, method: "CONNECT",
410
+ hostname: proxyHost,
411
+ port: proxyPort,
412
+ method: "CONNECT",
318
413
  path: `${connectHost}:${targetUrl.port || 443}`,
319
- headers: { "Proxy-Authorization": proxyAuth, Host: `${connectHost}:${targetUrl.port || 443}` },
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({ host: targetUrl.hostname, socket: sock, servername: targetUrl.hostname, minVersion: "TLSv1.2" }, () => {
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) => { bytes += c.length; if (bytes <= MAX_RESP + 16384)
334
- chunks.push(c); });
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.split("\r\n")[0].match(/^HTTP\/\d\.\d\s+(\d+)\s*(.*)/);
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.substring(ci + 1).trim();
462
+ hdrs[l.substring(0, ci).trim().toLowerCase()] = l
463
+ .substring(ci + 1)
464
+ .trim();
355
465
  }
356
466
  stripDangerousKeys(hdrs);
357
- resolve({ status: sm ? parseInt(sm[1], 10) : 0, statusText: sm ? sm[2] ?? "" : "", headers: hdrs, body });
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) => { clearTimeout(timer); reject(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) => { clearTimeout(timer); reject(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, port: proxyPort, method, path: targetUrl.toString(),
372
- headers: { "Proxy-Authorization": proxyAuth, Host: targetUrl.host ?? "", ...safeHeaders },
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) => { bytes += c.length; if (bytes <= MAX_RESP)
377
- chunks.push(c); });
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).toString("utf-8").substring(0, MAX_RESP);
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({ status: res.statusCode ?? 0, statusText: res.statusMessage ?? "", headers: hdrs, body });
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) => { clearTimeout(timer); reject(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 { error: `Failed to get proxy config: ${sanitizeError(message)}` };
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({ amountCents: amount_cents });
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 { error: `Failed to create PayPal top-up: ${sanitizeError(message)}` };
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({ amountCents: amount_cents });
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 { error: `Failed to create Stripe checkout: ${sanitizeError(message)}` };
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(["BTC", "ETH", "LTC", "XMR", "ZEC", "USDC", "SOL", "USDT", "DAI", "BNB", "LINK"])
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({ amountUsd: amount_usd, currency: currency.toLowerCase() });
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 { error: `Failed to create crypto invoice: ${sanitizeError(message)}` };
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
- "Authorization": `Bearer ${apiKey}`,
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.string().min(1).max(100).describe("Human-readable label for the wallet"),
737
- spending_limit_cents: z.number().int().min(1).max(2147483647).describe("Per-transaction spending limit in cents"),
738
- daily_limit_cents: z.number().int().min(1).max(1000000).optional().describe("Optional daily spending limit in cents"),
739
- allowed_domains: z.array(z.string().max(253).regex(DOMAIN_RE)).max(100).optional().describe("Optional list of allowed domains"),
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 = { label, spendingLimitCents: spending_limit_cents };
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 { error: `Failed to create agentic wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}` };
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.string().regex(UUID_RE, "Must be a valid UUID").describe("UUID of the agentic wallet"),
769
- amount_cents: z.number().int().min(1).max(2147483647).describe("Amount in cents to transfer"),
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 { error: `Failed to fund wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}` };
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.string().regex(UUID_RE, "Must be a valid UUID").describe("UUID of the agentic wallet"),
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 { error: `Failed to get wallet balance: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}` };
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 { error: `Failed to list wallets: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}` };
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.string().regex(UUID_RE, "Must be a valid UUID").describe("UUID of the agentic wallet"),
825
- limit: z.number().int().min(1).max(100).optional().describe("Maximum transactions to return (1-100)"),
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 { error: `Failed to get transactions: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}` };
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.string().regex(UUID_RE, "Must be a valid UUID").describe("UUID of the agentic wallet"),
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 { error: `Failed to freeze wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}` };
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.string().regex(UUID_RE, "Must be a valid UUID").describe("UUID of the agentic wallet"),
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 { error: `Failed to unfreeze wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}` };
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.string().regex(UUID_RE, "Must be a valid UUID").describe("UUID of the agentic wallet"),
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 { error: `Failed to delete wallet: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}` };
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.string().regex(UUID_RE, "Must be a valid UUID").describe("UUID of the agentic wallet"),
904
- daily_limit_cents: z.number().int().min(1).max(1000000).optional().describe("New daily spending limit in cents"),
905
- allowed_domains: z.array(z.string().max(253).regex(DOMAIN_RE)).max(100).optional().describe("New allowed domains list"),
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 { error: "At least one of daily_limit_cents or allowed_domains must be provided" };
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 { error: `Failed to update policy: ${sanitizeError(err instanceof Error ? err.message : "Unknown error")}` };
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