dominusnode-gemini 2.1.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.
- dominusnode_gemini-2.1.0/CHANGELOG.md +52 -0
- dominusnode_gemini-2.1.0/LICENSE +21 -0
- dominusnode_gemini-2.1.0/MANIFEST.in +8 -0
- dominusnode_gemini-2.1.0/PKG-INFO +142 -0
- dominusnode_gemini-2.1.0/README.md +123 -0
- dominusnode_gemini-2.1.0/dominusnode_gemini/__init__.py +58 -0
- dominusnode_gemini-2.1.0/dominusnode_gemini/_core.py +279 -0
- dominusnode_gemini-2.1.0/dominusnode_gemini.egg-info/PKG-INFO +142 -0
- dominusnode_gemini-2.1.0/dominusnode_gemini.egg-info/SOURCES.txt +12 -0
- dominusnode_gemini-2.1.0/dominusnode_gemini.egg-info/dependency_links.txt +1 -0
- dominusnode_gemini-2.1.0/dominusnode_gemini.egg-info/requires.txt +7 -0
- dominusnode_gemini-2.1.0/dominusnode_gemini.egg-info/top_level.txt +1 -0
- dominusnode_gemini-2.1.0/pyproject.toml +28 -0
- dominusnode_gemini-2.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.0.0 (2026-11-06)
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- Rebuilt on the DominusNode SDK v2: exposes exactly the 19 standard relay-network tools
|
|
8
|
+
(fetch, proxy_url, network_policy/pricing/stats, balance, transactions, topup_crypto, check_payment, usage,
|
|
9
|
+
active_sessions, report_session, node_list, node_tier, node_setup_guide, credits_balance, credits_convert,
|
|
10
|
+
payouts_list, x402_info).
|
|
11
|
+
- Removed Stripe/PayPal top-ups, plans, slots, teams/agentic-wallet/key management, email/password and MFA
|
|
12
|
+
flows, hardcoded prices and dc-/residential- pool prefixes.
|
|
13
|
+
- Removed the bundled HTTP client and SSRF code; the SDK now provides HTTP, DNS-rebinding checks, OFAC refusal
|
|
14
|
+
and credential scrubbing.
|
|
15
|
+
- TypeScript handler now uses @dominusnode/sdk v2; the Python handler is the dominusnode_gemini package on the dominusnode SDK v2. functions.json is generated and parity-tested in both languages.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Live pricing via network_pricing, node earnings tools and a node agent setup guide.
|
|
20
|
+
- The API key is read only from DOMINUSNODE_API_KEY/config; proxy_url returns a key placeholder.
|
|
21
|
+
|
|
22
|
+
## 1.0.1
|
|
23
|
+
|
|
24
|
+
- Fix branding: replace "DomiNode" with "Dominus Node" throughout
|
|
25
|
+
|
|
26
|
+
All notable changes to this project will be documented in this file.
|
|
27
|
+
|
|
28
|
+
## [2.1.0] - 2026-11-12
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- The fetch tool's `ip_class` accepts `masked` (cheapest, VPN/anonymizer exits, opt-in; never used unless asked).
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Requires the DominusNode SDK 2.1.0 or later (`masked` support).
|
|
35
|
+
|
|
36
|
+
## [1.0.0] - 2025-02-23
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- Initial release of Dominus Node Gemini / Vertex AI function declarations
|
|
40
|
+
- 22 Gemini-format function declarations in `functions.json`
|
|
41
|
+
- All 21 functions from OpenAI Functions integration adapted to Gemini schema
|
|
42
|
+
- New `dominusnode_topup_paypal` function for PayPal wallet top-ups
|
|
43
|
+
- TypeScript handler (`handler.ts`) with full security suite
|
|
44
|
+
- Python handler (`handler.py`) with full security suite
|
|
45
|
+
- Comprehensive SSRF prevention (private IPs, hex/octal/decimal, DNS rebinding, IPv6)
|
|
46
|
+
- OFAC sanctioned country blocking (CU, IR, KP, RU, SY)
|
|
47
|
+
- Credential sanitization in all error messages
|
|
48
|
+
- Prototype pollution prevention in JSON parsing
|
|
49
|
+
- HTTP method restriction (GET, HEAD, OPTIONS only for proxied fetch)
|
|
50
|
+
- 401 retry logic for expired JWT tokens
|
|
51
|
+
- TypeScript tests (`handler.test.ts`) -- 35+ test cases
|
|
52
|
+
- Python tests (`test_handler.py`) -- 35+ test cases
|
|
@@ -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,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dominusnode-gemini
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: DominusNode v2 function declarations and handler for Google Gemini / Vertex AI, built on the dominusnode SDK
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/0xcircuitbreaker/DomiNode/tree/master/integrations/gemini
|
|
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/gemini/CHANGELOG.md
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: dominusnode<3,>=2.1.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
|
+
Provides-Extra: genai
|
|
17
|
+
Requires-Dist: google-genai>=1.0; extra == "genai"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# DominusNode for Google Gemini / Vertex AI
|
|
21
|
+
|
|
22
|
+
Gives agents the DominusNode AI-agent relay network: fetch public web pages through exit IPs contributed by
|
|
23
|
+
other agents' nodes, pay per GB from a prepaid crypto wallet, and earn by contributing your own IP with the
|
|
24
|
+
node agent. Built on the official DominusNode SDK v2; HTTP, SSRF/DNS-rebinding protection, OFAC refusal and
|
|
25
|
+
credential scrubbing all come from the SDK.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @dominusnode/gemini-functions # TypeScript
|
|
31
|
+
pip install dominusnode-gemini # Python
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
| Variable | Required | Purpose |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `DOMINUSNODE_API_KEY` | yes | Your `dn_live_` API key. Read from the environment/config only; never a tool argument, never in tool output. |
|
|
39
|
+
| `DOMINUSNODE_BASE_URL` | no | API base URL (default `https://api.dominusnode.com`). |
|
|
40
|
+
| `DOMINUSNODE_PROXY_HOST` | no | Gateway host (default `proxy.dominusnode.com`). |
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
This package has a TypeScript part (`@dominusnode/gemini-functions`, on `@dominusnode/sdk` v2) and a Python part
|
|
45
|
+
(`dominusnode-gemini`, on the `dominusnode` SDK v2). Both expose the same 19 declarations, kept identical to
|
|
46
|
+
`functions.json` by tests in each language. Declarations use Gemini's schema subset (uppercase types, `enum`
|
|
47
|
+
with `format: "enum"`).
|
|
48
|
+
|
|
49
|
+
### TypeScript (@google/genai)
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { GoogleGenAI } from "@google/genai";
|
|
53
|
+
import { createDominusNodeFunctionHandler, functionDeclarations } from "@dominusnode/gemini-functions";
|
|
54
|
+
|
|
55
|
+
const ai = new GoogleGenAI({});
|
|
56
|
+
const handle = createDominusNodeFunctionHandler(); // key from DOMINUSNODE_API_KEY
|
|
57
|
+
const res = await ai.models.generateContent({
|
|
58
|
+
model: "gemini-2.5-flash",
|
|
59
|
+
contents: "Fetch https://example.com from a German exit",
|
|
60
|
+
config: { tools: [{ functionDeclarations }] },
|
|
61
|
+
});
|
|
62
|
+
for (const call of res.functionCalls ?? []) {
|
|
63
|
+
const result = await handle(call.name!, call.args ?? {});
|
|
64
|
+
// reply with { functionResponse: { name: call.name, response: JSON.parse(result) } }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Python (google-genai)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from google import genai
|
|
72
|
+
from google.genai import types
|
|
73
|
+
from dominusnode_gemini import create_dominusnode_function_handler, function_declarations
|
|
74
|
+
|
|
75
|
+
handle = create_dominusnode_function_handler() # key from DOMINUSNODE_API_KEY
|
|
76
|
+
tool = types.Tool(function_declarations=function_declarations())
|
|
77
|
+
resp = genai.Client().models.generate_content(
|
|
78
|
+
model="gemini-2.5-flash", contents="What does residential relay traffic cost per GB?",
|
|
79
|
+
config=types.GenerateContentConfig(tools=[tool]))
|
|
80
|
+
for call in resp.function_calls or []:
|
|
81
|
+
result = handle(call.name, call.args or {})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Handlers return JSON strings; errors are `{"error": "..."}` with credentials scrubbed.
|
|
85
|
+
|
|
86
|
+
## Tools (v2 standard set, 19)
|
|
87
|
+
|
|
88
|
+
| # | Tool | What it does |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| 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). |
|
|
91
|
+
| 2 | `dominusnode_proxy_url` | Build a relay gateway proxy URL for use in other HTTP clients. |
|
|
92
|
+
| 3 | `dominusnode_network_policy` | Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries and node attestation terms. |
|
|
93
|
+
| 4 | `dominusnode_network_pricing` | Live per-GB prices by exit IP class (residential, dc, mobile) and node tier revenue shares. |
|
|
94
|
+
| 5 | `dominusnode_network_stats` | Relay network statistics: online nodes, exit countries and IP classes. |
|
|
95
|
+
| 6 | `dominusnode_balance` | Prepaid requester wallet balance (cents) used to pay for relayed traffic. |
|
|
96
|
+
| 7 | `dominusnode_transactions` | Requester wallet transaction history (top-ups, usage debits, credit conversions). |
|
|
97
|
+
| 8 | `dominusnode_topup_crypto` | Create a crypto invoice to top up the requester wallet ($10-$1,000). |
|
|
98
|
+
| 9 | `dominusnode_check_payment` | Status of a crypto top-up invoice created by topup_crypto. |
|
|
99
|
+
| 10 | `dominusnode_usage` | Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range. |
|
|
100
|
+
| 11 | `dominusnode_active_sessions` | Your currently active relay sessions (id, start time, status). |
|
|
101
|
+
| 12 | `dominusnode_report_session` | Report a burned or bad exit for one of your relay sessions so the network can rotate it. |
|
|
102
|
+
| 13 | `dominusnode_node_list` | Relay nodes you contribute (status, observed IP class, last seen). |
|
|
103
|
+
| 14 | `dominusnode_node_tier` | Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation). |
|
|
104
|
+
| 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. |
|
|
105
|
+
| 16 | `dominusnode_credits_balance` | Node-operator earnings: pending and available credit (µUSD). |
|
|
106
|
+
| 17 | `dominusnode_credits_convert` | Convert available node earnings into your own requester wallet balance (idempotent; returns the idempotency key). |
|
|
107
|
+
| 18 | `dominusnode_payouts_list` | Your USDC payouts of node earnings with status and transaction hash. |
|
|
108
|
+
| 19 | `dominusnode_x402_info` | x402 micropayment info: facilitators, networks and server-reported prices. |
|
|
109
|
+
|
|
110
|
+
Notes for agents:
|
|
111
|
+
|
|
112
|
+
- Prices are never hardcoded: call `network_pricing` for the live per-GB price by IP class (residential, dc, mobile).
|
|
113
|
+
- `fetch` is GET/HEAD only on ports 80/443, follows no redirects and caps bodies at 10 MB. Plaintext `http://`
|
|
114
|
+
needs `allow_http` and is visible to the exit node.
|
|
115
|
+
- Routing: `country` (ISO code; sanctioned countries refused), `ip_class`, `session_id` (sticky exit).
|
|
116
|
+
- Signing flows (node register, withdraw, payout wallet) are deliberately not tools here; use the node agent CLI,
|
|
117
|
+
the DominusNode MCP server, or the SDK with a signer key file.
|
|
118
|
+
|
|
119
|
+
## Development (JS part)
|
|
120
|
+
|
|
121
|
+
`@dominusnode/sdk` 2.x may not be on npm yet; link the in-repo SDK for local builds and tests:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
(cd ../../sdks/node && npm ci && npm run build)
|
|
125
|
+
npm install && npm run link-sdk
|
|
126
|
+
npx tsc && npx vitest run
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`functions.json` is generated from `core.ts`; after changing a spec rebuild and regenerate it
|
|
130
|
+
(`node -e 'import("./dist/handler.js").then(m=>require("fs").writeFileSync("functions.json", JSON.stringify(m.functionDeclarations,null,2)+"\n"))'`).
|
|
131
|
+
Tests in both languages fail if it is stale.
|
|
132
|
+
|
|
133
|
+
## Testing
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pip install -e ../../sdks/python -e '.[dev]' # or npm install for the JS part
|
|
137
|
+
python -m pytest -q python_tests
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# DominusNode for Google Gemini / Vertex AI
|
|
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/gemini-functions # TypeScript
|
|
12
|
+
pip install dominusnode-gemini # 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
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
This package has a TypeScript part (`@dominusnode/gemini-functions`, on `@dominusnode/sdk` v2) and a Python part
|
|
26
|
+
(`dominusnode-gemini`, on the `dominusnode` SDK v2). Both expose the same 19 declarations, kept identical to
|
|
27
|
+
`functions.json` by tests in each language. Declarations use Gemini's schema subset (uppercase types, `enum`
|
|
28
|
+
with `format: "enum"`).
|
|
29
|
+
|
|
30
|
+
### TypeScript (@google/genai)
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { GoogleGenAI } from "@google/genai";
|
|
34
|
+
import { createDominusNodeFunctionHandler, functionDeclarations } from "@dominusnode/gemini-functions";
|
|
35
|
+
|
|
36
|
+
const ai = new GoogleGenAI({});
|
|
37
|
+
const handle = createDominusNodeFunctionHandler(); // key from DOMINUSNODE_API_KEY
|
|
38
|
+
const res = await ai.models.generateContent({
|
|
39
|
+
model: "gemini-2.5-flash",
|
|
40
|
+
contents: "Fetch https://example.com from a German exit",
|
|
41
|
+
config: { tools: [{ functionDeclarations }] },
|
|
42
|
+
});
|
|
43
|
+
for (const call of res.functionCalls ?? []) {
|
|
44
|
+
const result = await handle(call.name!, call.args ?? {});
|
|
45
|
+
// reply with { functionResponse: { name: call.name, response: JSON.parse(result) } }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Python (google-genai)
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from google import genai
|
|
53
|
+
from google.genai import types
|
|
54
|
+
from dominusnode_gemini import create_dominusnode_function_handler, function_declarations
|
|
55
|
+
|
|
56
|
+
handle = create_dominusnode_function_handler() # key from DOMINUSNODE_API_KEY
|
|
57
|
+
tool = types.Tool(function_declarations=function_declarations())
|
|
58
|
+
resp = genai.Client().models.generate_content(
|
|
59
|
+
model="gemini-2.5-flash", contents="What does residential relay traffic cost per GB?",
|
|
60
|
+
config=types.GenerateContentConfig(tools=[tool]))
|
|
61
|
+
for call in resp.function_calls or []:
|
|
62
|
+
result = handle(call.name, call.args or {})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Handlers return JSON strings; errors are `{"error": "..."}` with credentials scrubbed.
|
|
66
|
+
|
|
67
|
+
## Tools (v2 standard set, 19)
|
|
68
|
+
|
|
69
|
+
| # | Tool | What it does |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| 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). |
|
|
72
|
+
| 2 | `dominusnode_proxy_url` | Build a relay gateway proxy URL for use in other HTTP clients. |
|
|
73
|
+
| 3 | `dominusnode_network_policy` | Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries and node attestation terms. |
|
|
74
|
+
| 4 | `dominusnode_network_pricing` | Live per-GB prices by exit IP class (residential, dc, mobile) and node tier revenue shares. |
|
|
75
|
+
| 5 | `dominusnode_network_stats` | Relay network statistics: online nodes, exit countries and IP classes. |
|
|
76
|
+
| 6 | `dominusnode_balance` | Prepaid requester wallet balance (cents) used to pay for relayed traffic. |
|
|
77
|
+
| 7 | `dominusnode_transactions` | Requester wallet transaction history (top-ups, usage debits, credit conversions). |
|
|
78
|
+
| 8 | `dominusnode_topup_crypto` | Create a crypto invoice to top up the requester wallet ($10-$1,000). |
|
|
79
|
+
| 9 | `dominusnode_check_payment` | Status of a crypto top-up invoice created by topup_crypto. |
|
|
80
|
+
| 10 | `dominusnode_usage` | Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range. |
|
|
81
|
+
| 11 | `dominusnode_active_sessions` | Your currently active relay sessions (id, start time, status). |
|
|
82
|
+
| 12 | `dominusnode_report_session` | Report a burned or bad exit for one of your relay sessions so the network can rotate it. |
|
|
83
|
+
| 13 | `dominusnode_node_list` | Relay nodes you contribute (status, observed IP class, last seen). |
|
|
84
|
+
| 14 | `dominusnode_node_tier` | Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation). |
|
|
85
|
+
| 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. |
|
|
86
|
+
| 16 | `dominusnode_credits_balance` | Node-operator earnings: pending and available credit (µUSD). |
|
|
87
|
+
| 17 | `dominusnode_credits_convert` | Convert available node earnings into your own requester wallet balance (idempotent; returns the idempotency key). |
|
|
88
|
+
| 18 | `dominusnode_payouts_list` | Your USDC payouts of node earnings with status and transaction hash. |
|
|
89
|
+
| 19 | `dominusnode_x402_info` | x402 micropayment info: facilitators, networks and server-reported prices. |
|
|
90
|
+
|
|
91
|
+
Notes for agents:
|
|
92
|
+
|
|
93
|
+
- Prices are never hardcoded: call `network_pricing` for the live per-GB price by IP class (residential, dc, mobile).
|
|
94
|
+
- `fetch` is GET/HEAD only on ports 80/443, follows no redirects and caps bodies at 10 MB. Plaintext `http://`
|
|
95
|
+
needs `allow_http` and is visible to the exit node.
|
|
96
|
+
- Routing: `country` (ISO code; sanctioned countries refused), `ip_class`, `session_id` (sticky exit).
|
|
97
|
+
- Signing flows (node register, withdraw, payout wallet) are deliberately not tools here; use the node agent CLI,
|
|
98
|
+
the DominusNode MCP server, or the SDK with a signer key file.
|
|
99
|
+
|
|
100
|
+
## Development (JS part)
|
|
101
|
+
|
|
102
|
+
`@dominusnode/sdk` 2.x may not be on npm yet; link the in-repo SDK for local builds and tests:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
(cd ../../sdks/node && npm ci && npm run build)
|
|
106
|
+
npm install && npm run link-sdk
|
|
107
|
+
npx tsc && npx vitest run
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`functions.json` is generated from `core.ts`; after changing a spec rebuild and regenerate it
|
|
111
|
+
(`node -e 'import("./dist/handler.js").then(m=>require("fs").writeFileSync("functions.json", JSON.stringify(m.functionDeclarations,null,2)+"\n"))'`).
|
|
112
|
+
Tests in both languages fail if it is stale.
|
|
113
|
+
|
|
114
|
+
## Testing
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install -e ../../sdks/python -e '.[dev]' # or npm install for the JS part
|
|
118
|
+
python -m pytest -q python_tests
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""DominusNode v2 function declarations and handler for Google Gemini / Vertex AI (Python).
|
|
2
|
+
|
|
3
|
+
With the ``google-genai`` SDK::
|
|
4
|
+
|
|
5
|
+
from google import genai
|
|
6
|
+
from google.genai import types
|
|
7
|
+
from dominusnode_gemini import create_dominusnode_function_handler, function_declarations
|
|
8
|
+
|
|
9
|
+
handle = create_dominusnode_function_handler() # key from DOMINUSNODE_API_KEY
|
|
10
|
+
tool = types.Tool(function_declarations=function_declarations())
|
|
11
|
+
resp = genai.Client().models.generate_content(
|
|
12
|
+
model="gemini-2.5-flash", contents="What does a GB of residential relay cost?",
|
|
13
|
+
config=types.GenerateContentConfig(tools=[tool]))
|
|
14
|
+
for call in resp.function_calls or []:
|
|
15
|
+
result = handle(call.name, call.args or {}) # JSON string -> send back as a function response
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
from dominusnode_gemini._core import PREFIX, TOOL_NAMES, TOOL_SPECS, DominusNodeTools
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def to_declaration(spec: Dict[str, Any]) -> Dict[str, Any]:
|
|
26
|
+
"""Core spec -> Gemini FunctionDeclaration dict (uppercase types; no additionalProperties/min/max)."""
|
|
27
|
+
props: Dict[str, Any] = {}
|
|
28
|
+
for name, p in spec["parameters"]["properties"].items():
|
|
29
|
+
s: Dict[str, Any] = {"type": p["type"].upper(), "description": p["description"]}
|
|
30
|
+
if "enum" in p:
|
|
31
|
+
s["format"] = "enum"
|
|
32
|
+
s["enum"] = list(p["enum"])
|
|
33
|
+
props[name] = s
|
|
34
|
+
return {"name": PREFIX + spec["name"], "description": spec["description"],
|
|
35
|
+
"parameters": {"type": "OBJECT", "properties": props, "required": list(spec["parameters"]["required"])}}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def function_declarations() -> List[Dict[str, Any]]:
|
|
39
|
+
"""The 19 tools as Gemini FunctionDeclaration dicts (accepted by ``types.Tool(function_declarations=...)``)."""
|
|
40
|
+
return [to_declaration(s) for s in TOOL_SPECS]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_dominusnode_function_handler(
|
|
44
|
+
api_key: Optional[str] = None, base_url: Optional[str] = None, proxy_host: Optional[str] = None,
|
|
45
|
+
client: Optional[Any] = None,
|
|
46
|
+
) -> Callable[..., str]:
|
|
47
|
+
"""``handler(name, args) -> JSON string``. ``api_key`` defaults to DOMINUSNODE_API_KEY (host config only)."""
|
|
48
|
+
tools = DominusNodeTools(api_key=api_key, base_url=base_url, proxy_host=proxy_host, client=client)
|
|
49
|
+
|
|
50
|
+
def handler(name: str, args: Optional[Dict[str, Any]] = None) -> str:
|
|
51
|
+
return tools.call(name, dict(args or {}))
|
|
52
|
+
|
|
53
|
+
return handler
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__version__ = "2.1.0"
|
|
57
|
+
__all__ = ["create_dominusnode_function_handler", "function_declarations", "to_declaration", "DominusNodeTools",
|
|
58
|
+
"TOOL_NAMES", "__version__"]
|
|
@@ -0,0 +1,279 @@
|
|
|
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
|
+
def _obj(props: Optional[Dict[str, Any]] = None, required: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
36
|
+
return {"type": "object", "properties": props or {}, "required": required or [], "additionalProperties": False}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
TOOL_SPECS: List[Dict[str, Any]] = [
|
|
40
|
+
{"name": "fetch", "description": (
|
|
41
|
+
"Fetch a public URL through the DominusNode relay network (GET or HEAD only, ports 80/443, no redirects "
|
|
42
|
+
"followed, 10 MB cap, private/internal targets refused). Billed per GB at the live price for the exit IP "
|
|
43
|
+
"class (see network_pricing). http:// targets need allow_http=true and are visible to the exit node."),
|
|
44
|
+
"parameters": _obj({"url": {"type": "string", "description": "https:// URL (or http:// with allow_http)."},
|
|
45
|
+
"method": {"type": "string", "enum": ["GET", "HEAD"], "description": "HTTP method."},
|
|
46
|
+
"country": _COUNTRY, "ip_class": _IP_CLASS, "session_id": _SESSION,
|
|
47
|
+
"allow_http": _ALLOW_HTTP}, ["url"])},
|
|
48
|
+
{"name": "proxy_url", "description": (
|
|
49
|
+
"Build a relay gateway proxy URL for use in other HTTP clients. Routing flags go in the username "
|
|
50
|
+
"(country-XX, class-..., session-..., allowhttp-1). The password is shown as a ${DOMINUSNODE_API_KEY} "
|
|
51
|
+
"placeholder: substitute the key from the environment, never paste it into chat."),
|
|
52
|
+
"parameters": _obj({"country": _COUNTRY, "ip_class": _IP_CLASS, "session_id": _SESSION, "allow_http": _ALLOW_HTTP,
|
|
53
|
+
"protocol": {"type": "string", "enum": ["http", "socks5"], "description": "Proxy protocol."}})},
|
|
54
|
+
{"name": "network_policy", "description": (
|
|
55
|
+
"Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries "
|
|
56
|
+
"and node attestation terms."), "parameters": _obj()},
|
|
57
|
+
{"name": "network_pricing", "description": (
|
|
58
|
+
"Live per-GB prices by exit IP class (residential, dc, mobile, masked) and node tier revenue shares. Always use "
|
|
59
|
+
"this for prices; never assume them."), "parameters": _obj()},
|
|
60
|
+
{"name": "network_stats", "description": "Relay network statistics: online nodes, exit countries and IP classes.",
|
|
61
|
+
"parameters": _obj()},
|
|
62
|
+
{"name": "balance", "description": "Prepaid requester wallet balance (cents) used to pay for relayed traffic.",
|
|
63
|
+
"parameters": _obj()},
|
|
64
|
+
{"name": "transactions", "description": "Requester wallet transaction history (top-ups, usage debits, credit conversions).",
|
|
65
|
+
"parameters": _obj({"limit": _LIMIT, "offset": _OFFSET})},
|
|
66
|
+
{"name": "topup_crypto", "description": (
|
|
67
|
+
"Create a crypto invoice to top up the requester wallet ($10-$1,000). Returns a payment address/URL; "
|
|
68
|
+
"the wallet is credited after on-chain confirmation (check with check_payment)."),
|
|
69
|
+
"parameters": _obj({"amount_cents": {"type": "integer", "minimum": 1000, "maximum": 100000,
|
|
70
|
+
"description": "Amount in US cents (1000 = $10)."},
|
|
71
|
+
"currency": {"type": "string", "enum": ["btc", "eth", "ltc", "xmr", "zec", "usdc", "sol",
|
|
72
|
+
"usdt", "dai", "bnb", "link"],
|
|
73
|
+
"description": "Cryptocurrency to pay with."}}, ["amount_cents", "currency"])},
|
|
74
|
+
{"name": "check_payment", "description": "Status of a crypto top-up invoice created by topup_crypto.",
|
|
75
|
+
"parameters": _obj({"invoice_id": {"type": "string", "description": "Invoice UUID."}}, ["invoice_id"])},
|
|
76
|
+
{"name": "usage", "description": "Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range.",
|
|
77
|
+
"parameters": _obj({"from": {"type": "string", "description": "ISO 8601 start."},
|
|
78
|
+
"to": {"type": "string", "description": "ISO 8601 end."}})},
|
|
79
|
+
{"name": "active_sessions", "description": "Your currently active relay sessions (id, start time, status).",
|
|
80
|
+
"parameters": _obj()},
|
|
81
|
+
{"name": "report_session", "description": (
|
|
82
|
+
"Report a burned or bad exit for one of your relay sessions so the network can rotate it."),
|
|
83
|
+
"parameters": _obj({"session_id": {"type": "string", "description": "Session UUID from active_sessions."},
|
|
84
|
+
"reason": {"type": "string", "enum": ["blocked", "captcha", "rate_limited", "slow",
|
|
85
|
+
"bad_content", "other"],
|
|
86
|
+
"description": "Why the exit is bad."}}, ["session_id", "reason"])},
|
|
87
|
+
{"name": "node_list", "description": (
|
|
88
|
+
"Relay nodes you contribute (status, observed IP class, last seen). Contributing IPs earns credit."),
|
|
89
|
+
"parameters": _obj({"limit": _LIMIT, "offset": _OFFSET})},
|
|
90
|
+
{"name": "node_tier", "description": (
|
|
91
|
+
"Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation)."),
|
|
92
|
+
"parameters": _obj()},
|
|
93
|
+
{"name": "node_setup_guide", "description": (
|
|
94
|
+
"Step-by-step instructions to earn by contributing an IP: run the DominusNode node agent, register a node, "
|
|
95
|
+
"and how tiers and the self-dealing rule work."), "parameters": _obj()},
|
|
96
|
+
{"name": "credits_balance", "description": "Node-operator earnings: pending and available credit (µUSD).",
|
|
97
|
+
"parameters": _obj()},
|
|
98
|
+
{"name": "credits_convert", "description": (
|
|
99
|
+
"Convert available node earnings into your own requester wallet balance (idempotent; returns the idempotency key)."),
|
|
100
|
+
"parameters": _obj({"amount_musd": {"type": "string",
|
|
101
|
+
"description": "Amount in micro-USD as a digit string (min 10000 = $0.01)."}},
|
|
102
|
+
["amount_musd"])},
|
|
103
|
+
{"name": "payouts_list", "description": "Your USDC payouts of node earnings with status and transaction hash.",
|
|
104
|
+
"parameters": _obj({"limit": _LIMIT, "offset": _OFFSET})},
|
|
105
|
+
{"name": "x402_info", "description": "x402 micropayment info: facilitators, networks and server-reported prices.",
|
|
106
|
+
"parameters": _obj()},
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
TOOL_NAMES: List[str] = [t["name"] for t in TOOL_SPECS]
|
|
110
|
+
SPECS_BY_NAME: Dict[str, Dict[str, Any]] = {t["name"]: t for t in TOOL_SPECS}
|
|
111
|
+
|
|
112
|
+
NODE_SETUP_GUIDE = "\n".join([
|
|
113
|
+
"DominusNode relay node setup (earn by contributing an IP)",
|
|
114
|
+
"",
|
|
115
|
+
"How earning works",
|
|
116
|
+
" - Your node dials OUT to the gateway (no inbound ports) and relays other agents' traffic.",
|
|
117
|
+
" - You earn your tier's share of each paid GB relayed; live prices and shares: network_pricing.",
|
|
118
|
+
" - Self-dealing earns nothing: your own, your wallet's, team's or /24's traffic never routes to your node.",
|
|
119
|
+
" - Tiers rise with paid tenure, uptime, active IPs and reputation (node_tier).",
|
|
120
|
+
" - Nodes only dial ports 80/443, never private/metadata targets or SMTP/SSH/RDP (network_policy).",
|
|
121
|
+
"",
|
|
122
|
+
"Steps",
|
|
123
|
+
" 1. Store your owner wallet key in a private file (never pass it to a tool or chat):",
|
|
124
|
+
" umask 077 && printf '%s' \"$WALLET_PRIVATE_KEY_HEX\" > ~/.dominusnode/wallet.key",
|
|
125
|
+
" 2. On the machine whose IP you contribute (Node.js >= 18):",
|
|
126
|
+
" git clone https://github.com/0xcircuitbreaker/DomiNode.git && cd DomiNode",
|
|
127
|
+
" npm ci && npm run build -w @dominusnode/shared -w @dominusnode/node-agent",
|
|
128
|
+
" export DN_API_KEY # your dn_live_ key, from the environment only",
|
|
129
|
+
" node packages/node-agent/dist/src/main.js register --wallet-key-file ~/.dominusnode/wallet.key",
|
|
130
|
+
" 3. Run it and keep it running (systemd/pm2) - uptime drives your tier:",
|
|
131
|
+
" node packages/node-agent/dist/src/main.js run --key-file ~/.dominusnode/node.json",
|
|
132
|
+
" 4. Verify with node_list and node_tier; earnings appear in credits_balance.",
|
|
133
|
+
"",
|
|
134
|
+
"Signing flows (node register, withdraw, payout wallet) are not available as tools here; use the",
|
|
135
|
+
"node agent CLI, the DominusNode MCP server or the SDK with a signer key file.",
|
|
136
|
+
])
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _scrub(text: str, key: Optional[str]) -> str:
|
|
140
|
+
out = scrub_credentials(text)
|
|
141
|
+
if key:
|
|
142
|
+
out = out.replace(key, "[REDACTED]")
|
|
143
|
+
return out
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _opt_int(args: Dict[str, Any], name: str) -> Optional[int]:
|
|
147
|
+
v = args.get(name)
|
|
148
|
+
if v is None:
|
|
149
|
+
return None
|
|
150
|
+
if isinstance(v, bool):
|
|
151
|
+
raise ValueError(f"{name} must be an integer")
|
|
152
|
+
return int(v)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class DominusNodeTools:
|
|
156
|
+
"""Dispatches the 19 standard tools to a connected ``DominusNodeClient``."""
|
|
157
|
+
|
|
158
|
+
def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None,
|
|
159
|
+
proxy_host: Optional[str] = None, client: Optional[Any] = None) -> None:
|
|
160
|
+
self._api_key = api_key or os.environ.get("DOMINUSNODE_API_KEY") or None
|
|
161
|
+
self._base_url = base_url or os.environ.get("DOMINUSNODE_BASE_URL") or None
|
|
162
|
+
self._proxy_host = proxy_host or os.environ.get("DOMINUSNODE_PROXY_HOST") or None
|
|
163
|
+
self._client = client
|
|
164
|
+
self._connected = client is not None
|
|
165
|
+
self._lock = threading.Lock()
|
|
166
|
+
|
|
167
|
+
def __repr__(self) -> str:
|
|
168
|
+
return "DominusNodeTools(api_key=[REDACTED])"
|
|
169
|
+
|
|
170
|
+
__str__ = __repr__
|
|
171
|
+
|
|
172
|
+
def _sdk(self) -> Any:
|
|
173
|
+
with self._lock:
|
|
174
|
+
if self._client is None:
|
|
175
|
+
kw: Dict[str, Any] = {}
|
|
176
|
+
if self._base_url:
|
|
177
|
+
kw["base_url"] = self._base_url
|
|
178
|
+
if self._proxy_host:
|
|
179
|
+
kw["proxy_host"] = self._proxy_host
|
|
180
|
+
self._client = DominusNodeClient(**kw)
|
|
181
|
+
if not self._connected:
|
|
182
|
+
if not self._api_key:
|
|
183
|
+
raise DominusNodeError("DOMINUSNODE_API_KEY is not set")
|
|
184
|
+
self._client.connect_with_key(self._api_key)
|
|
185
|
+
self._connected = True
|
|
186
|
+
return self._client
|
|
187
|
+
|
|
188
|
+
# ---- tool implementations -------------------------------------------------
|
|
189
|
+
def _fetch(self, a: Dict[str, Any]) -> Any:
|
|
190
|
+
r = self._sdk().proxy.fetch(
|
|
191
|
+
str(a["url"]), method=str(a.get("method") or "GET").upper(), country=a.get("country"),
|
|
192
|
+
ip_class=a.get("ip_class"), session_id=a.get("session_id"), allow_http=bool(a.get("allow_http", False)))
|
|
193
|
+
body = r.text()
|
|
194
|
+
cut = len(body) > MAX_BODY_CHARS
|
|
195
|
+
return {"status": r.status, "statusText": r.status_text,
|
|
196
|
+
"contentType": (r.headers or {}).get("content-type"),
|
|
197
|
+
"location": (r.headers or {}).get("location"),
|
|
198
|
+
"truncated": bool(r.truncated or cut), "body": body[:MAX_BODY_CHARS]}
|
|
199
|
+
|
|
200
|
+
def _proxy_url(self, a: Dict[str, Any]) -> Any:
|
|
201
|
+
c = self._sdk()
|
|
202
|
+
url = c.proxy.build_url(country=a.get("country"), ip_class=a.get("ip_class"), session_id=a.get("session_id"),
|
|
203
|
+
allow_http=bool(a.get("allow_http", False)), protocol=str(a.get("protocol") or "http"))
|
|
204
|
+
user = url.split("://", 1)[1].split(":", 1)[0]
|
|
205
|
+
prefix, rest = url.split("@", 1)[0], url.split("@", 1)[1]
|
|
206
|
+
scheme = prefix.split("://", 1)[0]
|
|
207
|
+
return {"proxyUrl": f"{scheme}://{user}:{_KEY_PLACEHOLDER}@{rest}", "username": user,
|
|
208
|
+
"note": "Replace the placeholder with DOMINUSNODE_API_KEY from your environment."}
|
|
209
|
+
|
|
210
|
+
def _dispatch(self, name: str, a: Dict[str, Any]) -> Any:
|
|
211
|
+
if name == "fetch":
|
|
212
|
+
return self._fetch(a)
|
|
213
|
+
if name == "proxy_url":
|
|
214
|
+
return self._proxy_url(a)
|
|
215
|
+
if name == "node_setup_guide":
|
|
216
|
+
return {"guide": NODE_SETUP_GUIDE}
|
|
217
|
+
c = self._sdk()
|
|
218
|
+
if name == "network_policy":
|
|
219
|
+
return c.network.policy()
|
|
220
|
+
if name == "network_pricing":
|
|
221
|
+
return c.network.pricing()
|
|
222
|
+
if name == "network_stats":
|
|
223
|
+
return c.network.stats()
|
|
224
|
+
if name == "balance":
|
|
225
|
+
return c.wallet.get_balance()
|
|
226
|
+
if name == "transactions":
|
|
227
|
+
return c.wallet.get_transactions(limit=_opt_int(a, "limit"), offset=_opt_int(a, "offset"))
|
|
228
|
+
if name == "topup_crypto":
|
|
229
|
+
return c.wallet.top_up_crypto(int(a["amount_cents"]), str(a["currency"]))
|
|
230
|
+
if name == "check_payment":
|
|
231
|
+
return c.wallet.get_crypto_status(str(a["invoice_id"]))
|
|
232
|
+
if name == "usage":
|
|
233
|
+
return c.usage.get(from_=a.get("from"), to=a.get("to"))
|
|
234
|
+
if name == "active_sessions":
|
|
235
|
+
return c.sessions.get_active()
|
|
236
|
+
if name == "report_session":
|
|
237
|
+
return c.sessions.report(str(a["session_id"]), str(a["reason"]))
|
|
238
|
+
if name == "node_list":
|
|
239
|
+
return c.nodes.list(limit=_opt_int(a, "limit"), offset=_opt_int(a, "offset"))
|
|
240
|
+
if name == "node_tier":
|
|
241
|
+
return c.nodes.tier()
|
|
242
|
+
if name == "credits_balance":
|
|
243
|
+
return c.credits.balance()
|
|
244
|
+
if name == "credits_convert":
|
|
245
|
+
return c.credits.convert(str(a["amount_musd"]))
|
|
246
|
+
if name == "payouts_list":
|
|
247
|
+
return c.payouts.list(limit=_opt_int(a, "limit"), offset=_opt_int(a, "offset"))
|
|
248
|
+
if name == "x402_info":
|
|
249
|
+
return c.x402.get_info()
|
|
250
|
+
raise ValueError(f"unknown tool: {name}")
|
|
251
|
+
|
|
252
|
+
def call(self, name: str, args: Optional[Dict[str, Any]] = None) -> str:
|
|
253
|
+
"""Run a tool by its standard name (prefix optional). Always returns a scrubbed JSON string."""
|
|
254
|
+
if name.startswith(PREFIX):
|
|
255
|
+
name = name[len(PREFIX):]
|
|
256
|
+
args = {k: v for k, v in (args or {}).items() if v is not None}
|
|
257
|
+
try:
|
|
258
|
+
if name not in SPECS_BY_NAME:
|
|
259
|
+
raise ValueError(f"unknown tool: {name}")
|
|
260
|
+
allowed = SPECS_BY_NAME[name]["parameters"]["properties"]
|
|
261
|
+
unknown = [k for k in args if k not in allowed]
|
|
262
|
+
if unknown:
|
|
263
|
+
raise ValueError(f"unexpected argument(s): {', '.join(sorted(unknown))}")
|
|
264
|
+
out = json.dumps(self._dispatch(name, args), default=json_default)
|
|
265
|
+
except Exception as err: # noqa: BLE001 - every error is returned to the model, scrubbed
|
|
266
|
+
msg = f"{type(err).__name__}: {err}" if not isinstance(err, DominusNodeError) else str(err)
|
|
267
|
+
out = json.dumps({"error": msg})
|
|
268
|
+
return _scrub(out, self._api_key)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def make_tool_callables(tools: DominusNodeTools) -> Dict[str, Callable[..., str]]:
|
|
272
|
+
"""``{prefixed_name: fn(**kwargs) -> str}`` for frameworks that take plain functions."""
|
|
273
|
+
def mk(n: str) -> Callable[..., str]:
|
|
274
|
+
def fn(**kwargs: Any) -> str:
|
|
275
|
+
return tools.call(n, kwargs)
|
|
276
|
+
fn.__name__ = PREFIX + n
|
|
277
|
+
fn.__doc__ = SPECS_BY_NAME[n]["description"]
|
|
278
|
+
return fn
|
|
279
|
+
return {PREFIX + n: mk(n) for n in TOOL_NAMES}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dominusnode-gemini
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: DominusNode v2 function declarations and handler for Google Gemini / Vertex AI, built on the dominusnode SDK
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/0xcircuitbreaker/DomiNode/tree/master/integrations/gemini
|
|
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/gemini/CHANGELOG.md
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: dominusnode<3,>=2.1.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
|
+
Provides-Extra: genai
|
|
17
|
+
Requires-Dist: google-genai>=1.0; extra == "genai"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# DominusNode for Google Gemini / Vertex AI
|
|
21
|
+
|
|
22
|
+
Gives agents the DominusNode AI-agent relay network: fetch public web pages through exit IPs contributed by
|
|
23
|
+
other agents' nodes, pay per GB from a prepaid crypto wallet, and earn by contributing your own IP with the
|
|
24
|
+
node agent. Built on the official DominusNode SDK v2; HTTP, SSRF/DNS-rebinding protection, OFAC refusal and
|
|
25
|
+
credential scrubbing all come from the SDK.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @dominusnode/gemini-functions # TypeScript
|
|
31
|
+
pip install dominusnode-gemini # Python
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
| Variable | Required | Purpose |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `DOMINUSNODE_API_KEY` | yes | Your `dn_live_` API key. Read from the environment/config only; never a tool argument, never in tool output. |
|
|
39
|
+
| `DOMINUSNODE_BASE_URL` | no | API base URL (default `https://api.dominusnode.com`). |
|
|
40
|
+
| `DOMINUSNODE_PROXY_HOST` | no | Gateway host (default `proxy.dominusnode.com`). |
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
This package has a TypeScript part (`@dominusnode/gemini-functions`, on `@dominusnode/sdk` v2) and a Python part
|
|
45
|
+
(`dominusnode-gemini`, on the `dominusnode` SDK v2). Both expose the same 19 declarations, kept identical to
|
|
46
|
+
`functions.json` by tests in each language. Declarations use Gemini's schema subset (uppercase types, `enum`
|
|
47
|
+
with `format: "enum"`).
|
|
48
|
+
|
|
49
|
+
### TypeScript (@google/genai)
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { GoogleGenAI } from "@google/genai";
|
|
53
|
+
import { createDominusNodeFunctionHandler, functionDeclarations } from "@dominusnode/gemini-functions";
|
|
54
|
+
|
|
55
|
+
const ai = new GoogleGenAI({});
|
|
56
|
+
const handle = createDominusNodeFunctionHandler(); // key from DOMINUSNODE_API_KEY
|
|
57
|
+
const res = await ai.models.generateContent({
|
|
58
|
+
model: "gemini-2.5-flash",
|
|
59
|
+
contents: "Fetch https://example.com from a German exit",
|
|
60
|
+
config: { tools: [{ functionDeclarations }] },
|
|
61
|
+
});
|
|
62
|
+
for (const call of res.functionCalls ?? []) {
|
|
63
|
+
const result = await handle(call.name!, call.args ?? {});
|
|
64
|
+
// reply with { functionResponse: { name: call.name, response: JSON.parse(result) } }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Python (google-genai)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from google import genai
|
|
72
|
+
from google.genai import types
|
|
73
|
+
from dominusnode_gemini import create_dominusnode_function_handler, function_declarations
|
|
74
|
+
|
|
75
|
+
handle = create_dominusnode_function_handler() # key from DOMINUSNODE_API_KEY
|
|
76
|
+
tool = types.Tool(function_declarations=function_declarations())
|
|
77
|
+
resp = genai.Client().models.generate_content(
|
|
78
|
+
model="gemini-2.5-flash", contents="What does residential relay traffic cost per GB?",
|
|
79
|
+
config=types.GenerateContentConfig(tools=[tool]))
|
|
80
|
+
for call in resp.function_calls or []:
|
|
81
|
+
result = handle(call.name, call.args or {})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Handlers return JSON strings; errors are `{"error": "..."}` with credentials scrubbed.
|
|
85
|
+
|
|
86
|
+
## Tools (v2 standard set, 19)
|
|
87
|
+
|
|
88
|
+
| # | Tool | What it does |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| 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). |
|
|
91
|
+
| 2 | `dominusnode_proxy_url` | Build a relay gateway proxy URL for use in other HTTP clients. |
|
|
92
|
+
| 3 | `dominusnode_network_policy` | Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries and node attestation terms. |
|
|
93
|
+
| 4 | `dominusnode_network_pricing` | Live per-GB prices by exit IP class (residential, dc, mobile) and node tier revenue shares. |
|
|
94
|
+
| 5 | `dominusnode_network_stats` | Relay network statistics: online nodes, exit countries and IP classes. |
|
|
95
|
+
| 6 | `dominusnode_balance` | Prepaid requester wallet balance (cents) used to pay for relayed traffic. |
|
|
96
|
+
| 7 | `dominusnode_transactions` | Requester wallet transaction history (top-ups, usage debits, credit conversions). |
|
|
97
|
+
| 8 | `dominusnode_topup_crypto` | Create a crypto invoice to top up the requester wallet ($10-$1,000). |
|
|
98
|
+
| 9 | `dominusnode_check_payment` | Status of a crypto top-up invoice created by topup_crypto. |
|
|
99
|
+
| 10 | `dominusnode_usage` | Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range. |
|
|
100
|
+
| 11 | `dominusnode_active_sessions` | Your currently active relay sessions (id, start time, status). |
|
|
101
|
+
| 12 | `dominusnode_report_session` | Report a burned or bad exit for one of your relay sessions so the network can rotate it. |
|
|
102
|
+
| 13 | `dominusnode_node_list` | Relay nodes you contribute (status, observed IP class, last seen). |
|
|
103
|
+
| 14 | `dominusnode_node_tier` | Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation). |
|
|
104
|
+
| 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. |
|
|
105
|
+
| 16 | `dominusnode_credits_balance` | Node-operator earnings: pending and available credit (µUSD). |
|
|
106
|
+
| 17 | `dominusnode_credits_convert` | Convert available node earnings into your own requester wallet balance (idempotent; returns the idempotency key). |
|
|
107
|
+
| 18 | `dominusnode_payouts_list` | Your USDC payouts of node earnings with status and transaction hash. |
|
|
108
|
+
| 19 | `dominusnode_x402_info` | x402 micropayment info: facilitators, networks and server-reported prices. |
|
|
109
|
+
|
|
110
|
+
Notes for agents:
|
|
111
|
+
|
|
112
|
+
- Prices are never hardcoded: call `network_pricing` for the live per-GB price by IP class (residential, dc, mobile).
|
|
113
|
+
- `fetch` is GET/HEAD only on ports 80/443, follows no redirects and caps bodies at 10 MB. Plaintext `http://`
|
|
114
|
+
needs `allow_http` and is visible to the exit node.
|
|
115
|
+
- Routing: `country` (ISO code; sanctioned countries refused), `ip_class`, `session_id` (sticky exit).
|
|
116
|
+
- Signing flows (node register, withdraw, payout wallet) are deliberately not tools here; use the node agent CLI,
|
|
117
|
+
the DominusNode MCP server, or the SDK with a signer key file.
|
|
118
|
+
|
|
119
|
+
## Development (JS part)
|
|
120
|
+
|
|
121
|
+
`@dominusnode/sdk` 2.x may not be on npm yet; link the in-repo SDK for local builds and tests:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
(cd ../../sdks/node && npm ci && npm run build)
|
|
125
|
+
npm install && npm run link-sdk
|
|
126
|
+
npx tsc && npx vitest run
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`functions.json` is generated from `core.ts`; after changing a spec rebuild and regenerate it
|
|
130
|
+
(`node -e 'import("./dist/handler.js").then(m=>require("fs").writeFileSync("functions.json", JSON.stringify(m.functionDeclarations,null,2)+"\n"))'`).
|
|
131
|
+
Tests in both languages fail if it is stale.
|
|
132
|
+
|
|
133
|
+
## Testing
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pip install -e ../../sdks/python -e '.[dev]' # or npm install for the JS part
|
|
137
|
+
python -m pytest -q python_tests
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CHANGELOG.md
|
|
2
|
+
LICENSE
|
|
3
|
+
MANIFEST.in
|
|
4
|
+
README.md
|
|
5
|
+
pyproject.toml
|
|
6
|
+
dominusnode_gemini/__init__.py
|
|
7
|
+
dominusnode_gemini/_core.py
|
|
8
|
+
dominusnode_gemini.egg-info/PKG-INFO
|
|
9
|
+
dominusnode_gemini.egg-info/SOURCES.txt
|
|
10
|
+
dominusnode_gemini.egg-info/dependency_links.txt
|
|
11
|
+
dominusnode_gemini.egg-info/requires.txt
|
|
12
|
+
dominusnode_gemini.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dominusnode_gemini
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dominusnode-gemini"
|
|
7
|
+
version = "2.1.0"
|
|
8
|
+
description = "DominusNode v2 function declarations and handler for Google Gemini / Vertex AI, built on the dominusnode SDK"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
dependencies = ["dominusnode>=2.1.0,<3"]
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
dev = ["pytest>=7.0"]
|
|
16
|
+
genai = ["google-genai>=1.0"]
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
include = ["dominusnode_gemini*"]
|
|
20
|
+
|
|
21
|
+
[tool.pytest.ini_options]
|
|
22
|
+
testpaths = ["python_tests"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/0xcircuitbreaker/DomiNode/tree/master/integrations/gemini"
|
|
26
|
+
Repository = "https://github.com/0xcircuitbreaker/DomiNode"
|
|
27
|
+
Issues = "https://github.com/0xcircuitbreaker/DomiNode/issues"
|
|
28
|
+
Changelog = "https://github.com/0xcircuitbreaker/DomiNode/blob/master/integrations/gemini/CHANGELOG.md"
|