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.
@@ -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
+ )