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
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workspace Service - Enterprise workspace and fleet management
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional, Dict, Any
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from ..types import DID
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Workspace:
|
|
12
|
+
"""Workspace configuration"""
|
|
13
|
+
workspace_id: str
|
|
14
|
+
owner_did: DID
|
|
15
|
+
workspace_name: str
|
|
16
|
+
description: Optional[str]
|
|
17
|
+
budget_limit_cents: Optional[int]
|
|
18
|
+
enabled_chains: Optional[List[str]]
|
|
19
|
+
enabled: bool
|
|
20
|
+
created_at: str
|
|
21
|
+
updated_at: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class WorkspaceMember:
|
|
26
|
+
"""Workspace member"""
|
|
27
|
+
workspace_id: str
|
|
28
|
+
agent_did: DID
|
|
29
|
+
role: str # 'admin' | 'member'
|
|
30
|
+
added_at: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class AgentBudget:
|
|
35
|
+
"""Agent budget within workspace"""
|
|
36
|
+
agent_did: DID
|
|
37
|
+
spent_cents: int
|
|
38
|
+
percentage_of_total: float
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class WorkspaceAnalytics:
|
|
43
|
+
"""Workspace analytics"""
|
|
44
|
+
workspace_id: str
|
|
45
|
+
total_agents: int
|
|
46
|
+
total_memories: int
|
|
47
|
+
total_spent_usd: str
|
|
48
|
+
total_operations: int
|
|
49
|
+
operations_by_type: Dict[str, int]
|
|
50
|
+
period_start: str
|
|
51
|
+
period_end: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class WorkspaceBudget:
|
|
56
|
+
"""Workspace budget status"""
|
|
57
|
+
workspace_id: str
|
|
58
|
+
budget_limit_cents: int
|
|
59
|
+
total_spent_cents: int
|
|
60
|
+
remaining_cents: int
|
|
61
|
+
percentage_used: float
|
|
62
|
+
agent_budgets: List[AgentBudget]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class WorkspaceService:
|
|
66
|
+
"""
|
|
67
|
+
Workspace service for enterprise fleet management
|
|
68
|
+
|
|
69
|
+
Manage workspaces with budget controls, agent membership,
|
|
70
|
+
and analytics for enterprise AI deployments.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, client):
|
|
74
|
+
self.client = client
|
|
75
|
+
|
|
76
|
+
async def create(
|
|
77
|
+
self,
|
|
78
|
+
workspace_name: str,
|
|
79
|
+
description: Optional[str] = None,
|
|
80
|
+
budget_limit_cents: Optional[int] = None,
|
|
81
|
+
enabled_chains: Optional[List[str]] = None,
|
|
82
|
+
) -> Workspace:
|
|
83
|
+
"""
|
|
84
|
+
Create a new workspace
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
workspace_name: Name for the workspace
|
|
88
|
+
description: Optional description
|
|
89
|
+
budget_limit_cents: Optional budget limit in cents
|
|
90
|
+
enabled_chains: Optional list of enabled chains
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Created workspace
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
```python
|
|
97
|
+
workspace = await client.workspaces.create(
|
|
98
|
+
workspace_name="Production Agents",
|
|
99
|
+
description="Fleet of production AI agents",
|
|
100
|
+
budget_limit_cents=100000, # $1000
|
|
101
|
+
enabled_chains=["evm", "solana"]
|
|
102
|
+
)
|
|
103
|
+
print(f"Created: {workspace.workspace_id}")
|
|
104
|
+
```
|
|
105
|
+
"""
|
|
106
|
+
body = {"workspaceName": workspace_name}
|
|
107
|
+
if description:
|
|
108
|
+
body["description"] = description
|
|
109
|
+
if budget_limit_cents is not None:
|
|
110
|
+
body["budgetLimitCents"] = budget_limit_cents
|
|
111
|
+
if enabled_chains:
|
|
112
|
+
body["enabledChains"] = enabled_chains
|
|
113
|
+
|
|
114
|
+
response = await self.client.request("POST", "/v1/workspaces", body)
|
|
115
|
+
|
|
116
|
+
if not response.success or not response.data:
|
|
117
|
+
raise Exception(
|
|
118
|
+
response.error.get("message", "Failed to create workspace")
|
|
119
|
+
if response.error
|
|
120
|
+
else "Failed to create workspace"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return self._parse_workspace(response.data.get("workspace", response.data))
|
|
124
|
+
|
|
125
|
+
async def list(self) -> Dict:
|
|
126
|
+
"""
|
|
127
|
+
List all workspaces owned by the authenticated owner
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Dictionary with workspaces and count
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
```python
|
|
134
|
+
result = await client.workspaces.list()
|
|
135
|
+
for ws in result['workspaces']:
|
|
136
|
+
print(f"{ws.workspace_name}: {'active' if ws.enabled else 'disabled'}")
|
|
137
|
+
```
|
|
138
|
+
"""
|
|
139
|
+
response = await self.client.request("GET", "/v1/workspaces")
|
|
140
|
+
|
|
141
|
+
if not response.success or not response.data:
|
|
142
|
+
raise Exception(
|
|
143
|
+
response.error.get("message", "Failed to list workspaces")
|
|
144
|
+
if response.error
|
|
145
|
+
else "Failed to list workspaces"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"workspaces": [
|
|
150
|
+
self._parse_workspace(w) for w in response.data.get("workspaces", [])
|
|
151
|
+
],
|
|
152
|
+
"count": response.data.get("count", 0),
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async def get(self, workspace_id: str) -> Workspace:
|
|
156
|
+
"""
|
|
157
|
+
Get workspace by ID
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
workspace_id: Workspace identifier
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Workspace
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
```python
|
|
167
|
+
workspace = await client.workspaces.get("ws_abc123")
|
|
168
|
+
print(f"Budget limit: {workspace.budget_limit_cents}")
|
|
169
|
+
```
|
|
170
|
+
"""
|
|
171
|
+
from urllib.parse import quote
|
|
172
|
+
|
|
173
|
+
response = await self.client.request(
|
|
174
|
+
"GET", f"/v1/workspaces/{quote(workspace_id, safe='')}"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if not response.success or not response.data:
|
|
178
|
+
raise Exception(
|
|
179
|
+
response.error.get("message", "Workspace not found")
|
|
180
|
+
if response.error
|
|
181
|
+
else "Workspace not found"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return self._parse_workspace(response.data.get("workspace", response.data))
|
|
185
|
+
|
|
186
|
+
async def update(
|
|
187
|
+
self,
|
|
188
|
+
workspace_id: str,
|
|
189
|
+
workspace_name: Optional[str] = None,
|
|
190
|
+
description: Optional[str] = None,
|
|
191
|
+
budget_limit_cents: Optional[int] = None,
|
|
192
|
+
enabled_chains: Optional[List[str]] = None,
|
|
193
|
+
enabled: Optional[bool] = None,
|
|
194
|
+
) -> Workspace:
|
|
195
|
+
"""
|
|
196
|
+
Update workspace settings
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
workspace_id: Workspace identifier
|
|
200
|
+
workspace_name: Optional new name
|
|
201
|
+
description: Optional new description
|
|
202
|
+
budget_limit_cents: Optional new budget limit
|
|
203
|
+
enabled_chains: Optional new enabled chains
|
|
204
|
+
enabled: Optional enable/disable flag
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Updated workspace
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
```python
|
|
211
|
+
updated = await client.workspaces.update(
|
|
212
|
+
"ws_abc123",
|
|
213
|
+
budget_limit_cents=200000 # Increase to $2000
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
"""
|
|
217
|
+
from urllib.parse import quote
|
|
218
|
+
|
|
219
|
+
body = {}
|
|
220
|
+
if workspace_name is not None:
|
|
221
|
+
body["workspaceName"] = workspace_name
|
|
222
|
+
if description is not None:
|
|
223
|
+
body["description"] = description
|
|
224
|
+
if budget_limit_cents is not None:
|
|
225
|
+
body["budgetLimitCents"] = budget_limit_cents
|
|
226
|
+
if enabled_chains is not None:
|
|
227
|
+
body["enabledChains"] = enabled_chains
|
|
228
|
+
if enabled is not None:
|
|
229
|
+
body["enabled"] = enabled
|
|
230
|
+
|
|
231
|
+
response = await self.client.request(
|
|
232
|
+
"PUT", f"/v1/workspaces/{quote(workspace_id, safe='')}", body
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if not response.success or not response.data:
|
|
236
|
+
raise Exception(
|
|
237
|
+
response.error.get("message", "Failed to update workspace")
|
|
238
|
+
if response.error
|
|
239
|
+
else "Failed to update workspace"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return self._parse_workspace(response.data.get("workspace", response.data))
|
|
243
|
+
|
|
244
|
+
async def delete(self, workspace_id: str) -> None:
|
|
245
|
+
"""
|
|
246
|
+
Delete (disable) workspace
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
workspace_id: Workspace identifier
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
```python
|
|
253
|
+
await client.workspaces.delete("ws_abc123")
|
|
254
|
+
print("Workspace deleted")
|
|
255
|
+
```
|
|
256
|
+
"""
|
|
257
|
+
from urllib.parse import quote
|
|
258
|
+
|
|
259
|
+
response = await self.client.request(
|
|
260
|
+
"DELETE", f"/v1/workspaces/{quote(workspace_id, safe='')}"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if not response.success:
|
|
264
|
+
raise Exception(
|
|
265
|
+
response.error.get("message", "Failed to delete workspace")
|
|
266
|
+
if response.error
|
|
267
|
+
else "Failed to delete workspace"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
async def add_agent(
|
|
271
|
+
self, workspace_id: str, agent_did: DID, role: str = "member"
|
|
272
|
+
) -> WorkspaceMember:
|
|
273
|
+
"""
|
|
274
|
+
Add an agent to a workspace
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
workspace_id: Workspace identifier
|
|
278
|
+
agent_did: Agent DID to add
|
|
279
|
+
role: Role ('admin' or 'member', default: 'member')
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Workspace member
|
|
283
|
+
|
|
284
|
+
Example:
|
|
285
|
+
```python
|
|
286
|
+
member = await client.workspaces.add_agent(
|
|
287
|
+
"ws_abc123",
|
|
288
|
+
"did:agent:evm:0x1234...",
|
|
289
|
+
role="member"
|
|
290
|
+
)
|
|
291
|
+
print(f"Added with role: {member.role}")
|
|
292
|
+
```
|
|
293
|
+
"""
|
|
294
|
+
from urllib.parse import quote
|
|
295
|
+
|
|
296
|
+
body = {"agentDID": agent_did, "role": role}
|
|
297
|
+
|
|
298
|
+
response = await self.client.request(
|
|
299
|
+
"POST", f"/v1/workspaces/{quote(workspace_id, safe='')}/agents", body
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if not response.success or not response.data:
|
|
303
|
+
raise Exception(
|
|
304
|
+
response.error.get("message", "Failed to add agent")
|
|
305
|
+
if response.error
|
|
306
|
+
else "Failed to add agent"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
data = response.data.get("member", response.data)
|
|
310
|
+
return WorkspaceMember(
|
|
311
|
+
workspace_id=data["workspaceId"],
|
|
312
|
+
agent_did=data["agentDID"],
|
|
313
|
+
role=data["role"],
|
|
314
|
+
added_at=data["addedAt"],
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
async def remove_agent(self, workspace_id: str, agent_did: DID) -> None:
|
|
318
|
+
"""
|
|
319
|
+
Remove an agent from a workspace
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
workspace_id: Workspace identifier
|
|
323
|
+
agent_did: Agent DID to remove
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
```python
|
|
327
|
+
await client.workspaces.remove_agent("ws_abc123", "did:agent:evm:0x1234...")
|
|
328
|
+
print("Agent removed")
|
|
329
|
+
```
|
|
330
|
+
"""
|
|
331
|
+
from urllib.parse import quote
|
|
332
|
+
|
|
333
|
+
response = await self.client.request(
|
|
334
|
+
"DELETE",
|
|
335
|
+
f"/v1/workspaces/{quote(workspace_id, safe='')}/agents/{quote(agent_did, safe='')}",
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if not response.success:
|
|
339
|
+
raise Exception(
|
|
340
|
+
response.error.get("message", "Failed to remove agent")
|
|
341
|
+
if response.error
|
|
342
|
+
else "Failed to remove agent"
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
async def get_analytics(self, workspace_id: str) -> WorkspaceAnalytics:
|
|
346
|
+
"""
|
|
347
|
+
Get workspace analytics
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
workspace_id: Workspace identifier
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Workspace analytics
|
|
354
|
+
|
|
355
|
+
Example:
|
|
356
|
+
```python
|
|
357
|
+
analytics = await client.workspaces.get_analytics("ws_abc123")
|
|
358
|
+
print(f"Total agents: {analytics.total_agents}")
|
|
359
|
+
print(f"Total spent: {analytics.total_spent_usd}")
|
|
360
|
+
```
|
|
361
|
+
"""
|
|
362
|
+
from urllib.parse import quote
|
|
363
|
+
|
|
364
|
+
response = await self.client.request(
|
|
365
|
+
"GET", f"/v1/workspaces/{quote(workspace_id, safe='')}/analytics"
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if not response.success or not response.data:
|
|
369
|
+
raise Exception(
|
|
370
|
+
response.error.get("message", "Failed to get analytics")
|
|
371
|
+
if response.error
|
|
372
|
+
else "Failed to get analytics"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
data = response.data.get("analytics", response.data)
|
|
376
|
+
return WorkspaceAnalytics(
|
|
377
|
+
workspace_id=data["workspaceId"],
|
|
378
|
+
total_agents=data["totalAgents"],
|
|
379
|
+
total_memories=data["totalMemories"],
|
|
380
|
+
total_spent_usd=data["totalSpentUSD"],
|
|
381
|
+
total_operations=data["totalOperations"],
|
|
382
|
+
operations_by_type=data.get("operationsByType", {}),
|
|
383
|
+
period_start=data["periodStart"],
|
|
384
|
+
period_end=data["periodEnd"],
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
async def get_budget(self, workspace_id: str) -> WorkspaceBudget:
|
|
388
|
+
"""
|
|
389
|
+
Get workspace budget aggregation
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
workspace_id: Workspace identifier
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
Workspace budget status
|
|
396
|
+
|
|
397
|
+
Example:
|
|
398
|
+
```python
|
|
399
|
+
budget = await client.workspaces.get_budget("ws_abc123")
|
|
400
|
+
print(f"Limit: ${budget.budget_limit_cents / 100}")
|
|
401
|
+
print(f"Spent: ${budget.total_spent_cents / 100}")
|
|
402
|
+
print(f"Usage: {budget.percentage_used:.1f}%")
|
|
403
|
+
```
|
|
404
|
+
"""
|
|
405
|
+
from urllib.parse import quote
|
|
406
|
+
|
|
407
|
+
response = await self.client.request(
|
|
408
|
+
"GET", f"/v1/workspaces/{quote(workspace_id, safe='')}/budget"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
if not response.success or not response.data:
|
|
412
|
+
raise Exception(
|
|
413
|
+
response.error.get("message", "Failed to get budget")
|
|
414
|
+
if response.error
|
|
415
|
+
else "Failed to get budget"
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
data = response.data.get("budget", response.data)
|
|
419
|
+
return WorkspaceBudget(
|
|
420
|
+
workspace_id=data["workspaceId"],
|
|
421
|
+
budget_limit_cents=data["budgetLimitCents"],
|
|
422
|
+
total_spent_cents=data["totalSpentCents"],
|
|
423
|
+
remaining_cents=data["remainingCents"],
|
|
424
|
+
percentage_used=data["percentageUsed"],
|
|
425
|
+
agent_budgets=[
|
|
426
|
+
AgentBudget(
|
|
427
|
+
agent_did=ab["agentDID"],
|
|
428
|
+
spent_cents=ab["spentCents"],
|
|
429
|
+
percentage_of_total=ab["percentageOfTotal"],
|
|
430
|
+
)
|
|
431
|
+
for ab in data.get("agentBudgets", [])
|
|
432
|
+
],
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
def _parse_workspace(self, data: dict) -> Workspace:
|
|
436
|
+
"""Parse workspace data into Workspace object"""
|
|
437
|
+
return Workspace(
|
|
438
|
+
workspace_id=data["workspaceId"],
|
|
439
|
+
owner_did=data["ownerDID"],
|
|
440
|
+
workspace_name=data["workspaceName"],
|
|
441
|
+
description=data.get("description"),
|
|
442
|
+
budget_limit_cents=data.get("budgetLimitCents"),
|
|
443
|
+
enabled_chains=data.get("enabledChains"),
|
|
444
|
+
enabled=data.get("enabled", True),
|
|
445
|
+
created_at=data["createdAt"],
|
|
446
|
+
updated_at=data["updatedAt"],
|
|
447
|
+
)
|