hermes-spend 0.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.
- hermes_spend-0.1.0/PKG-INFO +221 -0
- hermes_spend-0.1.0/README.md +199 -0
- hermes_spend-0.1.0/hermes_spend/__init__.py +35 -0
- hermes_spend-0.1.0/hermes_spend/registration.py +122 -0
- hermes_spend-0.1.0/hermes_spend/reputation.py +176 -0
- hermes_spend-0.1.0/hermes_spend/skill.py +264 -0
- hermes_spend-0.1.0/hermes_spend/wallet.py +202 -0
- hermes_spend-0.1.0/hermes_spend.egg-info/PKG-INFO +221 -0
- hermes_spend-0.1.0/hermes_spend.egg-info/SOURCES.txt +13 -0
- hermes_spend-0.1.0/hermes_spend.egg-info/dependency_links.txt +1 -0
- hermes_spend-0.1.0/hermes_spend.egg-info/requires.txt +7 -0
- hermes_spend-0.1.0/hermes_spend.egg-info/top_level.txt +1 -0
- hermes_spend-0.1.0/pyproject.toml +36 -0
- hermes_spend-0.1.0/setup.cfg +4 -0
- hermes_spend-0.1.0/tests/test_hermes_spend.py +244 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hermes-spend
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Native wallet, service registration, and reputation ledger for Hermes Agent. Implements Hermes issue #38280.
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Project-URL: Homepage, https://bonanza-labs.com
|
|
7
|
+
Project-URL: Repository, https://github.com/c6zks4gssn-droid/hermes-spend
|
|
8
|
+
Project-URL: Issues, https://github.com/c6zks4gssn-droid/hermes-spend/issues
|
|
9
|
+
Keywords: hermes,agent,wallet,x402,payments,reputation,service-registry,ai,spending
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Provides-Extra: x402
|
|
18
|
+
Requires-Dist: x402-firewall>=0.1.0; extra == "x402"
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# hermes-spend
|
|
24
|
+
|
|
25
|
+
**Native wallet, service registry, and reputation ledger for Hermes Agent.**
|
|
26
|
+
|
|
27
|
+
Directly implements [Hermes Agent GitHub issue #38280](https://github.com/NousResearch/hermes/issues/38280):
|
|
28
|
+
> *"[Feature Request] Native Wallet, Service Registration, Reputation Ledger"*
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install hermes-spend
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## What this gives Hermes agents
|
|
37
|
+
|
|
38
|
+
| Feature | Description |
|
|
39
|
+
|---|---|
|
|
40
|
+
| **Wallet** | Pay x402 endpoints with hard budget caps, vendor lists, and approval callbacks |
|
|
41
|
+
| **Service Registry** | Discover and register paid services by query, category, or price |
|
|
42
|
+
| **Reputation Ledger** | Track vendor trustworthiness — auto-updates on every payment |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from hermes_spend import HermesSpendSkill
|
|
50
|
+
|
|
51
|
+
skill = HermesSpendSkill.from_config({
|
|
52
|
+
"budget_per_session": 10.00,
|
|
53
|
+
"require_approval_above": 2.00,
|
|
54
|
+
"block_vendors": ["untrusted.com"],
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
# Pay an x402 endpoint
|
|
58
|
+
result = await skill.wallet_pay(
|
|
59
|
+
vendor_url="https://data.example.com/report",
|
|
60
|
+
amount_usd=1.50,
|
|
61
|
+
chain="base",
|
|
62
|
+
)
|
|
63
|
+
# → {"status": "ok", "receipt": {"vendor": "data.example.com", ...}}
|
|
64
|
+
|
|
65
|
+
# Check balance
|
|
66
|
+
bal = skill.wallet_balance()
|
|
67
|
+
# → {"available_usd": 8.50, "spent_usd": 1.50, "payment_count": 1}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Wallet
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
# Pay with budget enforcement
|
|
76
|
+
result = await skill.wallet_pay("https://api.example.com/data", 1.50)
|
|
77
|
+
|
|
78
|
+
# Check balance
|
|
79
|
+
bal = skill.wallet_balance()
|
|
80
|
+
print(f"${bal['available_usd']:.2f} remaining")
|
|
81
|
+
|
|
82
|
+
# Full payment history
|
|
83
|
+
history = skill.wallet_history()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Config options:**
|
|
87
|
+
|
|
88
|
+
| Key | Default | Description |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `budget_per_session` | `∞` | Max total spend per session |
|
|
91
|
+
| `require_approval_above` | `None` | Payments ≥ this USD need human approval |
|
|
92
|
+
| `block_vendors` | `[]` | Blocked hostnames |
|
|
93
|
+
| `allow_vendors` | `None` | Allowlist (blocks all others if set) |
|
|
94
|
+
| `max_single_payment` | `∞` | Hard cap per payment |
|
|
95
|
+
| `webhook_url` | `""` | Webhook for approval requests |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Service Registry
|
|
100
|
+
|
|
101
|
+
Agents can register services they provide and discover services provided by others.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# Register a service you offer
|
|
105
|
+
skill.service_register(
|
|
106
|
+
service_id="weather-api-v1",
|
|
107
|
+
name="Real-time Weather",
|
|
108
|
+
endpoint_url="https://weather.example.com/v1",
|
|
109
|
+
price_per_call_usd=0.002,
|
|
110
|
+
chain="base",
|
|
111
|
+
category="data",
|
|
112
|
+
tags=["weather", "realtime"],
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Discover services
|
|
116
|
+
results = skill.service_search("weather", max_price_usd=0.01)
|
|
117
|
+
for svc in results:
|
|
118
|
+
print(f"{svc['name']} — ${svc['price_per_call_usd']:.4f}/call at {svc['endpoint_url']}")
|
|
119
|
+
|
|
120
|
+
# Pay and use a discovered service
|
|
121
|
+
service = results[0]
|
|
122
|
+
receipt = await skill.wallet_pay(service["endpoint_url"], service["price_per_call_usd"])
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Reputation Ledger
|
|
128
|
+
|
|
129
|
+
The reputation ledger auto-updates on every successful payment. Agents can also record data quality events.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# Automatic — every wallet_pay() records "payment_success"
|
|
133
|
+
await skill.wallet_pay("https://good-vendor.com/data", 1.00)
|
|
134
|
+
|
|
135
|
+
# Manual events
|
|
136
|
+
skill.reputation_record("good-vendor.com", "data_quality_good", note="accurate and fast")
|
|
137
|
+
skill.reputation_record("bad-vendor.com", "fraud_detected", note="returned fake data")
|
|
138
|
+
|
|
139
|
+
# Check before paying
|
|
140
|
+
score = skill.reputation_score("shady.example.com")
|
|
141
|
+
if not score["is_trusted"]:
|
|
142
|
+
raise Exception(f"Vendor not trusted: {score['trust_level']}")
|
|
143
|
+
|
|
144
|
+
# Top vendors
|
|
145
|
+
top = skill.reputation_top(limit=5)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Event types and their impact:**
|
|
149
|
+
|
|
150
|
+
| Event | Score delta | Use case |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `data_quality_good` | +1.0 | Service returned accurate data |
|
|
153
|
+
| `payment_success` | +0.5 | Payment completed successfully |
|
|
154
|
+
| `payment_failed` | -1.0 | Payment failed or bounced |
|
|
155
|
+
| `data_quality_bad` | -1.5 | Service returned bad/stale data |
|
|
156
|
+
| `service_unavailable` | -0.5 | Service was down |
|
|
157
|
+
| `payment_disputed` | -2.0 | Payment disputed |
|
|
158
|
+
| `fraud_detected` | -10.0 | Confirmed fraud |
|
|
159
|
+
|
|
160
|
+
**Trust levels:** `high` (score ≥ 1.0) · `medium` (≥ 0) · `low` (> -20) · `blocked` (≤ -20)
|
|
161
|
+
|
|
162
|
+
Scores decay over time (30-day half-life) so recent events matter more than old ones.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Direct module usage
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from hermes_spend import Wallet, ServiceRegistry, ReputationLedger
|
|
170
|
+
|
|
171
|
+
# Wallet
|
|
172
|
+
wallet = Wallet(
|
|
173
|
+
session_budget=10.00,
|
|
174
|
+
require_approval_above=2.00,
|
|
175
|
+
on_approval_needed=lambda receipt: my_approval_fn(receipt),
|
|
176
|
+
)
|
|
177
|
+
receipt = await wallet.pay("https://data.example.com", 1.50)
|
|
178
|
+
|
|
179
|
+
# Service Registry
|
|
180
|
+
registry = ServiceRegistry()
|
|
181
|
+
registry.register(ServiceRecord(
|
|
182
|
+
service_id="svc-1",
|
|
183
|
+
name="My API",
|
|
184
|
+
endpoint_url="https://my-api.com",
|
|
185
|
+
price_per_call_usd=0.001,
|
|
186
|
+
))
|
|
187
|
+
results = registry.search("my")
|
|
188
|
+
|
|
189
|
+
# Reputation Ledger
|
|
190
|
+
ledger = ReputationLedger()
|
|
191
|
+
ledger.record("vendor.com", "payment_success")
|
|
192
|
+
score = ledger.score("vendor.com")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Hermes skill config (hermes-spend.yaml)
|
|
198
|
+
|
|
199
|
+
Place in your Hermes skills directory:
|
|
200
|
+
|
|
201
|
+
```yaml
|
|
202
|
+
name: hermes-spend
|
|
203
|
+
version: "0.1.0"
|
|
204
|
+
description: "Native wallet, service registration, and reputation ledger"
|
|
205
|
+
author: bonanza-labs
|
|
206
|
+
|
|
207
|
+
config:
|
|
208
|
+
budget_per_session: 10.00
|
|
209
|
+
require_approval_above: 2.00
|
|
210
|
+
block_vendors: []
|
|
211
|
+
webhook_url: "${BONANZA_WEBHOOK_URL}"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
Apache 2.0. Built by [Bonanza Labs](https://bonanza-labs.com).
|
|
219
|
+
|
|
220
|
+
For a managed dashboard with approval UI, policy editor, and audit reports:
|
|
221
|
+
→ **[bonanza-labs.com/firewall](https://bonanza-labs.com/firewall)**
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# hermes-spend
|
|
2
|
+
|
|
3
|
+
**Native wallet, service registry, and reputation ledger for Hermes Agent.**
|
|
4
|
+
|
|
5
|
+
Directly implements [Hermes Agent GitHub issue #38280](https://github.com/NousResearch/hermes/issues/38280):
|
|
6
|
+
> *"[Feature Request] Native Wallet, Service Registration, Reputation Ledger"*
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install hermes-spend
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What this gives Hermes agents
|
|
15
|
+
|
|
16
|
+
| Feature | Description |
|
|
17
|
+
|---|---|
|
|
18
|
+
| **Wallet** | Pay x402 endpoints with hard budget caps, vendor lists, and approval callbacks |
|
|
19
|
+
| **Service Registry** | Discover and register paid services by query, category, or price |
|
|
20
|
+
| **Reputation Ledger** | Track vendor trustworthiness — auto-updates on every payment |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from hermes_spend import HermesSpendSkill
|
|
28
|
+
|
|
29
|
+
skill = HermesSpendSkill.from_config({
|
|
30
|
+
"budget_per_session": 10.00,
|
|
31
|
+
"require_approval_above": 2.00,
|
|
32
|
+
"block_vendors": ["untrusted.com"],
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
# Pay an x402 endpoint
|
|
36
|
+
result = await skill.wallet_pay(
|
|
37
|
+
vendor_url="https://data.example.com/report",
|
|
38
|
+
amount_usd=1.50,
|
|
39
|
+
chain="base",
|
|
40
|
+
)
|
|
41
|
+
# → {"status": "ok", "receipt": {"vendor": "data.example.com", ...}}
|
|
42
|
+
|
|
43
|
+
# Check balance
|
|
44
|
+
bal = skill.wallet_balance()
|
|
45
|
+
# → {"available_usd": 8.50, "spent_usd": 1.50, "payment_count": 1}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Wallet
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
# Pay with budget enforcement
|
|
54
|
+
result = await skill.wallet_pay("https://api.example.com/data", 1.50)
|
|
55
|
+
|
|
56
|
+
# Check balance
|
|
57
|
+
bal = skill.wallet_balance()
|
|
58
|
+
print(f"${bal['available_usd']:.2f} remaining")
|
|
59
|
+
|
|
60
|
+
# Full payment history
|
|
61
|
+
history = skill.wallet_history()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Config options:**
|
|
65
|
+
|
|
66
|
+
| Key | Default | Description |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `budget_per_session` | `∞` | Max total spend per session |
|
|
69
|
+
| `require_approval_above` | `None` | Payments ≥ this USD need human approval |
|
|
70
|
+
| `block_vendors` | `[]` | Blocked hostnames |
|
|
71
|
+
| `allow_vendors` | `None` | Allowlist (blocks all others if set) |
|
|
72
|
+
| `max_single_payment` | `∞` | Hard cap per payment |
|
|
73
|
+
| `webhook_url` | `""` | Webhook for approval requests |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Service Registry
|
|
78
|
+
|
|
79
|
+
Agents can register services they provide and discover services provided by others.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Register a service you offer
|
|
83
|
+
skill.service_register(
|
|
84
|
+
service_id="weather-api-v1",
|
|
85
|
+
name="Real-time Weather",
|
|
86
|
+
endpoint_url="https://weather.example.com/v1",
|
|
87
|
+
price_per_call_usd=0.002,
|
|
88
|
+
chain="base",
|
|
89
|
+
category="data",
|
|
90
|
+
tags=["weather", "realtime"],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Discover services
|
|
94
|
+
results = skill.service_search("weather", max_price_usd=0.01)
|
|
95
|
+
for svc in results:
|
|
96
|
+
print(f"{svc['name']} — ${svc['price_per_call_usd']:.4f}/call at {svc['endpoint_url']}")
|
|
97
|
+
|
|
98
|
+
# Pay and use a discovered service
|
|
99
|
+
service = results[0]
|
|
100
|
+
receipt = await skill.wallet_pay(service["endpoint_url"], service["price_per_call_usd"])
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Reputation Ledger
|
|
106
|
+
|
|
107
|
+
The reputation ledger auto-updates on every successful payment. Agents can also record data quality events.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
# Automatic — every wallet_pay() records "payment_success"
|
|
111
|
+
await skill.wallet_pay("https://good-vendor.com/data", 1.00)
|
|
112
|
+
|
|
113
|
+
# Manual events
|
|
114
|
+
skill.reputation_record("good-vendor.com", "data_quality_good", note="accurate and fast")
|
|
115
|
+
skill.reputation_record("bad-vendor.com", "fraud_detected", note="returned fake data")
|
|
116
|
+
|
|
117
|
+
# Check before paying
|
|
118
|
+
score = skill.reputation_score("shady.example.com")
|
|
119
|
+
if not score["is_trusted"]:
|
|
120
|
+
raise Exception(f"Vendor not trusted: {score['trust_level']}")
|
|
121
|
+
|
|
122
|
+
# Top vendors
|
|
123
|
+
top = skill.reputation_top(limit=5)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Event types and their impact:**
|
|
127
|
+
|
|
128
|
+
| Event | Score delta | Use case |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `data_quality_good` | +1.0 | Service returned accurate data |
|
|
131
|
+
| `payment_success` | +0.5 | Payment completed successfully |
|
|
132
|
+
| `payment_failed` | -1.0 | Payment failed or bounced |
|
|
133
|
+
| `data_quality_bad` | -1.5 | Service returned bad/stale data |
|
|
134
|
+
| `service_unavailable` | -0.5 | Service was down |
|
|
135
|
+
| `payment_disputed` | -2.0 | Payment disputed |
|
|
136
|
+
| `fraud_detected` | -10.0 | Confirmed fraud |
|
|
137
|
+
|
|
138
|
+
**Trust levels:** `high` (score ≥ 1.0) · `medium` (≥ 0) · `low` (> -20) · `blocked` (≤ -20)
|
|
139
|
+
|
|
140
|
+
Scores decay over time (30-day half-life) so recent events matter more than old ones.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Direct module usage
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from hermes_spend import Wallet, ServiceRegistry, ReputationLedger
|
|
148
|
+
|
|
149
|
+
# Wallet
|
|
150
|
+
wallet = Wallet(
|
|
151
|
+
session_budget=10.00,
|
|
152
|
+
require_approval_above=2.00,
|
|
153
|
+
on_approval_needed=lambda receipt: my_approval_fn(receipt),
|
|
154
|
+
)
|
|
155
|
+
receipt = await wallet.pay("https://data.example.com", 1.50)
|
|
156
|
+
|
|
157
|
+
# Service Registry
|
|
158
|
+
registry = ServiceRegistry()
|
|
159
|
+
registry.register(ServiceRecord(
|
|
160
|
+
service_id="svc-1",
|
|
161
|
+
name="My API",
|
|
162
|
+
endpoint_url="https://my-api.com",
|
|
163
|
+
price_per_call_usd=0.001,
|
|
164
|
+
))
|
|
165
|
+
results = registry.search("my")
|
|
166
|
+
|
|
167
|
+
# Reputation Ledger
|
|
168
|
+
ledger = ReputationLedger()
|
|
169
|
+
ledger.record("vendor.com", "payment_success")
|
|
170
|
+
score = ledger.score("vendor.com")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Hermes skill config (hermes-spend.yaml)
|
|
176
|
+
|
|
177
|
+
Place in your Hermes skills directory:
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
180
|
+
name: hermes-spend
|
|
181
|
+
version: "0.1.0"
|
|
182
|
+
description: "Native wallet, service registration, and reputation ledger"
|
|
183
|
+
author: bonanza-labs
|
|
184
|
+
|
|
185
|
+
config:
|
|
186
|
+
budget_per_session: 10.00
|
|
187
|
+
require_approval_above: 2.00
|
|
188
|
+
block_vendors: []
|
|
189
|
+
webhook_url: "${BONANZA_WEBHOOK_URL}"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
Apache 2.0. Built by [Bonanza Labs](https://bonanza-labs.com).
|
|
197
|
+
|
|
198
|
+
For a managed dashboard with approval UI, policy editor, and audit reports:
|
|
199
|
+
→ **[bonanza-labs.com/firewall](https://bonanza-labs.com/firewall)**
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
hermes-spend: Native wallet, service registration, and reputation ledger for Hermes Agent.
|
|
3
|
+
|
|
4
|
+
Directly addresses Hermes Agent GitHub issue #38280:
|
|
5
|
+
"[Feature Request] Native Wallet, Service Registration, Reputation Ledger"
|
|
6
|
+
|
|
7
|
+
Install as a Hermes skill:
|
|
8
|
+
hermes skill install bonanza/hermes-spend
|
|
9
|
+
|
|
10
|
+
Or manually:
|
|
11
|
+
pip install hermes-spend
|
|
12
|
+
# Add to your hermes config:
|
|
13
|
+
# skills:
|
|
14
|
+
# - name: hermes-spend
|
|
15
|
+
# config:
|
|
16
|
+
# api_key: "${BONANZA_API_KEY}"
|
|
17
|
+
# budget_per_session: 10.00
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .wallet import Wallet, WalletBalance, PaymentReceipt
|
|
21
|
+
from .registration import ServiceRegistry, ServiceRecord
|
|
22
|
+
from .reputation import ReputationLedger, ReputationScore
|
|
23
|
+
from .skill import HermesSpendSkill
|
|
24
|
+
|
|
25
|
+
__version__ = "0.1.0"
|
|
26
|
+
__all__ = [
|
|
27
|
+
"HermesSpendSkill",
|
|
28
|
+
"Wallet",
|
|
29
|
+
"WalletBalance",
|
|
30
|
+
"PaymentReceipt",
|
|
31
|
+
"ServiceRegistry",
|
|
32
|
+
"ServiceRecord",
|
|
33
|
+
"ReputationLedger",
|
|
34
|
+
"ReputationScore",
|
|
35
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Service Registry: lets Hermes agents discover and register paid services.
|
|
3
|
+
Addresses Hermes issue #38280 — "Service Registration".
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ServiceCategory = Literal["data", "compute", "storage", "api", "media", "other"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ServiceRecord:
|
|
19
|
+
"""A registered service that agents can discover and pay to use."""
|
|
20
|
+
service_id: str
|
|
21
|
+
name: str
|
|
22
|
+
endpoint_url: str
|
|
23
|
+
price_per_call_usd: float
|
|
24
|
+
chain: str = "base"
|
|
25
|
+
category: ServiceCategory = "other"
|
|
26
|
+
description: str = ""
|
|
27
|
+
tags: list[str] = field(default_factory=list)
|
|
28
|
+
registered_at: float = field(default_factory=time.time)
|
|
29
|
+
calls_total: int = 0
|
|
30
|
+
revenue_total_usd: float = 0.0
|
|
31
|
+
|
|
32
|
+
def matches(self, query: str) -> bool:
|
|
33
|
+
q = query.lower()
|
|
34
|
+
return (
|
|
35
|
+
q in self.name.lower()
|
|
36
|
+
or q in self.description.lower()
|
|
37
|
+
or q in self.category.lower()
|
|
38
|
+
or any(q in t.lower() for t in self.tags)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ServiceRegistry:
|
|
43
|
+
"""
|
|
44
|
+
In-memory service registry for Hermes agents.
|
|
45
|
+
|
|
46
|
+
Agents can register services they provide and discover services
|
|
47
|
+
provided by others. Combined with Wallet, agents can pay for
|
|
48
|
+
discovered services automatically.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
registry = ServiceRegistry()
|
|
52
|
+
|
|
53
|
+
# Register your service
|
|
54
|
+
registry.register(ServiceRecord(
|
|
55
|
+
service_id="my-data-api",
|
|
56
|
+
name="Real-time Market Data",
|
|
57
|
+
endpoint_url="https://market.example.com/v1",
|
|
58
|
+
price_per_call_usd=0.001,
|
|
59
|
+
category="data",
|
|
60
|
+
tags=["finance", "realtime"],
|
|
61
|
+
))
|
|
62
|
+
|
|
63
|
+
# Discover services
|
|
64
|
+
results = registry.search("market data")
|
|
65
|
+
service = results[0]
|
|
66
|
+
|
|
67
|
+
# Pay and call
|
|
68
|
+
receipt = await wallet.pay(service.endpoint_url, service.price_per_call_usd)
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self) -> None:
|
|
72
|
+
self._services: dict[str, ServiceRecord] = {}
|
|
73
|
+
self._lock = threading.Lock()
|
|
74
|
+
|
|
75
|
+
def register(self, service: ServiceRecord) -> ServiceRecord:
|
|
76
|
+
with self._lock:
|
|
77
|
+
self._services[service.service_id] = service
|
|
78
|
+
return service
|
|
79
|
+
|
|
80
|
+
def unregister(self, service_id: str) -> bool:
|
|
81
|
+
with self._lock:
|
|
82
|
+
return self._services.pop(service_id, None) is not None
|
|
83
|
+
|
|
84
|
+
def get(self, service_id: str) -> ServiceRecord | None:
|
|
85
|
+
with self._lock:
|
|
86
|
+
return self._services.get(service_id)
|
|
87
|
+
|
|
88
|
+
def search(
|
|
89
|
+
self,
|
|
90
|
+
query: str = "",
|
|
91
|
+
category: ServiceCategory | None = None,
|
|
92
|
+
max_price_usd: float | None = None,
|
|
93
|
+
chain: str | None = None,
|
|
94
|
+
limit: int = 20,
|
|
95
|
+
) -> list[ServiceRecord]:
|
|
96
|
+
with self._lock:
|
|
97
|
+
results = list(self._services.values())
|
|
98
|
+
|
|
99
|
+
if query:
|
|
100
|
+
results = [s for s in results if s.matches(query)]
|
|
101
|
+
if category:
|
|
102
|
+
results = [s for s in results if s.category == category]
|
|
103
|
+
if max_price_usd is not None:
|
|
104
|
+
results = [s for s in results if s.price_per_call_usd <= max_price_usd]
|
|
105
|
+
if chain:
|
|
106
|
+
results = [s for s in results if s.chain == chain]
|
|
107
|
+
|
|
108
|
+
return sorted(results, key=lambda s: s.price_per_call_usd)[:limit]
|
|
109
|
+
|
|
110
|
+
def list_all(self) -> list[ServiceRecord]:
|
|
111
|
+
with self._lock:
|
|
112
|
+
return list(self._services.values())
|
|
113
|
+
|
|
114
|
+
def record_call(self, service_id: str, revenue_usd: float) -> None:
|
|
115
|
+
with self._lock:
|
|
116
|
+
svc = self._services.get(service_id)
|
|
117
|
+
if svc:
|
|
118
|
+
svc.calls_total += 1
|
|
119
|
+
svc.revenue_total_usd += revenue_usd
|
|
120
|
+
|
|
121
|
+
def __len__(self) -> int:
|
|
122
|
+
return len(self._services)
|