webagents 0.1.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.
Files changed (94) hide show
  1. webagents/__init__.py +18 -0
  2. webagents/__main__.py +55 -0
  3. webagents/agents/__init__.py +13 -0
  4. webagents/agents/core/__init__.py +19 -0
  5. webagents/agents/core/base_agent.py +1834 -0
  6. webagents/agents/core/handoffs.py +293 -0
  7. webagents/agents/handoffs/__init__.py +0 -0
  8. webagents/agents/interfaces/__init__.py +0 -0
  9. webagents/agents/lifecycle/__init__.py +0 -0
  10. webagents/agents/skills/__init__.py +109 -0
  11. webagents/agents/skills/base.py +136 -0
  12. webagents/agents/skills/core/__init__.py +8 -0
  13. webagents/agents/skills/core/guardrails/__init__.py +0 -0
  14. webagents/agents/skills/core/llm/__init__.py +0 -0
  15. webagents/agents/skills/core/llm/anthropic/__init__.py +1 -0
  16. webagents/agents/skills/core/llm/litellm/__init__.py +10 -0
  17. webagents/agents/skills/core/llm/litellm/skill.py +538 -0
  18. webagents/agents/skills/core/llm/openai/__init__.py +1 -0
  19. webagents/agents/skills/core/llm/xai/__init__.py +1 -0
  20. webagents/agents/skills/core/mcp/README.md +375 -0
  21. webagents/agents/skills/core/mcp/__init__.py +15 -0
  22. webagents/agents/skills/core/mcp/skill.py +731 -0
  23. webagents/agents/skills/core/memory/__init__.py +11 -0
  24. webagents/agents/skills/core/memory/long_term_memory/__init__.py +10 -0
  25. webagents/agents/skills/core/memory/long_term_memory/memory_skill.py +639 -0
  26. webagents/agents/skills/core/memory/short_term_memory/__init__.py +9 -0
  27. webagents/agents/skills/core/memory/short_term_memory/skill.py +341 -0
  28. webagents/agents/skills/core/memory/vector_memory/skill.py +447 -0
  29. webagents/agents/skills/core/planning/__init__.py +9 -0
  30. webagents/agents/skills/core/planning/planner.py +343 -0
  31. webagents/agents/skills/ecosystem/__init__.py +0 -0
  32. webagents/agents/skills/ecosystem/crewai/__init__.py +1 -0
  33. webagents/agents/skills/ecosystem/database/__init__.py +1 -0
  34. webagents/agents/skills/ecosystem/filesystem/__init__.py +0 -0
  35. webagents/agents/skills/ecosystem/google/__init__.py +0 -0
  36. webagents/agents/skills/ecosystem/google/calendar/__init__.py +6 -0
  37. webagents/agents/skills/ecosystem/google/calendar/skill.py +306 -0
  38. webagents/agents/skills/ecosystem/n8n/__init__.py +0 -0
  39. webagents/agents/skills/ecosystem/openai_agents/__init__.py +0 -0
  40. webagents/agents/skills/ecosystem/web/__init__.py +0 -0
  41. webagents/agents/skills/ecosystem/zapier/__init__.py +0 -0
  42. webagents/agents/skills/robutler/__init__.py +11 -0
  43. webagents/agents/skills/robutler/auth/README.md +63 -0
  44. webagents/agents/skills/robutler/auth/__init__.py +17 -0
  45. webagents/agents/skills/robutler/auth/skill.py +354 -0
  46. webagents/agents/skills/robutler/crm/__init__.py +18 -0
  47. webagents/agents/skills/robutler/crm/skill.py +368 -0
  48. webagents/agents/skills/robutler/discovery/README.md +281 -0
  49. webagents/agents/skills/robutler/discovery/__init__.py +16 -0
  50. webagents/agents/skills/robutler/discovery/skill.py +230 -0
  51. webagents/agents/skills/robutler/kv/__init__.py +6 -0
  52. webagents/agents/skills/robutler/kv/skill.py +80 -0
  53. webagents/agents/skills/robutler/message_history/__init__.py +9 -0
  54. webagents/agents/skills/robutler/message_history/skill.py +270 -0
  55. webagents/agents/skills/robutler/messages/__init__.py +0 -0
  56. webagents/agents/skills/robutler/nli/__init__.py +13 -0
  57. webagents/agents/skills/robutler/nli/skill.py +687 -0
  58. webagents/agents/skills/robutler/notifications/__init__.py +5 -0
  59. webagents/agents/skills/robutler/notifications/skill.py +141 -0
  60. webagents/agents/skills/robutler/payments/__init__.py +41 -0
  61. webagents/agents/skills/robutler/payments/exceptions.py +255 -0
  62. webagents/agents/skills/robutler/payments/skill.py +610 -0
  63. webagents/agents/skills/robutler/storage/__init__.py +10 -0
  64. webagents/agents/skills/robutler/storage/files/__init__.py +9 -0
  65. webagents/agents/skills/robutler/storage/files/skill.py +445 -0
  66. webagents/agents/skills/robutler/storage/json/__init__.py +9 -0
  67. webagents/agents/skills/robutler/storage/json/skill.py +336 -0
  68. webagents/agents/skills/robutler/storage/kv/skill.py +88 -0
  69. webagents/agents/skills/robutler/storage.py +389 -0
  70. webagents/agents/tools/__init__.py +0 -0
  71. webagents/agents/tools/decorators.py +426 -0
  72. webagents/agents/tracing/__init__.py +0 -0
  73. webagents/agents/workflows/__init__.py +0 -0
  74. webagents/scripts/__init__.py +0 -0
  75. webagents/server/__init__.py +28 -0
  76. webagents/server/context/__init__.py +0 -0
  77. webagents/server/context/context_vars.py +121 -0
  78. webagents/server/core/__init__.py +0 -0
  79. webagents/server/core/app.py +843 -0
  80. webagents/server/core/middleware.py +69 -0
  81. webagents/server/core/models.py +98 -0
  82. webagents/server/core/monitoring.py +59 -0
  83. webagents/server/endpoints/__init__.py +0 -0
  84. webagents/server/interfaces/__init__.py +0 -0
  85. webagents/server/middleware.py +330 -0
  86. webagents/server/models.py +92 -0
  87. webagents/server/monitoring.py +659 -0
  88. webagents/utils/__init__.py +0 -0
  89. webagents/utils/logging.py +359 -0
  90. webagents-0.1.0.dist-info/METADATA +230 -0
  91. webagents-0.1.0.dist-info/RECORD +94 -0
  92. webagents-0.1.0.dist-info/WHEEL +4 -0
  93. webagents-0.1.0.dist-info/entry_points.txt +2 -0
  94. webagents-0.1.0.dist-info/licenses/LICENSE +20 -0
@@ -0,0 +1,336 @@
1
+ """
2
+ RobutlerJSONSkill - JSON Data Storage for Long-term Memory
3
+
4
+ Provides JSON data storage capabilities for agent long-term memory and persistent data.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ from typing import Dict, List, Any, Optional, Union
10
+ from datetime import datetime
11
+
12
+ from ....base import Skill
13
+ from webagents.agents.tools.decorators import tool
14
+ from robutler.api.client import RobutlerClient
15
+
16
+
17
+ class RobutlerJSONSkill(Skill):
18
+ """
19
+ WebAgents portal JSON storage skill for long-term memory using RobutlerClient.
20
+
21
+ Features:
22
+ - Store/retrieve JSON data for long-term memory
23
+ - Update and manage JSON data files
24
+ - Context-based scope access controls
25
+ - Agent identity determined by API key
26
+
27
+ Scope Configurations (from auth context):
28
+ - 'owner' scope: Full access to all JSON operations
29
+ - 'all' scope: No access (restricted)
30
+
31
+ Tool Scope Restrictions:
32
+ - store_json_data: owner scope only
33
+ - retrieve_json_data: owner scope only
34
+ - update_json_data: owner scope only
35
+ - delete_json_file: owner scope only
36
+ """
37
+
38
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
39
+ super().__init__(config)
40
+ self.portal_url = config.get('portal_url', 'http://localhost:3000') if config else 'http://localhost:3000'
41
+ self.api_key = config.get('api_key', os.getenv('ROBUTLER_API_KEY', 'rok_testapikey')) if config else os.getenv('ROBUTLER_API_KEY', 'rok_testapikey')
42
+
43
+ # Initialize RobutlerClient
44
+ self.client = RobutlerClient(
45
+ api_key=self.api_key,
46
+ base_url=self.portal_url
47
+ )
48
+
49
+ async def initialize(self, agent_reference):
50
+ """Initialize with agent reference"""
51
+ await super().initialize(agent_reference)
52
+ self.agent = agent_reference
53
+
54
+ async def cleanup(self):
55
+ """Cleanup method to close client session"""
56
+ if self.client:
57
+ await self.client.close()
58
+
59
+ def _get_scope_from_context(self) -> str:
60
+ """
61
+ Get scope from authentication context.
62
+
63
+ Returns:
64
+ 'owner' or 'all' based on auth context
65
+ """
66
+ try:
67
+ from webagents.server.context.context_vars import get_context
68
+ context = get_context()
69
+ if context:
70
+ # Check auth scope from context
71
+ auth_scope = context.get('auth_scope') or context.get('scope')
72
+ if auth_scope == 'owner':
73
+ return 'owner'
74
+ return 'all' # Default to 'all' scope
75
+ except Exception:
76
+ return 'all' # Fallback to 'all' scope
77
+
78
+ def _get_storage_visibility(self) -> str:
79
+ """
80
+ Get the appropriate visibility for storing content based on scope.
81
+
82
+ Returns:
83
+ - 'private' for owner scope (private agent storage)
84
+ - 'public' for all scope (public agent storage)
85
+ """
86
+ scope = self._get_scope_from_context()
87
+ if scope == 'owner':
88
+ return 'private' # Store privately for owner
89
+ else: # 'all' scope
90
+ return 'public' # Store publicly for general access
91
+
92
+ def _get_access_visibility(self) -> str:
93
+ """
94
+ Get the appropriate visibility for accessing content based on scope.
95
+
96
+ Returns:
97
+ - 'both' for owner scope (access to public and private content)
98
+ - 'public' for all scope (access to only public content)
99
+ """
100
+ scope = self._get_scope_from_context()
101
+ if scope == 'owner':
102
+ return 'both' # Access to both public and private content
103
+ else: # 'all' scope
104
+ return 'public' # Access to only public content
105
+
106
+ @tool(scope="owner")
107
+ async def store_json_data(
108
+ self,
109
+ filename: str,
110
+ data: Dict[str, Any],
111
+ description: Optional[str] = None
112
+ ) -> str:
113
+ """
114
+ Store JSON data for long-term memory.
115
+
116
+ Args:
117
+ filename: Name of the file (will add .json if not present)
118
+ data: JSON-serializable data to store
119
+ description: Optional description of the file
120
+
121
+ Returns:
122
+ JSON string with storage result
123
+ """
124
+ try:
125
+ # Ensure filename has .json extension
126
+ if not filename.endswith('.json'):
127
+ filename = f"{filename}.json"
128
+
129
+ # Convert data to JSON string and bytes
130
+ json_content = json.dumps(data, indent=2)
131
+ json_bytes = json_content.encode('utf-8')
132
+
133
+ # Upload using RobutlerClient
134
+ response = await self.client.upload_content(
135
+ filename=filename,
136
+ content_data=json_bytes,
137
+ content_type='application/json',
138
+ visibility=self._get_storage_visibility(),
139
+ description=description or "JSON data for long-term memory",
140
+ tags=['json_data', 'memory']
141
+ )
142
+
143
+ if response.success and response.data:
144
+ file_info = response.data.get('file', {})
145
+ return json.dumps({
146
+ "success": True,
147
+ "file_id": file_info.get('id'),
148
+ "filename": file_info.get('fileName'),
149
+ "url": file_info.get('url'),
150
+ "size": file_info.get('size')
151
+ }, indent=2)
152
+ else:
153
+ return json.dumps({
154
+ "success": False,
155
+ "error": f"Upload failed: {response.error or response.message}"
156
+ })
157
+
158
+ except Exception as e:
159
+ return json.dumps({
160
+ "success": False,
161
+ "error": f"Failed to store JSON data: {str(e)}"
162
+ })
163
+
164
+ @tool(scope="owner")
165
+ async def retrieve_json_data(self, filename: str) -> str:
166
+ """
167
+ Retrieve JSON data from long-term memory.
168
+
169
+ Args:
170
+ filename: Name of the file to retrieve
171
+
172
+ Returns:
173
+ JSON string with file content or error
174
+ """
175
+ try:
176
+ # Ensure filename has .json extension
177
+ if not filename.endswith('.json'):
178
+ filename = f"{filename}.json"
179
+
180
+ # Get content using RobutlerClient
181
+ response = await self.client.get_content(filename)
182
+
183
+ if response.success and response.data:
184
+ return json.dumps({
185
+ "success": True,
186
+ "filename": response.data.get('filename'),
187
+ "data": response.data.get('content'),
188
+ "metadata": response.data.get('metadata', {})
189
+ }, indent=2)
190
+ else:
191
+ # Get available files for better error message
192
+ list_response = await self.client.list_content(visibility=self._get_access_visibility())
193
+ available_files = []
194
+ if list_response.success and list_response.data:
195
+ files = list_response.data.get('files', [])
196
+ for file_info in files:
197
+ if 'json_data' in file_info.get('tags', []):
198
+ available_files.append(file_info.get('fileName', ''))
199
+
200
+ return json.dumps({
201
+ "success": False,
202
+ "error": response.error or response.message or "File not found",
203
+ "available_json_files": available_files
204
+ })
205
+
206
+ except Exception as e:
207
+ return json.dumps({
208
+ "success": False,
209
+ "error": f"Failed to retrieve JSON data: {str(e)}"
210
+ })
211
+
212
+ @tool(scope="owner")
213
+ async def update_json_data(
214
+ self,
215
+ filename: str,
216
+ data: Dict[str, Any],
217
+ description: Optional[str] = None
218
+ ) -> str:
219
+ """
220
+ Update existing JSON data in long-term memory.
221
+
222
+ Args:
223
+ filename: Name of the file to update
224
+ data: New JSON data
225
+ description: Optional new description
226
+
227
+ Returns:
228
+ JSON string with update result
229
+ """
230
+ try:
231
+ # Ensure filename has .json extension
232
+ if not filename.endswith('.json'):
233
+ filename = f"{filename}.json"
234
+
235
+ # Convert data to JSON string and bytes
236
+ json_content = json.dumps(data, indent=2)
237
+ json_bytes = json_content.encode('utf-8')
238
+
239
+ # Update using RobutlerClient
240
+ response = await self.client.update_content(
241
+ filename=filename,
242
+ content_data=json_bytes,
243
+ content_type='application/json',
244
+ description=description or "Updated JSON data for long-term memory",
245
+ tags=['json_data', 'memory'],
246
+ visibility=self._get_storage_visibility()
247
+ )
248
+
249
+ if response.success and response.data:
250
+ file_info = response.data.get('file', {})
251
+ return json.dumps({
252
+ "success": True,
253
+ "file_id": file_info.get('id'),
254
+ "filename": file_info.get('fileName'),
255
+ "url": file_info.get('url'),
256
+ "size": file_info.get('size')
257
+ }, indent=2)
258
+ else:
259
+ return json.dumps({
260
+ "success": False,
261
+ "error": f"Update failed: {response.error or response.message}"
262
+ })
263
+
264
+ except Exception as e:
265
+ return json.dumps({
266
+ "success": False,
267
+ "error": f"Failed to update JSON data: {str(e)}"
268
+ })
269
+
270
+ @tool(scope="owner")
271
+ async def delete_json_file(self, filename: str) -> str:
272
+ """
273
+ Delete a JSON file from long-term memory.
274
+
275
+ Args:
276
+ filename: Name of the file to delete
277
+
278
+ Returns:
279
+ JSON string with deletion result
280
+ """
281
+ try:
282
+ # Ensure filename has .json extension
283
+ if not filename.endswith('.json'):
284
+ filename = f"{filename}.json"
285
+
286
+ # Delete using RobutlerClient
287
+ response = await self.client.delete_content(filename)
288
+
289
+ if response.success:
290
+ return json.dumps({
291
+ "success": True,
292
+ "message": f"JSON file '{filename}' deleted successfully"
293
+ })
294
+ else:
295
+ return json.dumps({
296
+ "success": False,
297
+ "error": response.error or response.message or f"Delete failed"
298
+ })
299
+
300
+ except Exception as e:
301
+ return json.dumps({
302
+ "success": False,
303
+ "error": f"Failed to delete JSON file: {str(e)}"
304
+ })
305
+
306
+ def get_skill_info(self) -> Dict[str, Any]:
307
+ """Get comprehensive skill information"""
308
+ return {
309
+ "name": "RobutlerJSONSkill",
310
+ "description": "JSON data storage for long-term memory",
311
+ "version": "1.0.0",
312
+ "capabilities": [
313
+ "Store JSON data for long-term memory (owner scope only)",
314
+ "Retrieve JSON data from memory (owner scope only)",
315
+ "Update and manage JSON files (owner scope only)",
316
+ "Delete JSON files (owner scope only)",
317
+ "Agent identity determined by API key",
318
+ "Integration with portal authentication via RobutlerClient",
319
+ "Owner scope: Full access to JSON operations",
320
+ "All scope: No access (restricted)"
321
+ ],
322
+ "tools": [
323
+ "store_json_data",
324
+ "retrieve_json_data",
325
+ "update_json_data",
326
+ "delete_json_file",
327
+ ],
328
+ "config": {
329
+ "portal_url": self.portal_url,
330
+ "api_key_configured": bool(self.api_key),
331
+ "client_type": "RobutlerClient",
332
+ "scope": self._get_scope_from_context(),
333
+ "storage_visibility": self._get_storage_visibility(),
334
+ "access_visibility": self._get_access_visibility()
335
+ }
336
+ }
@@ -0,0 +1,88 @@
1
+ """
2
+ RobutlerKVSkill - Simple key/value storage backed by the portal /api/kv endpoint.
3
+
4
+ Used for storing small secrets and tokens like OAuth credentials per user/agent.
5
+ """
6
+
7
+ from typing import Any, Dict, Optional
8
+ import os
9
+
10
+ from ....base import Skill
11
+ from robutler.api.client import RobutlerClient
12
+ from webagents.agents.tools.decorators import tool
13
+
14
+
15
+ class RobutlerKVSkill(Skill):
16
+ """KV storage skill using the WebAgents portal API"""
17
+
18
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
19
+ super().__init__(config)
20
+ self.portal_url = config.get('portal_url') if config else os.getenv('ROBUTLER_API_URL', 'http://localhost:3000')
21
+ self.api_key = config.get('api_key') if config else os.getenv('ROBUTLER_API_KEY')
22
+ self.client: Optional[RobutlerClient] = None
23
+ self.agent = None
24
+
25
+ async def initialize(self, agent):
26
+ await super().initialize(agent)
27
+ self.agent = agent
28
+ # Prefer agent.api_key over env
29
+ api_key = getattr(agent, 'api_key', None) or self.api_key
30
+ self.client = RobutlerClient(api_key=api_key, base_url=self.portal_url)
31
+
32
+ async def cleanup(self):
33
+ if self.client:
34
+ await self.client.close()
35
+
36
+ # Internal helpers
37
+ def _subject(self) -> Dict[str, Optional[str]]:
38
+ # Use new subject addressing with type/id
39
+ agent_id = getattr(self.agent, 'id', None)
40
+ if agent_id:
41
+ return {"subjectType": "agent", "subjectId": agent_id}
42
+ # Fallback legacy agentId if missing (shouldn't happen)
43
+ return {"agentId": agent_id}
44
+
45
+ # Programmatic API
46
+ async def kv_set(self, key: str, value: str, namespace: Optional[str] = None) -> bool:
47
+ if not self.client:
48
+ raise RuntimeError("KV client not initialized")
49
+ body = {"key": key, "value": value}
50
+ body.update(self._subject())
51
+ if namespace is not None:
52
+ body["namespace"] = namespace
53
+ res = await self.client._make_request('POST', '/kv', data=body)
54
+ return bool(res.success)
55
+
56
+ async def kv_get(self, key: str, namespace: Optional[str] = None) -> Optional[str]:
57
+ if not self.client:
58
+ raise RuntimeError("KV client not initialized")
59
+ params = {"key": key}
60
+ params.update(self._subject())
61
+ if namespace is not None:
62
+ params["namespace"] = namespace
63
+ res = await self.client._make_request('GET', '/kv', params=params)
64
+ if not res.success:
65
+ return None
66
+ data = res.data or {}
67
+ if data.get('found'):
68
+ return data.get('value')
69
+ return None
70
+
71
+ # Expose minimal tools for manual ops (owner scope)
72
+ @tool(
73
+ description="Store a small piece of data (like credentials or settings) by key. Use sparingly - not for large data or temporary storage.",
74
+ scope="owner"
75
+ )
76
+ async def kv_store(self, key: str, value: str, namespace: Optional[str] = None) -> str:
77
+ ok = await self.kv_set(key, value, namespace)
78
+ return "stored" if ok else "failed"
79
+
80
+ @tool(
81
+ description="Retrieve a previously stored piece of data by key. Returns empty string if not found.",
82
+ scope="owner"
83
+ )
84
+ async def kv_read(self, key: str, namespace: Optional[str] = None) -> str:
85
+ val = await self.kv_get(key, namespace)
86
+ return val or ""
87
+
88
+