webagents 0.2.0__py3-none-any.whl → 0.2.3__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 (46) hide show
  1. webagents/__init__.py +9 -0
  2. webagents/agents/core/base_agent.py +865 -69
  3. webagents/agents/core/handoffs.py +14 -6
  4. webagents/agents/skills/base.py +33 -2
  5. webagents/agents/skills/core/llm/litellm/skill.py +906 -27
  6. webagents/agents/skills/core/memory/vector_memory/skill.py +8 -16
  7. webagents/agents/skills/ecosystem/crewai/__init__.py +3 -1
  8. webagents/agents/skills/ecosystem/crewai/skill.py +158 -0
  9. webagents/agents/skills/ecosystem/database/__init__.py +3 -1
  10. webagents/agents/skills/ecosystem/database/skill.py +522 -0
  11. webagents/agents/skills/ecosystem/mongodb/__init__.py +3 -0
  12. webagents/agents/skills/ecosystem/mongodb/skill.py +428 -0
  13. webagents/agents/skills/ecosystem/n8n/README.md +287 -0
  14. webagents/agents/skills/ecosystem/n8n/__init__.py +3 -0
  15. webagents/agents/skills/ecosystem/n8n/skill.py +341 -0
  16. webagents/agents/skills/ecosystem/openai/__init__.py +6 -0
  17. webagents/agents/skills/ecosystem/openai/skill.py +867 -0
  18. webagents/agents/skills/ecosystem/replicate/README.md +440 -0
  19. webagents/agents/skills/ecosystem/replicate/__init__.py +10 -0
  20. webagents/agents/skills/ecosystem/replicate/skill.py +517 -0
  21. webagents/agents/skills/ecosystem/x_com/README.md +401 -0
  22. webagents/agents/skills/ecosystem/x_com/__init__.py +3 -0
  23. webagents/agents/skills/ecosystem/x_com/skill.py +1048 -0
  24. webagents/agents/skills/ecosystem/zapier/README.md +363 -0
  25. webagents/agents/skills/ecosystem/zapier/__init__.py +3 -0
  26. webagents/agents/skills/ecosystem/zapier/skill.py +337 -0
  27. webagents/agents/skills/examples/__init__.py +6 -0
  28. webagents/agents/skills/examples/music_player.py +329 -0
  29. webagents/agents/skills/robutler/handoff/__init__.py +6 -0
  30. webagents/agents/skills/robutler/handoff/skill.py +191 -0
  31. webagents/agents/skills/robutler/nli/skill.py +180 -24
  32. webagents/agents/skills/robutler/payments/exceptions.py +27 -7
  33. webagents/agents/skills/robutler/payments/skill.py +64 -14
  34. webagents/agents/skills/robutler/storage/files/skill.py +2 -2
  35. webagents/agents/tools/decorators.py +243 -47
  36. webagents/agents/widgets/__init__.py +6 -0
  37. webagents/agents/widgets/renderer.py +150 -0
  38. webagents/server/core/app.py +130 -15
  39. webagents/server/core/models.py +1 -1
  40. webagents/utils/logging.py +13 -1
  41. {webagents-0.2.0.dist-info → webagents-0.2.3.dist-info}/METADATA +16 -9
  42. {webagents-0.2.0.dist-info → webagents-0.2.3.dist-info}/RECORD +45 -24
  43. webagents/agents/skills/ecosystem/openai_agents/__init__.py +0 -0
  44. {webagents-0.2.0.dist-info → webagents-0.2.3.dist-info}/WHEEL +0 -0
  45. {webagents-0.2.0.dist-info → webagents-0.2.3.dist-info}/entry_points.txt +0 -0
  46. {webagents-0.2.0.dist-info → webagents-0.2.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,428 @@
1
+ """
2
+ Minimalistic MongoDB Skill for WebAgents
3
+
4
+ This skill allows users to:
5
+ - Connect to MongoDB databases (local, Atlas, or self-hosted)
6
+ - Perform CRUD operations on collections
7
+ - Execute aggregation pipelines and queries
8
+ - Manage database schemas and indexes
9
+
10
+ Uses auth skill for user context and kv skill for secure connection storage.
11
+ """
12
+
13
+ import os
14
+ import json
15
+ from typing import Dict, Any, Optional, List, Union
16
+ from datetime import datetime
17
+
18
+ from webagents.agents.skills.base import Skill
19
+ from webagents.agents.tools.decorators import tool, prompt
20
+ from webagents.server.context.context_vars import get_context
21
+
22
+ try:
23
+ from pymongo import MongoClient
24
+ from pymongo.errors import ConnectionFailure, OperationFailure, ConfigurationError
25
+ from bson import ObjectId
26
+ from bson.errors import InvalidId
27
+ MONGODB_AVAILABLE = True
28
+ except ImportError:
29
+ MONGODB_AVAILABLE = False
30
+ # Create dummy classes for type hints when pymongo is not available
31
+ class MongoClient:
32
+ pass
33
+ class ObjectId:
34
+ pass
35
+ class ConnectionFailure(Exception):
36
+ pass
37
+
38
+
39
+ class MongoDBSkill(Skill):
40
+ """Minimalistic MongoDB skill for document database operations"""
41
+
42
+ def __init__(self):
43
+ super().__init__()
44
+ if not MONGODB_AVAILABLE:
45
+ raise ImportError("MongoDB dependencies not installed. Install with: pip install pymongo")
46
+
47
+ def get_dependencies(self) -> List[str]:
48
+ """Skill dependencies"""
49
+ return ['auth', 'kv']
50
+
51
+ @prompt(priority=40, scope=["owner", "all"])
52
+ def mongodb_prompt(self) -> str:
53
+ """Prompt describing MongoDB capabilities"""
54
+ return """
55
+ Minimalistic MongoDB integration for document database operations. Available tools:
56
+
57
+ • mongodb_setup(config) - Set up MongoDB connection securely (Atlas, local, or self-hosted)
58
+ • mongodb_query(database, collection, operation, query, data) - Execute database operations
59
+ • mongodb_aggregate(database, collection, pipeline) - Run aggregation pipelines
60
+ • mongodb_status() - Check database connection and configuration status
61
+
62
+ Features:
63
+ - MongoDB Atlas, local, and self-hosted support
64
+ - Document CRUD operations (Create, Read, Update, Delete)
65
+ - Aggregation pipeline execution
66
+ - Index management and optimization
67
+ - Secure connection string storage
68
+ - Per-user database isolation via Auth skill
69
+
70
+ Setup: Configure with your MongoDB connection string or Atlas credentials.
71
+ """
72
+
73
+ # Helper methods for auth and kv skills
74
+ async def _get_auth_skill(self):
75
+ """Get auth skill for user context"""
76
+ return self.agent.skills.get('auth')
77
+
78
+ async def _get_kv_skill(self):
79
+ """Get KV skill for secure storage"""
80
+ return self.agent.skills.get('kv')
81
+
82
+ async def _get_authenticated_user_id(self) -> Optional[str]:
83
+ """Get authenticated user ID from context"""
84
+ try:
85
+ context = get_context()
86
+ if context and context.auth and context.auth.authenticated:
87
+ return context.auth.user_id
88
+ return None
89
+ except Exception as e:
90
+ self.logger.error(f"Failed to get user context: {e}")
91
+ return None
92
+
93
+ async def _save_mongodb_config(self, user_id: str, config: Dict[str, Any]) -> bool:
94
+ """Save MongoDB configuration securely using KV skill"""
95
+ try:
96
+ kv_skill = await self._get_kv_skill()
97
+ if kv_skill:
98
+ config_data = {
99
+ **config,
100
+ 'created_at': datetime.now().isoformat(),
101
+ 'updated_at': datetime.now().isoformat()
102
+ }
103
+ await kv_skill.kv_set(
104
+ key='config',
105
+ value=json.dumps(config_data),
106
+ namespace=f'mongodb:{user_id}'
107
+ )
108
+ return True
109
+ else:
110
+ # Fallback to in-memory storage
111
+ if not hasattr(self.agent, '_mongodb_configs'):
112
+ self.agent._mongodb_configs = {}
113
+ self.agent._mongodb_configs[user_id] = config
114
+ return True
115
+ except Exception as e:
116
+ self.logger.error(f"Failed to save MongoDB config: {e}")
117
+ return False
118
+
119
+ async def _load_mongodb_config(self, user_id: str) -> Optional[Dict[str, Any]]:
120
+ """Load MongoDB configuration from KV skill"""
121
+ try:
122
+ kv_skill = await self._get_kv_skill()
123
+ if kv_skill:
124
+ config_json = await kv_skill.kv_get(
125
+ key='config',
126
+ namespace=f'mongodb:{user_id}'
127
+ )
128
+ if config_json:
129
+ return json.loads(config_json)
130
+ else:
131
+ # Fallback to in-memory storage
132
+ if hasattr(self.agent, '_mongodb_configs'):
133
+ return self.agent._mongodb_configs.get(user_id)
134
+ return None
135
+ except Exception as e:
136
+ self.logger.error(f"Failed to load MongoDB config: {e}")
137
+ return None
138
+
139
+ def _create_mongodb_client(self, config: Dict[str, Any]) -> Optional[MongoClient]:
140
+ """Create MongoDB client from configuration"""
141
+ try:
142
+ connection_string = config.get('connection_string')
143
+ if connection_string:
144
+ # Use connection string (Atlas, local, or custom)
145
+ return MongoClient(connection_string)
146
+
147
+ # Alternative: individual connection parameters
148
+ host = config.get('host', 'localhost')
149
+ port = config.get('port', 27017)
150
+ username = config.get('username')
151
+ password = config.get('password')
152
+ database = config.get('database')
153
+
154
+ if username and password:
155
+ if database:
156
+ connection_string = f"mongodb://{username}:{password}@{host}:{port}/{database}"
157
+ else:
158
+ connection_string = f"mongodb://{username}:{password}@{host}:{port}"
159
+ else:
160
+ connection_string = f"mongodb://{host}:{port}"
161
+
162
+ return MongoClient(connection_string)
163
+
164
+ except Exception as e:
165
+ self.logger.error(f"Failed to create MongoDB client: {e}")
166
+ return None
167
+
168
+ def _serialize_mongodb_result(self, data: Any) -> Any:
169
+ """Serialize MongoDB results for JSON output"""
170
+ if isinstance(data, ObjectId) or (hasattr(data, '__class__') and data.__class__.__name__ == 'ObjectId'):
171
+ return str(data)
172
+ elif isinstance(data, dict):
173
+ return {k: self._serialize_mongodb_result(v) for k, v in data.items()}
174
+ elif isinstance(data, list):
175
+ return [self._serialize_mongodb_result(item) for item in data]
176
+ elif isinstance(data, datetime):
177
+ return data.isoformat()
178
+ else:
179
+ return data
180
+
181
+ # Public tools
182
+ @tool(description="Set up MongoDB database connection", scope="owner")
183
+ async def mongodb_setup(self, config: Dict[str, Any]) -> str:
184
+ """Set up MongoDB connection configuration"""
185
+ user_id = await self._get_authenticated_user_id()
186
+ if not user_id:
187
+ return "❌ Authentication required"
188
+
189
+ if not config:
190
+ return "❌ Configuration is required"
191
+
192
+ try:
193
+ # Validate configuration
194
+ if not config.get('connection_string') and not all([
195
+ config.get('host'), config.get('port')
196
+ ]):
197
+ return "❌ MongoDB configuration requires either 'connection_string' or 'host' and 'port'"
198
+
199
+ # Test connection
200
+ client = self._create_mongodb_client(config)
201
+ if not client:
202
+ return "❌ Failed to create MongoDB client"
203
+
204
+ # Test connection with ping
205
+ try:
206
+ client.admin.command('ping')
207
+ client.close()
208
+ except ConnectionFailure:
209
+ return "❌ MongoDB connection test failed"
210
+ except Exception as e:
211
+ return f"❌ MongoDB connection error: {str(e)}"
212
+
213
+ # Save configuration
214
+ success = await self._save_mongodb_config(user_id, config)
215
+
216
+ if success:
217
+ connection_type = "Atlas" if "mongodb.net" in config.get('connection_string', '') else "MongoDB"
218
+ return f"✅ {connection_type} configuration saved successfully!\n🔧 Database type: MongoDB\n🔒 Connection details stored securely"
219
+ else:
220
+ return "❌ Failed to save configuration"
221
+
222
+ except Exception as e:
223
+ return f"❌ Setup failed: {str(e)}"
224
+
225
+ @tool(description="Execute MongoDB operations on collections")
226
+ async def mongodb_query(self, database: str, collection: str, operation: str,
227
+ query: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None) -> str:
228
+ """Execute MongoDB operations (find, insert, update, delete)"""
229
+ user_id = await self._get_authenticated_user_id()
230
+ if not user_id:
231
+ return "❌ Authentication required"
232
+
233
+ if not database or not collection or not operation:
234
+ return "❌ Database, collection, and operation are required"
235
+
236
+ operation = operation.lower().strip()
237
+ valid_operations = ['find', 'find_one', 'insert_one', 'insert_many', 'update_one', 'update_many', 'delete_one', 'delete_many', 'count']
238
+
239
+ if operation not in valid_operations:
240
+ return f"❌ Invalid operation. Supported: {', '.join(valid_operations)}"
241
+
242
+ try:
243
+ # Load user configuration
244
+ config = await self._load_mongodb_config(user_id)
245
+ if not config:
246
+ return "❌ MongoDB not configured. Please run mongodb_setup() first."
247
+
248
+ # Create client and get collection
249
+ client = self._create_mongodb_client(config)
250
+ if not client:
251
+ return "❌ Failed to connect to MongoDB"
252
+
253
+ try:
254
+ db = client[database]
255
+ coll = db[collection]
256
+
257
+ # Execute operation
258
+ if operation == 'find':
259
+ query = query or {}
260
+ results = list(coll.find(query))
261
+ serialized_results = self._serialize_mongodb_result(results)
262
+ return f"✅ Find operation successful!\n📊 Results ({len(results)} documents):\n{json.dumps(serialized_results, indent=2, default=str)}"
263
+
264
+ elif operation == 'find_one':
265
+ query = query or {}
266
+ result = coll.find_one(query)
267
+ if result:
268
+ serialized_result = self._serialize_mongodb_result(result)
269
+ return f"✅ Find one operation successful!\n📊 Result:\n{json.dumps(serialized_result, indent=2, default=str)}"
270
+ else:
271
+ return "✅ Find one operation successful!\n📊 No document found"
272
+
273
+ elif operation == 'insert_one':
274
+ if not data:
275
+ return "❌ Data is required for insert_one operation"
276
+
277
+ result = coll.insert_one(data)
278
+ return f"✅ Insert one operation successful!\n📝 Inserted document ID: {result.inserted_id}"
279
+
280
+ elif operation == 'insert_many':
281
+ if not data or not isinstance(data.get('documents'), list):
282
+ return "❌ Data with 'documents' array is required for insert_many operation"
283
+
284
+ result = coll.insert_many(data['documents'])
285
+ return f"✅ Insert many operation successful!\n📝 Inserted {len(result.inserted_ids)} documents"
286
+
287
+ elif operation == 'update_one':
288
+ if not query:
289
+ return "❌ Query filter is required for update_one operation"
290
+ if not data:
291
+ return "❌ Update data is required for update_one operation"
292
+
293
+ result = coll.update_one(query, {'$set': data})
294
+ return f"✅ Update one operation successful!\n📝 Matched: {result.matched_count}, Modified: {result.modified_count}"
295
+
296
+ elif operation == 'update_many':
297
+ if not query:
298
+ return "❌ Query filter is required for update_many operation"
299
+ if not data:
300
+ return "❌ Update data is required for update_many operation"
301
+
302
+ result = coll.update_many(query, {'$set': data})
303
+ return f"✅ Update many operation successful!\n📝 Matched: {result.matched_count}, Modified: {result.modified_count}"
304
+
305
+ elif operation == 'delete_one':
306
+ if not query:
307
+ return "❌ Query filter is required for delete_one operation"
308
+
309
+ result = coll.delete_one(query)
310
+ return f"✅ Delete one operation successful!\n📝 Deleted: {result.deleted_count} document"
311
+
312
+ elif operation == 'delete_many':
313
+ if not query:
314
+ return "❌ Query filter is required for delete_many operation"
315
+
316
+ result = coll.delete_many(query)
317
+ return f"✅ Delete many operation successful!\n📝 Deleted: {result.deleted_count} documents"
318
+
319
+ elif operation == 'count':
320
+ query = query or {}
321
+ count = coll.count_documents(query)
322
+ return f"✅ Count operation successful!\n📊 Document count: {count}"
323
+
324
+ finally:
325
+ client.close()
326
+
327
+ except Exception as e:
328
+ return f"❌ MongoDB operation failed: {str(e)}"
329
+
330
+ @tool(description="Execute MongoDB aggregation pipelines")
331
+ async def mongodb_aggregate(self, database: str, collection: str, pipeline: List[Dict[str, Any]]) -> str:
332
+ """Execute MongoDB aggregation pipeline"""
333
+ user_id = await self._get_authenticated_user_id()
334
+ if not user_id:
335
+ return "❌ Authentication required"
336
+
337
+ if not database or not collection or not pipeline:
338
+ return "❌ Database, collection, and pipeline are required"
339
+
340
+ if not isinstance(pipeline, list):
341
+ return "❌ Pipeline must be a list of aggregation stages"
342
+
343
+ try:
344
+ # Load user configuration
345
+ config = await self._load_mongodb_config(user_id)
346
+ if not config:
347
+ return "❌ MongoDB not configured. Please run mongodb_setup() first."
348
+
349
+ # Create client and get collection
350
+ client = self._create_mongodb_client(config)
351
+ if not client:
352
+ return "❌ Failed to connect to MongoDB"
353
+
354
+ try:
355
+ db = client[database]
356
+ coll = db[collection]
357
+
358
+ # Execute aggregation pipeline
359
+ results = list(coll.aggregate(pipeline))
360
+ serialized_results = self._serialize_mongodb_result(results)
361
+
362
+ return f"✅ Aggregation pipeline executed successfully!\n📊 Results ({len(results)} documents):\n{json.dumps(serialized_results, indent=2, default=str)}"
363
+
364
+ finally:
365
+ client.close()
366
+
367
+ except Exception as e:
368
+ return f"❌ Aggregation pipeline failed: {str(e)}"
369
+
370
+ @tool(description="Check MongoDB connection status and configuration")
371
+ async def mongodb_status(self) -> str:
372
+ """Check MongoDB connection and configuration status"""
373
+ user_id = await self._get_authenticated_user_id()
374
+ if not user_id:
375
+ return "❌ Authentication required"
376
+
377
+ try:
378
+ # Load configuration
379
+ config = await self._load_mongodb_config(user_id)
380
+
381
+ result = ["📋 MongoDB Status:\n"]
382
+
383
+ if config:
384
+ connection_string = config.get('connection_string', '')
385
+ created_at = config.get('created_at', 'unknown')
386
+
387
+ # Determine connection type
388
+ if 'mongodb.net' in connection_string:
389
+ db_type = "MongoDB Atlas"
390
+ elif 'localhost' in connection_string or '127.0.0.1' in connection_string:
391
+ db_type = "Local MongoDB"
392
+ else:
393
+ db_type = "MongoDB"
394
+
395
+ result.append(f"✅ Configuration: Active")
396
+ result.append(f"🗄️ Database Type: {db_type}")
397
+ result.append(f"🕐 Created: {created_at}")
398
+
399
+ # Test connection
400
+ try:
401
+ client = self._create_mongodb_client(config)
402
+ if client:
403
+ client.admin.command('ping')
404
+
405
+ # Get server info
406
+ server_info = client.server_info()
407
+ version = server_info.get('version', 'unknown')
408
+
409
+ client.close()
410
+ result.append("🟢 Connection: Active")
411
+ result.append(f"📦 MongoDB Version: {version}")
412
+ else:
413
+ result.append("🔴 Connection: Failed")
414
+
415
+ except Exception as e:
416
+ result.append(f"🟡 Connection: Warning - {str(e)}")
417
+
418
+ else:
419
+ result.append("❌ No configuration found")
420
+ result.append("💡 Use mongodb_setup() to configure your MongoDB connection")
421
+ return "\n".join(result)
422
+
423
+ result.append("\n💡 Use mongodb_query() for CRUD operations or mongodb_aggregate() for pipelines")
424
+
425
+ return "\n".join(result)
426
+
427
+ except Exception as e:
428
+ return f"❌ Error checking status: {str(e)}"