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,517 @@
1
+ """
2
+ Replicate Skill for WebAgents
3
+
4
+ This skill allows users to:
5
+ - Set up Replicate API key securely (via auth/kv skills)
6
+ - List available models on Replicate
7
+ - Run predictions with models
8
+ - Get prediction status and results
9
+ - Cancel running predictions
10
+
11
+ Uses auth skill for user context and kv skill for secure API key storage.
12
+ """
13
+
14
+ import os
15
+ import httpx
16
+ import json
17
+ import asyncio
18
+ from typing import Dict, Any, Optional, List, Union
19
+ from datetime import datetime
20
+
21
+ from webagents.agents.skills.base import Skill
22
+ from webagents.agents.tools.decorators import tool, prompt
23
+ from webagents.server.context.context_vars import get_context
24
+
25
+
26
+ class ReplicateSkill(Skill):
27
+ """Replicate skill for running machine learning models"""
28
+
29
+ def __init__(self):
30
+ super().__init__()
31
+ self.api_base = "https://api.replicate.com/v1"
32
+
33
+ def get_dependencies(self) -> List[str]:
34
+ """Skill dependencies"""
35
+ return ['auth', 'kv']
36
+
37
+ @prompt(priority=40, scope=["owner", "all"])
38
+ def replicate_prompt(self) -> str:
39
+ """Prompt describing Replicate capabilities"""
40
+ return """
41
+ Replicate integration for running machine learning models. Available tools:
42
+
43
+ • replicate_setup(api_token) - Set up Replicate API credentials securely
44
+ • replicate_list_models(owner) - List models from a specific owner or popular models
45
+ • replicate_run_prediction(model, input_data) - Run a prediction with a model
46
+ • replicate_get_prediction(prediction_id) - Get prediction status and results
47
+ • replicate_cancel_prediction(prediction_id) - Cancel a running prediction
48
+ • replicate_get_model_info(model) - Get detailed information about a model
49
+
50
+ Features:
51
+ - Secure API token storage via KV skill
52
+ - Per-user credential isolation via Auth skill
53
+ - Run any public model on Replicate
54
+ - Monitor prediction progress and results
55
+ - Handle both sync and async predictions
56
+ - Support for text, image, audio, and video models
57
+
58
+ Setup: First run replicate_setup() with your Replicate API token from your account settings.
59
+ """
60
+
61
+ # Helper methods for auth and kv skills
62
+ async def _get_auth_skill(self):
63
+ """Get auth skill for user context"""
64
+ return self.agent.skills.get('auth')
65
+
66
+ async def _get_kv_skill(self):
67
+ """Get KV skill for secure storage"""
68
+ return self.agent.skills.get('kv')
69
+
70
+ async def _get_authenticated_user_id(self) -> Optional[str]:
71
+ """Get authenticated user ID from context"""
72
+ try:
73
+ context = get_context()
74
+ if context and context.auth and context.auth.authenticated:
75
+ return context.auth.user_id
76
+ return None
77
+ except Exception as e:
78
+ if hasattr(self, 'logger'):
79
+ self.logger.error(f"Failed to get user context: {e}")
80
+ return None
81
+
82
+ async def _save_replicate_credentials(self, user_id: str, api_token: str) -> bool:
83
+ """Save Replicate credentials securely using KV skill"""
84
+ try:
85
+ kv_skill = await self._get_kv_skill()
86
+ if kv_skill:
87
+ credentials = {
88
+ 'api_token': api_token,
89
+ 'created_at': datetime.now().isoformat()
90
+ }
91
+ try:
92
+ await kv_skill.kv_set(
93
+ key='credentials',
94
+ value=json.dumps(credentials),
95
+ namespace=f'replicate:{user_id}'
96
+ )
97
+ return True
98
+ except Exception as kv_error:
99
+ if hasattr(self, 'logger'):
100
+ self.logger.error(f"KV storage failed, falling back to memory: {kv_error}")
101
+ # Fall through to memory storage
102
+
103
+ # Fallback to in-memory storage (either no KV skill or KV failed)
104
+ if not hasattr(self.agent, '_replicate_credentials'):
105
+ self.agent._replicate_credentials = {}
106
+ self.agent._replicate_credentials[user_id] = {
107
+ 'api_token': api_token,
108
+ 'created_at': datetime.now().isoformat()
109
+ }
110
+ return True
111
+ except Exception as e:
112
+ if hasattr(self, 'logger'):
113
+ self.logger.error(f"Failed to save Replicate credentials: {e}")
114
+ return False
115
+
116
+ async def _load_replicate_credentials(self, user_id: str) -> Optional[Dict[str, str]]:
117
+ """Load Replicate credentials from KV skill"""
118
+ try:
119
+ kv_skill = await self._get_kv_skill()
120
+ if kv_skill:
121
+ try:
122
+ credentials_json = await kv_skill.kv_get(
123
+ key='credentials',
124
+ namespace=f'replicate:{user_id}'
125
+ )
126
+ if credentials_json:
127
+ return json.loads(credentials_json)
128
+ except Exception as kv_error:
129
+ if hasattr(self, 'logger'):
130
+ self.logger.error(f"KV retrieval failed, falling back to memory: {kv_error}")
131
+ # Fall through to memory storage
132
+
133
+ # Fallback to in-memory storage (either no KV skill or KV failed)
134
+ if hasattr(self.agent, '_replicate_credentials'):
135
+ return self.agent._replicate_credentials.get(user_id)
136
+ return None
137
+ except Exception as e:
138
+ if hasattr(self, 'logger'):
139
+ self.logger.error(f"Failed to load Replicate credentials: {e}")
140
+ return None
141
+
142
+ async def _make_replicate_request(self, method: str, endpoint: str, data: Optional[Dict] = None, user_id: str = None) -> Dict[str, Any]:
143
+ """Make authenticated request to Replicate API"""
144
+ if not user_id:
145
+ user_id = await self._get_authenticated_user_id()
146
+ if not user_id:
147
+ raise Exception("Authentication required")
148
+
149
+ credentials = await self._load_replicate_credentials(user_id)
150
+ if not credentials:
151
+ raise Exception("Replicate credentials not found. Please run replicate_setup() first.")
152
+
153
+ api_token = credentials['api_token']
154
+
155
+ headers = {
156
+ 'Authorization': f'Token {api_token}',
157
+ 'Content-Type': 'application/json'
158
+ }
159
+
160
+ url = f"{self.api_base}{endpoint}"
161
+
162
+ async with httpx.AsyncClient(timeout=30.0) as client:
163
+ if method.upper() == 'GET':
164
+ response = await client.get(url, headers=headers)
165
+ elif method.upper() == 'POST':
166
+ response = await client.post(url, headers=headers, json=data)
167
+ elif method.upper() == 'DELETE':
168
+ response = await client.delete(url, headers=headers)
169
+ else:
170
+ raise Exception(f"Unsupported HTTP method: {method}")
171
+
172
+ response.raise_for_status()
173
+ return response.json() if response.content else {}
174
+
175
+ # Public tools
176
+ @tool(description="Set up Replicate API token securely. Get your token from Replicate account settings.", scope="owner")
177
+ async def replicate_setup(self, api_token: str) -> str:
178
+ """Set up Replicate API credentials for secure access"""
179
+ user_id = await self._get_authenticated_user_id()
180
+ if not user_id:
181
+ return "❌ Authentication required"
182
+
183
+ if not api_token or not api_token.strip():
184
+ return "❌ API token is required. Get one from your Replicate account settings."
185
+
186
+ try:
187
+ # Test the API token by making a simple request
188
+ test_response = await self._make_replicate_request('GET', '/models', user_id=user_id)
189
+
190
+ # If test succeeds, save credentials
191
+ success = await self._save_replicate_credentials(user_id, api_token.strip())
192
+
193
+ if success:
194
+ return f"✅ Replicate credentials saved successfully!\n🔑 API token configured\n📊 Connection to Replicate API verified"
195
+ else:
196
+ return "❌ Failed to save credentials"
197
+
198
+ except httpx.HTTPStatusError as e:
199
+ if e.response.status_code == 401:
200
+ return "❌ Invalid API token. Please check your Replicate API token."
201
+ elif e.response.status_code == 403:
202
+ return "❌ API token doesn't have required permissions."
203
+ else:
204
+ return f"❌ API test failed: HTTP {e.response.status_code}"
205
+ except Exception as e:
206
+ return f"❌ Setup failed: {str(e)}"
207
+
208
+ @tool(description="List models from a specific owner or popular models")
209
+ async def replicate_list_models(self, owner: str = None) -> str:
210
+ """List available models on Replicate"""
211
+ user_id = await self._get_authenticated_user_id()
212
+ if not user_id:
213
+ return "❌ Authentication required"
214
+
215
+ try:
216
+ # Build endpoint
217
+ if owner:
218
+ endpoint = f"/models?owner={owner}"
219
+ else:
220
+ endpoint = "/models"
221
+
222
+ response = await self._make_replicate_request('GET', endpoint, user_id=user_id)
223
+
224
+ models = response.get('results', [])
225
+
226
+ if not models:
227
+ return f"📭 No models found{f' for owner {owner}' if owner else ''}"
228
+
229
+ result = [f"🤖 Available Models{f' from {owner}' if owner else ''}:\n"]
230
+
231
+ for model in models[:10]: # Limit to first 10 models
232
+ name = model.get('name', 'Unknown')
233
+ full_name = f"{model.get('owner', 'unknown')}/{name}"
234
+ description = model.get('description', 'No description')
235
+ visibility = model.get('visibility', 'unknown')
236
+
237
+ # Visibility icon
238
+ vis_icon = "🔒" if visibility == 'private' else "🌍"
239
+
240
+ result.append(f"{vis_icon} **{full_name}**")
241
+ result.append(f" 📝 {description[:100]}{'...' if len(description) > 100 else ''}")
242
+ result.append("") # Empty line for spacing
243
+
244
+ if len(models) > 10:
245
+ result.append(f"... and {len(models) - 10} more models")
246
+ result.append("")
247
+
248
+ result.append("💡 Use replicate_run_prediction(model, input_data) to run a model")
249
+
250
+ return "\n".join(result)
251
+
252
+ except httpx.HTTPStatusError as e:
253
+ if e.response.status_code == 401:
254
+ return "❌ Authentication failed. Please run replicate_setup() again."
255
+ else:
256
+ return f"❌ Failed to list models: HTTP {e.response.status_code}"
257
+ except Exception as e:
258
+ return f"❌ Error listing models: {str(e)}"
259
+
260
+ @tool(description="Get detailed information about a specific model")
261
+ async def replicate_get_model_info(self, model: str) -> str:
262
+ """Get detailed information about a model"""
263
+ user_id = await self._get_authenticated_user_id()
264
+ if not user_id:
265
+ return "❌ Authentication required"
266
+
267
+ if not model or not model.strip():
268
+ return "❌ Model name is required (format: owner/model-name)"
269
+
270
+ try:
271
+ # Clean model name
272
+ model_name = model.strip()
273
+ if '/' not in model_name:
274
+ return "❌ Model name must be in format: owner/model-name"
275
+
276
+ response = await self._make_replicate_request('GET', f'/models/{model_name}', user_id=user_id)
277
+
278
+ name = response.get('name', 'Unknown')
279
+ owner = response.get('owner', 'unknown')
280
+ description = response.get('description', 'No description')
281
+ visibility = response.get('visibility', 'unknown')
282
+ github_url = response.get('github_url', '')
283
+ paper_url = response.get('paper_url', '')
284
+ license_url = response.get('license_url', '')
285
+
286
+ # Get latest version info
287
+ latest_version = response.get('latest_version', {})
288
+ version_id = latest_version.get('id', 'unknown')
289
+ created_at = latest_version.get('created_at', 'unknown')
290
+
291
+ # Schema info
292
+ schema = latest_version.get('openapi_schema', {})
293
+ input_schema = schema.get('components', {}).get('schemas', {}).get('Input', {}).get('properties', {})
294
+
295
+ vis_icon = "🔒" if visibility == 'private' else "🌍"
296
+
297
+ result = [
298
+ f"🤖 Model Information: {owner}/{name}",
299
+ f"{vis_icon} Visibility: {visibility}",
300
+ f"📝 Description: {description}",
301
+ f"🆔 Latest Version: {version_id}",
302
+ f"📅 Created: {created_at}",
303
+ ""
304
+ ]
305
+
306
+ if github_url:
307
+ result.append(f"🔗 GitHub: {github_url}")
308
+ if paper_url:
309
+ result.append(f"📄 Paper: {paper_url}")
310
+ if license_url:
311
+ result.append(f"📜 License: {license_url}")
312
+
313
+ if input_schema:
314
+ result.append("\n📋 Input Parameters:")
315
+ for param_name, param_info in input_schema.items():
316
+ param_type = param_info.get('type', 'unknown')
317
+ param_desc = param_info.get('description', 'No description')
318
+ required = param_name in schema.get('components', {}).get('schemas', {}).get('Input', {}).get('required', [])
319
+ req_icon = "⚠️" if required else "📝"
320
+ result.append(f" {req_icon} {param_name} ({param_type}): {param_desc}")
321
+
322
+ result.append("\n💡 Use replicate_run_prediction() to run this model")
323
+
324
+ return "\n".join(result)
325
+
326
+ except httpx.HTTPStatusError as e:
327
+ if e.response.status_code == 401:
328
+ return "❌ Authentication failed. Please run replicate_setup() again."
329
+ elif e.response.status_code == 404:
330
+ return f"❌ Model '{model}' not found"
331
+ else:
332
+ return f"❌ Failed to get model info: HTTP {e.response.status_code}"
333
+ except Exception as e:
334
+ return f"❌ Error getting model info: {str(e)}"
335
+
336
+ @tool(description="Run a prediction with a Replicate model")
337
+ async def replicate_run_prediction(self, model: str, input_data: Dict[str, Any]) -> str:
338
+ """Run a prediction with a model"""
339
+ user_id = await self._get_authenticated_user_id()
340
+ if not user_id:
341
+ return "❌ Authentication required"
342
+
343
+ if not model or not model.strip():
344
+ return "❌ Model name is required (format: owner/model-name)"
345
+
346
+ if not input_data:
347
+ return "❌ Input data is required"
348
+
349
+ try:
350
+ # Clean model name
351
+ model_name = model.strip()
352
+ if '/' not in model_name:
353
+ return "❌ Model name must be in format: owner/model-name"
354
+
355
+ # Prepare prediction data
356
+ prediction_data = {
357
+ "version": f"{model_name}:latest",
358
+ "input": input_data
359
+ }
360
+
361
+ # Create prediction
362
+ response = await self._make_replicate_request(
363
+ 'POST',
364
+ '/predictions',
365
+ prediction_data,
366
+ user_id
367
+ )
368
+
369
+ prediction_id = response.get('id', 'unknown')
370
+ status = response.get('status', 'starting')
371
+
372
+ result = [
373
+ f"🚀 Prediction started successfully!",
374
+ f"🆔 Prediction ID: {prediction_id}",
375
+ f"🤖 Model: {model_name}",
376
+ f"📊 Status: {status}"
377
+ ]
378
+
379
+ # If prediction completed immediately, show results
380
+ if status == 'succeeded':
381
+ output = response.get('output')
382
+ if output:
383
+ result.append(f"✅ Output: {str(output)[:200]}{'...' if len(str(output)) > 200 else ''}")
384
+ elif status == 'failed':
385
+ error = response.get('error', 'Unknown error')
386
+ result.append(f"❌ Error: {error}")
387
+ else:
388
+ result.append("⏳ Use replicate_get_prediction() to check status and get results")
389
+
390
+ return "\n".join(result)
391
+
392
+ except httpx.HTTPStatusError as e:
393
+ if e.response.status_code == 401:
394
+ return "❌ Authentication failed. Please run replicate_setup() again."
395
+ elif e.response.status_code == 404:
396
+ return f"❌ Model '{model}' not found"
397
+ elif e.response.status_code == 422:
398
+ return "❌ Invalid input data. Check model requirements with replicate_get_model_info()"
399
+ else:
400
+ return f"❌ Prediction failed: HTTP {e.response.status_code}"
401
+ except Exception as e:
402
+ return f"❌ Error running prediction: {str(e)}"
403
+
404
+ @tool(description="Get prediction status and results")
405
+ async def replicate_get_prediction(self, prediction_id: str) -> str:
406
+ """Get prediction status and results"""
407
+ user_id = await self._get_authenticated_user_id()
408
+ if not user_id:
409
+ return "❌ Authentication required"
410
+
411
+ if not prediction_id or not prediction_id.strip():
412
+ return "❌ Prediction ID is required"
413
+
414
+ try:
415
+ response = await self._make_replicate_request(
416
+ 'GET',
417
+ f'/predictions/{prediction_id.strip()}',
418
+ user_id=user_id
419
+ )
420
+
421
+ status = response.get('status', 'unknown')
422
+ model = response.get('model', 'unknown')
423
+ created_at = response.get('created_at', 'unknown')
424
+ started_at = response.get('started_at', '')
425
+ completed_at = response.get('completed_at', '')
426
+
427
+ # Status icons
428
+ status_icons = {
429
+ 'starting': '🔄',
430
+ 'processing': '⚙️',
431
+ 'succeeded': '✅',
432
+ 'failed': '❌',
433
+ 'canceled': '🚫'
434
+ }
435
+
436
+ status_icon = status_icons.get(status, '❓')
437
+
438
+ result = [
439
+ f"📊 Prediction Status Report",
440
+ f"🆔 Prediction ID: {prediction_id}",
441
+ f"🤖 Model: {model}",
442
+ f"{status_icon} Status: {status}",
443
+ f"🕐 Created: {created_at}"
444
+ ]
445
+
446
+ if started_at:
447
+ result.append(f"▶️ Started: {started_at}")
448
+ if completed_at:
449
+ result.append(f"🏁 Completed: {completed_at}")
450
+
451
+ # Add results if succeeded
452
+ if status == 'succeeded':
453
+ output = response.get('output')
454
+ if output:
455
+ output_str = str(output)
456
+ if len(output_str) > 500:
457
+ result.append(f"📋 Output: {output_str[:500]}...")
458
+ result.append("💡 Output truncated. Full results available via API.")
459
+ else:
460
+ result.append(f"📋 Output: {output_str}")
461
+
462
+ # Add error details if failed
463
+ elif status == 'failed':
464
+ error = response.get('error', 'Unknown error')
465
+ logs = response.get('logs', '')
466
+ result.append(f"❌ Error: {error}")
467
+ if logs:
468
+ result.append(f"📝 Logs: {logs[-200:]}...") # Show last 200 chars of logs
469
+
470
+ return "\n".join(result)
471
+
472
+ except httpx.HTTPStatusError as e:
473
+ if e.response.status_code == 401:
474
+ return "❌ Authentication failed. Please run replicate_setup() again."
475
+ elif e.response.status_code == 404:
476
+ return f"❌ Prediction '{prediction_id}' not found"
477
+ else:
478
+ return f"❌ Status check failed: HTTP {e.response.status_code}"
479
+ except Exception as e:
480
+ return f"❌ Error checking prediction: {str(e)}"
481
+
482
+ @tool(description="Cancel a running prediction")
483
+ async def replicate_cancel_prediction(self, prediction_id: str) -> str:
484
+ """Cancel a running prediction"""
485
+ user_id = await self._get_authenticated_user_id()
486
+ if not user_id:
487
+ return "❌ Authentication required"
488
+
489
+ if not prediction_id or not prediction_id.strip():
490
+ return "❌ Prediction ID is required"
491
+
492
+ try:
493
+ response = await self._make_replicate_request(
494
+ 'POST',
495
+ f'/predictions/{prediction_id.strip()}/cancel',
496
+ user_id=user_id
497
+ )
498
+
499
+ status = response.get('status', 'unknown')
500
+
501
+ if status == 'canceled':
502
+ return f"✅ Prediction {prediction_id} canceled successfully"
503
+ else:
504
+ return f"⚠️ Prediction status: {status} (may not be cancelable)"
505
+
506
+ except httpx.HTTPStatusError as e:
507
+ if e.response.status_code == 401:
508
+ return "❌ Authentication failed. Please run replicate_setup() again."
509
+ elif e.response.status_code == 404:
510
+ return f"❌ Prediction '{prediction_id}' not found"
511
+ elif e.response.status_code == 422:
512
+ return f"❌ Cannot cancel prediction (may be completed or already canceled)"
513
+ else:
514
+ return f"❌ Cancel failed: HTTP {e.response.status_code}"
515
+ except Exception as e:
516
+ return f"❌ Error canceling prediction: {str(e)}"
517
+