xache 5.0.0__py3-none-any.whl
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.
- xache/__init__.py +142 -0
- xache/client.py +331 -0
- xache/crypto/__init__.py +17 -0
- xache/crypto/signing.py +244 -0
- xache/crypto/wallet.py +240 -0
- xache/errors.py +184 -0
- xache/payment/__init__.py +5 -0
- xache/payment/handler.py +244 -0
- xache/services/__init__.py +29 -0
- xache/services/budget.py +285 -0
- xache/services/collective.py +174 -0
- xache/services/extraction.py +173 -0
- xache/services/facilitator.py +296 -0
- xache/services/identity.py +415 -0
- xache/services/memory.py +401 -0
- xache/services/owner.py +293 -0
- xache/services/receipts.py +202 -0
- xache/services/reputation.py +274 -0
- xache/services/royalty.py +290 -0
- xache/services/sessions.py +268 -0
- xache/services/workspaces.py +447 -0
- xache/types.py +399 -0
- xache/utils/__init__.py +5 -0
- xache/utils/cache.py +214 -0
- xache/utils/http.py +209 -0
- xache/utils/retry.py +101 -0
- xache-5.0.0.dist-info/METADATA +337 -0
- xache-5.0.0.dist-info/RECORD +30 -0
- xache-5.0.0.dist-info/WHEEL +5 -0
- xache-5.0.0.dist-info/top_level.txt +1 -0
xache/utils/http.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTTP client utilities with retry logic
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from typing import Dict, Optional, Any, Literal
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
import aiohttp
|
|
11
|
+
|
|
12
|
+
from ..errors import (
|
|
13
|
+
create_error_from_response,
|
|
14
|
+
PaymentRequiredError,
|
|
15
|
+
NetworkError,
|
|
16
|
+
XacheError,
|
|
17
|
+
)
|
|
18
|
+
from ..types import APIResponse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class RetryConfig:
|
|
23
|
+
"""Retry configuration"""
|
|
24
|
+
max_retries: int = 3
|
|
25
|
+
initial_delay: float = 1.0
|
|
26
|
+
max_delay: float = 10.0
|
|
27
|
+
backoff_multiplier: float = 2.0
|
|
28
|
+
retryable_status_codes: list = None
|
|
29
|
+
|
|
30
|
+
def __post_init__(self):
|
|
31
|
+
if self.retryable_status_codes is None:
|
|
32
|
+
self.retryable_status_codes = [408, 429, 500, 502, 503, 504]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class HttpClient:
|
|
36
|
+
"""HTTP client with retry logic"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
timeout: int = 30,
|
|
41
|
+
retry_config: Optional[RetryConfig] = None,
|
|
42
|
+
debug: bool = False,
|
|
43
|
+
):
|
|
44
|
+
self.timeout = timeout
|
|
45
|
+
self.retry_config = retry_config or RetryConfig()
|
|
46
|
+
self.debug = debug
|
|
47
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
48
|
+
|
|
49
|
+
async def __aenter__(self):
|
|
50
|
+
"""Async context manager entry"""
|
|
51
|
+
await self._ensure_session()
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
55
|
+
"""Async context manager exit"""
|
|
56
|
+
await self.close()
|
|
57
|
+
|
|
58
|
+
async def _ensure_session(self):
|
|
59
|
+
"""Ensure aiohttp session exists"""
|
|
60
|
+
if self._session is None or self._session.closed:
|
|
61
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
|
62
|
+
self._session = aiohttp.ClientSession(timeout=timeout)
|
|
63
|
+
|
|
64
|
+
async def close(self):
|
|
65
|
+
"""Close HTTP session"""
|
|
66
|
+
if self._session and not self._session.closed:
|
|
67
|
+
await self._session.close()
|
|
68
|
+
|
|
69
|
+
async def request(
|
|
70
|
+
self,
|
|
71
|
+
method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
72
|
+
url: str,
|
|
73
|
+
headers: Optional[Dict[str, str]] = None,
|
|
74
|
+
body: Optional[str] = None,
|
|
75
|
+
) -> APIResponse:
|
|
76
|
+
"""
|
|
77
|
+
Make HTTP request with retry logic
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
method: HTTP method
|
|
81
|
+
url: Full URL
|
|
82
|
+
headers: Request headers
|
|
83
|
+
body: Request body (JSON string)
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
API response
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
XacheError: API error
|
|
90
|
+
NetworkError: Network error
|
|
91
|
+
"""
|
|
92
|
+
await self._ensure_session()
|
|
93
|
+
|
|
94
|
+
headers = headers or {}
|
|
95
|
+
headers["Content-Type"] = "application/json"
|
|
96
|
+
|
|
97
|
+
last_error: Optional[Exception] = None
|
|
98
|
+
attempt = 0
|
|
99
|
+
|
|
100
|
+
while attempt <= self.retry_config.max_retries:
|
|
101
|
+
try:
|
|
102
|
+
if self.debug and attempt > 0:
|
|
103
|
+
print(f"Retry attempt {attempt}/{self.retry_config.max_retries} for {method} {url}")
|
|
104
|
+
|
|
105
|
+
response = await self._make_request(method, url, headers, body)
|
|
106
|
+
return response
|
|
107
|
+
|
|
108
|
+
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
|
109
|
+
last_error = e
|
|
110
|
+
attempt += 1
|
|
111
|
+
|
|
112
|
+
if attempt <= self.retry_config.max_retries:
|
|
113
|
+
delay = self._calculate_delay(attempt)
|
|
114
|
+
await asyncio.sleep(delay)
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
except PaymentRequiredError:
|
|
118
|
+
# Don't retry 402 errors
|
|
119
|
+
raise
|
|
120
|
+
|
|
121
|
+
except XacheError:
|
|
122
|
+
# Don't retry API errors (400, 401, 403, 404, 409, etc.)
|
|
123
|
+
raise
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
last_error = e
|
|
127
|
+
attempt += 1
|
|
128
|
+
|
|
129
|
+
if attempt <= self.retry_config.max_retries:
|
|
130
|
+
delay = self._calculate_delay(attempt)
|
|
131
|
+
await asyncio.sleep(delay)
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
# All retries exhausted
|
|
135
|
+
raise NetworkError(
|
|
136
|
+
f"Request failed after {self.retry_config.max_retries} retries",
|
|
137
|
+
last_error,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
async def _make_request(
|
|
141
|
+
self,
|
|
142
|
+
method: str,
|
|
143
|
+
url: str,
|
|
144
|
+
headers: Dict[str, str],
|
|
145
|
+
body: Optional[str],
|
|
146
|
+
) -> APIResponse:
|
|
147
|
+
"""Make single HTTP request"""
|
|
148
|
+
async with self._session.request(
|
|
149
|
+
method,
|
|
150
|
+
url,
|
|
151
|
+
headers=headers,
|
|
152
|
+
data=body,
|
|
153
|
+
) as response:
|
|
154
|
+
# Parse response
|
|
155
|
+
try:
|
|
156
|
+
response_json = await response.json()
|
|
157
|
+
except json.JSONDecodeError as e:
|
|
158
|
+
raise NetworkError("Failed to parse response JSON", e)
|
|
159
|
+
|
|
160
|
+
# Handle 402 Payment Required
|
|
161
|
+
if response.status == 402:
|
|
162
|
+
payment_data = response_json.get("payment", {})
|
|
163
|
+
meta = response_json.get("meta", {})
|
|
164
|
+
raise PaymentRequiredError(
|
|
165
|
+
response_json.get("error", {}).get("message", "Payment required"),
|
|
166
|
+
payment_data.get("challengeId", ""),
|
|
167
|
+
payment_data.get("amount", ""),
|
|
168
|
+
payment_data.get("chainHint", ""),
|
|
169
|
+
payment_data.get("payTo", ""),
|
|
170
|
+
payment_data.get("description", ""),
|
|
171
|
+
meta.get("requestId"),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Handle API error responses
|
|
175
|
+
if not response_json.get("success") and response_json.get("error"):
|
|
176
|
+
error = response_json["error"]
|
|
177
|
+
meta = response_json.get("meta", {})
|
|
178
|
+
raise create_error_from_response(
|
|
179
|
+
error.get("code", "INTERNAL"),
|
|
180
|
+
error.get("message", "Unknown error"),
|
|
181
|
+
response.status,
|
|
182
|
+
error.get("details"),
|
|
183
|
+
meta.get("requestId"),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Check if retryable status code
|
|
187
|
+
if response.status in self.retry_config.retryable_status_codes:
|
|
188
|
+
raise NetworkError(f"HTTP {response.status}: {response.reason}")
|
|
189
|
+
|
|
190
|
+
# Success response
|
|
191
|
+
return APIResponse(
|
|
192
|
+
success=response_json.get("success", True),
|
|
193
|
+
data=response_json.get("data"),
|
|
194
|
+
error=None,
|
|
195
|
+
meta=None,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def _calculate_delay(self, attempt: int) -> float:
|
|
199
|
+
"""Calculate exponential backoff delay with jitter"""
|
|
200
|
+
import random
|
|
201
|
+
|
|
202
|
+
delay = min(
|
|
203
|
+
self.retry_config.initial_delay * (self.retry_config.backoff_multiplier ** (attempt - 1)),
|
|
204
|
+
self.retry_config.max_delay,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Add jitter (±25%)
|
|
208
|
+
jitter = delay * 0.25 * (random.random() * 2 - 1)
|
|
209
|
+
return delay + jitter
|
xache/utils/retry.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Retry utility with exponential backoff
|
|
3
|
+
Production-ready automatic error recovery
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
from typing import Callable, TypeVar, List, Optional, Any
|
|
8
|
+
from ..types import ErrorCode
|
|
9
|
+
|
|
10
|
+
T = TypeVar('T')
|
|
11
|
+
|
|
12
|
+
class RetryPolicy:
|
|
13
|
+
"""Configuration for retry behavior"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
max_retries: int = 3,
|
|
18
|
+
backoff_ms: Optional[List[int]] = None,
|
|
19
|
+
retryable_errors: Optional[List[ErrorCode]] = None,
|
|
20
|
+
timeout: int = 60000
|
|
21
|
+
):
|
|
22
|
+
self.max_retries = max_retries
|
|
23
|
+
self.backoff_ms = backoff_ms or [1000, 2000, 4000]
|
|
24
|
+
self.retryable_errors = retryable_errors or ['RETRY_LATER', 'INTERNAL']
|
|
25
|
+
self.timeout = timeout
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_retryable_error(error: Exception, retryable_errors: List[ErrorCode]) -> bool:
|
|
29
|
+
"""Check if error is retryable"""
|
|
30
|
+
if hasattr(error, 'code'):
|
|
31
|
+
return error.code in retryable_errors
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def with_retry(
|
|
36
|
+
fn: Callable[[], T],
|
|
37
|
+
policy: Optional[RetryPolicy] = None,
|
|
38
|
+
debug: bool = False
|
|
39
|
+
) -> T:
|
|
40
|
+
"""
|
|
41
|
+
Execute function with automatic retry logic
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
fn: Function to execute
|
|
45
|
+
policy: Retry policy configuration
|
|
46
|
+
debug: Enable debug logging
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Result from successful function execution
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
Last exception if all retries exhausted
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> def fetch_data():
|
|
56
|
+
... return api.get('/data')
|
|
57
|
+
>>> result = with_retry(fetch_data, RetryPolicy(max_retries=5))
|
|
58
|
+
"""
|
|
59
|
+
if policy is None:
|
|
60
|
+
policy = RetryPolicy()
|
|
61
|
+
|
|
62
|
+
start_time = time.time() * 1000 # Convert to milliseconds
|
|
63
|
+
last_error: Optional[Exception] = None
|
|
64
|
+
|
|
65
|
+
for attempt in range(policy.max_retries + 1):
|
|
66
|
+
# Check timeout
|
|
67
|
+
if (time.time() * 1000 - start_time) > policy.timeout:
|
|
68
|
+
raise TimeoutError(
|
|
69
|
+
f"Operation timed out after {policy.timeout}ms ({attempt} attempts)"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
return fn()
|
|
74
|
+
except Exception as error:
|
|
75
|
+
last_error = error
|
|
76
|
+
|
|
77
|
+
# If this is the last attempt, don't retry
|
|
78
|
+
if attempt == policy.max_retries:
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
# Check if error is retryable
|
|
82
|
+
if not is_retryable_error(error, policy.retryable_errors):
|
|
83
|
+
raise error
|
|
84
|
+
|
|
85
|
+
# Calculate delay
|
|
86
|
+
if attempt < len(policy.backoff_ms):
|
|
87
|
+
delay = policy.backoff_ms[attempt]
|
|
88
|
+
else:
|
|
89
|
+
delay = policy.backoff_ms[-1]
|
|
90
|
+
|
|
91
|
+
if debug:
|
|
92
|
+
print(f"Retry attempt {attempt + 1}/{policy.max_retries} after {delay}ms delay")
|
|
93
|
+
|
|
94
|
+
# Sleep for delay (convert ms to seconds)
|
|
95
|
+
time.sleep(delay / 1000)
|
|
96
|
+
|
|
97
|
+
# All retries exhausted, raise last error
|
|
98
|
+
if last_error:
|
|
99
|
+
raise last_error
|
|
100
|
+
else:
|
|
101
|
+
raise RuntimeError("Unexpected error in retry logic")
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xache
|
|
3
|
+
Version: 5.0.0
|
|
4
|
+
Summary: Official Python SDK for Xache Protocol
|
|
5
|
+
Home-page: https://github.com/xache-ai/xache-protocol
|
|
6
|
+
Author: Xache Protocol
|
|
7
|
+
Author-email: Xache Protocol <dev@xache.xyz>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://xache.xyz
|
|
10
|
+
Project-URL: Documentation, https://docs.xache.xyz
|
|
11
|
+
Project-URL: Repository, https://github.com/oliveskin/xache
|
|
12
|
+
Project-URL: Bug Reports, https://github.com/oliveskin/xache/issues
|
|
13
|
+
Keywords: xache,ai,agent,memory,blockchain,decentralized
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
26
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
27
|
+
Requires-Dist: mnemonic>=0.20
|
|
28
|
+
Requires-Dist: eth-account>=0.10.0
|
|
29
|
+
Requires-Dist: bip-utils>=2.9.0
|
|
30
|
+
Requires-Dist: PyNaCl>=1.5.0
|
|
31
|
+
Requires-Dist: base58>=2.1.1
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pylint>=2.17.0; extra == "dev"
|
|
39
|
+
Provides-Extra: encryption
|
|
40
|
+
Requires-Dist: PyNaCl>=1.5.0; extra == "encryption"
|
|
41
|
+
Dynamic: author
|
|
42
|
+
Dynamic: home-page
|
|
43
|
+
Dynamic: requires-python
|
|
44
|
+
|
|
45
|
+
# Xache Protocol Python SDK
|
|
46
|
+
|
|
47
|
+
Official Python SDK for [Xache Protocol](https://xache.ai) - decentralized agent memory and collective intelligence marketplace.
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
✅ **Async/Await** - Full asyncio support for concurrent operations
|
|
52
|
+
✅ **Type Hints** - Complete type annotations for better IDE support
|
|
53
|
+
✅ **Authentication** - Automatic request signing per protocol spec
|
|
54
|
+
✅ **Payment Flow** - Built-in 402 payment handling (manual or Coinbase Commerce)
|
|
55
|
+
✅ **Encryption** - Client-side encryption for memory storage
|
|
56
|
+
✅ **Error Handling** - Typed exceptions with automatic retry logic
|
|
57
|
+
✅ **Budget Management** - Track and control spending limits
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install xache
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
With encryption support:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install xache[encryption]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import asyncio
|
|
75
|
+
from xache import XacheClient
|
|
76
|
+
|
|
77
|
+
async def main():
|
|
78
|
+
# Initialize client
|
|
79
|
+
async with XacheClient(
|
|
80
|
+
api_url="https://api.xache.xyz",
|
|
81
|
+
did="did:agent:evm:0xYourWalletAddress",
|
|
82
|
+
private_key="0x...",
|
|
83
|
+
) as client:
|
|
84
|
+
# Register identity
|
|
85
|
+
identity = await client.identity.register(
|
|
86
|
+
wallet_address="0xYourWalletAddress",
|
|
87
|
+
key_type="evm",
|
|
88
|
+
chain="base",
|
|
89
|
+
)
|
|
90
|
+
print(f"DID: {identity.did}")
|
|
91
|
+
|
|
92
|
+
asyncio.run(main())
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Usage Examples
|
|
96
|
+
|
|
97
|
+
### Memory Storage
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# Store encrypted memory (automatic encryption + 402 payment)
|
|
101
|
+
memory = await client.memory.store(
|
|
102
|
+
data={
|
|
103
|
+
"context": "user preferences",
|
|
104
|
+
"theme": "dark",
|
|
105
|
+
"language": "en",
|
|
106
|
+
},
|
|
107
|
+
storage_tier="hot",
|
|
108
|
+
)
|
|
109
|
+
print(f"Memory ID: {memory.memory_id}")
|
|
110
|
+
|
|
111
|
+
# Retrieve memory (automatic decryption + 402 payment)
|
|
112
|
+
retrieved = await client.memory.retrieve(memory_id=memory.memory_id)
|
|
113
|
+
print(f"Data: {retrieved.data}")
|
|
114
|
+
|
|
115
|
+
# Delete memory (free)
|
|
116
|
+
await client.memory.delete(memory.memory_id)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Collective Intelligence
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# Contribute a heuristic (automatic 402 payment)
|
|
123
|
+
heuristic = await client.collective.contribute(
|
|
124
|
+
pattern="Use async/await for cleaner async code in Python",
|
|
125
|
+
domain="python",
|
|
126
|
+
tags=["async", "best-practices", "readability"],
|
|
127
|
+
context_type="code-review",
|
|
128
|
+
)
|
|
129
|
+
print(f"Heuristic ID: {heuristic.heuristic_id}")
|
|
130
|
+
|
|
131
|
+
# Query collective (automatic 402 payment)
|
|
132
|
+
results = await client.collective.query(
|
|
133
|
+
query_text="How to optimize database queries in Python",
|
|
134
|
+
domain="python",
|
|
135
|
+
limit=10,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
for match in results.matches:
|
|
139
|
+
print(f"Pattern: {match.pattern}")
|
|
140
|
+
print(f"Score: {match.relevance_score}")
|
|
141
|
+
print(f"Royalty: ${match.royalty_amount}")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Budget Management
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# Check budget status
|
|
148
|
+
budget = await client.budget.get_status()
|
|
149
|
+
print(f"Limit: ${budget.limit_cents / 100}")
|
|
150
|
+
print(f"Spent: ${budget.spent_cents / 100}")
|
|
151
|
+
print(f"Remaining: ${budget.remaining_cents / 100}")
|
|
152
|
+
print(f"Usage: {budget.percentage_used:.1f}%")
|
|
153
|
+
|
|
154
|
+
# Update budget limit
|
|
155
|
+
await client.budget.update_limit(5000) # $50/month
|
|
156
|
+
|
|
157
|
+
# Check if you can afford an operation
|
|
158
|
+
can_afford = await client.budget.can_afford(100) # 100 cents = $1
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Receipts & Analytics
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# List receipts
|
|
165
|
+
result = await client.receipts.list(limit=20, offset=0)
|
|
166
|
+
for receipt in result["receipts"]:
|
|
167
|
+
print(f"{receipt.operation}: ${receipt.amount_usd}")
|
|
168
|
+
|
|
169
|
+
# Get Merkle proof for verification
|
|
170
|
+
proof = await client.receipts.get_proof("receipt_abc123")
|
|
171
|
+
print(f"Merkle Root: {proof.merkle_root}")
|
|
172
|
+
|
|
173
|
+
# Get usage analytics
|
|
174
|
+
analytics = await client.receipts.get_analytics(
|
|
175
|
+
start_date="2024-01-01",
|
|
176
|
+
end_date="2024-01-31",
|
|
177
|
+
)
|
|
178
|
+
print(f"Total spent: ${analytics.total_spent}")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Configuration
|
|
182
|
+
|
|
183
|
+
### Basic Configuration
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
client = XacheClient(
|
|
187
|
+
api_url="https://api.xache.xyz",
|
|
188
|
+
did="did:agent:evm:0xYourWalletAddress",
|
|
189
|
+
private_key="0x...",
|
|
190
|
+
timeout=30, # Optional: request timeout in seconds
|
|
191
|
+
debug=False, # Optional: enable debug logging
|
|
192
|
+
)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Payment Configuration
|
|
196
|
+
|
|
197
|
+
#### Manual Payment (Default)
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
client = XacheClient(
|
|
201
|
+
# ... basic config
|
|
202
|
+
payment_provider={
|
|
203
|
+
"type": "manual",
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# When payment is required, SDK will prompt you in console
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Coinbase Commerce
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
client = XacheClient(
|
|
214
|
+
# ... basic config
|
|
215
|
+
payment_provider={
|
|
216
|
+
"type": "coinbase-commerce",
|
|
217
|
+
"api_key": "YOUR_COINBASE_API_KEY",
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Payments will be handled automatically via Coinbase Commerce
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Error Handling
|
|
225
|
+
|
|
226
|
+
The SDK provides typed errors for all API error codes:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from xache import (
|
|
230
|
+
XacheError,
|
|
231
|
+
UnauthenticatedError,
|
|
232
|
+
PaymentRequiredError,
|
|
233
|
+
RateLimitedError,
|
|
234
|
+
BudgetExceededError,
|
|
235
|
+
InvalidInputError,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
await client.memory.store(data=data, storage_tier="hot")
|
|
240
|
+
except PaymentRequiredError as e:
|
|
241
|
+
print(f"Payment required: ${e.amount}")
|
|
242
|
+
print(f"Challenge ID: {e.challenge_id}")
|
|
243
|
+
except RateLimitedError as e:
|
|
244
|
+
print(f"Rate limited. Retry at: {e.reset_at}")
|
|
245
|
+
except BudgetExceededError as e:
|
|
246
|
+
print("Budget exceeded")
|
|
247
|
+
except InvalidInputError as e:
|
|
248
|
+
print(f"Invalid input: {e.message}")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Context Manager
|
|
252
|
+
|
|
253
|
+
Always use the client as an async context manager to ensure proper cleanup:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
async with XacheClient(...) as client:
|
|
257
|
+
# Your code here
|
|
258
|
+
pass
|
|
259
|
+
# HTTP session automatically closed
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Or manually manage the lifecycle:
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
client = XacheClient(...)
|
|
266
|
+
try:
|
|
267
|
+
# Your code here
|
|
268
|
+
pass
|
|
269
|
+
finally:
|
|
270
|
+
await client.close()
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## API Reference
|
|
274
|
+
|
|
275
|
+
### XacheClient
|
|
276
|
+
|
|
277
|
+
Main client class for interacting with Xache Protocol.
|
|
278
|
+
|
|
279
|
+
#### Properties
|
|
280
|
+
|
|
281
|
+
- `client.identity` - Identity registration
|
|
282
|
+
- `client.memory` - Memory storage and retrieval
|
|
283
|
+
- `client.collective` - Collective intelligence marketplace
|
|
284
|
+
- `client.budget` - Budget management
|
|
285
|
+
- `client.receipts` - Receipt access and analytics
|
|
286
|
+
|
|
287
|
+
### Types
|
|
288
|
+
|
|
289
|
+
All request/response types are available:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from xache import (
|
|
293
|
+
RegisterIdentityRequest,
|
|
294
|
+
RegisterIdentityResponse,
|
|
295
|
+
StoreMemoryRequest,
|
|
296
|
+
StoreMemoryResponse,
|
|
297
|
+
QueryCollectiveRequest,
|
|
298
|
+
BudgetStatus,
|
|
299
|
+
Receipt,
|
|
300
|
+
)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Development
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Install with dev dependencies
|
|
307
|
+
pip install -e ".[dev]"
|
|
308
|
+
|
|
309
|
+
# Run tests
|
|
310
|
+
pytest
|
|
311
|
+
|
|
312
|
+
# Run type checking
|
|
313
|
+
mypy xache
|
|
314
|
+
|
|
315
|
+
# Format code
|
|
316
|
+
black xache
|
|
317
|
+
|
|
318
|
+
# Lint
|
|
319
|
+
pylint xache
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Requirements
|
|
323
|
+
|
|
324
|
+
- Python 3.8+
|
|
325
|
+
- aiohttp
|
|
326
|
+
- typing-extensions
|
|
327
|
+
|
|
328
|
+
## License
|
|
329
|
+
|
|
330
|
+
MIT
|
|
331
|
+
|
|
332
|
+
## Links
|
|
333
|
+
|
|
334
|
+
- [Documentation](https://docs.xache.ai)
|
|
335
|
+
- [Protocol Specification](https://github.com/xache-ai/xache-protocol)
|
|
336
|
+
- [API Reference](https://api.xache.xyz/docs)
|
|
337
|
+
- [Discord](https://discord.gg/xache)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
xache/__init__.py,sha256=RFenUVyM4Mt5ycz_OvqYNg2oZVwrOF9EogK3jZ_yEpE,2989
|
|
2
|
+
xache/client.py,sha256=ABjFJFnrv4KmY5ihBXK5YC8sg50s48tGdDUSKq7Vd8g,10217
|
|
3
|
+
xache/errors.py,sha256=sHLjfKGw1JxN3TdV_bswG9erH2vVAOFPzvOkWb1k0EI,5007
|
|
4
|
+
xache/types.py,sha256=YpepxdWdWbkCRzRWQX9wckCL1R6zjHswrWTrI3CnrB4,9479
|
|
5
|
+
xache/crypto/__init__.py,sha256=uG6liKoXEY0gdRW9uh_LDNfIp-Cs5ZHE80d_FRKu9jk,308
|
|
6
|
+
xache/crypto/signing.py,sha256=lUHXJ6ySp7hqx7cuX91ooRt2w6CfeHURP-RScNx0Gd8,6851
|
|
7
|
+
xache/crypto/wallet.py,sha256=-VWcxZwjeK1saNgp5qJTukj62yi9BsAisYhWyNwL7qY,6983
|
|
8
|
+
xache/payment/__init__.py,sha256=ggjkHACtiZwVmnt-OW4makx0RAzrNqlZNsdVgSf3VH0,90
|
|
9
|
+
xache/payment/handler.py,sha256=X3RL1mvjHCn5FbloOjiNUv_CythRlJU0Vo524lx4DLg,8901
|
|
10
|
+
xache/services/__init__.py,sha256=ERuZXa7R-Iv0oFBknxJiBO5nJC-oCtIut_4VITw_uOE,775
|
|
11
|
+
xache/services/budget.py,sha256=6sZ3nojb888a3k774XPnJHfdkqIC2Y1BxJKt8zy76V0,10599
|
|
12
|
+
xache/services/collective.py,sha256=utYLIHv3tuOYHbdYJYXqIy1hS-aYIRems744CsCUIUw,6229
|
|
13
|
+
xache/services/extraction.py,sha256=5KElr4ldBR3kcOofvz4d3NwAuTvKVskZ-NTQ7BI2ChY,5317
|
|
14
|
+
xache/services/facilitator.py,sha256=ikZNTqEkmcQixGSMJYGlspmbKfivMBFO3sg5Tzy7si8,9412
|
|
15
|
+
xache/services/identity.py,sha256=gOs5fN9juyoBfXQVm-G4whyUMJ6Oha2VmP_i3mQw0G0,13478
|
|
16
|
+
xache/services/memory.py,sha256=ng9_cwL4jE4c3gdlwQDZyqaBdQgwtqApEwj0LkZYWRY,13290
|
|
17
|
+
xache/services/owner.py,sha256=2ASJFjApS3AiQEpoS2oM9M3sisi0Y6xSjmU1fwUM0rA,8912
|
|
18
|
+
xache/services/receipts.py,sha256=LkVBZ-2KExWXEuA0f6GYreVPrIo_UnP1CDesSnJ9pNo,6729
|
|
19
|
+
xache/services/reputation.py,sha256=dwOTSUKxOo0ajeIZwZYEpzAkmswsqGF1q6vwq9S3Jno,9733
|
|
20
|
+
xache/services/royalty.py,sha256=uIf6q4BQ_VS-7alum1P7jexMzjtRUPxDIV6Lg7L1eLU,8595
|
|
21
|
+
xache/services/sessions.py,sha256=9f_sLkp6QTFsP7wnZVlImFxPEkIHSitPkjR6UDSdNIk,8025
|
|
22
|
+
xache/services/workspaces.py,sha256=bDu8VGXIhAX3l8Y1ReX5JH8ocNIaY3BF_Nc6QJeKlqc,13685
|
|
23
|
+
xache/utils/__init__.py,sha256=8VrQm0QnyqxdplpCG7BDRiAVdBGWrjUs9ipH2zsJOBM,106
|
|
24
|
+
xache/utils/cache.py,sha256=9zhE9dIXFTofj7jz1TX-FkAqmclqoYXTe4FwwGLeKT4,5479
|
|
25
|
+
xache/utils/http.py,sha256=rIQCYvYrziNrNfEbOnIKbCOGGf7bcdTvZrrU_W6CcZA,6547
|
|
26
|
+
xache/utils/retry.py,sha256=OJYBGozKIoteCvKw50dqd4ThhOo-WisorcKa8Tr6mnE,2860
|
|
27
|
+
xache-5.0.0.dist-info/METADATA,sha256=Xk9jdBWRacl2aVkn89dGbdLr9xHNmi8FWbK-B2BHyQg,8469
|
|
28
|
+
xache-5.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
29
|
+
xache-5.0.0.dist-info/top_level.txt,sha256=FBWE4IVb7zoLS9arsdrl97QVETlwFvYGAx6xEJZOEUU,6
|
|
30
|
+
xache-5.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xache
|