dominusnode-openai-functions 2.2.0__tar.gz

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.
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ ## 2.3.0
4
+
5
+ ### Changed
6
+ - Requires `@dominusnode/sdk` ^2.4.0 (adds `network.status` and `wallet.get`).
7
+
8
+ ## 2.0.0 (2026-11-06)
9
+
10
+ ### Breaking
11
+
12
+ - Rebuilt on the DominusNode SDK v2: exposes exactly the 19 standard relay-network tools
13
+ (fetch, proxy_url, network_policy/pricing/stats, balance, transactions, topup_crypto, check_payment, usage,
14
+ active_sessions, report_session, node_list, node_tier, node_setup_guide, credits_balance, credits_convert,
15
+ payouts_list, x402_info).
16
+ - Removed Stripe/PayPal top-ups, plans, slots, teams/agentic-wallet/key management, email/password and MFA
17
+ flows, hardcoded prices and dc-/residential- pool prefixes.
18
+ - Removed the bundled HTTP client and SSRF code; the SDK now provides HTTP, DNS-rebinding checks, OFAC refusal
19
+ and credential scrubbing.
20
+ - TypeScript handler now uses @dominusnode/sdk v2 and accepts raw JSON argument strings; the Python handler is the dominusnode_openai_functions package on the dominusnode SDK v2. functions.json is generated and parity-tested in both languages.
21
+
22
+ ### Added
23
+
24
+ - Live pricing via network_pricing, node earnings tools and a node agent setup guide.
25
+ - The API key is read only from DOMINUSNODE_API_KEY/config; proxy_url returns a key placeholder.
26
+
27
+ ## [2.2.0] - 2026-11-13
28
+
29
+ ### Changed
30
+ - Proxy traffic now goes to the gateway's TLS (HTTPS proxy) listener on port 8443 by default, and `proxy_url` returns `https://...:8443`. Production gateways refuse credentials on plaintext 8080/1080, so `fetch` failed on first use before. `DOMINUSNODE_PROXY_URL=https://host[:port]` sets the gateway host and TLS port (same variable as the MCP server; self-hosted coordinators publish 8444), `DOMINUSNODE_PROXY_TLS_PORT` sets only the port, and `DOMINUSNODE_PROXY_INSECURE=1` selects plaintext for local development.
31
+ - The node setup guide uses the published node image (`ghcr.io/0xcircuitbreaker/dominusnode-node`), which has the official coordinator pins baked in. A plain source build has none and now refuses to start.
32
+ - Requires the DominusNode SDK 2.2.0 or later (relay error codes on refused CONNECTs).
33
+
34
+ ## [2.1.0] - 2026-11-12
35
+
36
+ ### Added
37
+ - The fetch tool's `ip_class` accepts `masked` (cheapest, VPN/anonymizer exits, opt-in; never used unless asked).
38
+
39
+ ### Changed
40
+ - Requires the DominusNode SDK 2.1.0 or later (`masked` support).
41
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dominus Node
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,8 @@
1
+ # sdist contents: package source, metadata and docs only (see scripts/release/check-artifacts.mjs)
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include LICENSE
5
+ prune tests
6
+ exclude conftest.py
7
+ prune examples
8
+ global-exclude *.py[cod] __pycache__ *.map .env .env.* *.log
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: dominusnode-openai-functions
3
+ Version: 2.2.0
4
+ Summary: DominusNode v2 OpenAI-compatible function definitions and handler, built on the dominusnode SDK
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/0xcircuitbreaker/DomiNode/tree/master/integrations/openai-functions
7
+ Project-URL: Repository, https://github.com/0xcircuitbreaker/DomiNode
8
+ Project-URL: Issues, https://github.com/0xcircuitbreaker/DomiNode/issues
9
+ Project-URL: Changelog, https://github.com/0xcircuitbreaker/DomiNode/blob/master/integrations/openai-functions/CHANGELOG.md
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: dominusnode<3,>=2.2.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=7.0; extra == "dev"
16
+ Dynamic: license-file
17
+
18
+ # DominusNode for OpenAI-compatible function calling
19
+
20
+ Gives agents the DominusNode AI-agent relay network: fetch public web pages through exit IPs contributed by
21
+ other agents' nodes, pay per GB from a prepaid crypto wallet, and earn by contributing your own IP with the
22
+ node agent. Built on the official DominusNode SDK v2; HTTP, SSRF/DNS-rebinding protection, OFAC refusal and
23
+ credential scrubbing all come from the SDK.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @dominusnode/openai-functions # TypeScript
29
+ pip install dominusnode-openai-functions # Python
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ | Variable | Required | Purpose |
35
+ |---|---|---|
36
+ | `DOMINUSNODE_API_KEY` | yes | Your `dn_live_` API key. Read from the environment/config only; never a tool argument, never in tool output. |
37
+ | `DOMINUSNODE_BASE_URL` | no | API base URL (default `https://api.dominusnode.com`). |
38
+ | `DOMINUSNODE_PROXY_HOST` | no | Gateway host (default `proxy.dominusnode.com`). |
39
+ | `DOMINUSNODE_PROXY_URL` | no | `https://host[:port]` of the gateway's TLS proxy (default port 8443; self-hosted coordinators use 8443 too). |
40
+ | `DOMINUSNODE_PROXY_INSECURE` | no | `1` = plaintext gateway on 8080 (local development only; production refuses it). |
41
+
42
+ ## Usage
43
+
44
+ This package has a TypeScript part (`@dominusnode/openai-functions`, on `@dominusnode/sdk` v2) and a Python part
45
+ (`dominusnode-openai-functions`, on the `dominusnode` SDK v2). Both expose the same 19 JSON Schema function
46
+ definitions, kept identical to `functions.json` by tests in each language. The definitions work with any
47
+ OpenAI-compatible tool-calling API.
48
+
49
+ ### TypeScript (openai, Chat Completions)
50
+
51
+ ```ts
52
+ import OpenAI from "openai";
53
+ import { chatCompletionTools, createDominusNodeFunctionHandler } from "@dominusnode/openai-functions";
54
+
55
+ const openai = new OpenAI();
56
+ const handle = createDominusNodeFunctionHandler(); // key from DOMINUSNODE_API_KEY
57
+ const completion = await openai.chat.completions.create({ model: "gpt-4.1", messages, tools: chatCompletionTools });
58
+ for (const call of completion.choices[0].message.tool_calls ?? []) {
59
+ const content = await handle(call.function.name, call.function.arguments); // raw JSON string accepted
60
+ messages.push({ role: "tool", tool_call_id: call.id, content });
61
+ }
62
+ ```
63
+
64
+ For the Responses API use `responsesTools`; the plain definitions are `functionDefinitions` / `functions.json`.
65
+
66
+ ### Python (openai)
67
+
68
+ ```python
69
+ from openai import OpenAI
70
+ from dominusnode_openai_functions import chat_completion_tools, create_dominusnode_function_handler
71
+
72
+ handle = create_dominusnode_function_handler() # key from DOMINUSNODE_API_KEY
73
+ resp = OpenAI().chat.completions.create(model="gpt-4.1", messages=messages, tools=chat_completion_tools())
74
+ for call in resp.choices[0].message.tool_calls or []:
75
+ messages.append({"role": "tool", "tool_call_id": call.id,
76
+ "content": handle(call.function.name, call.function.arguments)})
77
+ ```
78
+
79
+ Handlers return JSON strings; errors are `{"error": "..."}` with credentials scrubbed.
80
+
81
+ ## Tools (v2 standard set, 19)
82
+
83
+ | # | Tool | What it does |
84
+ |---|---|---|
85
+ | 1 | `dominusnode_fetch` | Fetch a public URL through the DominusNode relay network (GET or HEAD only, ports 80/443, no redirects followed, 10 MB cap, private/internal targets refused). |
86
+ | 2 | `dominusnode_proxy_url` | Build a relay gateway proxy URL for use in other HTTP clients. |
87
+ | 3 | `dominusnode_network_policy` | Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries and node attestation terms. |
88
+ | 4 | `dominusnode_network_pricing` | Live per-GB prices by exit IP class (residential, dc, mobile) and node tier revenue shares. |
89
+ | 5 | `dominusnode_network_stats` | Relay network statistics: online nodes, exit countries and IP classes. |
90
+ | 6 | `dominusnode_balance` | Prepaid requester wallet balance (cents) used to pay for relayed traffic. |
91
+ | 7 | `dominusnode_transactions` | Requester wallet transaction history (top-ups, usage debits, credit conversions). |
92
+ | 8 | `dominusnode_topup_crypto` | Create a crypto invoice to top up the requester wallet ($10-$1,000). |
93
+ | 9 | `dominusnode_check_payment` | Status of a crypto top-up invoice created by topup_crypto. |
94
+ | 10 | `dominusnode_usage` | Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range. |
95
+ | 11 | `dominusnode_active_sessions` | Your currently active relay sessions (id, start time, status). |
96
+ | 12 | `dominusnode_report_session` | Report a burned or bad exit for one of your relay sessions so the network can rotate it. |
97
+ | 13 | `dominusnode_node_list` | Relay nodes you contribute (status, observed IP class, last seen). |
98
+ | 14 | `dominusnode_node_tier` | Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation). |
99
+ | 15 | `dominusnode_node_setup_guide` | Step-by-step instructions to earn by contributing an IP: run the DominusNode node agent, register a node, and how tiers and the self-dealing rule work. |
100
+ | 16 | `dominusnode_credits_balance` | Node-operator earnings: pending and available credit (µUSD). |
101
+ | 17 | `dominusnode_credits_convert` | Explains how to convert node credits into wallet balance. Conversion needs a wallet signature, so it runs through the DominusNode MCP server or SDK with a signer key file, not through this tool. |
102
+ | 18 | `dominusnode_payouts_list` | Your USDC payouts of node earnings with status and transaction hash. |
103
+ | 19 | `dominusnode_x402_info` | x402 micropayment info: facilitators, networks and server-reported prices. |
104
+
105
+ Notes for agents:
106
+
107
+ - Prices are never hardcoded: call `network_pricing` for the live per-GB price by IP class (residential, dc, mobile).
108
+ - `fetch` is GET/HEAD only on ports 80/443, follows no redirects and caps bodies at 10 MB. Plaintext `http://`
109
+ needs `allow_http` and is visible to the exit node.
110
+ - Routing: `country` (ISO code; sanctioned countries refused), `ip_class`, `session_id` (sticky exit).
111
+ - Signing flows (node register, withdraw, payout wallet) are deliberately not tools here; use the node agent CLI,
112
+ the DominusNode MCP server, or the SDK with a signer key file.
113
+
114
+ ## Development (JS part)
115
+
116
+ `@dominusnode/sdk` 2.x may not be on npm yet; link the in-repo SDK for local builds and tests:
117
+
118
+ ```bash
119
+ (cd ../../sdks/node && npm ci && npm run build)
120
+ npm install && npm run link-sdk
121
+ npx tsc && npx vitest run
122
+ ```
123
+
124
+ `functions.json` is generated from `core.ts`; after changing a spec rebuild and regenerate it
125
+ (`node -e 'import("./dist/handler.js").then(m=>require("fs").writeFileSync("functions.json", JSON.stringify(m.functionDefinitions,null,2)+"\n"))'`).
126
+ Tests in both languages fail if it is stale.
127
+
128
+ ## Testing
129
+
130
+ ```bash
131
+ pip install -e ../../sdks/python -e '.[dev]' # or npm install for the JS part
132
+ python -m pytest -q python_tests
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,120 @@
1
+ # DominusNode for OpenAI-compatible function calling
2
+
3
+ Gives agents the DominusNode AI-agent relay network: fetch public web pages through exit IPs contributed by
4
+ other agents' nodes, pay per GB from a prepaid crypto wallet, and earn by contributing your own IP with the
5
+ node agent. Built on the official DominusNode SDK v2; HTTP, SSRF/DNS-rebinding protection, OFAC refusal and
6
+ credential scrubbing all come from the SDK.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install @dominusnode/openai-functions # TypeScript
12
+ pip install dominusnode-openai-functions # Python
13
+ ```
14
+
15
+ ## Configuration
16
+
17
+ | Variable | Required | Purpose |
18
+ |---|---|---|
19
+ | `DOMINUSNODE_API_KEY` | yes | Your `dn_live_` API key. Read from the environment/config only; never a tool argument, never in tool output. |
20
+ | `DOMINUSNODE_BASE_URL` | no | API base URL (default `https://api.dominusnode.com`). |
21
+ | `DOMINUSNODE_PROXY_HOST` | no | Gateway host (default `proxy.dominusnode.com`). |
22
+ | `DOMINUSNODE_PROXY_URL` | no | `https://host[:port]` of the gateway's TLS proxy (default port 8443; self-hosted coordinators use 8443 too). |
23
+ | `DOMINUSNODE_PROXY_INSECURE` | no | `1` = plaintext gateway on 8080 (local development only; production refuses it). |
24
+
25
+ ## Usage
26
+
27
+ This package has a TypeScript part (`@dominusnode/openai-functions`, on `@dominusnode/sdk` v2) and a Python part
28
+ (`dominusnode-openai-functions`, on the `dominusnode` SDK v2). Both expose the same 19 JSON Schema function
29
+ definitions, kept identical to `functions.json` by tests in each language. The definitions work with any
30
+ OpenAI-compatible tool-calling API.
31
+
32
+ ### TypeScript (openai, Chat Completions)
33
+
34
+ ```ts
35
+ import OpenAI from "openai";
36
+ import { chatCompletionTools, createDominusNodeFunctionHandler } from "@dominusnode/openai-functions";
37
+
38
+ const openai = new OpenAI();
39
+ const handle = createDominusNodeFunctionHandler(); // key from DOMINUSNODE_API_KEY
40
+ const completion = await openai.chat.completions.create({ model: "gpt-4.1", messages, tools: chatCompletionTools });
41
+ for (const call of completion.choices[0].message.tool_calls ?? []) {
42
+ const content = await handle(call.function.name, call.function.arguments); // raw JSON string accepted
43
+ messages.push({ role: "tool", tool_call_id: call.id, content });
44
+ }
45
+ ```
46
+
47
+ For the Responses API use `responsesTools`; the plain definitions are `functionDefinitions` / `functions.json`.
48
+
49
+ ### Python (openai)
50
+
51
+ ```python
52
+ from openai import OpenAI
53
+ from dominusnode_openai_functions import chat_completion_tools, create_dominusnode_function_handler
54
+
55
+ handle = create_dominusnode_function_handler() # key from DOMINUSNODE_API_KEY
56
+ resp = OpenAI().chat.completions.create(model="gpt-4.1", messages=messages, tools=chat_completion_tools())
57
+ for call in resp.choices[0].message.tool_calls or []:
58
+ messages.append({"role": "tool", "tool_call_id": call.id,
59
+ "content": handle(call.function.name, call.function.arguments)})
60
+ ```
61
+
62
+ Handlers return JSON strings; errors are `{"error": "..."}` with credentials scrubbed.
63
+
64
+ ## Tools (v2 standard set, 19)
65
+
66
+ | # | Tool | What it does |
67
+ |---|---|---|
68
+ | 1 | `dominusnode_fetch` | Fetch a public URL through the DominusNode relay network (GET or HEAD only, ports 80/443, no redirects followed, 10 MB cap, private/internal targets refused). |
69
+ | 2 | `dominusnode_proxy_url` | Build a relay gateway proxy URL for use in other HTTP clients. |
70
+ | 3 | `dominusnode_network_policy` | Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries and node attestation terms. |
71
+ | 4 | `dominusnode_network_pricing` | Live per-GB prices by exit IP class (residential, dc, mobile) and node tier revenue shares. |
72
+ | 5 | `dominusnode_network_stats` | Relay network statistics: online nodes, exit countries and IP classes. |
73
+ | 6 | `dominusnode_balance` | Prepaid requester wallet balance (cents) used to pay for relayed traffic. |
74
+ | 7 | `dominusnode_transactions` | Requester wallet transaction history (top-ups, usage debits, credit conversions). |
75
+ | 8 | `dominusnode_topup_crypto` | Create a crypto invoice to top up the requester wallet ($10-$1,000). |
76
+ | 9 | `dominusnode_check_payment` | Status of a crypto top-up invoice created by topup_crypto. |
77
+ | 10 | `dominusnode_usage` | Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range. |
78
+ | 11 | `dominusnode_active_sessions` | Your currently active relay sessions (id, start time, status). |
79
+ | 12 | `dominusnode_report_session` | Report a burned or bad exit for one of your relay sessions so the network can rotate it. |
80
+ | 13 | `dominusnode_node_list` | Relay nodes you contribute (status, observed IP class, last seen). |
81
+ | 14 | `dominusnode_node_tier` | Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation). |
82
+ | 15 | `dominusnode_node_setup_guide` | Step-by-step instructions to earn by contributing an IP: run the DominusNode node agent, register a node, and how tiers and the self-dealing rule work. |
83
+ | 16 | `dominusnode_credits_balance` | Node-operator earnings: pending and available credit (µUSD). |
84
+ | 17 | `dominusnode_credits_convert` | Explains how to convert node credits into wallet balance. Conversion needs a wallet signature, so it runs through the DominusNode MCP server or SDK with a signer key file, not through this tool. |
85
+ | 18 | `dominusnode_payouts_list` | Your USDC payouts of node earnings with status and transaction hash. |
86
+ | 19 | `dominusnode_x402_info` | x402 micropayment info: facilitators, networks and server-reported prices. |
87
+
88
+ Notes for agents:
89
+
90
+ - Prices are never hardcoded: call `network_pricing` for the live per-GB price by IP class (residential, dc, mobile).
91
+ - `fetch` is GET/HEAD only on ports 80/443, follows no redirects and caps bodies at 10 MB. Plaintext `http://`
92
+ needs `allow_http` and is visible to the exit node.
93
+ - Routing: `country` (ISO code; sanctioned countries refused), `ip_class`, `session_id` (sticky exit).
94
+ - Signing flows (node register, withdraw, payout wallet) are deliberately not tools here; use the node agent CLI,
95
+ the DominusNode MCP server, or the SDK with a signer key file.
96
+
97
+ ## Development (JS part)
98
+
99
+ `@dominusnode/sdk` 2.x may not be on npm yet; link the in-repo SDK for local builds and tests:
100
+
101
+ ```bash
102
+ (cd ../../sdks/node && npm ci && npm run build)
103
+ npm install && npm run link-sdk
104
+ npx tsc && npx vitest run
105
+ ```
106
+
107
+ `functions.json` is generated from `core.ts`; after changing a spec rebuild and regenerate it
108
+ (`node -e 'import("./dist/handler.js").then(m=>require("fs").writeFileSync("functions.json", JSON.stringify(m.functionDefinitions,null,2)+"\n"))'`).
109
+ Tests in both languages fail if it is stale.
110
+
111
+ ## Testing
112
+
113
+ ```bash
114
+ pip install -e ../../sdks/python -e '.[dev]' # or npm install for the JS part
115
+ python -m pytest -q python_tests
116
+ ```
117
+
118
+ ## License
119
+
120
+ MIT
@@ -0,0 +1,67 @@
1
+ """DominusNode v2 OpenAI-compatible function definitions and handler (Python).
2
+
3
+ With the ``openai`` SDK (Chat Completions)::
4
+
5
+ from openai import OpenAI
6
+ from dominusnode_openai_functions import chat_completion_tools, create_dominusnode_function_handler
7
+
8
+ handle = create_dominusnode_function_handler() # key from DOMINUSNODE_API_KEY
9
+ resp = OpenAI().chat.completions.create(model="gpt-4.1", messages=messages, tools=chat_completion_tools())
10
+ for call in resp.choices[0].message.tool_calls or []:
11
+ content = handle(call.function.name, call.function.arguments) # raw JSON string is accepted
12
+ messages.append({"role": "tool", "tool_call_id": call.id, "content": content})
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import copy
18
+ import json
19
+ from typing import Any, Callable, Dict, List, Optional, Union
20
+
21
+ from dominusnode_openai_functions._core import PREFIX, TOOL_NAMES, TOOL_SPECS, DominusNodeTools
22
+
23
+ _DANGEROUS = ("__proto__", "constructor", "prototype")
24
+
25
+
26
+ def function_definitions() -> List[Dict[str, Any]]:
27
+ """The 19 tools as ``{name, description, parameters}`` JSON Schema definitions (same as functions.json)."""
28
+ return [{"name": PREFIX + s["name"], "description": s["description"], "parameters": copy.deepcopy(s["parameters"])}
29
+ for s in TOOL_SPECS]
30
+
31
+
32
+ def chat_completion_tools() -> List[Dict[str, Any]]:
33
+ """Chat Completions ``tools=`` list."""
34
+ return [{"type": "function", "function": f} for f in function_definitions()]
35
+
36
+
37
+ def responses_tools() -> List[Dict[str, Any]]:
38
+ """Responses API ``tools=`` list."""
39
+ return [{"type": "function", **f} for f in function_definitions()]
40
+
41
+
42
+ def create_dominusnode_function_handler(
43
+ api_key: Optional[str] = None, base_url: Optional[str] = None, proxy_host: Optional[str] = None,
44
+ client: Optional[Any] = None,
45
+ ) -> Callable[..., str]:
46
+ """``handler(name, args) -> JSON string``; ``args`` may be a dict or the model's raw JSON string."""
47
+ tools = DominusNodeTools(api_key=api_key, base_url=base_url, proxy_host=proxy_host, client=client)
48
+
49
+ def handler(name: str, args: Union[Dict[str, Any], str, None] = None) -> str:
50
+ if isinstance(args, str):
51
+ try:
52
+ parsed = json.loads(args) if args.strip() else {}
53
+ except ValueError:
54
+ return json.dumps({"error": "arguments must be a JSON object"})
55
+ else:
56
+ parsed = args or {}
57
+ if not isinstance(parsed, dict):
58
+ return json.dumps({"error": "arguments must be a JSON object"})
59
+ parsed = {k: v for k, v in parsed.items() if k not in _DANGEROUS}
60
+ return tools.call(name, parsed)
61
+
62
+ return handler
63
+
64
+
65
+ __version__ = "2.2.0"
66
+ __all__ = ["create_dominusnode_function_handler", "function_definitions", "chat_completion_tools", "responses_tools",
67
+ "DominusNodeTools", "TOOL_NAMES", "__version__"]
@@ -0,0 +1,351 @@
1
+ """DominusNode v2 standard tool set (19 tools), built on the ``dominusnode`` Python SDK.
2
+
3
+ Framework-neutral: each tool has a name, an LLM-facing description and a JSON
4
+ Schema for its arguments. ``DominusNodeTools.call(name, args)`` dispatches to
5
+ the SDK and always returns a JSON string with credentials scrubbed. HTTP,
6
+ SSRF/DNS-rebinding checks, OFAC refusal and signing all live in the SDK.
7
+
8
+ The API key is read only from ``DOMINUSNODE_API_KEY`` (or passed by the host
9
+ application as configuration); it is never a tool argument or tool output.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ import threading
17
+ from typing import Any, Callable, Dict, List, Optional
18
+
19
+ from dominusnode import DominusNodeClient, DominusNodeError, scrub_credentials, json_default
20
+
21
+ PREFIX = "dominusnode_"
22
+ MAX_BODY_CHARS = 20_000
23
+ _KEY_PLACEHOLDER = "${DOMINUSNODE_API_KEY}"
24
+
25
+ _COUNTRY = {"type": "string", "description": "ISO 3166-1 alpha-2 exit country, e.g. US. Sanctioned countries are refused."}
26
+ _IP_CLASS = {"type": "string", "enum": ["residential", "dc", "mobile", "masked"],
27
+ "description": "Exit IP class. Each class has its own per-GB price (see network_pricing); masked = cheapest, VPN/anonymizer exits, opt-in."}
28
+ _SESSION = {"type": "string", "description": "Sticky session id (1-64 alphanumeric) to keep the same exit IP."}
29
+ _ALLOW_HTTP = {"type": "boolean",
30
+ "description": "Opt in to plaintext http:// targets. Plaintext is visible to the exit node; prefer https."}
31
+ _LIMIT = {"type": "integer", "minimum": 1, "description": "Maximum number of items."}
32
+ _OFFSET = {"type": "integer", "minimum": 0, "description": "Items to skip."}
33
+
34
+
35
+ #: credits_convert needs a wallet signature; signing flows are not exposed as LLM tools.
36
+ CREDITS_CONVERT_SIGNING_MESSAGE = (
37
+ "Converting credits needs a fresh signature from the wallet linked to this account, so this tool does not convert. Use the DominusNode MCP server (tool dominusnode_credits_convert, with DOMINUSNODE_WALLET_KEY_FILE pointing at a chmod-600 wallet key file) or a DominusNode SDK's credits.convert with a signer key file. Check available credit with credits_balance."
38
+ )
39
+
40
+
41
+ def _obj(props: Optional[Dict[str, Any]] = None, required: Optional[List[str]] = None) -> Dict[str, Any]:
42
+ return {"type": "object", "properties": props or {}, "required": required or [], "additionalProperties": False}
43
+
44
+
45
+ TOOL_SPECS: List[Dict[str, Any]] = [
46
+ {"name": "fetch", "description": (
47
+ "Fetch a public URL through the DominusNode relay network (GET or HEAD only, ports 80/443, no redirects "
48
+ "followed, 10 MB cap, private/internal targets refused). Billed per GB at the live price for the exit IP "
49
+ "class (see network_pricing). http:// targets need allow_http=true and are visible to the exit node."),
50
+ "parameters": _obj({"url": {"type": "string", "description": "https:// URL (or http:// with allow_http)."},
51
+ "method": {"type": "string", "enum": ["GET", "HEAD"], "description": "HTTP method."},
52
+ "country": _COUNTRY, "ip_class": _IP_CLASS, "session_id": _SESSION,
53
+ "allow_http": _ALLOW_HTTP}, ["url"])},
54
+ {"name": "proxy_url", "description": (
55
+ "Build a relay gateway proxy URL for use in other HTTP clients. Routing flags go in the username "
56
+ "(country-XX, class-..., session-..., allowhttp-1). The password is shown as a ${DOMINUSNODE_API_KEY} "
57
+ "placeholder: substitute the key from the environment, never paste it into chat."),
58
+ "parameters": _obj({"country": _COUNTRY, "ip_class": _IP_CLASS, "session_id": _SESSION, "allow_http": _ALLOW_HTTP,
59
+ "protocol": {"type": "string", "enum": ["https", "socks5", "http"],
60
+ "description": "https (default: TLS to the gateway), socks5, or http (plaintext gateway; local dev only)."}})},
61
+ {"name": "network_policy", "description": (
62
+ "Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries "
63
+ "and node attestation terms."), "parameters": _obj()},
64
+ {"name": "network_pricing", "description": (
65
+ "Live per-GB prices by exit IP class (residential, dc, mobile, masked) and node tier revenue shares. Always use "
66
+ "this for prices; never assume them."), "parameters": _obj()},
67
+ {"name": "network_stats", "description": "Relay network statistics: online nodes, exit countries and IP classes.",
68
+ "parameters": _obj()},
69
+ {"name": "balance", "description": "Prepaid requester wallet balance (cents) used to pay for relayed traffic.",
70
+ "parameters": _obj()},
71
+ {"name": "transactions", "description": "Requester wallet transaction history (top-ups, usage debits, credit conversions).",
72
+ "parameters": _obj({"limit": _LIMIT, "offset": _OFFSET})},
73
+ {"name": "topup_crypto", "description": (
74
+ "Create a crypto invoice to top up the requester wallet ($10-$1,000). Returns a payment address/URL; "
75
+ "the wallet is credited after on-chain confirmation (check with check_payment)."),
76
+ "parameters": _obj({"amount_cents": {"type": "integer", "minimum": 1000, "maximum": 100000,
77
+ "description": "Amount in US cents (1000 = $10)."},
78
+ "currency": {"type": "string", "enum": ["btc", "eth", "ltc", "xmr", "zec", "usdc", "sol",
79
+ "usdt", "dai", "bnb", "link"],
80
+ "description": "Cryptocurrency to pay with."}}, ["amount_cents", "currency"])},
81
+ {"name": "check_payment", "description": "Status of a crypto top-up invoice created by topup_crypto.",
82
+ "parameters": _obj({"invoice_id": {"type": "string", "description": "Invoice UUID."}}, ["invoice_id"])},
83
+ {"name": "usage", "description": "Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range.",
84
+ "parameters": _obj({"from": {"type": "string", "description": "ISO 8601 start."},
85
+ "to": {"type": "string", "description": "ISO 8601 end."}})},
86
+ {"name": "active_sessions", "description": "Your currently active relay sessions (id, start time, status).",
87
+ "parameters": _obj()},
88
+ {"name": "report_session", "description": (
89
+ "Report a burned or bad exit for one of your relay sessions so the network can rotate it."),
90
+ "parameters": _obj({"session_id": {"type": "string", "description": "Session UUID from active_sessions."},
91
+ "reason": {"type": "string", "enum": ["blocked", "captcha", "rate_limited", "slow",
92
+ "bad_content", "other"],
93
+ "description": "Why the exit is bad."}}, ["session_id", "reason"])},
94
+ {"name": "node_list", "description": (
95
+ "Relay nodes you contribute (status, observed IP class, last seen). Contributing IPs earns credit."),
96
+ "parameters": _obj({"limit": _LIMIT, "offset": _OFFSET})},
97
+ {"name": "node_tier", "description": (
98
+ "Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation)."),
99
+ "parameters": _obj()},
100
+ {"name": "node_setup_guide", "description": (
101
+ "Step-by-step instructions to earn by contributing an IP: run the DominusNode node agent, register a node, "
102
+ "and how tiers and the self-dealing rule work."), "parameters": _obj()},
103
+ {"name": "credits_balance", "description": "Node-operator earnings: pending and available credit (µUSD).",
104
+ "parameters": _obj()},
105
+ {"name": "credits_convert", "description": (
106
+ "Explains how to convert node credits into wallet balance. Conversion needs a wallet signature, so it runs through the DominusNode MCP server or SDK with a signer key file, not through this tool."),
107
+ "parameters": _obj({"amount_musd": {"type": "string",
108
+ "description": "Amount in micro-USD as a digit string (min 10000 = $0.01)."}},
109
+ ["amount_musd"])},
110
+ {"name": "payouts_list", "description": "Your USDC payouts of node earnings with status and transaction hash.",
111
+ "parameters": _obj({"limit": _LIMIT, "offset": _OFFSET})},
112
+ {"name": "x402_info", "description": "x402 micropayment info: facilitators, networks and server-reported prices.",
113
+ "parameters": _obj()},
114
+ ]
115
+
116
+ TOOL_NAMES: List[str] = [t["name"] for t in TOOL_SPECS]
117
+ SPECS_BY_NAME: Dict[str, Dict[str, Any]] = {t["name"]: t for t in TOOL_SPECS}
118
+
119
+ NODE_SETUP_GUIDE = "\n".join([
120
+ "DominusNode relay node setup (earn by contributing an IP)",
121
+ "",
122
+ "How earning works",
123
+ " - Your node dials OUT to the gateway (no inbound ports) and relays other agents' traffic.",
124
+ " - You earn your tier's share of each paid GB relayed; live prices and shares: network_pricing.",
125
+ " - Self-dealing earns nothing: your own, your wallet's, team's or /24's traffic never routes to your node.",
126
+ " - Tiers rise with paid tenure, uptime, active IPs and reputation (node_tier).",
127
+ " - Nodes only dial ports 80/443, never private/metadata targets or SMTP/SSH/RDP (network_policy).",
128
+ "",
129
+ "Steps",
130
+ " 1. Store your owner wallet key in a private file (never pass it to a tool or chat):",
131
+ " umask 077 && printf '%s' \"$WALLET_PRIVATE_KEY_HEX\" > ~/.dominusnode/wallet.key",
132
+ " 2. On the machine whose IP you contribute (Docker), register with the published image",
133
+ " (official coordinator pins baked in; a plain source build has none and refuses to start):",
134
+ " sudo install -d -o 1001 -g 1001 -m 700 /etc/dominusnode",
135
+ " sudo install -o 1001 -g 1001 -m 600 ~/.dominusnode/wallet.key /etc/dominusnode/wallet.key",
136
+ " export DN_API_KEY # your dn_live_ key, from the environment only",
137
+ " docker run --rm -e DN_API_KEY -e DN_WALLET_KEY_FILE=/run/dominusnode/wallet.key \\",
138
+ " -v /etc/dominusnode:/run/dominusnode ghcr.io/0xcircuitbreaker/dominusnode-node register",
139
+ " sudo rm /etc/dominusnode/wallet.key # not needed after registration",
140
+ " 3. Run it and keep it running - uptime drives your tier:",
141
+ " docker run -d --name dominusnode-node --restart unless-stopped \\",
142
+ " -v /etc/dominusnode/node.json:/run/dominusnode/node.json:ro ghcr.io/0xcircuitbreaker/dominusnode-node",
143
+ " 4. Verify with node_list and node_tier; earnings appear in credits_balance.",
144
+ "",
145
+ "Signing flows (node register, withdraw, payout wallet) are not available as tools here; use the",
146
+ "node agent CLI, the DominusNode MCP server or the SDK with a signer key file.",
147
+ ])
148
+
149
+
150
+ #: Gateway TLS (HTTPS proxy) port: production 8443; self-hosted coordinators use 8443 too.
151
+ DEFAULT_TLS_PROXY_PORT = 8443
152
+
153
+
154
+ def resolve_gateway(
155
+ proxy_host: Optional[str] = None,
156
+ tls_port: Optional[int] = None,
157
+ http_port: Optional[int] = None,
158
+ insecure: Optional[bool] = None,
159
+ env: Optional[Any] = None,
160
+ ) -> Dict[str, Any]:
161
+ """Where proxy traffic goes. TLS to the gateway's HTTPS proxy is the default: production
162
+ gateways refuse credentials on plaintext 8080/1080. ``DOMINUSNODE_PROXY_URL=https://host[:port]``
163
+ sets host and TLS port (same variable as the MCP server), ``DOMINUSNODE_PROXY_TLS_PORT`` the TLS
164
+ port, and ``DOMINUSNODE_PROXY_INSECURE=1`` selects plaintext (local development only).
165
+
166
+ Returns ``{"proxy_host", "http_port", "gateway_tls"}`` where ``gateway_tls`` is ``False`` or
167
+ ``{"port": n}``, ready for ``DominusNodeClient(gateway_tls=...)``.
168
+ """
169
+ from urllib.parse import urlsplit
170
+
171
+ e = os.environ if env is None else env
172
+
173
+ def _port(v: Any) -> Optional[int]:
174
+ try:
175
+ n = int(v)
176
+ except (TypeError, ValueError):
177
+ return None
178
+ return n if 1 <= n <= 65535 else None
179
+
180
+ url_host: Optional[str] = None
181
+ url_port: Optional[int] = None
182
+ raw = (e.get("DOMINUSNODE_PROXY_URL") or "").strip()
183
+ if raw:
184
+ u = urlsplit(raw)
185
+ if u.scheme != "https" or not u.hostname:
186
+ raise ValueError("DOMINUSNODE_PROXY_URL must be https://host[:port] (TLS to the gateway)")
187
+ if u.username or u.password:
188
+ raise ValueError("DOMINUSNODE_PROXY_URL must not contain credentials")
189
+ url_host = u.hostname.lower()
190
+ try:
191
+ url_port = _port(u.port) if u.port else DEFAULT_TLS_PROXY_PORT
192
+ except ValueError:
193
+ raise ValueError("DOMINUSNODE_PROXY_URL has an invalid port") from None
194
+ if insecure is None:
195
+ insecure = (e.get("DOMINUSNODE_PROXY_INSECURE") or "").strip() == "1"
196
+ if insecure and raw:
197
+ raise ValueError("DOMINUSNODE_PROXY_URL (TLS) and DOMINUSNODE_PROXY_INSECURE=1 are mutually exclusive")
198
+ tls = _port(tls_port) or url_port or _port(e.get("DOMINUSNODE_PROXY_TLS_PORT")) or DEFAULT_TLS_PROXY_PORT
199
+ return {
200
+ "proxy_host": proxy_host or url_host or (e.get("DOMINUSNODE_PROXY_HOST") or "").strip() or None,
201
+ "http_port": _port(http_port) or _port(e.get("DOMINUSNODE_PROXY_PORT")),
202
+ "gateway_tls": False if insecure else {"port": tls},
203
+ }
204
+
205
+
206
+ def _scrub(text: str, key: Optional[str]) -> str:
207
+ out = scrub_credentials(text)
208
+ if key:
209
+ out = out.replace(key, "[REDACTED]")
210
+ return out
211
+
212
+
213
+ def _opt_int(args: Dict[str, Any], name: str) -> Optional[int]:
214
+ v = args.get(name)
215
+ if v is None:
216
+ return None
217
+ if isinstance(v, bool):
218
+ raise ValueError(f"{name} must be an integer")
219
+ return int(v)
220
+
221
+
222
+ class DominusNodeTools:
223
+ """Dispatches the 19 standard tools to a connected ``DominusNodeClient``."""
224
+
225
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None,
226
+ proxy_host: Optional[str] = None, client: Optional[Any] = None,
227
+ tls_proxy_port: Optional[int] = None, insecure_proxy: Optional[bool] = None) -> None:
228
+ self._api_key = api_key or os.environ.get("DOMINUSNODE_API_KEY") or None
229
+ self._base_url = base_url or os.environ.get("DOMINUSNODE_BASE_URL") or None
230
+ self._gateway = resolve_gateway(proxy_host, tls_proxy_port, None, insecure_proxy)
231
+ self._proxy_host = self._gateway["proxy_host"]
232
+ self._client = client
233
+ self._connected = client is not None
234
+ self._lock = threading.Lock()
235
+
236
+ def __repr__(self) -> str:
237
+ return "DominusNodeTools(api_key=[REDACTED])"
238
+
239
+ __str__ = __repr__
240
+
241
+ def _sdk(self) -> Any:
242
+ with self._lock:
243
+ if self._client is None:
244
+ kw: Dict[str, Any] = {}
245
+ if self._base_url:
246
+ kw["base_url"] = self._base_url
247
+ if self._proxy_host:
248
+ kw["proxy_host"] = self._proxy_host
249
+ if self._gateway["http_port"]:
250
+ kw["http_proxy_port"] = self._gateway["http_port"]
251
+ kw["gateway_tls"] = self._gateway["gateway_tls"]
252
+ self._client = DominusNodeClient(**kw)
253
+ if not self._connected:
254
+ if not self._api_key:
255
+ raise DominusNodeError("DOMINUSNODE_API_KEY is not set")
256
+ self._client.connect_with_key(self._api_key)
257
+ self._connected = True
258
+ return self._client
259
+
260
+ # ---- tool implementations -------------------------------------------------
261
+ def _fetch(self, a: Dict[str, Any]) -> Any:
262
+ r = self._sdk().proxy.fetch(
263
+ str(a["url"]), method=str(a.get("method") or "GET").upper(), country=a.get("country"),
264
+ ip_class=a.get("ip_class"), session_id=a.get("session_id"), allow_http=bool(a.get("allow_http", False)))
265
+ body = r.text()
266
+ cut = len(body) > MAX_BODY_CHARS
267
+ return {"status": r.status, "statusText": r.status_text,
268
+ "contentType": (r.headers or {}).get("content-type"),
269
+ "location": (r.headers or {}).get("location"),
270
+ "truncated": bool(r.truncated or cut), "body": body[:MAX_BODY_CHARS]}
271
+
272
+ def _proxy_url(self, a: Dict[str, Any]) -> Any:
273
+ c = self._sdk()
274
+ url = c.proxy.build_url(country=a.get("country"), ip_class=a.get("ip_class"), session_id=a.get("session_id"),
275
+ allow_http=bool(a.get("allow_http", False)), protocol=a.get("protocol") or None)
276
+ user = url.split("://", 1)[1].split(":", 1)[0]
277
+ prefix, rest = url.split("@", 1)[0], url.split("@", 1)[1]
278
+ scheme = prefix.split("://", 1)[0]
279
+ return {"proxyUrl": f"{scheme}://{user}:{_KEY_PLACEHOLDER}@{rest}", "username": user,
280
+ "note": "Replace the placeholder with DOMINUSNODE_API_KEY from your environment."}
281
+
282
+ def _dispatch(self, name: str, a: Dict[str, Any]) -> Any:
283
+ if name == "fetch":
284
+ return self._fetch(a)
285
+ if name == "proxy_url":
286
+ return self._proxy_url(a)
287
+ if name == "node_setup_guide":
288
+ return {"guide": NODE_SETUP_GUIDE}
289
+ if name == "credits_convert":
290
+ return {"status": "signing_required", "message": CREDITS_CONVERT_SIGNING_MESSAGE}
291
+ c = self._sdk()
292
+ if name == "network_policy":
293
+ return c.network.policy()
294
+ if name == "network_pricing":
295
+ return c.network.pricing()
296
+ if name == "network_stats":
297
+ return c.network.stats()
298
+ if name == "balance":
299
+ return c.wallet.get_balance()
300
+ if name == "transactions":
301
+ return c.wallet.get_transactions(limit=_opt_int(a, "limit"), offset=_opt_int(a, "offset"))
302
+ if name == "topup_crypto":
303
+ return c.wallet.top_up_crypto(int(a["amount_cents"]), str(a["currency"]))
304
+ if name == "check_payment":
305
+ return c.wallet.get_crypto_status(str(a["invoice_id"]))
306
+ if name == "usage":
307
+ return c.usage.get(from_=a.get("from"), to=a.get("to"))
308
+ if name == "active_sessions":
309
+ return c.sessions.get_active()
310
+ if name == "report_session":
311
+ return c.sessions.report(str(a["session_id"]), str(a["reason"]))
312
+ if name == "node_list":
313
+ return c.nodes.list(limit=_opt_int(a, "limit"), offset=_opt_int(a, "offset"))
314
+ if name == "node_tier":
315
+ return c.nodes.tier()
316
+ if name == "credits_balance":
317
+ return c.credits.balance()
318
+ if name == "payouts_list":
319
+ return c.payouts.list(limit=_opt_int(a, "limit"), offset=_opt_int(a, "offset"))
320
+ if name == "x402_info":
321
+ return c.x402.get_info()
322
+ raise ValueError(f"unknown tool: {name}")
323
+
324
+ def call(self, name: str, args: Optional[Dict[str, Any]] = None) -> str:
325
+ """Run a tool by its standard name (prefix optional). Always returns a scrubbed JSON string."""
326
+ if name.startswith(PREFIX):
327
+ name = name[len(PREFIX):]
328
+ args = {k: v for k, v in (args or {}).items() if v is not None}
329
+ try:
330
+ if name not in SPECS_BY_NAME:
331
+ raise ValueError(f"unknown tool: {name}")
332
+ allowed = SPECS_BY_NAME[name]["parameters"]["properties"]
333
+ unknown = [k for k in args if k not in allowed]
334
+ if unknown:
335
+ raise ValueError(f"unexpected argument(s): {', '.join(sorted(unknown))}")
336
+ out = json.dumps(self._dispatch(name, args), default=json_default)
337
+ except Exception as err: # noqa: BLE001 - every error is returned to the model, scrubbed
338
+ msg = f"{type(err).__name__}: {err}" if not isinstance(err, DominusNodeError) else str(err)
339
+ out = json.dumps({"error": msg})
340
+ return _scrub(out, self._api_key)
341
+
342
+
343
+ def make_tool_callables(tools: DominusNodeTools) -> Dict[str, Callable[..., str]]:
344
+ """``{prefixed_name: fn(**kwargs) -> str}`` for frameworks that take plain functions."""
345
+ def mk(n: str) -> Callable[..., str]:
346
+ def fn(**kwargs: Any) -> str:
347
+ return tools.call(n, kwargs)
348
+ fn.__name__ = PREFIX + n
349
+ fn.__doc__ = SPECS_BY_NAME[n]["description"]
350
+ return fn
351
+ return {PREFIX + n: mk(n) for n in TOOL_NAMES}
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: dominusnode-openai-functions
3
+ Version: 2.2.0
4
+ Summary: DominusNode v2 OpenAI-compatible function definitions and handler, built on the dominusnode SDK
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/0xcircuitbreaker/DomiNode/tree/master/integrations/openai-functions
7
+ Project-URL: Repository, https://github.com/0xcircuitbreaker/DomiNode
8
+ Project-URL: Issues, https://github.com/0xcircuitbreaker/DomiNode/issues
9
+ Project-URL: Changelog, https://github.com/0xcircuitbreaker/DomiNode/blob/master/integrations/openai-functions/CHANGELOG.md
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: dominusnode<3,>=2.2.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=7.0; extra == "dev"
16
+ Dynamic: license-file
17
+
18
+ # DominusNode for OpenAI-compatible function calling
19
+
20
+ Gives agents the DominusNode AI-agent relay network: fetch public web pages through exit IPs contributed by
21
+ other agents' nodes, pay per GB from a prepaid crypto wallet, and earn by contributing your own IP with the
22
+ node agent. Built on the official DominusNode SDK v2; HTTP, SSRF/DNS-rebinding protection, OFAC refusal and
23
+ credential scrubbing all come from the SDK.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @dominusnode/openai-functions # TypeScript
29
+ pip install dominusnode-openai-functions # Python
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ | Variable | Required | Purpose |
35
+ |---|---|---|
36
+ | `DOMINUSNODE_API_KEY` | yes | Your `dn_live_` API key. Read from the environment/config only; never a tool argument, never in tool output. |
37
+ | `DOMINUSNODE_BASE_URL` | no | API base URL (default `https://api.dominusnode.com`). |
38
+ | `DOMINUSNODE_PROXY_HOST` | no | Gateway host (default `proxy.dominusnode.com`). |
39
+ | `DOMINUSNODE_PROXY_URL` | no | `https://host[:port]` of the gateway's TLS proxy (default port 8443; self-hosted coordinators use 8443 too). |
40
+ | `DOMINUSNODE_PROXY_INSECURE` | no | `1` = plaintext gateway on 8080 (local development only; production refuses it). |
41
+
42
+ ## Usage
43
+
44
+ This package has a TypeScript part (`@dominusnode/openai-functions`, on `@dominusnode/sdk` v2) and a Python part
45
+ (`dominusnode-openai-functions`, on the `dominusnode` SDK v2). Both expose the same 19 JSON Schema function
46
+ definitions, kept identical to `functions.json` by tests in each language. The definitions work with any
47
+ OpenAI-compatible tool-calling API.
48
+
49
+ ### TypeScript (openai, Chat Completions)
50
+
51
+ ```ts
52
+ import OpenAI from "openai";
53
+ import { chatCompletionTools, createDominusNodeFunctionHandler } from "@dominusnode/openai-functions";
54
+
55
+ const openai = new OpenAI();
56
+ const handle = createDominusNodeFunctionHandler(); // key from DOMINUSNODE_API_KEY
57
+ const completion = await openai.chat.completions.create({ model: "gpt-4.1", messages, tools: chatCompletionTools });
58
+ for (const call of completion.choices[0].message.tool_calls ?? []) {
59
+ const content = await handle(call.function.name, call.function.arguments); // raw JSON string accepted
60
+ messages.push({ role: "tool", tool_call_id: call.id, content });
61
+ }
62
+ ```
63
+
64
+ For the Responses API use `responsesTools`; the plain definitions are `functionDefinitions` / `functions.json`.
65
+
66
+ ### Python (openai)
67
+
68
+ ```python
69
+ from openai import OpenAI
70
+ from dominusnode_openai_functions import chat_completion_tools, create_dominusnode_function_handler
71
+
72
+ handle = create_dominusnode_function_handler() # key from DOMINUSNODE_API_KEY
73
+ resp = OpenAI().chat.completions.create(model="gpt-4.1", messages=messages, tools=chat_completion_tools())
74
+ for call in resp.choices[0].message.tool_calls or []:
75
+ messages.append({"role": "tool", "tool_call_id": call.id,
76
+ "content": handle(call.function.name, call.function.arguments)})
77
+ ```
78
+
79
+ Handlers return JSON strings; errors are `{"error": "..."}` with credentials scrubbed.
80
+
81
+ ## Tools (v2 standard set, 19)
82
+
83
+ | # | Tool | What it does |
84
+ |---|---|---|
85
+ | 1 | `dominusnode_fetch` | Fetch a public URL through the DominusNode relay network (GET or HEAD only, ports 80/443, no redirects followed, 10 MB cap, private/internal targets refused). |
86
+ | 2 | `dominusnode_proxy_url` | Build a relay gateway proxy URL for use in other HTTP clients. |
87
+ | 3 | `dominusnode_network_policy` | Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries and node attestation terms. |
88
+ | 4 | `dominusnode_network_pricing` | Live per-GB prices by exit IP class (residential, dc, mobile) and node tier revenue shares. |
89
+ | 5 | `dominusnode_network_stats` | Relay network statistics: online nodes, exit countries and IP classes. |
90
+ | 6 | `dominusnode_balance` | Prepaid requester wallet balance (cents) used to pay for relayed traffic. |
91
+ | 7 | `dominusnode_transactions` | Requester wallet transaction history (top-ups, usage debits, credit conversions). |
92
+ | 8 | `dominusnode_topup_crypto` | Create a crypto invoice to top up the requester wallet ($10-$1,000). |
93
+ | 9 | `dominusnode_check_payment` | Status of a crypto top-up invoice created by topup_crypto. |
94
+ | 10 | `dominusnode_usage` | Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range. |
95
+ | 11 | `dominusnode_active_sessions` | Your currently active relay sessions (id, start time, status). |
96
+ | 12 | `dominusnode_report_session` | Report a burned or bad exit for one of your relay sessions so the network can rotate it. |
97
+ | 13 | `dominusnode_node_list` | Relay nodes you contribute (status, observed IP class, last seen). |
98
+ | 14 | `dominusnode_node_tier` | Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation). |
99
+ | 15 | `dominusnode_node_setup_guide` | Step-by-step instructions to earn by contributing an IP: run the DominusNode node agent, register a node, and how tiers and the self-dealing rule work. |
100
+ | 16 | `dominusnode_credits_balance` | Node-operator earnings: pending and available credit (µUSD). |
101
+ | 17 | `dominusnode_credits_convert` | Explains how to convert node credits into wallet balance. Conversion needs a wallet signature, so it runs through the DominusNode MCP server or SDK with a signer key file, not through this tool. |
102
+ | 18 | `dominusnode_payouts_list` | Your USDC payouts of node earnings with status and transaction hash. |
103
+ | 19 | `dominusnode_x402_info` | x402 micropayment info: facilitators, networks and server-reported prices. |
104
+
105
+ Notes for agents:
106
+
107
+ - Prices are never hardcoded: call `network_pricing` for the live per-GB price by IP class (residential, dc, mobile).
108
+ - `fetch` is GET/HEAD only on ports 80/443, follows no redirects and caps bodies at 10 MB. Plaintext `http://`
109
+ needs `allow_http` and is visible to the exit node.
110
+ - Routing: `country` (ISO code; sanctioned countries refused), `ip_class`, `session_id` (sticky exit).
111
+ - Signing flows (node register, withdraw, payout wallet) are deliberately not tools here; use the node agent CLI,
112
+ the DominusNode MCP server, or the SDK with a signer key file.
113
+
114
+ ## Development (JS part)
115
+
116
+ `@dominusnode/sdk` 2.x may not be on npm yet; link the in-repo SDK for local builds and tests:
117
+
118
+ ```bash
119
+ (cd ../../sdks/node && npm ci && npm run build)
120
+ npm install && npm run link-sdk
121
+ npx tsc && npx vitest run
122
+ ```
123
+
124
+ `functions.json` is generated from `core.ts`; after changing a spec rebuild and regenerate it
125
+ (`node -e 'import("./dist/handler.js").then(m=>require("fs").writeFileSync("functions.json", JSON.stringify(m.functionDefinitions,null,2)+"\n"))'`).
126
+ Tests in both languages fail if it is stale.
127
+
128
+ ## Testing
129
+
130
+ ```bash
131
+ pip install -e ../../sdks/python -e '.[dev]' # or npm install for the JS part
132
+ python -m pytest -q python_tests
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,12 @@
1
+ CHANGELOG.md
2
+ LICENSE
3
+ MANIFEST.in
4
+ README.md
5
+ pyproject.toml
6
+ dominusnode_openai_functions/__init__.py
7
+ dominusnode_openai_functions/_core.py
8
+ dominusnode_openai_functions.egg-info/PKG-INFO
9
+ dominusnode_openai_functions.egg-info/SOURCES.txt
10
+ dominusnode_openai_functions.egg-info/dependency_links.txt
11
+ dominusnode_openai_functions.egg-info/requires.txt
12
+ dominusnode_openai_functions.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ dominusnode<3,>=2.2.0
2
+
3
+ [dev]
4
+ pytest>=7.0
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dominusnode-openai-functions"
7
+ version = "2.2.0"
8
+ description = "DominusNode v2 OpenAI-compatible function definitions and handler, built on the dominusnode SDK"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ dependencies = ["dominusnode>=2.2.0,<3"]
13
+
14
+ [project.optional-dependencies]
15
+ dev = ["pytest>=7.0"]
16
+
17
+ [tool.setuptools.packages.find]
18
+ include = ["dominusnode_openai_functions*"]
19
+
20
+ [tool.pytest.ini_options]
21
+ testpaths = ["python_tests"]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/0xcircuitbreaker/DomiNode/tree/master/integrations/openai-functions"
25
+ Repository = "https://github.com/0xcircuitbreaker/DomiNode"
26
+ Issues = "https://github.com/0xcircuitbreaker/DomiNode/issues"
27
+ Changelog = "https://github.com/0xcircuitbreaker/DomiNode/blob/master/integrations/openai-functions/CHANGELOG.md"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+