@dominusnode/openai-functions 1.2.0 → 1.5.2

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 CHANGED
@@ -6,26 +6,26 @@ OpenAI function-calling schemas and handler implementations for the Dominus Node
6
6
 
7
7
  This directory contains:
8
8
 
9
- | File | Description |
10
- |------|-------------|
11
- | `functions.json` | Array of 8 OpenAI function definitions (JSON Schema format) |
12
- | `handler.ts` | TypeScript handler that dispatches function calls to the Dominus Node API |
13
- | `handler.py` | Python handler that dispatches function calls to the Dominus Node API |
9
+ | File | Description |
10
+ | ---------------- | ------------------------------------------------------------------------- |
11
+ | `functions.json` | Array of 8 OpenAI function definitions (JSON Schema format) |
12
+ | `handler.ts` | TypeScript handler that dispatches function calls to the Dominus Node API |
13
+ | `handler.py` | Python handler that dispatches function calls to the Dominus Node API |
14
14
 
15
15
  The function schemas follow the [OpenAI function calling specification](https://platform.openai.com/docs/guides/function-calling), which has become a de facto standard adopted by Claude (tool_use), Gemini, Mistral, Llama, and others.
16
16
 
17
17
  ## Available Functions
18
18
 
19
- | Function | Description | Auth Required |
20
- |----------|-------------|---------------|
21
- | `dominusnode_proxied_fetch` | Make an HTTP request through Dominus Node's rotating proxy network | Yes |
22
- | `dominusnode_check_balance` | Check wallet balance (cents, USD, currency) | Yes |
23
- | `dominusnode_check_usage` | Check usage stats for a time period (day/week/month) | Yes |
24
- | `dominusnode_get_proxy_config` | Get proxy endpoints, supported countries, geo-targeting info | Yes |
25
- | `dominusnode_list_sessions` | List currently active proxy sessions | Yes |
26
- | `dominusnode_create_agentic_wallet` | Create a sub-wallet for an AI agent with spending limits | Yes |
27
- | `dominusnode_fund_agentic_wallet` | Transfer funds from main wallet to an agentic wallet | Yes |
28
- | `dominusnode_agentic_wallet_balance` | Check an agentic wallet's balance and status | Yes |
19
+ | Function | Description | Auth Required |
20
+ | ------------------------------------ | ------------------------------------------------------------------ | ------------- |
21
+ | `dominusnode_proxied_fetch` | Make an HTTP request through Dominus Node's rotating proxy network | Yes |
22
+ | `dominusnode_check_balance` | Check wallet balance (cents, USD, currency) | Yes |
23
+ | `dominusnode_check_usage` | Check usage stats for a time period (day/week/month) | Yes |
24
+ | `dominusnode_get_proxy_config` | Get proxy endpoints, supported countries, geo-targeting info | Yes |
25
+ | `dominusnode_list_sessions` | List currently active proxy sessions | Yes |
26
+ | `dominusnode_create_agentic_wallet` | Create a sub-wallet for an AI agent with spending limits | Yes |
27
+ | `dominusnode_fund_agentic_wallet` | Transfer funds from main wallet to an agentic wallet | Yes |
28
+ | `dominusnode_agentic_wallet_balance` | Check an agentic wallet's balance and status | Yes |
29
29
 
30
30
  ## Usage with OpenAI GPT (Function Calling)
31
31
 
@@ -176,13 +176,13 @@ console.log(JSON.parse(fetchResult));
176
176
  // Example: create and fund an agentic wallet for an AI agent
177
177
  const wallet = await handler("dominusnode_create_agentic_wallet", {
178
178
  label: "research-agent-1",
179
- spending_limit_cents: 500, // $5.00 per-transaction limit
179
+ spending_limit_cents: 500, // $5.00 per-transaction limit
180
180
  });
181
181
  const walletData = JSON.parse(wallet);
182
182
 
183
183
  await handler("dominusnode_fund_agentic_wallet", {
184
184
  wallet_id: walletData.id,
185
- amount_cents: 2000, // Fund with $20.00
185
+ amount_cents: 2000, // Fund with $20.00
186
186
  });
187
187
  ```
188
188
 
@@ -199,15 +199,15 @@ The `functions.json` file follows the standard OpenAI function definition schema
199
199
 
200
200
  ### Framework-Specific Notes
201
201
 
202
- | Framework | How to Use |
203
- |-----------|-----------|
204
- | **OpenAI SDK** | Pass `functions` directly to `chat.completions.create()` |
205
- | **Anthropic SDK** | Convert to `tools` format (rename `parameters` to `input_schema`) |
206
- | **Google Gemini** | Convert to `FunctionDeclaration` objects |
207
- | **LangChain** | Use `StructuredTool.from_function()` or the Dominus Node LangChain integration |
208
- | **Vercel AI SDK** | See the `integrations/vercel-ai/` package |
209
- | **LlamaIndex** | Use `FunctionTool.from_defaults()` with the handler |
210
- | **Ollama** | Pass as `tools` parameter in chat API |
202
+ | Framework | How to Use |
203
+ | ----------------- | ------------------------------------------------------------------------------ |
204
+ | **OpenAI SDK** | Pass `functions` directly to `chat.completions.create()` |
205
+ | **Anthropic SDK** | Convert to `tools` format (rename `parameters` to `input_schema`) |
206
+ | **Google Gemini** | Convert to `FunctionDeclaration` objects |
207
+ | **LangChain** | Use `StructuredTool.from_function()` or the Dominus Node LangChain integration |
208
+ | **Vercel AI SDK** | See the `integrations/vercel-ai/` package |
209
+ | **LlamaIndex** | Use `FunctionTool.from_defaults()` with the handler |
210
+ | **Ollama** | Pass as `tools` parameter in chat API |
211
211
 
212
212
  ## Security
213
213
 
@@ -224,6 +224,7 @@ Both handlers (TypeScript and Python) include comprehensive SSRF prevention that
224
224
  ### Header Injection Prevention
225
225
 
226
226
  User-supplied HTTP headers are filtered to block:
227
+
227
228
  - Security-sensitive headers (Host, Authorization, Proxy-Authorization, etc.)
228
229
  - CRLF injection attempts (headers containing \r or \n)
229
230
  - Null byte injection
@@ -248,18 +249,20 @@ Requests targeting OFAC-sanctioned countries (CU, IR, KP, RU, SY) are blocked at
248
249
 
249
250
  ## Proxy Pricing
250
251
 
251
- | Proxy Type | Price | Best For |
252
- |------------|-------|----------|
252
+ | Proxy Type | Price | Best For |
253
+ | ----------------- | -------- | -------------------------------------- |
253
254
  | Datacenter (`dc`) | $3.00/GB | General scraping, speed-critical tasks |
254
- | Residential | $5.00/GB | Anti-detection, geo-restricted content |
255
+ | Residential | $5.00/GB | Anti-detection, geo-restricted content |
255
256
 
256
257
  ## Dependencies
257
258
 
258
259
  ### TypeScript Handler
260
+
259
261
  - Node.js 18+ (uses native `fetch` and `AbortSignal.timeout`)
260
262
  - No external dependencies
261
263
 
262
264
  ### Python Handler
265
+
263
266
  - Python 3.8+
264
267
  - `httpx` (`pip install httpx`)
265
268
 
package/dist/handler.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dominus Node OpenAI-compatible function calling handler (TypeScript).
3
3
  *
4
- * 53 tools covering proxy, wallet, usage, account lifecycle, API keys,
4
+ * 57 tools covering proxy, wallet, usage, account lifecycle, API keys,
5
5
  * plans, and teams.
6
6
  *
7
7
  * Provides a factory function that creates a handler for dispatching
package/dist/handler.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dominus Node OpenAI-compatible function calling handler (TypeScript).
3
3
  *
4
- * 53 tools covering proxy, wallet, usage, account lifecycle, API keys,
4
+ * 57 tools covering proxy, wallet, usage, account lifecycle, API keys,
5
5
  * plans, and teams.
6
6
  *
7
7
  * Provides a factory function that creates a handler for dispatching
@@ -25,10 +25,63 @@
25
25
  *
26
26
  * @module
27
27
  */
28
+ import * as crypto from "node:crypto";
28
29
  import dns from "dns/promises";
29
30
  import * as http from "node:http";
30
31
  import * as tls from "node:tls";
31
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
+ // ---------------------------------------------------------------------------
32
85
  // SSRF Prevention -- URL validation
33
86
  // ---------------------------------------------------------------------------
34
87
  const BLOCKED_HOSTNAMES = new Set([
@@ -284,7 +337,7 @@ async function checkDnsRebinding(hostname) {
284
337
  // ---------------------------------------------------------------------------
285
338
  // Credential sanitization
286
339
  // ---------------------------------------------------------------------------
287
- const CREDENTIAL_RE = /dn_(live|test)_[a-zA-Z0-9]+/g;
340
+ const CREDENTIAL_RE = /dn_(live|test|proxy)_[a-zA-Z0-9]+/g;
288
341
  function sanitizeError(message) {
289
342
  return message.replace(CREDENTIAL_RE, "***");
290
343
  }
@@ -504,8 +557,13 @@ export function createDominusNodeFunctionHandler(config) {
504
557
  try {
505
558
  // Strip security-sensitive headers from user-provided headers
506
559
  const BLOCKED_HEADERS = new Set([
507
- "host", "connection", "content-length", "transfer-encoding",
508
- "proxy-authorization", "authorization", "user-agent",
560
+ "host",
561
+ "connection",
562
+ "content-length",
563
+ "transfer-encoding",
564
+ "proxy-authorization",
565
+ "authorization",
566
+ "user-agent",
509
567
  ]);
510
568
  const safeHeaders = {};
511
569
  if (headers) {
@@ -536,13 +594,18 @@ export function createDominusNodeFunctionHandler(config) {
536
594
  const timeout = setTimeout(() => reject(new Error("Proxy request timed out after 30000ms")), 30_000);
537
595
  if (parsed.protocol === "https:") {
538
596
  // HTTPS: CONNECT tunnel + TLS
539
- const connectHost = parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname;
597
+ const connectHost = parsed.hostname.includes(":")
598
+ ? `[${parsed.hostname}]`
599
+ : parsed.hostname;
540
600
  const connectReq = http.request({
541
601
  hostname: proxyHost,
542
602
  port: proxyPort,
543
603
  method: "CONNECT",
544
604
  path: `${connectHost}:${parsed.port || 443}`,
545
- headers: { "Proxy-Authorization": proxyAuth, Host: `${connectHost}:${parsed.port || 443}` },
605
+ headers: {
606
+ "Proxy-Authorization": proxyAuth,
607
+ Host: `${connectHost}:${parsed.port || 443}`,
608
+ },
546
609
  });
547
610
  connectReq.on("connect", (_res, tunnelSocket) => {
548
611
  if (_res.statusCode !== 200) {
@@ -586,7 +649,9 @@ export function createDominusNodeFunctionHandler(config) {
586
649
  return;
587
650
  }
588
651
  const headerSection = raw.substring(0, headerEnd);
589
- 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);
590
655
  const statusLine = headerSection.split("\r\n")[0];
591
656
  const statusMatch = statusLine.match(/^HTTP\/\d\.\d\s+(\d+)/);
592
657
  const status = statusMatch ? parseInt(statusMatch[1], 10) : 0;
@@ -594,18 +659,29 @@ export function createDominusNodeFunctionHandler(config) {
594
659
  for (const line of headerSection.split("\r\n").slice(1)) {
595
660
  const ci = line.indexOf(":");
596
661
  if (ci > 0)
597
- 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();
598
665
  }
599
666
  stripDangerousKeys(headers);
600
667
  resolve({ status, headers, body });
601
668
  };
602
669
  tlsSocket.on("end", finalize);
603
670
  tlsSocket.on("close", finalize);
604
- tlsSocket.on("error", (err) => { clearTimeout(timeout); reject(err); });
671
+ tlsSocket.on("error", (err) => {
672
+ clearTimeout(timeout);
673
+ reject(err);
674
+ });
605
675
  });
606
- 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);
607
684
  });
608
- connectReq.on("error", (err) => { clearTimeout(timeout); reject(err); });
609
685
  connectReq.end();
610
686
  }
611
687
  else {
@@ -615,19 +691,28 @@ export function createDominusNodeFunctionHandler(config) {
615
691
  port: proxyPort,
616
692
  method,
617
693
  path: url,
618
- headers: { ...safeHeaders, "Proxy-Authorization": proxyAuth, Host: parsed.host },
694
+ headers: {
695
+ ...safeHeaders,
696
+ "Proxy-Authorization": proxyAuth,
697
+ Host: parsed.host,
698
+ },
619
699
  }, (res) => {
620
700
  const chunks = [];
621
701
  let byteCount = 0;
622
- res.on("data", (chunk) => { byteCount += chunk.length; if (byteCount <= MAX_BODY_BYTES)
623
- chunks.push(chunk); });
702
+ res.on("data", (chunk) => {
703
+ byteCount += chunk.length;
704
+ if (byteCount <= MAX_BODY_BYTES)
705
+ chunks.push(chunk);
706
+ });
624
707
  let httpFinalized = false;
625
708
  const finalize = () => {
626
709
  if (httpFinalized)
627
710
  return;
628
711
  httpFinalized = true;
629
712
  clearTimeout(timeout);
630
- 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);
631
716
  const headers = {};
632
717
  for (const [k, v] of Object.entries(res.headers)) {
633
718
  if (v)
@@ -638,9 +723,15 @@ export function createDominusNodeFunctionHandler(config) {
638
723
  };
639
724
  res.on("end", finalize);
640
725
  res.on("close", finalize);
641
- 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);
642
734
  });
643
- req.on("error", (err) => { clearTimeout(timeout); reject(err); });
644
735
  req.end();
645
736
  }
646
737
  });
@@ -680,13 +771,17 @@ export function createDominusNodeFunctionHandler(config) {
680
771
  const label = args.label;
681
772
  const spendingLimitCents = args.spending_limit_cents;
682
773
  if (!label || typeof label !== "string") {
683
- 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
+ });
684
777
  }
685
778
  if (label.length > 100) {
686
779
  return JSON.stringify({ error: "label must be 100 characters or fewer" });
687
780
  }
688
781
  if (/[\x00-\x1f\x7f]/.test(label)) {
689
- return JSON.stringify({ error: "label contains invalid control characters" });
782
+ return JSON.stringify({
783
+ error: "label contains invalid control characters",
784
+ });
690
785
  }
691
786
  if (!Number.isInteger(spendingLimitCents) ||
692
787
  spendingLimitCents <= 0 ||
@@ -702,7 +797,9 @@ export function createDominusNodeFunctionHandler(config) {
702
797
  // Optional daily_limit_cents
703
798
  if (args.daily_limit_cents !== undefined) {
704
799
  const dailyLimit = args.daily_limit_cents;
705
- if (!Number.isInteger(dailyLimit) || dailyLimit < 1 || dailyLimit > 1_000_000) {
800
+ if (!Number.isInteger(dailyLimit) ||
801
+ dailyLimit < 1 ||
802
+ dailyLimit > 1_000_000) {
706
803
  return JSON.stringify({
707
804
  error: "daily_limit_cents must be an integer between 1 and 1,000,000",
708
805
  });
@@ -713,18 +810,26 @@ export function createDominusNodeFunctionHandler(config) {
713
810
  if (args.allowed_domains !== undefined) {
714
811
  const domains = args.allowed_domains;
715
812
  if (!Array.isArray(domains)) {
716
- 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
+ });
717
816
  }
718
817
  if (domains.length > 100) {
719
- 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
+ });
720
821
  }
721
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])?)*$/;
722
823
  for (const d of domains) {
723
824
  if (typeof d !== "string") {
724
- 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
+ });
725
828
  }
726
829
  if (d.length > 253) {
727
- 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
+ });
728
833
  }
729
834
  if (!domainRe.test(d)) {
730
835
  return JSON.stringify({ error: `Invalid domain format: ${d}` });
@@ -739,7 +844,9 @@ export function createDominusNodeFunctionHandler(config) {
739
844
  const walletId = args.wallet_id;
740
845
  const amountCents = args.amount_cents;
741
846
  if (!walletId || typeof walletId !== "string") {
742
- 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
+ });
743
850
  }
744
851
  if (!Number.isInteger(amountCents) ||
745
852
  amountCents <= 0 ||
@@ -754,7 +861,9 @@ export function createDominusNodeFunctionHandler(config) {
754
861
  async function handleAgenticWalletBalance(args) {
755
862
  const walletId = args.wallet_id;
756
863
  if (!walletId || typeof walletId !== "string") {
757
- 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
+ });
758
867
  }
759
868
  const result = await api("GET", `/api/agent-wallet/${encodeURIComponent(walletId)}`);
760
869
  return JSON.stringify(result);
@@ -766,13 +875,17 @@ export function createDominusNodeFunctionHandler(config) {
766
875
  async function handleAgenticTransactions(args) {
767
876
  const walletId = args.wallet_id;
768
877
  if (!walletId || typeof walletId !== "string") {
769
- 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
+ });
770
881
  }
771
882
  const limit = args.limit;
772
883
  const params = new URLSearchParams();
773
884
  if (limit !== undefined) {
774
885
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
775
- 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
+ });
776
889
  }
777
890
  params.set("limit", String(limit));
778
891
  }
@@ -783,7 +896,9 @@ export function createDominusNodeFunctionHandler(config) {
783
896
  async function handleFreezeAgenticWallet(args) {
784
897
  const walletId = args.wallet_id;
785
898
  if (!walletId || typeof walletId !== "string") {
786
- 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
+ });
787
902
  }
788
903
  const result = await api("POST", `/api/agent-wallet/${encodeURIComponent(walletId)}/freeze`);
789
904
  return JSON.stringify(result);
@@ -791,7 +906,9 @@ export function createDominusNodeFunctionHandler(config) {
791
906
  async function handleUnfreezeAgenticWallet(args) {
792
907
  const walletId = args.wallet_id;
793
908
  if (!walletId || typeof walletId !== "string") {
794
- 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
+ });
795
912
  }
796
913
  const result = await api("POST", `/api/agent-wallet/${encodeURIComponent(walletId)}/unfreeze`);
797
914
  return JSON.stringify(result);
@@ -799,7 +916,9 @@ export function createDominusNodeFunctionHandler(config) {
799
916
  async function handleDeleteAgenticWallet(args) {
800
917
  const walletId = args.wallet_id;
801
918
  if (!walletId || typeof walletId !== "string") {
802
- 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
+ });
803
922
  }
804
923
  const result = await api("DELETE", `/api/agent-wallet/${encodeURIComponent(walletId)}`);
805
924
  return JSON.stringify(result);
@@ -813,13 +932,17 @@ export function createDominusNodeFunctionHandler(config) {
813
932
  return JSON.stringify({ error: "name must be 100 characters or fewer" });
814
933
  }
815
934
  if (/[\x00-\x1f\x7f]/.test(name)) {
816
- return JSON.stringify({ error: "name contains invalid control characters" });
935
+ return JSON.stringify({
936
+ error: "name contains invalid control characters",
937
+ });
817
938
  }
818
939
  const body = { name };
819
940
  if (args.max_members !== undefined) {
820
941
  const maxMembers = Number(args.max_members);
821
942
  if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
822
- 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
+ });
823
946
  }
824
947
  body.maxMembers = maxMembers;
825
948
  }
@@ -833,7 +956,9 @@ export function createDominusNodeFunctionHandler(config) {
833
956
  async function handleTeamDetails(args) {
834
957
  const teamId = args.team_id;
835
958
  if (!teamId || typeof teamId !== "string") {
836
- 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
+ });
837
962
  }
838
963
  const result = await api("GET", `/api/teams/${encodeURIComponent(teamId)}`);
839
964
  return JSON.stringify(result);
@@ -842,7 +967,9 @@ export function createDominusNodeFunctionHandler(config) {
842
967
  const teamId = args.team_id;
843
968
  const amountCents = args.amount_cents;
844
969
  if (!teamId || typeof teamId !== "string") {
845
- 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
+ });
846
973
  }
847
974
  if (!Number.isInteger(amountCents) ||
848
975
  amountCents < 100 ||
@@ -858,16 +985,22 @@ export function createDominusNodeFunctionHandler(config) {
858
985
  const teamId = args.team_id;
859
986
  const label = args.label;
860
987
  if (!teamId || typeof teamId !== "string") {
861
- 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
+ });
862
991
  }
863
992
  if (!label || typeof label !== "string") {
864
- 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
+ });
865
996
  }
866
997
  if (label.length > 100) {
867
998
  return JSON.stringify({ error: "label must be 100 characters or fewer" });
868
999
  }
869
1000
  if (/[\x00-\x1f\x7f]/.test(label)) {
870
- return JSON.stringify({ error: "label contains invalid control characters" });
1001
+ return JSON.stringify({
1002
+ error: "label contains invalid control characters",
1003
+ });
871
1004
  }
872
1005
  const result = await api("POST", `/api/teams/${encodeURIComponent(teamId)}/keys`, { label });
873
1006
  return JSON.stringify(result);
@@ -875,13 +1008,17 @@ export function createDominusNodeFunctionHandler(config) {
875
1008
  async function handleTeamUsage(args) {
876
1009
  const teamId = args.team_id;
877
1010
  if (!teamId || typeof teamId !== "string") {
878
- 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
+ });
879
1014
  }
880
1015
  const limit = args.limit;
881
1016
  const params = new URLSearchParams();
882
1017
  if (limit !== undefined) {
883
1018
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
884
- 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
+ });
885
1022
  }
886
1023
  params.set("limit", String(limit));
887
1024
  }
@@ -892,7 +1029,9 @@ export function createDominusNodeFunctionHandler(config) {
892
1029
  async function handleUpdateTeam(args) {
893
1030
  const teamId = args.team_id;
894
1031
  if (!teamId || typeof teamId !== "string") {
895
- 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
+ });
896
1035
  }
897
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)) {
898
1037
  return JSON.stringify({ error: "team_id must be a valid UUID" });
@@ -904,22 +1043,30 @@ export function createDominusNodeFunctionHandler(config) {
904
1043
  return JSON.stringify({ error: "name must be a non-empty string" });
905
1044
  }
906
1045
  if (name.length > 100) {
907
- 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
+ });
908
1049
  }
909
1050
  if (/[\x00-\x1f\x7f]/.test(name)) {
910
- return JSON.stringify({ error: "name contains invalid control characters" });
1051
+ return JSON.stringify({
1052
+ error: "name contains invalid control characters",
1053
+ });
911
1054
  }
912
1055
  body.name = name;
913
1056
  }
914
1057
  if (args.max_members !== undefined) {
915
1058
  const maxMembers = Number(args.max_members);
916
1059
  if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
917
- 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
+ });
918
1063
  }
919
1064
  body.maxMembers = maxMembers;
920
1065
  }
921
1066
  if (Object.keys(body).length === 0) {
922
- 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
+ });
923
1070
  }
924
1071
  const result = await api("PATCH", `/api/teams/${encodeURIComponent(teamId)}`, body);
925
1072
  return JSON.stringify(result);
@@ -933,7 +1080,9 @@ export function createDominusNodeFunctionHandler(config) {
933
1080
  error: "amount_cents must be an integer between 500 ($5) and 100,000 ($1,000)",
934
1081
  });
935
1082
  }
936
- const result = await api("POST", "/api/wallet/topup/paypal", { amountCents });
1083
+ const result = await api("POST", "/api/wallet/topup/paypal", {
1084
+ amountCents,
1085
+ });
937
1086
  return JSON.stringify(result);
938
1087
  }
939
1088
  async function handleTopupStripe(args) {
@@ -945,21 +1094,38 @@ export function createDominusNodeFunctionHandler(config) {
945
1094
  error: "amount_cents must be an integer between 500 ($5) and 100,000 ($1,000)",
946
1095
  });
947
1096
  }
948
- const result = await api("POST", "/api/wallet/topup/stripe", { amountCents });
1097
+ const result = await api("POST", "/api/wallet/topup/stripe", {
1098
+ amountCents,
1099
+ });
949
1100
  return JSON.stringify(result);
950
1101
  }
951
1102
  async function handleTopupCrypto(args) {
952
1103
  const amountUsd = args.amount_usd;
953
1104
  const currency = args.currency;
954
- 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) {
955
1109
  return JSON.stringify({
956
1110
  error: "amount_usd must be a number between 5 and 1,000",
957
1111
  });
958
1112
  }
959
1113
  const validCurrencies = new Set([
960
- "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",
961
1125
  ]);
962
- if (!currency || typeof currency !== "string" || !validCurrencies.has(currency.toUpperCase())) {
1126
+ if (!currency ||
1127
+ typeof currency !== "string" ||
1128
+ !validCurrencies.has(currency.toUpperCase())) {
963
1129
  return JSON.stringify({
964
1130
  error: "currency must be one of: BTC, ETH, LTC, XMR, ZEC, USDC, SOL, USDT, DAI, BNB, LINK",
965
1131
  });
@@ -973,7 +1139,9 @@ export function createDominusNodeFunctionHandler(config) {
973
1139
  async function handleUpdateWalletPolicy(args) {
974
1140
  const walletId = args.wallet_id;
975
1141
  if (!walletId || typeof walletId !== "string") {
976
- 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
+ });
977
1145
  }
978
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)) {
979
1147
  return JSON.stringify({ error: "wallet_id must be a valid UUID" });
@@ -986,7 +1154,9 @@ export function createDominusNodeFunctionHandler(config) {
986
1154
  }
987
1155
  else {
988
1156
  const dailyLimit = args.daily_limit_cents;
989
- if (!Number.isInteger(dailyLimit) || dailyLimit < 1 || dailyLimit > 1_000_000) {
1157
+ if (!Number.isInteger(dailyLimit) ||
1158
+ dailyLimit < 1 ||
1159
+ dailyLimit > 1_000_000) {
990
1160
  return JSON.stringify({
991
1161
  error: "daily_limit_cents must be an integer between 1 and 1,000,000 (or null to remove)",
992
1162
  });
@@ -1002,18 +1172,26 @@ export function createDominusNodeFunctionHandler(config) {
1002
1172
  else {
1003
1173
  const domains = args.allowed_domains;
1004
1174
  if (!Array.isArray(domains)) {
1005
- 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
+ });
1006
1178
  }
1007
1179
  if (domains.length > 100) {
1008
- 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
+ });
1009
1183
  }
1010
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])?)*$/;
1011
1185
  for (const d of domains) {
1012
1186
  if (typeof d !== "string") {
1013
- 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
+ });
1014
1190
  }
1015
1191
  if (d.length > 253) {
1016
- 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
+ });
1017
1195
  }
1018
1196
  if (!domainRe.test(d)) {
1019
1197
  return JSON.stringify({ error: `Invalid domain format: ${d}` });
@@ -1023,7 +1201,9 @@ export function createDominusNodeFunctionHandler(config) {
1023
1201
  }
1024
1202
  }
1025
1203
  if (Object.keys(body).length === 0) {
1026
- 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
+ });
1027
1207
  }
1028
1208
  const result = await api("PATCH", `/api/agent-wallet/${encodeURIComponent(walletId)}/policy`, body);
1029
1209
  return JSON.stringify(result);
@@ -1033,13 +1213,17 @@ export function createDominusNodeFunctionHandler(config) {
1033
1213
  const userId = args.user_id;
1034
1214
  const role = args.role;
1035
1215
  if (!teamId || typeof teamId !== "string") {
1036
- 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
+ });
1037
1219
  }
1038
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)) {
1039
1221
  return JSON.stringify({ error: "team_id must be a valid UUID" });
1040
1222
  }
1041
1223
  if (!userId || typeof userId !== "string") {
1042
- 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
+ });
1043
1227
  }
1044
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)) {
1045
1229
  return JSON.stringify({ error: "user_id must be a valid UUID" });
@@ -1068,7 +1252,9 @@ export function createDominusNodeFunctionHandler(config) {
1068
1252
  if (args.limit !== undefined) {
1069
1253
  const limit = args.limit;
1070
1254
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
1071
- return JSON.stringify({ error: "limit must be an integer between 1 and 100" });
1255
+ return JSON.stringify({
1256
+ error: "limit must be an integer between 1 and 100",
1257
+ });
1072
1258
  }
1073
1259
  params.set("limit", String(limit));
1074
1260
  }
@@ -1083,7 +1269,9 @@ export function createDominusNodeFunctionHandler(config) {
1083
1269
  async function handleCheckPayment(args) {
1084
1270
  const invoiceId = args.invoice_id;
1085
1271
  if (!invoiceId || typeof invoiceId !== "string") {
1086
- return JSON.stringify({ error: "invoice_id is required and must be a string" });
1272
+ return JSON.stringify({
1273
+ error: "invoice_id is required and must be a string",
1274
+ });
1087
1275
  }
1088
1276
  const result = await api("GET", `/api/wallet/topup/crypto/${encodeURIComponent(invoiceId)}/status`);
1089
1277
  return JSON.stringify(result);
@@ -1094,7 +1282,9 @@ export function createDominusNodeFunctionHandler(config) {
1094
1282
  async function handleGetDailyUsage(args) {
1095
1283
  const days = args.days ?? 30;
1096
1284
  if (!Number.isInteger(days) || days < 1 || days > 90) {
1097
- return JSON.stringify({ error: "days must be an integer between 1 and 90" });
1285
+ return JSON.stringify({
1286
+ error: "days must be an integer between 1 and 90",
1287
+ });
1098
1288
  }
1099
1289
  const params = new URLSearchParams({ days: String(days) });
1100
1290
  const result = await api("GET", `/api/usage/daily?${params.toString()}`);
@@ -1105,7 +1295,9 @@ export function createDominusNodeFunctionHandler(config) {
1105
1295
  if (args.limit !== undefined) {
1106
1296
  const limit = args.limit;
1107
1297
  if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
1108
- return JSON.stringify({ error: "limit must be an integer between 1 and 50" });
1298
+ return JSON.stringify({
1299
+ error: "limit must be an integer between 1 and 50",
1300
+ });
1109
1301
  }
1110
1302
  params.set("limit", String(limit));
1111
1303
  }
@@ -1120,16 +1312,22 @@ export function createDominusNodeFunctionHandler(config) {
1120
1312
  const email = args.email;
1121
1313
  const password = args.password;
1122
1314
  if (!email || typeof email !== "string") {
1123
- return JSON.stringify({ error: "email is required and must be a string" });
1315
+ return JSON.stringify({
1316
+ error: "email is required and must be a string",
1317
+ });
1124
1318
  }
1125
1319
  if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1126
1320
  return JSON.stringify({ error: "email must be a valid email address" });
1127
1321
  }
1128
1322
  if (!password || typeof password !== "string") {
1129
- return JSON.stringify({ error: "password is required and must be a string" });
1323
+ return JSON.stringify({
1324
+ error: "password is required and must be a string",
1325
+ });
1130
1326
  }
1131
1327
  if (password.length < 8 || password.length > 128) {
1132
- return JSON.stringify({ error: "password must be between 8 and 128 characters" });
1328
+ return JSON.stringify({
1329
+ error: "password must be between 8 and 128 characters",
1330
+ });
1133
1331
  }
1134
1332
  const headers = {
1135
1333
  "User-Agent": "dominusnode-openai-functions/1.0.0",
@@ -1139,10 +1337,15 @@ export function createDominusNodeFunctionHandler(config) {
1139
1337
  headers["X-DominusNode-Agent"] = "mcp";
1140
1338
  headers["X-DominusNode-Agent-Secret"] = agentSecret;
1141
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;
1142
1345
  const response = await fetch(`${baseUrl}/api/auth/register`, {
1143
1346
  method: "POST",
1144
1347
  headers,
1145
- body: JSON.stringify({ email, password }),
1348
+ body: JSON.stringify(regBody),
1146
1349
  signal: AbortSignal.timeout(timeoutMs),
1147
1350
  redirect: "error",
1148
1351
  });
@@ -1161,26 +1364,38 @@ export function createDominusNodeFunctionHandler(config) {
1161
1364
  }
1162
1365
  if (message.length > 500)
1163
1366
  message = message.slice(0, 500) + "... [truncated]";
1164
- return JSON.stringify({ error: `Registration failed: ${sanitizeError(message)}` });
1367
+ return JSON.stringify({
1368
+ error: `Registration failed: ${sanitizeError(message)}`,
1369
+ });
1165
1370
  }
1166
1371
  const data = safeJsonParse(text);
1167
1372
  stripDangerousKeys(data);
1168
- return JSON.stringify({ userId: data.userId, email: data.email, message: data.message });
1373
+ return JSON.stringify({
1374
+ userId: data.userId,
1375
+ email: data.email,
1376
+ message: data.message,
1377
+ });
1169
1378
  }
1170
1379
  async function handleLogin(args) {
1171
1380
  const email = args.email;
1172
1381
  const password = args.password;
1173
1382
  if (!email || typeof email !== "string") {
1174
- return JSON.stringify({ error: "email is required and must be a string" });
1383
+ return JSON.stringify({
1384
+ error: "email is required and must be a string",
1385
+ });
1175
1386
  }
1176
1387
  if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1177
1388
  return JSON.stringify({ error: "email must be a valid email address" });
1178
1389
  }
1179
1390
  if (!password || typeof password !== "string") {
1180
- return JSON.stringify({ error: "password is required and must be a string" });
1391
+ return JSON.stringify({
1392
+ error: "password is required and must be a string",
1393
+ });
1181
1394
  }
1182
1395
  if (password.length < 8 || password.length > 128) {
1183
- return JSON.stringify({ error: "password must be between 8 and 128 characters" });
1396
+ return JSON.stringify({
1397
+ error: "password must be between 8 and 128 characters",
1398
+ });
1184
1399
  }
1185
1400
  const headers = {
1186
1401
  "User-Agent": "dominusnode-openai-functions/1.0.0",
@@ -1212,7 +1427,9 @@ export function createDominusNodeFunctionHandler(config) {
1212
1427
  }
1213
1428
  if (message.length > 500)
1214
1429
  message = message.slice(0, 500) + "... [truncated]";
1215
- return JSON.stringify({ error: `Login failed: ${sanitizeError(message)}` });
1430
+ return JSON.stringify({
1431
+ error: `Login failed: ${sanitizeError(message)}`,
1432
+ });
1216
1433
  }
1217
1434
  const data = safeJsonParse(text);
1218
1435
  stripDangerousKeys(data);
@@ -1225,7 +1442,9 @@ export function createDominusNodeFunctionHandler(config) {
1225
1442
  async function handleVerifyEmail(args) {
1226
1443
  const token = args.token;
1227
1444
  if (!token || typeof token !== "string") {
1228
- return JSON.stringify({ error: "token is required and must be a string" });
1445
+ return JSON.stringify({
1446
+ error: "token is required and must be a string",
1447
+ });
1229
1448
  }
1230
1449
  const headers = {
1231
1450
  "User-Agent": "dominusnode-openai-functions/1.0.0",
@@ -1257,7 +1476,9 @@ export function createDominusNodeFunctionHandler(config) {
1257
1476
  }
1258
1477
  if (message.length > 500)
1259
1478
  message = message.slice(0, 500) + "... [truncated]";
1260
- return JSON.stringify({ error: `Email verification failed: ${sanitizeError(message)}` });
1479
+ return JSON.stringify({
1480
+ error: `Email verification failed: ${sanitizeError(message)}`,
1481
+ });
1261
1482
  }
1262
1483
  const data = safeJsonParse(text);
1263
1484
  stripDangerousKeys(data);
@@ -1271,13 +1492,19 @@ export function createDominusNodeFunctionHandler(config) {
1271
1492
  const currentPassword = args.current_password;
1272
1493
  const newPassword = args.new_password;
1273
1494
  if (!currentPassword || typeof currentPassword !== "string") {
1274
- return JSON.stringify({ error: "current_password is required and must be a string" });
1495
+ return JSON.stringify({
1496
+ error: "current_password is required and must be a string",
1497
+ });
1275
1498
  }
1276
1499
  if (!newPassword || typeof newPassword !== "string") {
1277
- return JSON.stringify({ error: "new_password is required and must be a string" });
1500
+ return JSON.stringify({
1501
+ error: "new_password is required and must be a string",
1502
+ });
1278
1503
  }
1279
1504
  if (newPassword.length < 8 || newPassword.length > 128) {
1280
- return JSON.stringify({ error: "new_password must be between 8 and 128 characters" });
1505
+ return JSON.stringify({
1506
+ error: "new_password must be between 8 and 128 characters",
1507
+ });
1281
1508
  }
1282
1509
  const result = await api("POST", "/api/auth/change-password", {
1283
1510
  currentPassword,
@@ -1295,13 +1522,17 @@ export function createDominusNodeFunctionHandler(config) {
1295
1522
  async function handleCreateKey(args) {
1296
1523
  const label = args.label;
1297
1524
  if (!label || typeof label !== "string") {
1298
- return JSON.stringify({ error: "label is required and must be a string" });
1525
+ return JSON.stringify({
1526
+ error: "label is required and must be a string",
1527
+ });
1299
1528
  }
1300
1529
  if (label.length > 100) {
1301
1530
  return JSON.stringify({ error: "label must be 100 characters or fewer" });
1302
1531
  }
1303
1532
  if (/[\x00-\x1f\x7f]/.test(label)) {
1304
- return JSON.stringify({ error: "label contains invalid control characters" });
1533
+ return JSON.stringify({
1534
+ error: "label contains invalid control characters",
1535
+ });
1305
1536
  }
1306
1537
  const result = await api("POST", "/api/keys", { label });
1307
1538
  return JSON.stringify(result);
@@ -1309,7 +1540,9 @@ export function createDominusNodeFunctionHandler(config) {
1309
1540
  async function handleRevokeKey(args) {
1310
1541
  const keyId = args.key_id;
1311
1542
  if (!keyId || typeof keyId !== "string") {
1312
- return JSON.stringify({ error: "key_id is required and must be a string" });
1543
+ return JSON.stringify({
1544
+ error: "key_id is required and must be a string",
1545
+ });
1313
1546
  }
1314
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)) {
1315
1548
  return JSON.stringify({ error: "key_id must be a valid UUID" });
@@ -1331,7 +1564,9 @@ export function createDominusNodeFunctionHandler(config) {
1331
1564
  async function handleChangePlan(args) {
1332
1565
  const planId = args.plan_id;
1333
1566
  if (!planId || typeof planId !== "string") {
1334
- return JSON.stringify({ error: "plan_id is required and must be a string" });
1567
+ return JSON.stringify({
1568
+ error: "plan_id is required and must be a string",
1569
+ });
1335
1570
  }
1336
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)) {
1337
1572
  return JSON.stringify({ error: "plan_id must be a valid UUID" });
@@ -1345,7 +1580,9 @@ export function createDominusNodeFunctionHandler(config) {
1345
1580
  async function handleTeamDelete(args) {
1346
1581
  const teamId = args.team_id;
1347
1582
  if (!teamId || typeof teamId !== "string") {
1348
- return JSON.stringify({ error: "team_id is required and must be a string" });
1583
+ return JSON.stringify({
1584
+ error: "team_id is required and must be a string",
1585
+ });
1349
1586
  }
1350
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)) {
1351
1588
  return JSON.stringify({ error: "team_id must be a valid UUID" });
@@ -1357,13 +1594,17 @@ export function createDominusNodeFunctionHandler(config) {
1357
1594
  const teamId = args.team_id;
1358
1595
  const keyId = args.key_id;
1359
1596
  if (!teamId || typeof teamId !== "string") {
1360
- return JSON.stringify({ error: "team_id is required and must be a string" });
1597
+ return JSON.stringify({
1598
+ error: "team_id is required and must be a string",
1599
+ });
1361
1600
  }
1362
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)) {
1363
1602
  return JSON.stringify({ error: "team_id must be a valid UUID" });
1364
1603
  }
1365
1604
  if (!keyId || typeof keyId !== "string") {
1366
- return JSON.stringify({ error: "key_id is required and must be a string" });
1605
+ return JSON.stringify({
1606
+ error: "key_id is required and must be a string",
1607
+ });
1367
1608
  }
1368
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)) {
1369
1610
  return JSON.stringify({ error: "key_id must be a valid UUID" });
@@ -1374,7 +1615,9 @@ export function createDominusNodeFunctionHandler(config) {
1374
1615
  async function handleTeamListKeys(args) {
1375
1616
  const teamId = args.team_id;
1376
1617
  if (!teamId || typeof teamId !== "string") {
1377
- return JSON.stringify({ error: "team_id is required and must be a string" });
1618
+ return JSON.stringify({
1619
+ error: "team_id is required and must be a string",
1620
+ });
1378
1621
  }
1379
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)) {
1380
1623
  return JSON.stringify({ error: "team_id must be a valid UUID" });
@@ -1385,7 +1628,9 @@ export function createDominusNodeFunctionHandler(config) {
1385
1628
  async function handleTeamListMembers(args) {
1386
1629
  const teamId = args.team_id;
1387
1630
  if (!teamId || typeof teamId !== "string") {
1388
- return JSON.stringify({ error: "team_id is required and must be a string" });
1631
+ return JSON.stringify({
1632
+ error: "team_id is required and must be a string",
1633
+ });
1389
1634
  }
1390
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)) {
1391
1636
  return JSON.stringify({ error: "team_id must be a valid UUID" });
@@ -1397,13 +1642,17 @@ export function createDominusNodeFunctionHandler(config) {
1397
1642
  const teamId = args.team_id;
1398
1643
  const userId = args.user_id;
1399
1644
  if (!teamId || typeof teamId !== "string") {
1400
- return JSON.stringify({ error: "team_id is required and must be a string" });
1645
+ return JSON.stringify({
1646
+ error: "team_id is required and must be a string",
1647
+ });
1401
1648
  }
1402
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)) {
1403
1650
  return JSON.stringify({ error: "team_id must be a valid UUID" });
1404
1651
  }
1405
1652
  if (!userId || typeof userId !== "string") {
1406
- return JSON.stringify({ error: "user_id is required and must be a string" });
1653
+ return JSON.stringify({
1654
+ error: "user_id is required and must be a string",
1655
+ });
1407
1656
  }
1408
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)) {
1409
1658
  return JSON.stringify({ error: "user_id must be a valid UUID" });
@@ -1415,13 +1664,17 @@ export function createDominusNodeFunctionHandler(config) {
1415
1664
  const teamId = args.team_id;
1416
1665
  const userId = args.user_id;
1417
1666
  if (!teamId || typeof teamId !== "string") {
1418
- return JSON.stringify({ error: "team_id is required and must be a string" });
1667
+ return JSON.stringify({
1668
+ error: "team_id is required and must be a string",
1669
+ });
1419
1670
  }
1420
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)) {
1421
1672
  return JSON.stringify({ error: "team_id must be a valid UUID" });
1422
1673
  }
1423
1674
  if (!userId || typeof userId !== "string") {
1424
- return JSON.stringify({ error: "user_id is required and must be a string" });
1675
+ return JSON.stringify({
1676
+ error: "user_id is required and must be a string",
1677
+ });
1425
1678
  }
1426
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)) {
1427
1680
  return JSON.stringify({ error: "user_id must be a valid UUID" });
@@ -1434,13 +1687,17 @@ export function createDominusNodeFunctionHandler(config) {
1434
1687
  const email = args.email;
1435
1688
  const role = args.role;
1436
1689
  if (!teamId || typeof teamId !== "string") {
1437
- return JSON.stringify({ error: "team_id is required and must be a string" });
1690
+ return JSON.stringify({
1691
+ error: "team_id is required and must be a string",
1692
+ });
1438
1693
  }
1439
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)) {
1440
1695
  return JSON.stringify({ error: "team_id must be a valid UUID" });
1441
1696
  }
1442
1697
  if (!email || typeof email !== "string") {
1443
- return JSON.stringify({ error: "email is required and must be a string" });
1698
+ return JSON.stringify({
1699
+ error: "email is required and must be a string",
1700
+ });
1444
1701
  }
1445
1702
  if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1446
1703
  return JSON.stringify({ error: "email must be a valid email address" });
@@ -1457,7 +1714,9 @@ export function createDominusNodeFunctionHandler(config) {
1457
1714
  async function handleTeamListInvites(args) {
1458
1715
  const teamId = args.team_id;
1459
1716
  if (!teamId || typeof teamId !== "string") {
1460
- return JSON.stringify({ error: "team_id is required and must be a string" });
1717
+ return JSON.stringify({
1718
+ error: "team_id is required and must be a string",
1719
+ });
1461
1720
  }
1462
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)) {
1463
1722
  return JSON.stringify({ error: "team_id must be a valid UUID" });
@@ -1469,13 +1728,17 @@ export function createDominusNodeFunctionHandler(config) {
1469
1728
  const teamId = args.team_id;
1470
1729
  const inviteId = args.invite_id;
1471
1730
  if (!teamId || typeof teamId !== "string") {
1472
- return JSON.stringify({ error: "team_id is required and must be a string" });
1731
+ return JSON.stringify({
1732
+ error: "team_id is required and must be a string",
1733
+ });
1473
1734
  }
1474
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)) {
1475
1736
  return JSON.stringify({ error: "team_id must be a valid UUID" });
1476
1737
  }
1477
1738
  if (!inviteId || typeof inviteId !== "string") {
1478
- return JSON.stringify({ error: "invite_id is required and must be a string" });
1739
+ return JSON.stringify({
1740
+ error: "invite_id is required and must be a string",
1741
+ });
1479
1742
  }
1480
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)) {
1481
1744
  return JSON.stringify({ error: "invite_id must be a valid UUID" });
@@ -1550,12 +1813,133 @@ export function createDominusNodeFunctionHandler(config) {
1550
1813
  dominusnode_team_cancel_invite: handleTeamCancelInvite,
1551
1814
  // x402 (1)
1552
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,
1553
1822
  };
1554
1823
  async function handleX402Info() {
1555
1824
  const result = await api("GET", "/api/x402/info");
1556
1825
  return JSON.stringify(result);
1557
1826
  }
1558
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
+ // -----------------------------------------------------------------------
1559
1943
  // Main handler
1560
1944
  // -----------------------------------------------------------------------
1561
1945
  return async function handler(name, args) {
package/functions.json CHANGED
@@ -447,7 +447,19 @@
447
447
  "currency": {
448
448
  "type": "string",
449
449
  "description": "Cryptocurrency to pay with: BTC, ETH, LTC, XMR, ZEC, USDC, SOL, USDT, DAI, BNB, or LINK.",
450
- "enum": ["BTC", "ETH", "LTC", "XMR", "ZEC", "USDC", "SOL", "USDT", "DAI", "BNB", "LINK"]
450
+ "enum": [
451
+ "BTC",
452
+ "ETH",
453
+ "LTC",
454
+ "XMR",
455
+ "ZEC",
456
+ "USDC",
457
+ "SOL",
458
+ "USDT",
459
+ "DAI",
460
+ "BNB",
461
+ "LINK"
462
+ ]
451
463
  }
452
464
  },
453
465
  "required": ["amount_usd", "currency"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dominusnode/openai-functions",
3
- "version": "1.2.0",
3
+ "version": "1.5.2",
4
4
  "description": "Dominus Node OpenAI-compatible function calling handler — dispatches LLM function calls to the Dominus Node REST API",
5
5
  "main": "dist/handler.js",
6
6
  "types": "dist/handler.d.ts",
@@ -8,10 +8,12 @@
8
8
  "license": "MIT",
9
9
  "scripts": {
10
10
  "build": "tsc",
11
+ "prepare": "npm run build",
11
12
  "test": "vitest run",
12
13
  "test:py": "python -m pytest test_handler.py -v"
13
14
  },
14
15
  "devDependencies": {
16
+ "@types/node": "^22.0.0",
15
17
  "typescript": "^5.4.0",
16
18
  "vitest": "^1.6.0"
17
19
  },