@dominusnode/openai-functions 1.1.0 → 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/handler.js CHANGED
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * Dominus Node OpenAI-compatible function calling handler (TypeScript).
3
3
  *
4
+ * 57 tools covering proxy, wallet, usage, account lifecycle, API keys,
5
+ * plans, and teams.
6
+ *
4
7
  * Provides a factory function that creates a handler for dispatching
5
8
  * OpenAI function calls to the Dominus Node REST API. Works with any
6
9
  * function-calling LLM system: OpenAI GPT, Anthropic Claude tool_use,
@@ -22,10 +25,63 @@
22
25
  *
23
26
  * @module
24
27
  */
28
+ import * as crypto from "node:crypto";
25
29
  import dns from "dns/promises";
26
30
  import * as http from "node:http";
27
31
  import * as tls from "node:tls";
28
32
  // ---------------------------------------------------------------------------
33
+ // SHA-256 Proof-of-Work solver
34
+ // ---------------------------------------------------------------------------
35
+ function countLeadingZeroBits(buf) {
36
+ let count = 0;
37
+ for (const byte of buf) {
38
+ if (byte === 0) {
39
+ count += 8;
40
+ continue;
41
+ }
42
+ let mask = 0x80;
43
+ while (mask && !(byte & mask)) {
44
+ count++;
45
+ mask >>= 1;
46
+ }
47
+ break;
48
+ }
49
+ return count;
50
+ }
51
+ async function solvePoW(baseUrl) {
52
+ try {
53
+ const resp = await fetch(`${baseUrl}/api/auth/pow/challenge`, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ redirect: "error",
57
+ });
58
+ if (!resp.ok)
59
+ return null;
60
+ const text = await resp.text();
61
+ if (text.length > 10_485_760)
62
+ return null;
63
+ const challenge = JSON.parse(text);
64
+ const prefix = challenge.prefix ?? "";
65
+ const difficulty = challenge.difficulty ?? 20;
66
+ const challengeId = challenge.challengeId ?? "";
67
+ if (!prefix || !challengeId)
68
+ return null;
69
+ for (let nonce = 0; nonce < 100_000_000; nonce++) {
70
+ const hash = crypto
71
+ .createHash("sha256")
72
+ .update(prefix + nonce.toString())
73
+ .digest();
74
+ if (countLeadingZeroBits(hash) >= difficulty) {
75
+ return { challengeId, nonce: nonce.toString() };
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ }
84
+ // ---------------------------------------------------------------------------
29
85
  // SSRF Prevention -- URL validation
30
86
  // ---------------------------------------------------------------------------
31
87
  const BLOCKED_HOSTNAMES = new Set([
@@ -281,7 +337,7 @@ async function checkDnsRebinding(hostname) {
281
337
  // ---------------------------------------------------------------------------
282
338
  // Credential sanitization
283
339
  // ---------------------------------------------------------------------------
284
- const CREDENTIAL_RE = /dn_(live|test)_[a-zA-Z0-9]+/g;
340
+ const CREDENTIAL_RE = /dn_(live|test|proxy)_[a-zA-Z0-9]+/g;
285
341
  function sanitizeError(message) {
286
342
  return message.replace(CREDENTIAL_RE, "***");
287
343
  }
@@ -501,8 +557,13 @@ export function createDominusNodeFunctionHandler(config) {
501
557
  try {
502
558
  // Strip security-sensitive headers from user-provided headers
503
559
  const BLOCKED_HEADERS = new Set([
504
- "host", "connection", "content-length", "transfer-encoding",
505
- "proxy-authorization", "authorization", "user-agent",
560
+ "host",
561
+ "connection",
562
+ "content-length",
563
+ "transfer-encoding",
564
+ "proxy-authorization",
565
+ "authorization",
566
+ "user-agent",
506
567
  ]);
507
568
  const safeHeaders = {};
508
569
  if (headers) {
@@ -533,13 +594,18 @@ export function createDominusNodeFunctionHandler(config) {
533
594
  const timeout = setTimeout(() => reject(new Error("Proxy request timed out after 30000ms")), 30_000);
534
595
  if (parsed.protocol === "https:") {
535
596
  // HTTPS: CONNECT tunnel + TLS
536
- const connectHost = parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname;
597
+ const connectHost = parsed.hostname.includes(":")
598
+ ? `[${parsed.hostname}]`
599
+ : parsed.hostname;
537
600
  const connectReq = http.request({
538
601
  hostname: proxyHost,
539
602
  port: proxyPort,
540
603
  method: "CONNECT",
541
604
  path: `${connectHost}:${parsed.port || 443}`,
542
- headers: { "Proxy-Authorization": proxyAuth, Host: `${connectHost}:${parsed.port || 443}` },
605
+ headers: {
606
+ "Proxy-Authorization": proxyAuth,
607
+ Host: `${connectHost}:${parsed.port || 443}`,
608
+ },
543
609
  });
544
610
  connectReq.on("connect", (_res, tunnelSocket) => {
545
611
  if (_res.statusCode !== 200) {
@@ -583,7 +649,9 @@ export function createDominusNodeFunctionHandler(config) {
583
649
  return;
584
650
  }
585
651
  const headerSection = raw.substring(0, headerEnd);
586
- const body = raw.substring(headerEnd + 4).substring(0, MAX_BODY_BYTES);
652
+ const body = raw
653
+ .substring(headerEnd + 4)
654
+ .substring(0, MAX_BODY_BYTES);
587
655
  const statusLine = headerSection.split("\r\n")[0];
588
656
  const statusMatch = statusLine.match(/^HTTP\/\d\.\d\s+(\d+)/);
589
657
  const status = statusMatch ? parseInt(statusMatch[1], 10) : 0;
@@ -591,18 +659,29 @@ export function createDominusNodeFunctionHandler(config) {
591
659
  for (const line of headerSection.split("\r\n").slice(1)) {
592
660
  const ci = line.indexOf(":");
593
661
  if (ci > 0)
594
- headers[line.substring(0, ci).trim().toLowerCase()] = line.substring(ci + 1).trim();
662
+ headers[line.substring(0, ci).trim().toLowerCase()] = line
663
+ .substring(ci + 1)
664
+ .trim();
595
665
  }
596
666
  stripDangerousKeys(headers);
597
667
  resolve({ status, headers, body });
598
668
  };
599
669
  tlsSocket.on("end", finalize);
600
670
  tlsSocket.on("close", finalize);
601
- tlsSocket.on("error", (err) => { clearTimeout(timeout); reject(err); });
671
+ tlsSocket.on("error", (err) => {
672
+ clearTimeout(timeout);
673
+ reject(err);
674
+ });
602
675
  });
603
- tlsSocket.on("error", (err) => { clearTimeout(timeout); reject(err); });
676
+ tlsSocket.on("error", (err) => {
677
+ clearTimeout(timeout);
678
+ reject(err);
679
+ });
680
+ });
681
+ connectReq.on("error", (err) => {
682
+ clearTimeout(timeout);
683
+ reject(err);
604
684
  });
605
- connectReq.on("error", (err) => { clearTimeout(timeout); reject(err); });
606
685
  connectReq.end();
607
686
  }
608
687
  else {
@@ -612,19 +691,28 @@ export function createDominusNodeFunctionHandler(config) {
612
691
  port: proxyPort,
613
692
  method,
614
693
  path: url,
615
- headers: { ...safeHeaders, "Proxy-Authorization": proxyAuth, Host: parsed.host },
694
+ headers: {
695
+ ...safeHeaders,
696
+ "Proxy-Authorization": proxyAuth,
697
+ Host: parsed.host,
698
+ },
616
699
  }, (res) => {
617
700
  const chunks = [];
618
701
  let byteCount = 0;
619
- res.on("data", (chunk) => { byteCount += chunk.length; if (byteCount <= MAX_BODY_BYTES)
620
- chunks.push(chunk); });
702
+ res.on("data", (chunk) => {
703
+ byteCount += chunk.length;
704
+ if (byteCount <= MAX_BODY_BYTES)
705
+ chunks.push(chunk);
706
+ });
621
707
  let httpFinalized = false;
622
708
  const finalize = () => {
623
709
  if (httpFinalized)
624
710
  return;
625
711
  httpFinalized = true;
626
712
  clearTimeout(timeout);
627
- const body = Buffer.concat(chunks).toString("utf-8").substring(0, MAX_BODY_BYTES);
713
+ const body = Buffer.concat(chunks)
714
+ .toString("utf-8")
715
+ .substring(0, MAX_BODY_BYTES);
628
716
  const headers = {};
629
717
  for (const [k, v] of Object.entries(res.headers)) {
630
718
  if (v)
@@ -635,9 +723,15 @@ export function createDominusNodeFunctionHandler(config) {
635
723
  };
636
724
  res.on("end", finalize);
637
725
  res.on("close", finalize);
638
- res.on("error", (err) => { clearTimeout(timeout); reject(err); });
726
+ res.on("error", (err) => {
727
+ clearTimeout(timeout);
728
+ reject(err);
729
+ });
730
+ });
731
+ req.on("error", (err) => {
732
+ clearTimeout(timeout);
733
+ reject(err);
639
734
  });
640
- req.on("error", (err) => { clearTimeout(timeout); reject(err); });
641
735
  req.end();
642
736
  }
643
737
  });
@@ -677,13 +771,17 @@ export function createDominusNodeFunctionHandler(config) {
677
771
  const label = args.label;
678
772
  const spendingLimitCents = args.spending_limit_cents;
679
773
  if (!label || typeof label !== "string") {
680
- return JSON.stringify({ error: "label is required and must be a string" });
774
+ return JSON.stringify({
775
+ error: "label is required and must be a string",
776
+ });
681
777
  }
682
778
  if (label.length > 100) {
683
779
  return JSON.stringify({ error: "label must be 100 characters or fewer" });
684
780
  }
685
781
  if (/[\x00-\x1f\x7f]/.test(label)) {
686
- return JSON.stringify({ error: "label contains invalid control characters" });
782
+ return JSON.stringify({
783
+ error: "label contains invalid control characters",
784
+ });
687
785
  }
688
786
  if (!Number.isInteger(spendingLimitCents) ||
689
787
  spendingLimitCents <= 0 ||
@@ -699,7 +797,9 @@ export function createDominusNodeFunctionHandler(config) {
699
797
  // Optional daily_limit_cents
700
798
  if (args.daily_limit_cents !== undefined) {
701
799
  const dailyLimit = args.daily_limit_cents;
702
- if (!Number.isInteger(dailyLimit) || dailyLimit < 1 || dailyLimit > 1_000_000) {
800
+ if (!Number.isInteger(dailyLimit) ||
801
+ dailyLimit < 1 ||
802
+ dailyLimit > 1_000_000) {
703
803
  return JSON.stringify({
704
804
  error: "daily_limit_cents must be an integer between 1 and 1,000,000",
705
805
  });
@@ -710,18 +810,26 @@ export function createDominusNodeFunctionHandler(config) {
710
810
  if (args.allowed_domains !== undefined) {
711
811
  const domains = args.allowed_domains;
712
812
  if (!Array.isArray(domains)) {
713
- return JSON.stringify({ error: "allowed_domains must be an array of strings" });
813
+ return JSON.stringify({
814
+ error: "allowed_domains must be an array of strings",
815
+ });
714
816
  }
715
817
  if (domains.length > 100) {
716
- return JSON.stringify({ error: "allowed_domains must have 100 or fewer entries" });
818
+ return JSON.stringify({
819
+ error: "allowed_domains must have 100 or fewer entries",
820
+ });
717
821
  }
718
822
  const domainRe = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/;
719
823
  for (const d of domains) {
720
824
  if (typeof d !== "string") {
721
- return JSON.stringify({ error: "Each allowed_domains entry must be a string" });
825
+ return JSON.stringify({
826
+ error: "Each allowed_domains entry must be a string",
827
+ });
722
828
  }
723
829
  if (d.length > 253) {
724
- return JSON.stringify({ error: "Each allowed_domains entry must be 253 characters or fewer" });
830
+ return JSON.stringify({
831
+ error: "Each allowed_domains entry must be 253 characters or fewer",
832
+ });
725
833
  }
726
834
  if (!domainRe.test(d)) {
727
835
  return JSON.stringify({ error: `Invalid domain format: ${d}` });
@@ -736,7 +844,9 @@ export function createDominusNodeFunctionHandler(config) {
736
844
  const walletId = args.wallet_id;
737
845
  const amountCents = args.amount_cents;
738
846
  if (!walletId || typeof walletId !== "string") {
739
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
847
+ return JSON.stringify({
848
+ error: "wallet_id is required and must be a string",
849
+ });
740
850
  }
741
851
  if (!Number.isInteger(amountCents) ||
742
852
  amountCents <= 0 ||
@@ -751,7 +861,9 @@ export function createDominusNodeFunctionHandler(config) {
751
861
  async function handleAgenticWalletBalance(args) {
752
862
  const walletId = args.wallet_id;
753
863
  if (!walletId || typeof walletId !== "string") {
754
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
864
+ return JSON.stringify({
865
+ error: "wallet_id is required and must be a string",
866
+ });
755
867
  }
756
868
  const result = await api("GET", `/api/agent-wallet/${encodeURIComponent(walletId)}`);
757
869
  return JSON.stringify(result);
@@ -763,13 +875,17 @@ export function createDominusNodeFunctionHandler(config) {
763
875
  async function handleAgenticTransactions(args) {
764
876
  const walletId = args.wallet_id;
765
877
  if (!walletId || typeof walletId !== "string") {
766
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
878
+ return JSON.stringify({
879
+ error: "wallet_id is required and must be a string",
880
+ });
767
881
  }
768
882
  const limit = args.limit;
769
883
  const params = new URLSearchParams();
770
884
  if (limit !== undefined) {
771
885
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
772
- return JSON.stringify({ error: "limit must be an integer between 1 and 100" });
886
+ return JSON.stringify({
887
+ error: "limit must be an integer between 1 and 100",
888
+ });
773
889
  }
774
890
  params.set("limit", String(limit));
775
891
  }
@@ -780,7 +896,9 @@ export function createDominusNodeFunctionHandler(config) {
780
896
  async function handleFreezeAgenticWallet(args) {
781
897
  const walletId = args.wallet_id;
782
898
  if (!walletId || typeof walletId !== "string") {
783
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
899
+ return JSON.stringify({
900
+ error: "wallet_id is required and must be a string",
901
+ });
784
902
  }
785
903
  const result = await api("POST", `/api/agent-wallet/${encodeURIComponent(walletId)}/freeze`);
786
904
  return JSON.stringify(result);
@@ -788,7 +906,9 @@ export function createDominusNodeFunctionHandler(config) {
788
906
  async function handleUnfreezeAgenticWallet(args) {
789
907
  const walletId = args.wallet_id;
790
908
  if (!walletId || typeof walletId !== "string") {
791
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
909
+ return JSON.stringify({
910
+ error: "wallet_id is required and must be a string",
911
+ });
792
912
  }
793
913
  const result = await api("POST", `/api/agent-wallet/${encodeURIComponent(walletId)}/unfreeze`);
794
914
  return JSON.stringify(result);
@@ -796,7 +916,9 @@ export function createDominusNodeFunctionHandler(config) {
796
916
  async function handleDeleteAgenticWallet(args) {
797
917
  const walletId = args.wallet_id;
798
918
  if (!walletId || typeof walletId !== "string") {
799
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
919
+ return JSON.stringify({
920
+ error: "wallet_id is required and must be a string",
921
+ });
800
922
  }
801
923
  const result = await api("DELETE", `/api/agent-wallet/${encodeURIComponent(walletId)}`);
802
924
  return JSON.stringify(result);
@@ -810,13 +932,17 @@ export function createDominusNodeFunctionHandler(config) {
810
932
  return JSON.stringify({ error: "name must be 100 characters or fewer" });
811
933
  }
812
934
  if (/[\x00-\x1f\x7f]/.test(name)) {
813
- return JSON.stringify({ error: "name contains invalid control characters" });
935
+ return JSON.stringify({
936
+ error: "name contains invalid control characters",
937
+ });
814
938
  }
815
939
  const body = { name };
816
940
  if (args.max_members !== undefined) {
817
941
  const maxMembers = Number(args.max_members);
818
942
  if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
819
- return JSON.stringify({ error: "max_members must be an integer between 1 and 100" });
943
+ return JSON.stringify({
944
+ error: "max_members must be an integer between 1 and 100",
945
+ });
820
946
  }
821
947
  body.maxMembers = maxMembers;
822
948
  }
@@ -830,7 +956,9 @@ export function createDominusNodeFunctionHandler(config) {
830
956
  async function handleTeamDetails(args) {
831
957
  const teamId = args.team_id;
832
958
  if (!teamId || typeof teamId !== "string") {
833
- return JSON.stringify({ error: "team_id is required and must be a string" });
959
+ return JSON.stringify({
960
+ error: "team_id is required and must be a string",
961
+ });
834
962
  }
835
963
  const result = await api("GET", `/api/teams/${encodeURIComponent(teamId)}`);
836
964
  return JSON.stringify(result);
@@ -839,7 +967,9 @@ export function createDominusNodeFunctionHandler(config) {
839
967
  const teamId = args.team_id;
840
968
  const amountCents = args.amount_cents;
841
969
  if (!teamId || typeof teamId !== "string") {
842
- return JSON.stringify({ error: "team_id is required and must be a string" });
970
+ return JSON.stringify({
971
+ error: "team_id is required and must be a string",
972
+ });
843
973
  }
844
974
  if (!Number.isInteger(amountCents) ||
845
975
  amountCents < 100 ||
@@ -855,16 +985,22 @@ export function createDominusNodeFunctionHandler(config) {
855
985
  const teamId = args.team_id;
856
986
  const label = args.label;
857
987
  if (!teamId || typeof teamId !== "string") {
858
- return JSON.stringify({ error: "team_id is required and must be a string" });
988
+ return JSON.stringify({
989
+ error: "team_id is required and must be a string",
990
+ });
859
991
  }
860
992
  if (!label || typeof label !== "string") {
861
- return JSON.stringify({ error: "label is required and must be a string" });
993
+ return JSON.stringify({
994
+ error: "label is required and must be a string",
995
+ });
862
996
  }
863
997
  if (label.length > 100) {
864
998
  return JSON.stringify({ error: "label must be 100 characters or fewer" });
865
999
  }
866
1000
  if (/[\x00-\x1f\x7f]/.test(label)) {
867
- return JSON.stringify({ error: "label contains invalid control characters" });
1001
+ return JSON.stringify({
1002
+ error: "label contains invalid control characters",
1003
+ });
868
1004
  }
869
1005
  const result = await api("POST", `/api/teams/${encodeURIComponent(teamId)}/keys`, { label });
870
1006
  return JSON.stringify(result);
@@ -872,13 +1008,17 @@ export function createDominusNodeFunctionHandler(config) {
872
1008
  async function handleTeamUsage(args) {
873
1009
  const teamId = args.team_id;
874
1010
  if (!teamId || typeof teamId !== "string") {
875
- return JSON.stringify({ error: "team_id is required and must be a string" });
1011
+ return JSON.stringify({
1012
+ error: "team_id is required and must be a string",
1013
+ });
876
1014
  }
877
1015
  const limit = args.limit;
878
1016
  const params = new URLSearchParams();
879
1017
  if (limit !== undefined) {
880
1018
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
881
- return JSON.stringify({ error: "limit must be an integer between 1 and 100" });
1019
+ return JSON.stringify({
1020
+ error: "limit must be an integer between 1 and 100",
1021
+ });
882
1022
  }
883
1023
  params.set("limit", String(limit));
884
1024
  }
@@ -889,7 +1029,9 @@ export function createDominusNodeFunctionHandler(config) {
889
1029
  async function handleUpdateTeam(args) {
890
1030
  const teamId = args.team_id;
891
1031
  if (!teamId || typeof teamId !== "string") {
892
- return JSON.stringify({ error: "team_id is required and must be a string" });
1032
+ return JSON.stringify({
1033
+ error: "team_id is required and must be a string",
1034
+ });
893
1035
  }
894
1036
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
895
1037
  return JSON.stringify({ error: "team_id must be a valid UUID" });
@@ -901,22 +1043,30 @@ export function createDominusNodeFunctionHandler(config) {
901
1043
  return JSON.stringify({ error: "name must be a non-empty string" });
902
1044
  }
903
1045
  if (name.length > 100) {
904
- return JSON.stringify({ error: "name must be 100 characters or fewer" });
1046
+ return JSON.stringify({
1047
+ error: "name must be 100 characters or fewer",
1048
+ });
905
1049
  }
906
1050
  if (/[\x00-\x1f\x7f]/.test(name)) {
907
- return JSON.stringify({ error: "name contains invalid control characters" });
1051
+ return JSON.stringify({
1052
+ error: "name contains invalid control characters",
1053
+ });
908
1054
  }
909
1055
  body.name = name;
910
1056
  }
911
1057
  if (args.max_members !== undefined) {
912
1058
  const maxMembers = Number(args.max_members);
913
1059
  if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
914
- return JSON.stringify({ error: "max_members must be an integer between 1 and 100" });
1060
+ return JSON.stringify({
1061
+ error: "max_members must be an integer between 1 and 100",
1062
+ });
915
1063
  }
916
1064
  body.maxMembers = maxMembers;
917
1065
  }
918
1066
  if (Object.keys(body).length === 0) {
919
- return JSON.stringify({ error: "At least one of name or max_members must be provided" });
1067
+ return JSON.stringify({
1068
+ error: "At least one of name or max_members must be provided",
1069
+ });
920
1070
  }
921
1071
  const result = await api("PATCH", `/api/teams/${encodeURIComponent(teamId)}`, body);
922
1072
  return JSON.stringify(result);
@@ -930,7 +1080,9 @@ export function createDominusNodeFunctionHandler(config) {
930
1080
  error: "amount_cents must be an integer between 500 ($5) and 100,000 ($1,000)",
931
1081
  });
932
1082
  }
933
- const result = await api("POST", "/api/wallet/topup/paypal", { amountCents });
1083
+ const result = await api("POST", "/api/wallet/topup/paypal", {
1084
+ amountCents,
1085
+ });
934
1086
  return JSON.stringify(result);
935
1087
  }
936
1088
  async function handleTopupStripe(args) {
@@ -942,21 +1094,38 @@ export function createDominusNodeFunctionHandler(config) {
942
1094
  error: "amount_cents must be an integer between 500 ($5) and 100,000 ($1,000)",
943
1095
  });
944
1096
  }
945
- const result = await api("POST", "/api/wallet/topup/stripe", { amountCents });
1097
+ const result = await api("POST", "/api/wallet/topup/stripe", {
1098
+ amountCents,
1099
+ });
946
1100
  return JSON.stringify(result);
947
1101
  }
948
1102
  async function handleTopupCrypto(args) {
949
1103
  const amountUsd = args.amount_usd;
950
1104
  const currency = args.currency;
951
- if (typeof amountUsd !== "number" || !Number.isFinite(amountUsd) || amountUsd < 5 || amountUsd > 1000) {
1105
+ if (typeof amountUsd !== "number" ||
1106
+ !Number.isFinite(amountUsd) ||
1107
+ amountUsd < 5 ||
1108
+ amountUsd > 1000) {
952
1109
  return JSON.stringify({
953
1110
  error: "amount_usd must be a number between 5 and 1,000",
954
1111
  });
955
1112
  }
956
1113
  const validCurrencies = new Set([
957
- "BTC", "ETH", "LTC", "XMR", "ZEC", "USDC", "SOL", "USDT", "DAI", "BNB", "LINK",
1114
+ "BTC",
1115
+ "ETH",
1116
+ "LTC",
1117
+ "XMR",
1118
+ "ZEC",
1119
+ "USDC",
1120
+ "SOL",
1121
+ "USDT",
1122
+ "DAI",
1123
+ "BNB",
1124
+ "LINK",
958
1125
  ]);
959
- if (!currency || typeof currency !== "string" || !validCurrencies.has(currency.toUpperCase())) {
1126
+ if (!currency ||
1127
+ typeof currency !== "string" ||
1128
+ !validCurrencies.has(currency.toUpperCase())) {
960
1129
  return JSON.stringify({
961
1130
  error: "currency must be one of: BTC, ETH, LTC, XMR, ZEC, USDC, SOL, USDT, DAI, BNB, LINK",
962
1131
  });
@@ -970,7 +1139,9 @@ export function createDominusNodeFunctionHandler(config) {
970
1139
  async function handleUpdateWalletPolicy(args) {
971
1140
  const walletId = args.wallet_id;
972
1141
  if (!walletId || typeof walletId !== "string") {
973
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
1142
+ return JSON.stringify({
1143
+ error: "wallet_id is required and must be a string",
1144
+ });
974
1145
  }
975
1146
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(walletId)) {
976
1147
  return JSON.stringify({ error: "wallet_id must be a valid UUID" });
@@ -983,7 +1154,9 @@ export function createDominusNodeFunctionHandler(config) {
983
1154
  }
984
1155
  else {
985
1156
  const dailyLimit = args.daily_limit_cents;
986
- if (!Number.isInteger(dailyLimit) || dailyLimit < 1 || dailyLimit > 1_000_000) {
1157
+ if (!Number.isInteger(dailyLimit) ||
1158
+ dailyLimit < 1 ||
1159
+ dailyLimit > 1_000_000) {
987
1160
  return JSON.stringify({
988
1161
  error: "daily_limit_cents must be an integer between 1 and 1,000,000 (or null to remove)",
989
1162
  });
@@ -999,18 +1172,26 @@ export function createDominusNodeFunctionHandler(config) {
999
1172
  else {
1000
1173
  const domains = args.allowed_domains;
1001
1174
  if (!Array.isArray(domains)) {
1002
- return JSON.stringify({ error: "allowed_domains must be an array of strings or null" });
1175
+ return JSON.stringify({
1176
+ error: "allowed_domains must be an array of strings or null",
1177
+ });
1003
1178
  }
1004
1179
  if (domains.length > 100) {
1005
- return JSON.stringify({ error: "allowed_domains must have 100 or fewer entries" });
1180
+ return JSON.stringify({
1181
+ error: "allowed_domains must have 100 or fewer entries",
1182
+ });
1006
1183
  }
1007
1184
  const domainRe = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/;
1008
1185
  for (const d of domains) {
1009
1186
  if (typeof d !== "string") {
1010
- return JSON.stringify({ error: "Each allowed_domains entry must be a string" });
1187
+ return JSON.stringify({
1188
+ error: "Each allowed_domains entry must be a string",
1189
+ });
1011
1190
  }
1012
1191
  if (d.length > 253) {
1013
- return JSON.stringify({ error: "Each allowed_domains entry must be 253 characters or fewer" });
1192
+ return JSON.stringify({
1193
+ error: "Each allowed_domains entry must be 253 characters or fewer",
1194
+ });
1014
1195
  }
1015
1196
  if (!domainRe.test(d)) {
1016
1197
  return JSON.stringify({ error: `Invalid domain format: ${d}` });
@@ -1020,7 +1201,9 @@ export function createDominusNodeFunctionHandler(config) {
1020
1201
  }
1021
1202
  }
1022
1203
  if (Object.keys(body).length === 0) {
1023
- return JSON.stringify({ error: "At least one of daily_limit_cents or allowed_domains must be provided" });
1204
+ return JSON.stringify({
1205
+ error: "At least one of daily_limit_cents or allowed_domains must be provided",
1206
+ });
1024
1207
  }
1025
1208
  const result = await api("PATCH", `/api/agent-wallet/${encodeURIComponent(walletId)}/policy`, body);
1026
1209
  return JSON.stringify(result);
@@ -1030,13 +1213,17 @@ export function createDominusNodeFunctionHandler(config) {
1030
1213
  const userId = args.user_id;
1031
1214
  const role = args.role;
1032
1215
  if (!teamId || typeof teamId !== "string") {
1033
- return JSON.stringify({ error: "team_id is required and must be a string" });
1216
+ return JSON.stringify({
1217
+ error: "team_id is required and must be a string",
1218
+ });
1034
1219
  }
1035
1220
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1036
1221
  return JSON.stringify({ error: "team_id must be a valid UUID" });
1037
1222
  }
1038
1223
  if (!userId || typeof userId !== "string") {
1039
- return JSON.stringify({ error: "user_id is required and must be a string" });
1224
+ return JSON.stringify({
1225
+ error: "user_id is required and must be a string",
1226
+ });
1040
1227
  }
1041
1228
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(userId)) {
1042
1229
  return JSON.stringify({ error: "user_id must be a valid UUID" });
@@ -1051,14 +1238,552 @@ export function createDominusNodeFunctionHandler(config) {
1051
1238
  return JSON.stringify(result);
1052
1239
  }
1053
1240
  // -----------------------------------------------------------------------
1241
+ // Proxy extended
1242
+ // -----------------------------------------------------------------------
1243
+ async function handleGetProxyStatus() {
1244
+ const result = await api("GET", "/api/proxy/status");
1245
+ return JSON.stringify(result);
1246
+ }
1247
+ // -----------------------------------------------------------------------
1248
+ // Wallet extended
1249
+ // -----------------------------------------------------------------------
1250
+ async function handleGetTransactions(args) {
1251
+ const params = new URLSearchParams();
1252
+ if (args.limit !== undefined) {
1253
+ const limit = args.limit;
1254
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
1255
+ return JSON.stringify({
1256
+ error: "limit must be an integer between 1 and 100",
1257
+ });
1258
+ }
1259
+ params.set("limit", String(limit));
1260
+ }
1261
+ const qs = params.toString();
1262
+ const result = await api("GET", `/api/wallet/transactions${qs ? `?${qs}` : ""}`);
1263
+ return JSON.stringify(result);
1264
+ }
1265
+ async function handleGetForecast() {
1266
+ const result = await api("GET", "/api/wallet/forecast");
1267
+ return JSON.stringify(result);
1268
+ }
1269
+ async function handleCheckPayment(args) {
1270
+ const invoiceId = args.invoice_id;
1271
+ if (!invoiceId || typeof invoiceId !== "string") {
1272
+ return JSON.stringify({
1273
+ error: "invoice_id is required and must be a string",
1274
+ });
1275
+ }
1276
+ const result = await api("GET", `/api/wallet/topup/crypto/${encodeURIComponent(invoiceId)}/status`);
1277
+ return JSON.stringify(result);
1278
+ }
1279
+ // -----------------------------------------------------------------------
1280
+ // Usage extended
1281
+ // -----------------------------------------------------------------------
1282
+ async function handleGetDailyUsage(args) {
1283
+ const days = args.days ?? 30;
1284
+ if (!Number.isInteger(days) || days < 1 || days > 90) {
1285
+ return JSON.stringify({
1286
+ error: "days must be an integer between 1 and 90",
1287
+ });
1288
+ }
1289
+ const params = new URLSearchParams({ days: String(days) });
1290
+ const result = await api("GET", `/api/usage/daily?${params.toString()}`);
1291
+ return JSON.stringify(result);
1292
+ }
1293
+ async function handleGetTopHosts(args) {
1294
+ const params = new URLSearchParams();
1295
+ if (args.limit !== undefined) {
1296
+ const limit = args.limit;
1297
+ if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
1298
+ return JSON.stringify({
1299
+ error: "limit must be an integer between 1 and 50",
1300
+ });
1301
+ }
1302
+ params.set("limit", String(limit));
1303
+ }
1304
+ const qs = params.toString();
1305
+ const result = await api("GET", `/api/usage/top-hosts${qs ? `?${qs}` : ""}`);
1306
+ return JSON.stringify(result);
1307
+ }
1308
+ // -----------------------------------------------------------------------
1309
+ // Account lifecycle
1310
+ // -----------------------------------------------------------------------
1311
+ async function handleRegister(args) {
1312
+ const email = args.email;
1313
+ const password = args.password;
1314
+ if (!email || typeof email !== "string") {
1315
+ return JSON.stringify({
1316
+ error: "email is required and must be a string",
1317
+ });
1318
+ }
1319
+ if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1320
+ return JSON.stringify({ error: "email must be a valid email address" });
1321
+ }
1322
+ if (!password || typeof password !== "string") {
1323
+ return JSON.stringify({
1324
+ error: "password is required and must be a string",
1325
+ });
1326
+ }
1327
+ if (password.length < 8 || password.length > 128) {
1328
+ return JSON.stringify({
1329
+ error: "password must be between 8 and 128 characters",
1330
+ });
1331
+ }
1332
+ const headers = {
1333
+ "User-Agent": "dominusnode-openai-functions/1.0.0",
1334
+ "Content-Type": "application/json",
1335
+ };
1336
+ if (agentSecret) {
1337
+ headers["X-DominusNode-Agent"] = "mcp";
1338
+ headers["X-DominusNode-Agent-Secret"] = agentSecret;
1339
+ }
1340
+ // Solve PoW for CAPTCHA-free registration
1341
+ const pow = await solvePoW(baseUrl);
1342
+ const regBody = { email, password };
1343
+ if (pow)
1344
+ regBody.pow = pow;
1345
+ const response = await fetch(`${baseUrl}/api/auth/register`, {
1346
+ method: "POST",
1347
+ headers,
1348
+ body: JSON.stringify(regBody),
1349
+ signal: AbortSignal.timeout(timeoutMs),
1350
+ redirect: "error",
1351
+ });
1352
+ const text = await response.text();
1353
+ if (text.length > MAX_RESPONSE_BYTES) {
1354
+ return JSON.stringify({ error: "Response body exceeds size limit" });
1355
+ }
1356
+ if (!response.ok) {
1357
+ let message;
1358
+ try {
1359
+ const parsed = JSON.parse(text);
1360
+ message = parsed.error ?? parsed.message ?? text;
1361
+ }
1362
+ catch {
1363
+ message = text;
1364
+ }
1365
+ if (message.length > 500)
1366
+ message = message.slice(0, 500) + "... [truncated]";
1367
+ return JSON.stringify({
1368
+ error: `Registration failed: ${sanitizeError(message)}`,
1369
+ });
1370
+ }
1371
+ const data = safeJsonParse(text);
1372
+ stripDangerousKeys(data);
1373
+ return JSON.stringify({
1374
+ userId: data.userId,
1375
+ email: data.email,
1376
+ message: data.message,
1377
+ });
1378
+ }
1379
+ async function handleLogin(args) {
1380
+ const email = args.email;
1381
+ const password = args.password;
1382
+ if (!email || typeof email !== "string") {
1383
+ return JSON.stringify({
1384
+ error: "email is required and must be a string",
1385
+ });
1386
+ }
1387
+ if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1388
+ return JSON.stringify({ error: "email must be a valid email address" });
1389
+ }
1390
+ if (!password || typeof password !== "string") {
1391
+ return JSON.stringify({
1392
+ error: "password is required and must be a string",
1393
+ });
1394
+ }
1395
+ if (password.length < 8 || password.length > 128) {
1396
+ return JSON.stringify({
1397
+ error: "password must be between 8 and 128 characters",
1398
+ });
1399
+ }
1400
+ const headers = {
1401
+ "User-Agent": "dominusnode-openai-functions/1.0.0",
1402
+ "Content-Type": "application/json",
1403
+ };
1404
+ if (agentSecret) {
1405
+ headers["X-DominusNode-Agent"] = "mcp";
1406
+ headers["X-DominusNode-Agent-Secret"] = agentSecret;
1407
+ }
1408
+ const response = await fetch(`${baseUrl}/api/auth/login`, {
1409
+ method: "POST",
1410
+ headers,
1411
+ body: JSON.stringify({ email, password }),
1412
+ signal: AbortSignal.timeout(timeoutMs),
1413
+ redirect: "error",
1414
+ });
1415
+ const text = await response.text();
1416
+ if (text.length > MAX_RESPONSE_BYTES) {
1417
+ return JSON.stringify({ error: "Response body exceeds size limit" });
1418
+ }
1419
+ if (!response.ok) {
1420
+ let message;
1421
+ try {
1422
+ const parsed = JSON.parse(text);
1423
+ message = parsed.error ?? parsed.message ?? text;
1424
+ }
1425
+ catch {
1426
+ message = text;
1427
+ }
1428
+ if (message.length > 500)
1429
+ message = message.slice(0, 500) + "... [truncated]";
1430
+ return JSON.stringify({
1431
+ error: `Login failed: ${sanitizeError(message)}`,
1432
+ });
1433
+ }
1434
+ const data = safeJsonParse(text);
1435
+ stripDangerousKeys(data);
1436
+ return JSON.stringify({ token: data.token, message: data.message });
1437
+ }
1438
+ async function handleGetAccountInfo() {
1439
+ const result = await api("GET", "/api/auth/me");
1440
+ return JSON.stringify(result);
1441
+ }
1442
+ async function handleVerifyEmail(args) {
1443
+ const token = args.token;
1444
+ if (!token || typeof token !== "string") {
1445
+ return JSON.stringify({
1446
+ error: "token is required and must be a string",
1447
+ });
1448
+ }
1449
+ const headers = {
1450
+ "User-Agent": "dominusnode-openai-functions/1.0.0",
1451
+ "Content-Type": "application/json",
1452
+ };
1453
+ if (agentSecret) {
1454
+ headers["X-DominusNode-Agent"] = "mcp";
1455
+ headers["X-DominusNode-Agent-Secret"] = agentSecret;
1456
+ }
1457
+ const response = await fetch(`${baseUrl}/api/auth/verify-email`, {
1458
+ method: "POST",
1459
+ headers,
1460
+ body: JSON.stringify({ token }),
1461
+ signal: AbortSignal.timeout(timeoutMs),
1462
+ redirect: "error",
1463
+ });
1464
+ const text = await response.text();
1465
+ if (text.length > MAX_RESPONSE_BYTES) {
1466
+ return JSON.stringify({ error: "Response body exceeds size limit" });
1467
+ }
1468
+ if (!response.ok) {
1469
+ let message;
1470
+ try {
1471
+ const parsed = JSON.parse(text);
1472
+ message = parsed.error ?? parsed.message ?? text;
1473
+ }
1474
+ catch {
1475
+ message = text;
1476
+ }
1477
+ if (message.length > 500)
1478
+ message = message.slice(0, 500) + "... [truncated]";
1479
+ return JSON.stringify({
1480
+ error: `Email verification failed: ${sanitizeError(message)}`,
1481
+ });
1482
+ }
1483
+ const data = safeJsonParse(text);
1484
+ stripDangerousKeys(data);
1485
+ return JSON.stringify(data);
1486
+ }
1487
+ async function handleResendVerification() {
1488
+ const result = await api("POST", "/api/auth/resend-verification");
1489
+ return JSON.stringify(result);
1490
+ }
1491
+ async function handleUpdatePassword(args) {
1492
+ const currentPassword = args.current_password;
1493
+ const newPassword = args.new_password;
1494
+ if (!currentPassword || typeof currentPassword !== "string") {
1495
+ return JSON.stringify({
1496
+ error: "current_password is required and must be a string",
1497
+ });
1498
+ }
1499
+ if (!newPassword || typeof newPassword !== "string") {
1500
+ return JSON.stringify({
1501
+ error: "new_password is required and must be a string",
1502
+ });
1503
+ }
1504
+ if (newPassword.length < 8 || newPassword.length > 128) {
1505
+ return JSON.stringify({
1506
+ error: "new_password must be between 8 and 128 characters",
1507
+ });
1508
+ }
1509
+ const result = await api("POST", "/api/auth/change-password", {
1510
+ currentPassword,
1511
+ newPassword,
1512
+ });
1513
+ return JSON.stringify(result);
1514
+ }
1515
+ // -----------------------------------------------------------------------
1516
+ // API Keys
1517
+ // -----------------------------------------------------------------------
1518
+ async function handleListKeys() {
1519
+ const result = await api("GET", "/api/keys");
1520
+ return JSON.stringify(result);
1521
+ }
1522
+ async function handleCreateKey(args) {
1523
+ const label = args.label;
1524
+ if (!label || typeof label !== "string") {
1525
+ return JSON.stringify({
1526
+ error: "label is required and must be a string",
1527
+ });
1528
+ }
1529
+ if (label.length > 100) {
1530
+ return JSON.stringify({ error: "label must be 100 characters or fewer" });
1531
+ }
1532
+ if (/[\x00-\x1f\x7f]/.test(label)) {
1533
+ return JSON.stringify({
1534
+ error: "label contains invalid control characters",
1535
+ });
1536
+ }
1537
+ const result = await api("POST", "/api/keys", { label });
1538
+ return JSON.stringify(result);
1539
+ }
1540
+ async function handleRevokeKey(args) {
1541
+ const keyId = args.key_id;
1542
+ if (!keyId || typeof keyId !== "string") {
1543
+ return JSON.stringify({
1544
+ error: "key_id is required and must be a string",
1545
+ });
1546
+ }
1547
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(keyId)) {
1548
+ return JSON.stringify({ error: "key_id must be a valid UUID" });
1549
+ }
1550
+ const result = await api("DELETE", `/api/keys/${encodeURIComponent(keyId)}`);
1551
+ return JSON.stringify(result);
1552
+ }
1553
+ // -----------------------------------------------------------------------
1554
+ // Plans
1555
+ // -----------------------------------------------------------------------
1556
+ async function handleGetPlan() {
1557
+ const result = await api("GET", "/api/plans/user/plan");
1558
+ return JSON.stringify(result);
1559
+ }
1560
+ async function handleListPlans() {
1561
+ const result = await api("GET", "/api/plans");
1562
+ return JSON.stringify(result);
1563
+ }
1564
+ async function handleChangePlan(args) {
1565
+ const planId = args.plan_id;
1566
+ if (!planId || typeof planId !== "string") {
1567
+ return JSON.stringify({
1568
+ error: "plan_id is required and must be a string",
1569
+ });
1570
+ }
1571
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(planId)) {
1572
+ return JSON.stringify({ error: "plan_id must be a valid UUID" });
1573
+ }
1574
+ const result = await api("PUT", "/api/plans/user/plan", { planId });
1575
+ return JSON.stringify(result);
1576
+ }
1577
+ // -----------------------------------------------------------------------
1578
+ // Teams extended
1579
+ // -----------------------------------------------------------------------
1580
+ async function handleTeamDelete(args) {
1581
+ const teamId = args.team_id;
1582
+ if (!teamId || typeof teamId !== "string") {
1583
+ return JSON.stringify({
1584
+ error: "team_id is required and must be a string",
1585
+ });
1586
+ }
1587
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1588
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1589
+ }
1590
+ const result = await api("DELETE", `/api/teams/${encodeURIComponent(teamId)}`);
1591
+ return JSON.stringify(result);
1592
+ }
1593
+ async function handleTeamRevokeKey(args) {
1594
+ const teamId = args.team_id;
1595
+ const keyId = args.key_id;
1596
+ if (!teamId || typeof teamId !== "string") {
1597
+ return JSON.stringify({
1598
+ error: "team_id is required and must be a string",
1599
+ });
1600
+ }
1601
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1602
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1603
+ }
1604
+ if (!keyId || typeof keyId !== "string") {
1605
+ return JSON.stringify({
1606
+ error: "key_id is required and must be a string",
1607
+ });
1608
+ }
1609
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(keyId)) {
1610
+ return JSON.stringify({ error: "key_id must be a valid UUID" });
1611
+ }
1612
+ const result = await api("DELETE", `/api/teams/${encodeURIComponent(teamId)}/keys/${encodeURIComponent(keyId)}`);
1613
+ return JSON.stringify(result);
1614
+ }
1615
+ async function handleTeamListKeys(args) {
1616
+ const teamId = args.team_id;
1617
+ if (!teamId || typeof teamId !== "string") {
1618
+ return JSON.stringify({
1619
+ error: "team_id is required and must be a string",
1620
+ });
1621
+ }
1622
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1623
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1624
+ }
1625
+ const result = await api("GET", `/api/teams/${encodeURIComponent(teamId)}/keys`);
1626
+ return JSON.stringify(result);
1627
+ }
1628
+ async function handleTeamListMembers(args) {
1629
+ const teamId = args.team_id;
1630
+ if (!teamId || typeof teamId !== "string") {
1631
+ return JSON.stringify({
1632
+ error: "team_id is required and must be a string",
1633
+ });
1634
+ }
1635
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1636
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1637
+ }
1638
+ const result = await api("GET", `/api/teams/${encodeURIComponent(teamId)}/members`);
1639
+ return JSON.stringify(result);
1640
+ }
1641
+ async function handleTeamAddMember(args) {
1642
+ const teamId = args.team_id;
1643
+ const userId = args.user_id;
1644
+ if (!teamId || typeof teamId !== "string") {
1645
+ return JSON.stringify({
1646
+ error: "team_id is required and must be a string",
1647
+ });
1648
+ }
1649
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1650
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1651
+ }
1652
+ if (!userId || typeof userId !== "string") {
1653
+ return JSON.stringify({
1654
+ error: "user_id is required and must be a string",
1655
+ });
1656
+ }
1657
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(userId)) {
1658
+ return JSON.stringify({ error: "user_id must be a valid UUID" });
1659
+ }
1660
+ const result = await api("POST", `/api/teams/${encodeURIComponent(teamId)}/members`, { userId });
1661
+ return JSON.stringify(result);
1662
+ }
1663
+ async function handleTeamRemoveMember(args) {
1664
+ const teamId = args.team_id;
1665
+ const userId = args.user_id;
1666
+ if (!teamId || typeof teamId !== "string") {
1667
+ return JSON.stringify({
1668
+ error: "team_id is required and must be a string",
1669
+ });
1670
+ }
1671
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1672
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1673
+ }
1674
+ if (!userId || typeof userId !== "string") {
1675
+ return JSON.stringify({
1676
+ error: "user_id is required and must be a string",
1677
+ });
1678
+ }
1679
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(userId)) {
1680
+ return JSON.stringify({ error: "user_id must be a valid UUID" });
1681
+ }
1682
+ const result = await api("DELETE", `/api/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(userId)}`);
1683
+ return JSON.stringify(result);
1684
+ }
1685
+ async function handleTeamInviteMember(args) {
1686
+ const teamId = args.team_id;
1687
+ const email = args.email;
1688
+ const role = args.role;
1689
+ if (!teamId || typeof teamId !== "string") {
1690
+ return JSON.stringify({
1691
+ error: "team_id is required and must be a string",
1692
+ });
1693
+ }
1694
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1695
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1696
+ }
1697
+ if (!email || typeof email !== "string") {
1698
+ return JSON.stringify({
1699
+ error: "email is required and must be a string",
1700
+ });
1701
+ }
1702
+ if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1703
+ return JSON.stringify({ error: "email must be a valid email address" });
1704
+ }
1705
+ if (!role || typeof role !== "string") {
1706
+ return JSON.stringify({ error: "role is required and must be a string" });
1707
+ }
1708
+ if (role !== "member" && role !== "admin") {
1709
+ return JSON.stringify({ error: "role must be 'member' or 'admin'" });
1710
+ }
1711
+ const result = await api("POST", `/api/teams/${encodeURIComponent(teamId)}/invites`, { email, role });
1712
+ return JSON.stringify(result);
1713
+ }
1714
+ async function handleTeamListInvites(args) {
1715
+ const teamId = args.team_id;
1716
+ if (!teamId || typeof teamId !== "string") {
1717
+ return JSON.stringify({
1718
+ error: "team_id is required and must be a string",
1719
+ });
1720
+ }
1721
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1722
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1723
+ }
1724
+ const result = await api("GET", `/api/teams/${encodeURIComponent(teamId)}/invites`);
1725
+ return JSON.stringify(result);
1726
+ }
1727
+ async function handleTeamCancelInvite(args) {
1728
+ const teamId = args.team_id;
1729
+ const inviteId = args.invite_id;
1730
+ if (!teamId || typeof teamId !== "string") {
1731
+ return JSON.stringify({
1732
+ error: "team_id is required and must be a string",
1733
+ });
1734
+ }
1735
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1736
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1737
+ }
1738
+ if (!inviteId || typeof inviteId !== "string") {
1739
+ return JSON.stringify({
1740
+ error: "invite_id is required and must be a string",
1741
+ });
1742
+ }
1743
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(inviteId)) {
1744
+ return JSON.stringify({ error: "invite_id must be a valid UUID" });
1745
+ }
1746
+ const result = await api("DELETE", `/api/teams/${encodeURIComponent(teamId)}/invites/${encodeURIComponent(inviteId)}`);
1747
+ return JSON.stringify(result);
1748
+ }
1749
+ // -----------------------------------------------------------------------
1054
1750
  // Dispatch table
1055
1751
  // -----------------------------------------------------------------------
1056
1752
  const handlers = {
1753
+ // Proxy (3)
1057
1754
  dominusnode_proxied_fetch: handleProxiedFetch,
1755
+ dominusnode_get_proxy_config: handleGetProxyConfig,
1756
+ dominusnode_get_proxy_status: handleGetProxyStatus,
1757
+ // Wallet (7)
1058
1758
  dominusnode_check_balance: handleCheckBalance,
1759
+ dominusnode_get_transactions: handleGetTransactions,
1760
+ dominusnode_get_forecast: handleGetForecast,
1761
+ dominusnode_check_payment: handleCheckPayment,
1762
+ dominusnode_topup_paypal: handleTopupPaypal,
1763
+ dominusnode_topup_stripe: handleTopupStripe,
1764
+ dominusnode_topup_crypto: handleTopupCrypto,
1765
+ // Usage (3)
1059
1766
  dominusnode_check_usage: handleCheckUsage,
1060
- dominusnode_get_proxy_config: handleGetProxyConfig,
1767
+ dominusnode_get_daily_usage: handleGetDailyUsage,
1768
+ dominusnode_get_top_hosts: handleGetTopHosts,
1769
+ // Sessions (1)
1061
1770
  dominusnode_list_sessions: handleListSessions,
1771
+ // Account lifecycle (6)
1772
+ dominusnode_register: handleRegister,
1773
+ dominusnode_login: handleLogin,
1774
+ dominusnode_get_account_info: handleGetAccountInfo,
1775
+ dominusnode_verify_email: handleVerifyEmail,
1776
+ dominusnode_resend_verification: handleResendVerification,
1777
+ dominusnode_update_password: handleUpdatePassword,
1778
+ // API Keys (3)
1779
+ dominusnode_list_keys: handleListKeys,
1780
+ dominusnode_create_key: handleCreateKey,
1781
+ dominusnode_revoke_key: handleRevokeKey,
1782
+ // Plans (3)
1783
+ dominusnode_get_plan: handleGetPlan,
1784
+ dominusnode_list_plans: handleListPlans,
1785
+ dominusnode_change_plan: handleChangePlan,
1786
+ // Agentic wallets (7)
1062
1787
  dominusnode_create_agentic_wallet: handleCreateAgenticWallet,
1063
1788
  dominusnode_fund_agentic_wallet: handleFundAgenticWallet,
1064
1789
  dominusnode_agentic_wallet_balance: handleAgenticWalletBalance,
@@ -1068,6 +1793,7 @@ export function createDominusNodeFunctionHandler(config) {
1068
1793
  dominusnode_unfreeze_agentic_wallet: handleUnfreezeAgenticWallet,
1069
1794
  dominusnode_delete_agentic_wallet: handleDeleteAgenticWallet,
1070
1795
  dominusnode_update_wallet_policy: handleUpdateWalletPolicy,
1796
+ // Teams (17)
1071
1797
  dominusnode_create_team: handleCreateTeam,
1072
1798
  dominusnode_list_teams: handleListTeams,
1073
1799
  dominusnode_team_details: handleTeamDetails,
@@ -1076,16 +1802,144 @@ export function createDominusNodeFunctionHandler(config) {
1076
1802
  dominusnode_team_usage: handleTeamUsage,
1077
1803
  dominusnode_update_team: handleUpdateTeam,
1078
1804
  dominusnode_update_team_member_role: handleUpdateTeamMemberRole,
1079
- dominusnode_topup_paypal: handleTopupPaypal,
1080
- dominusnode_topup_stripe: handleTopupStripe,
1081
- dominusnode_topup_crypto: handleTopupCrypto,
1805
+ dominusnode_team_delete: handleTeamDelete,
1806
+ dominusnode_team_revoke_key: handleTeamRevokeKey,
1807
+ dominusnode_team_list_keys: handleTeamListKeys,
1808
+ dominusnode_team_list_members: handleTeamListMembers,
1809
+ dominusnode_team_add_member: handleTeamAddMember,
1810
+ dominusnode_team_remove_member: handleTeamRemoveMember,
1811
+ dominusnode_team_invite_member: handleTeamInviteMember,
1812
+ dominusnode_team_list_invites: handleTeamListInvites,
1813
+ dominusnode_team_cancel_invite: handleTeamCancelInvite,
1814
+ // x402 (1)
1082
1815
  dominusnode_x402_info: handleX402Info,
1816
+ // MPP (5)
1817
+ dominusnode_mpp_info: handleMppInfo,
1818
+ dominusnode_mpp_challenge: handleMppChallenge,
1819
+ dominusnode_pay_mpp: handlePayMpp,
1820
+ dominusnode_mpp_session_open: handleMppSessionOpen,
1821
+ dominusnode_mpp_session_close: handleMppSessionClose,
1083
1822
  };
1084
1823
  async function handleX402Info() {
1085
1824
  const result = await api("GET", "/api/x402/info");
1086
1825
  return JSON.stringify(result);
1087
1826
  }
1088
1827
  // -----------------------------------------------------------------------
1828
+ // MPP (Machine Payment Protocol) handlers
1829
+ // -----------------------------------------------------------------------
1830
+ async function handleMppInfo() {
1831
+ // MPP info is public (no auth required), but we use the authenticated path
1832
+ // for simplicity since the handler is already authenticated.
1833
+ const result = await api("GET", "/api/mpp/info");
1834
+ return JSON.stringify(result);
1835
+ }
1836
+ async function handleMppChallenge(args) {
1837
+ const poolType = String(args.pool_type ?? "dc");
1838
+ if (poolType !== "dc" && poolType !== "residential") {
1839
+ return JSON.stringify({
1840
+ error: "pool_type must be 'dc' or 'residential'",
1841
+ });
1842
+ }
1843
+ // This is a public endpoint -- no auth required
1844
+ try {
1845
+ const reqHeaders = {
1846
+ "User-Agent": "dominusnode-openai-functions/1.0.0",
1847
+ "Content-Type": "application/json",
1848
+ };
1849
+ if (agentSecret) {
1850
+ reqHeaders["X-DominusNode-Agent"] = "mcp";
1851
+ reqHeaders["X-DominusNode-Agent-Secret"] = agentSecret;
1852
+ }
1853
+ const response = await fetch(`${baseUrl}/api/mpp/challenge`, {
1854
+ method: "POST",
1855
+ headers: reqHeaders,
1856
+ body: JSON.stringify({ poolType }),
1857
+ signal: AbortSignal.timeout(timeoutMs),
1858
+ redirect: "error",
1859
+ });
1860
+ const responseText = await response.text();
1861
+ if (responseText.length > MAX_RESPONSE_BYTES) {
1862
+ return JSON.stringify({ error: "Response body exceeds size limit" });
1863
+ }
1864
+ if (!response.ok) {
1865
+ let message;
1866
+ try {
1867
+ const parsed = JSON.parse(responseText);
1868
+ message = parsed.error ?? parsed.message ?? responseText;
1869
+ }
1870
+ catch {
1871
+ message = responseText;
1872
+ }
1873
+ if (message.length > 500)
1874
+ message = message.slice(0, 500) + "... [truncated]";
1875
+ return JSON.stringify({
1876
+ error: `MPP challenge failed: ${sanitizeError(message)}`,
1877
+ });
1878
+ }
1879
+ const data = safeJsonParse(responseText);
1880
+ return JSON.stringify(data);
1881
+ }
1882
+ catch (err) {
1883
+ return JSON.stringify({
1884
+ error: `MPP challenge failed: ${sanitizeError(err instanceof Error ? err.message : String(err))}`,
1885
+ });
1886
+ }
1887
+ }
1888
+ async function handlePayMpp(args) {
1889
+ const amountCents = args.amount_cents;
1890
+ const method = String(args.method ?? "");
1891
+ if (!Number.isInteger(amountCents) ||
1892
+ amountCents < 500 ||
1893
+ amountCents > 100_000) {
1894
+ return JSON.stringify({
1895
+ error: "amount_cents must be an integer between 500 ($5) and 100,000 ($1,000)",
1896
+ });
1897
+ }
1898
+ if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
1899
+ return JSON.stringify({
1900
+ error: "method must be one of: tempo, stripe_spt, lightning",
1901
+ });
1902
+ }
1903
+ const result = await api("POST", "/api/mpp/topup", { amountCents, method });
1904
+ return JSON.stringify(result);
1905
+ }
1906
+ async function handleMppSessionOpen(args) {
1907
+ const maxDepositCents = args.max_deposit_cents;
1908
+ const method = String(args.method ?? "");
1909
+ const poolType = String(args.pool_type ?? "dc");
1910
+ if (!Number.isInteger(maxDepositCents) ||
1911
+ maxDepositCents < 500 ||
1912
+ maxDepositCents > 100_000) {
1913
+ return JSON.stringify({
1914
+ error: "max_deposit_cents must be an integer between 500 and 100,000",
1915
+ });
1916
+ }
1917
+ if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
1918
+ return JSON.stringify({
1919
+ error: "method must be one of: tempo, stripe_spt, lightning",
1920
+ });
1921
+ }
1922
+ if (!["dc", "residential"].includes(poolType)) {
1923
+ return JSON.stringify({
1924
+ error: "pool_type must be one of: dc, residential",
1925
+ });
1926
+ }
1927
+ const result = await api("POST", "/api/mpp/session/open", {
1928
+ maxDepositCents,
1929
+ method,
1930
+ poolType,
1931
+ });
1932
+ return JSON.stringify(result);
1933
+ }
1934
+ async function handleMppSessionClose(args) {
1935
+ const channelId = String(args.channel_id ?? "");
1936
+ if (!channelId) {
1937
+ return JSON.stringify({ error: "channel_id is required" });
1938
+ }
1939
+ const result = await api("POST", "/api/mpp/session/close", { channelId });
1940
+ return JSON.stringify(result);
1941
+ }
1942
+ // -----------------------------------------------------------------------
1089
1943
  // Main handler
1090
1944
  // -----------------------------------------------------------------------
1091
1945
  return async function handler(name, args) {