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