helix-agent-sdk 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.
- helix_agent_sdk-0.1.0/PKG-INFO +95 -0
- helix_agent_sdk-0.1.0/README.md +74 -0
- helix_agent_sdk-0.1.0/helix_agent/__init__.py +13 -0
- helix_agent_sdk-0.1.0/helix_agent/client.py +146 -0
- helix_agent_sdk-0.1.0/helix_agent/decorator.py +75 -0
- helix_agent_sdk-0.1.0/helix_agent/guard.py +60 -0
- helix_agent_sdk-0.1.0/helix_agent/py.typed +0 -0
- helix_agent_sdk-0.1.0/helix_agent_sdk.egg-info/PKG-INFO +95 -0
- helix_agent_sdk-0.1.0/helix_agent_sdk.egg-info/SOURCES.txt +15 -0
- helix_agent_sdk-0.1.0/helix_agent_sdk.egg-info/dependency_links.txt +1 -0
- helix_agent_sdk-0.1.0/helix_agent_sdk.egg-info/requires.txt +5 -0
- helix_agent_sdk-0.1.0/helix_agent_sdk.egg-info/top_level.txt +1 -0
- helix_agent_sdk-0.1.0/pyproject.toml +36 -0
- helix_agent_sdk-0.1.0/setup.cfg +4 -0
- helix_agent_sdk-0.1.0/tests/test_client.py +46 -0
- helix_agent_sdk-0.1.0/tests/test_decorator.py +58 -0
- helix_agent_sdk-0.1.0/tests/test_guard.py +24 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: helix-agent-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Helix — self-healing infrastructure for AI agent payments
|
|
5
|
+
Author: Helix Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/adrianhihi/helix
|
|
8
|
+
Project-URL: Repository, https://github.com/adrianhihi/helix
|
|
9
|
+
Keywords: helix,ai-agents,self-healing,payments,web3
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: requests>=2.28.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
20
|
+
Requires-Dist: responses>=0.23; extra == "dev"
|
|
21
|
+
|
|
22
|
+
# helix-agent
|
|
23
|
+
|
|
24
|
+
Python SDK for [Helix](https://github.com/adrianhihi/helix) — self-healing infrastructure for AI agent payments.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install helix-agent
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires a running Helix server:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g @helix-agent/core
|
|
36
|
+
npx helix serve --port 7842
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Method 1: Client
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from helix_agent import HelixClient
|
|
45
|
+
|
|
46
|
+
client = HelixClient(platform="coinbase")
|
|
47
|
+
result = client.repair("AA25 invalid account nonce")
|
|
48
|
+
print(f"Strategy: {result.strategy}") # refresh_nonce
|
|
49
|
+
print(f"Immune: {result.immune}") # True on 2nd call
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Method 2: Decorator
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from helix_agent import helix_wrap
|
|
56
|
+
|
|
57
|
+
@helix_wrap(platform="coinbase", max_retries=3)
|
|
58
|
+
def send_payment(to: str, amount: float):
|
|
59
|
+
return agent.transfer(to, amount)
|
|
60
|
+
|
|
61
|
+
result = send_payment("0x...", 1.5)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Method 3: Context Manager
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from helix_agent import helix_guard
|
|
68
|
+
|
|
69
|
+
with helix_guard("tempo") as guard:
|
|
70
|
+
try:
|
|
71
|
+
result = agent.transfer(to, amount)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
repair = guard.repair(str(e))
|
|
74
|
+
if repair.immune:
|
|
75
|
+
result = agent.transfer(to, amount)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
HELIX_URL=http://localhost:7842 # env var
|
|
82
|
+
|
|
83
|
+
client = HelixClient(
|
|
84
|
+
base_url="http://localhost:7842",
|
|
85
|
+
platform="coinbase",
|
|
86
|
+
agent_id="my-agent-1",
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Supported Platforms
|
|
91
|
+
|
|
92
|
+
- `tempo` — Tempo L1 blockchain
|
|
93
|
+
- `privy` — Privy embedded wallets
|
|
94
|
+
- `coinbase` — Coinbase CDP + AgentKit
|
|
95
|
+
- `generic` — Any HTTP/RPC service
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# helix-agent
|
|
2
|
+
|
|
3
|
+
Python SDK for [Helix](https://github.com/adrianhihi/helix) — self-healing infrastructure for AI agent payments.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install helix-agent
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires a running Helix server:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @helix-agent/core
|
|
15
|
+
npx helix serve --port 7842
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Method 1: Client
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from helix_agent import HelixClient
|
|
24
|
+
|
|
25
|
+
client = HelixClient(platform="coinbase")
|
|
26
|
+
result = client.repair("AA25 invalid account nonce")
|
|
27
|
+
print(f"Strategy: {result.strategy}") # refresh_nonce
|
|
28
|
+
print(f"Immune: {result.immune}") # True on 2nd call
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Method 2: Decorator
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from helix_agent import helix_wrap
|
|
35
|
+
|
|
36
|
+
@helix_wrap(platform="coinbase", max_retries=3)
|
|
37
|
+
def send_payment(to: str, amount: float):
|
|
38
|
+
return agent.transfer(to, amount)
|
|
39
|
+
|
|
40
|
+
result = send_payment("0x...", 1.5)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Method 3: Context Manager
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from helix_agent import helix_guard
|
|
47
|
+
|
|
48
|
+
with helix_guard("tempo") as guard:
|
|
49
|
+
try:
|
|
50
|
+
result = agent.transfer(to, amount)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
repair = guard.repair(str(e))
|
|
53
|
+
if repair.immune:
|
|
54
|
+
result = agent.transfer(to, amount)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
HELIX_URL=http://localhost:7842 # env var
|
|
61
|
+
|
|
62
|
+
client = HelixClient(
|
|
63
|
+
base_url="http://localhost:7842",
|
|
64
|
+
platform="coinbase",
|
|
65
|
+
agent_id="my-agent-1",
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Supported Platforms
|
|
70
|
+
|
|
71
|
+
- `tempo` — Tempo L1 blockchain
|
|
72
|
+
- `privy` — Privy embedded wallets
|
|
73
|
+
- `coinbase` — Coinbase CDP + AgentKit
|
|
74
|
+
- `generic` — Any HTTP/RPC service
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helix Agent — Python SDK for self-healing AI agent payments.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from helix_agent import HelixClient, helix_wrap, helix_guard
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .client import HelixClient
|
|
9
|
+
from .decorator import helix_wrap
|
|
10
|
+
from .guard import helix_guard
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
__all__ = ["HelixClient", "helix_wrap", "helix_guard"]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HelixClient — Core client for Helix REST API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("helix")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class RepairResult:
|
|
18
|
+
"""Result of a repair attempt."""
|
|
19
|
+
success: bool
|
|
20
|
+
immune: bool = False
|
|
21
|
+
strategy: Optional[str] = None
|
|
22
|
+
strategy_params: dict = field(default_factory=dict)
|
|
23
|
+
confidence: float = 0.0
|
|
24
|
+
q_value: float = 0.0
|
|
25
|
+
llm_used: bool = False
|
|
26
|
+
repair_time_ms: float = 0.0
|
|
27
|
+
error: Optional[str] = None
|
|
28
|
+
raw: dict = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_api(cls, data: dict) -> "RepairResult":
|
|
32
|
+
strategy = data.get("strategy") or {}
|
|
33
|
+
failure = data.get("failure") or {}
|
|
34
|
+
return cls(
|
|
35
|
+
success=True,
|
|
36
|
+
immune=data.get("immune", False),
|
|
37
|
+
strategy=strategy.get("name") if strategy else None,
|
|
38
|
+
strategy_params=strategy.get("params", {}) if strategy else {},
|
|
39
|
+
confidence=strategy.get("confidence", 0) if strategy else 0,
|
|
40
|
+
llm_used=failure.get("llmClassified", False),
|
|
41
|
+
repair_time_ms=data.get("repairMs", 0),
|
|
42
|
+
raw=data,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def failed(cls, error: str) -> "RepairResult":
|
|
47
|
+
return cls(success=False, error=error)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class HelixClient:
|
|
51
|
+
"""
|
|
52
|
+
Client for Helix self-healing server.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
base_url: Helix server URL. Default: http://localhost:7842
|
|
56
|
+
timeout: Request timeout in seconds. Default: 10
|
|
57
|
+
agent_id: Identifier for this agent instance.
|
|
58
|
+
platform: Default platform (tempo/privy/coinbase/generic).
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
base_url: Optional[str] = None,
|
|
64
|
+
timeout: int = 10,
|
|
65
|
+
agent_id: Optional[str] = None,
|
|
66
|
+
platform: str = "generic",
|
|
67
|
+
):
|
|
68
|
+
self.base_url = (
|
|
69
|
+
base_url or os.environ.get("HELIX_URL") or "http://localhost:7842"
|
|
70
|
+
)
|
|
71
|
+
self.timeout = timeout
|
|
72
|
+
self.agent_id = agent_id or f"py-{os.getpid()}"
|
|
73
|
+
self.platform = platform
|
|
74
|
+
self._session = requests.Session()
|
|
75
|
+
self._session.headers.update({"Content-Type": "application/json"})
|
|
76
|
+
|
|
77
|
+
def repair(
|
|
78
|
+
self,
|
|
79
|
+
error: str,
|
|
80
|
+
*,
|
|
81
|
+
platform: Optional[str] = None,
|
|
82
|
+
agent_id: Optional[str] = None,
|
|
83
|
+
context: Optional[dict] = None,
|
|
84
|
+
) -> RepairResult:
|
|
85
|
+
"""Send an error to Helix for diagnosis and repair strategy."""
|
|
86
|
+
start = time.monotonic()
|
|
87
|
+
try:
|
|
88
|
+
payload = {
|
|
89
|
+
"error": error,
|
|
90
|
+
"platform": platform or self.platform,
|
|
91
|
+
"agentId": agent_id or self.agent_id,
|
|
92
|
+
}
|
|
93
|
+
if context:
|
|
94
|
+
payload["context"] = context
|
|
95
|
+
|
|
96
|
+
resp = self._session.post(
|
|
97
|
+
f"{self.base_url}/repair", json=payload, timeout=self.timeout
|
|
98
|
+
)
|
|
99
|
+
resp.raise_for_status()
|
|
100
|
+
result = RepairResult.from_api(resp.json())
|
|
101
|
+
result.repair_time_ms = (time.monotonic() - start) * 1000
|
|
102
|
+
|
|
103
|
+
status = "IMMUNE" if result.immune else f"REPAIR -> {result.strategy}"
|
|
104
|
+
logger.info(f"[helix] {status} ({result.repair_time_ms:.0f}ms)")
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
except requests.RequestException as e:
|
|
108
|
+
logger.warning(f"[helix] Server unreachable: {e}")
|
|
109
|
+
return RepairResult.failed(f"Server unreachable: {e}")
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.warning(f"[helix] Repair failed: {e}")
|
|
112
|
+
return RepairResult.failed(str(e))
|
|
113
|
+
|
|
114
|
+
def health(self) -> dict:
|
|
115
|
+
"""Check Helix server health."""
|
|
116
|
+
try:
|
|
117
|
+
return self._session.get(f"{self.base_url}/health", timeout=self.timeout).json()
|
|
118
|
+
except Exception as e:
|
|
119
|
+
return {"status": "unreachable", "error": str(e)}
|
|
120
|
+
|
|
121
|
+
def genes(self) -> dict:
|
|
122
|
+
"""List all genes in the Gene Map."""
|
|
123
|
+
try:
|
|
124
|
+
return self._session.get(f"{self.base_url}/genes", timeout=self.timeout).json()
|
|
125
|
+
except Exception as e:
|
|
126
|
+
return {"error": str(e)}
|
|
127
|
+
|
|
128
|
+
def status(self) -> dict:
|
|
129
|
+
"""Get full server status."""
|
|
130
|
+
try:
|
|
131
|
+
return self._session.get(f"{self.base_url}/status", timeout=self.timeout).json()
|
|
132
|
+
except Exception as e:
|
|
133
|
+
return {"error": str(e)}
|
|
134
|
+
|
|
135
|
+
def is_healthy(self) -> bool:
|
|
136
|
+
"""Quick check if server is reachable."""
|
|
137
|
+
return self.health().get("status") in ("ok", "running")
|
|
138
|
+
|
|
139
|
+
def close(self):
|
|
140
|
+
self._session.close()
|
|
141
|
+
|
|
142
|
+
def __enter__(self):
|
|
143
|
+
return self
|
|
144
|
+
|
|
145
|
+
def __exit__(self, *args):
|
|
146
|
+
self.close()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@helix_wrap — Decorator for automatic error repair.
|
|
3
|
+
|
|
4
|
+
Catches exceptions, sends to Helix, applies repair strategy, retries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
import logging
|
|
9
|
+
import time
|
|
10
|
+
from typing import Optional, Callable, Any
|
|
11
|
+
|
|
12
|
+
from .client import HelixClient, RepairResult
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("helix")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def helix_wrap(
|
|
18
|
+
platform: str = "generic",
|
|
19
|
+
*,
|
|
20
|
+
base_url: Optional[str] = None,
|
|
21
|
+
max_retries: int = 3,
|
|
22
|
+
retry_delay: float = 1.0,
|
|
23
|
+
on_repair: Optional[Callable[[RepairResult], None]] = None,
|
|
24
|
+
on_failure: Optional[Callable[[Exception, RepairResult], Any]] = None,
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Decorator that adds self-healing to any function.
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
@helix_wrap(platform="coinbase", max_retries=3)
|
|
31
|
+
def send_payment(to, amount):
|
|
32
|
+
return agent.transfer(to, amount)
|
|
33
|
+
"""
|
|
34
|
+
client = HelixClient(base_url=base_url, platform=platform)
|
|
35
|
+
|
|
36
|
+
def decorator(func):
|
|
37
|
+
@functools.wraps(func)
|
|
38
|
+
def wrapper(*args, **kwargs):
|
|
39
|
+
last_error = None
|
|
40
|
+
repair = RepairResult.failed("no repair attempted")
|
|
41
|
+
for attempt in range(1, max_retries + 1):
|
|
42
|
+
try:
|
|
43
|
+
return func(*args, **kwargs)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
last_error = e
|
|
46
|
+
logger.info(f"[helix] Attempt {attempt}/{max_retries} failed: {str(e)[:100]}")
|
|
47
|
+
|
|
48
|
+
repair = client.repair(str(e))
|
|
49
|
+
|
|
50
|
+
if not repair.success:
|
|
51
|
+
time.sleep(retry_delay * attempt)
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
if on_repair:
|
|
55
|
+
on_repair(repair)
|
|
56
|
+
|
|
57
|
+
if repair.strategy == "backoff_retry":
|
|
58
|
+
delay = retry_delay * (2 ** attempt)
|
|
59
|
+
elif repair.immune:
|
|
60
|
+
delay = 0.1
|
|
61
|
+
else:
|
|
62
|
+
delay = retry_delay * attempt
|
|
63
|
+
|
|
64
|
+
time.sleep(delay)
|
|
65
|
+
|
|
66
|
+
if on_failure:
|
|
67
|
+
result = on_failure(last_error, repair)
|
|
68
|
+
if result is not None:
|
|
69
|
+
return result
|
|
70
|
+
raise last_error
|
|
71
|
+
|
|
72
|
+
wrapper.helix_client = client
|
|
73
|
+
return wrapper
|
|
74
|
+
|
|
75
|
+
return decorator
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
helix_guard — Context manager for block-level protection.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
|
|
8
|
+
from .client import HelixClient, RepairResult
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("helix")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class helix_guard:
|
|
14
|
+
"""
|
|
15
|
+
Context manager for Helix self-healing.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
with helix_guard("coinbase") as guard:
|
|
19
|
+
try:
|
|
20
|
+
result = agent.transfer(to, amount)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
repair = guard.repair(str(e))
|
|
23
|
+
if repair.immune:
|
|
24
|
+
result = agent.transfer(to, amount)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
platform: str = "generic",
|
|
30
|
+
*,
|
|
31
|
+
base_url: Optional[str] = None,
|
|
32
|
+
agent_id: Optional[str] = None,
|
|
33
|
+
):
|
|
34
|
+
self._client = HelixClient(
|
|
35
|
+
base_url=base_url, platform=platform, agent_id=agent_id
|
|
36
|
+
)
|
|
37
|
+
self.repairs: List[RepairResult] = []
|
|
38
|
+
|
|
39
|
+
def repair(self, error: str, **kwargs) -> RepairResult:
|
|
40
|
+
result = self._client.repair(error, **kwargs)
|
|
41
|
+
self.repairs.append(result)
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
def health(self) -> dict:
|
|
45
|
+
return self._client.health()
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def total_repairs(self) -> int:
|
|
49
|
+
return len(self.repairs)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def immune_count(self) -> int:
|
|
53
|
+
return sum(1 for r in self.repairs if r.immune)
|
|
54
|
+
|
|
55
|
+
def __enter__(self):
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
59
|
+
self._client.close()
|
|
60
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: helix-agent-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Helix — self-healing infrastructure for AI agent payments
|
|
5
|
+
Author: Helix Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/adrianhihi/helix
|
|
8
|
+
Project-URL: Repository, https://github.com/adrianhihi/helix
|
|
9
|
+
Keywords: helix,ai-agents,self-healing,payments,web3
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: requests>=2.28.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
20
|
+
Requires-Dist: responses>=0.23; extra == "dev"
|
|
21
|
+
|
|
22
|
+
# helix-agent
|
|
23
|
+
|
|
24
|
+
Python SDK for [Helix](https://github.com/adrianhihi/helix) — self-healing infrastructure for AI agent payments.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install helix-agent
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires a running Helix server:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g @helix-agent/core
|
|
36
|
+
npx helix serve --port 7842
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Method 1: Client
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from helix_agent import HelixClient
|
|
45
|
+
|
|
46
|
+
client = HelixClient(platform="coinbase")
|
|
47
|
+
result = client.repair("AA25 invalid account nonce")
|
|
48
|
+
print(f"Strategy: {result.strategy}") # refresh_nonce
|
|
49
|
+
print(f"Immune: {result.immune}") # True on 2nd call
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Method 2: Decorator
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from helix_agent import helix_wrap
|
|
56
|
+
|
|
57
|
+
@helix_wrap(platform="coinbase", max_retries=3)
|
|
58
|
+
def send_payment(to: str, amount: float):
|
|
59
|
+
return agent.transfer(to, amount)
|
|
60
|
+
|
|
61
|
+
result = send_payment("0x...", 1.5)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Method 3: Context Manager
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from helix_agent import helix_guard
|
|
68
|
+
|
|
69
|
+
with helix_guard("tempo") as guard:
|
|
70
|
+
try:
|
|
71
|
+
result = agent.transfer(to, amount)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
repair = guard.repair(str(e))
|
|
74
|
+
if repair.immune:
|
|
75
|
+
result = agent.transfer(to, amount)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
HELIX_URL=http://localhost:7842 # env var
|
|
82
|
+
|
|
83
|
+
client = HelixClient(
|
|
84
|
+
base_url="http://localhost:7842",
|
|
85
|
+
platform="coinbase",
|
|
86
|
+
agent_id="my-agent-1",
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Supported Platforms
|
|
91
|
+
|
|
92
|
+
- `tempo` — Tempo L1 blockchain
|
|
93
|
+
- `privy` — Privy embedded wallets
|
|
94
|
+
- `coinbase` — Coinbase CDP + AgentKit
|
|
95
|
+
- `generic` — Any HTTP/RPC service
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
helix_agent/__init__.py
|
|
4
|
+
helix_agent/client.py
|
|
5
|
+
helix_agent/decorator.py
|
|
6
|
+
helix_agent/guard.py
|
|
7
|
+
helix_agent/py.typed
|
|
8
|
+
helix_agent_sdk.egg-info/PKG-INFO
|
|
9
|
+
helix_agent_sdk.egg-info/SOURCES.txt
|
|
10
|
+
helix_agent_sdk.egg-info/dependency_links.txt
|
|
11
|
+
helix_agent_sdk.egg-info/requires.txt
|
|
12
|
+
helix_agent_sdk.egg-info/top_level.txt
|
|
13
|
+
tests/test_client.py
|
|
14
|
+
tests/test_decorator.py
|
|
15
|
+
tests/test_guard.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
helix_agent
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "helix-agent-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for Helix — self-healing infrastructure for AI agent payments"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Helix Team"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["helix", "ai-agents", "self-healing", "payments", "web3"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Software Development :: Libraries",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"requests>=2.28.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = ["pytest>=7.0", "responses>=0.23"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/adrianhihi/helix"
|
|
32
|
+
Repository = "https://github.com/adrianhihi/helix"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["."]
|
|
36
|
+
include = ["helix_agent*"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Tests for HelixClient — uses a real running server."""
|
|
2
|
+
import pytest
|
|
3
|
+
from helix_agent import HelixClient
|
|
4
|
+
|
|
5
|
+
SERVER_PORT = 17842
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def client():
|
|
10
|
+
return HelixClient(base_url=f"http://localhost:{SERVER_PORT}", platform="tempo")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestHelixClient:
|
|
14
|
+
def test_health(self, client):
|
|
15
|
+
h = client.health()
|
|
16
|
+
assert h.get("status") in ("ok", "running")
|
|
17
|
+
|
|
18
|
+
def test_repair_known_error(self, client):
|
|
19
|
+
result = client.repair("nonce mismatch: expected 5, got 3")
|
|
20
|
+
assert result.success
|
|
21
|
+
assert result.strategy is not None
|
|
22
|
+
|
|
23
|
+
def test_immune_on_repeat(self, client):
|
|
24
|
+
r1 = client.repair("session expired, please re-authenticate")
|
|
25
|
+
assert r1.success
|
|
26
|
+
r2 = client.repair("session expired, please re-authenticate")
|
|
27
|
+
assert r2.success
|
|
28
|
+
assert r2.immune
|
|
29
|
+
|
|
30
|
+
def test_genes_list(self, client):
|
|
31
|
+
genes = client.genes()
|
|
32
|
+
assert "genes" in genes or "total" in genes
|
|
33
|
+
|
|
34
|
+
def test_platform_override(self, client):
|
|
35
|
+
result = client.repair("AA25 invalid account nonce", platform="coinbase")
|
|
36
|
+
assert result.success
|
|
37
|
+
|
|
38
|
+
def test_unreachable_server(self):
|
|
39
|
+
bad_client = HelixClient(base_url="http://localhost:19999", timeout=2)
|
|
40
|
+
result = bad_client.repair("test error")
|
|
41
|
+
assert not result.success
|
|
42
|
+
assert "unreachable" in result.error.lower()
|
|
43
|
+
|
|
44
|
+
def test_context_manager(self):
|
|
45
|
+
with HelixClient(base_url=f"http://localhost:{SERVER_PORT}") as c:
|
|
46
|
+
assert c.is_healthy()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Tests for @helix_wrap decorator."""
|
|
2
|
+
import pytest
|
|
3
|
+
from helix_agent import helix_wrap
|
|
4
|
+
|
|
5
|
+
SERVER_PORT = 17842
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestHelixWrap:
|
|
9
|
+
def test_decorator_retries_on_error(self):
|
|
10
|
+
call_count = 0
|
|
11
|
+
|
|
12
|
+
@helix_wrap(platform="tempo", base_url=f"http://localhost:{SERVER_PORT}", max_retries=3, retry_delay=0.1)
|
|
13
|
+
def flaky_function():
|
|
14
|
+
nonlocal call_count
|
|
15
|
+
call_count += 1
|
|
16
|
+
if call_count < 3:
|
|
17
|
+
raise Exception("nonce mismatch: expected 5, got 3")
|
|
18
|
+
return "success"
|
|
19
|
+
|
|
20
|
+
result = flaky_function()
|
|
21
|
+
assert result == "success"
|
|
22
|
+
assert call_count == 3
|
|
23
|
+
|
|
24
|
+
def test_decorator_exposes_client(self):
|
|
25
|
+
@helix_wrap(platform="tempo", base_url=f"http://localhost:{SERVER_PORT}")
|
|
26
|
+
def my_func():
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
assert hasattr(my_func, "helix_client")
|
|
30
|
+
assert my_func.helix_client.is_healthy()
|
|
31
|
+
|
|
32
|
+
def test_decorator_raises_after_max_retries(self):
|
|
33
|
+
@helix_wrap(platform="tempo", base_url=f"http://localhost:{SERVER_PORT}", max_retries=2, retry_delay=0.1)
|
|
34
|
+
def always_fails():
|
|
35
|
+
raise Exception("permanent error that never resolves")
|
|
36
|
+
|
|
37
|
+
with pytest.raises(Exception, match="permanent error"):
|
|
38
|
+
always_fails()
|
|
39
|
+
|
|
40
|
+
def test_on_repair_callback(self):
|
|
41
|
+
repairs = []
|
|
42
|
+
|
|
43
|
+
@helix_wrap(
|
|
44
|
+
platform="tempo",
|
|
45
|
+
base_url=f"http://localhost:{SERVER_PORT}",
|
|
46
|
+
max_retries=2,
|
|
47
|
+
retry_delay=0.1,
|
|
48
|
+
on_repair=lambda r: repairs.append(r),
|
|
49
|
+
)
|
|
50
|
+
def fails_once():
|
|
51
|
+
if len(repairs) == 0:
|
|
52
|
+
raise Exception("nonce mismatch: expected 1, got 0")
|
|
53
|
+
return "ok"
|
|
54
|
+
|
|
55
|
+
result = fails_once()
|
|
56
|
+
assert result == "ok"
|
|
57
|
+
assert len(repairs) >= 1
|
|
58
|
+
assert repairs[0].success
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Tests for helix_guard context manager."""
|
|
2
|
+
from helix_agent import helix_guard
|
|
3
|
+
|
|
4
|
+
SERVER_PORT = 17842
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestHelixGuard:
|
|
8
|
+
def test_basic_guard(self):
|
|
9
|
+
with helix_guard("tempo", base_url=f"http://localhost:{SERVER_PORT}") as guard:
|
|
10
|
+
result = guard.repair("nonce mismatch: expected 5, got 3")
|
|
11
|
+
assert result.success
|
|
12
|
+
assert result.strategy is not None
|
|
13
|
+
|
|
14
|
+
def test_repair_count(self):
|
|
15
|
+
with helix_guard("tempo", base_url=f"http://localhost:{SERVER_PORT}") as guard:
|
|
16
|
+
guard.repair("error 1")
|
|
17
|
+
guard.repair("error 2")
|
|
18
|
+
assert guard.total_repairs == 2
|
|
19
|
+
|
|
20
|
+
def test_immune_tracking(self):
|
|
21
|
+
with helix_guard("tempo", base_url=f"http://localhost:{SERVER_PORT}") as guard:
|
|
22
|
+
guard.repair("session expired, please re-authenticate")
|
|
23
|
+
guard.repair("session expired, please re-authenticate")
|
|
24
|
+
assert guard.immune_count >= 1
|