webagents 0.1.12__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 (96) hide show
  1. webagents/__init__.py +18 -0
  2. webagents/agents/__init__.py +13 -0
  3. webagents/agents/core/__init__.py +19 -0
  4. webagents/agents/core/base_agent.py +1834 -0
  5. webagents/agents/core/handoffs.py +293 -0
  6. webagents/agents/handoffs/__init__.py +0 -0
  7. webagents/agents/interfaces/__init__.py +0 -0
  8. webagents/agents/lifecycle/__init__.py +0 -0
  9. webagents/agents/skills/__init__.py +109 -0
  10. webagents/agents/skills/base.py +136 -0
  11. webagents/agents/skills/core/__init__.py +8 -0
  12. webagents/agents/skills/core/guardrails/__init__.py +0 -0
  13. webagents/agents/skills/core/llm/__init__.py +0 -0
  14. webagents/agents/skills/core/llm/anthropic/__init__.py +1 -0
  15. webagents/agents/skills/core/llm/litellm/__init__.py +10 -0
  16. webagents/agents/skills/core/llm/litellm/skill.py +538 -0
  17. webagents/agents/skills/core/llm/openai/__init__.py +1 -0
  18. webagents/agents/skills/core/llm/xai/__init__.py +1 -0
  19. webagents/agents/skills/core/mcp/README.md +375 -0
  20. webagents/agents/skills/core/mcp/__init__.py +15 -0
  21. webagents/agents/skills/core/mcp/skill.py +731 -0
  22. webagents/agents/skills/core/memory/__init__.py +11 -0
  23. webagents/agents/skills/core/memory/long_term_memory/__init__.py +10 -0
  24. webagents/agents/skills/core/memory/long_term_memory/memory_skill.py +639 -0
  25. webagents/agents/skills/core/memory/short_term_memory/__init__.py +9 -0
  26. webagents/agents/skills/core/memory/short_term_memory/skill.py +341 -0
  27. webagents/agents/skills/core/memory/vector_memory/skill.py +447 -0
  28. webagents/agents/skills/core/planning/__init__.py +9 -0
  29. webagents/agents/skills/core/planning/planner.py +343 -0
  30. webagents/agents/skills/ecosystem/__init__.py +0 -0
  31. webagents/agents/skills/ecosystem/crewai/__init__.py +1 -0
  32. webagents/agents/skills/ecosystem/database/__init__.py +1 -0
  33. webagents/agents/skills/ecosystem/filesystem/__init__.py +0 -0
  34. webagents/agents/skills/ecosystem/google/__init__.py +0 -0
  35. webagents/agents/skills/ecosystem/google/calendar/__init__.py +6 -0
  36. webagents/agents/skills/ecosystem/google/calendar/skill.py +306 -0
  37. webagents/agents/skills/ecosystem/n8n/__init__.py +0 -0
  38. webagents/agents/skills/ecosystem/openai_agents/__init__.py +0 -0
  39. webagents/agents/skills/ecosystem/web/__init__.py +0 -0
  40. webagents/agents/skills/ecosystem/zapier/__init__.py +0 -0
  41. webagents/agents/skills/robutler/__init__.py +11 -0
  42. webagents/agents/skills/robutler/auth/README.md +63 -0
  43. webagents/agents/skills/robutler/auth/__init__.py +17 -0
  44. webagents/agents/skills/robutler/auth/skill.py +354 -0
  45. webagents/agents/skills/robutler/crm/__init__.py +18 -0
  46. webagents/agents/skills/robutler/crm/skill.py +368 -0
  47. webagents/agents/skills/robutler/discovery/README.md +281 -0
  48. webagents/agents/skills/robutler/discovery/__init__.py +16 -0
  49. webagents/agents/skills/robutler/discovery/skill.py +230 -0
  50. webagents/agents/skills/robutler/kv/__init__.py +6 -0
  51. webagents/agents/skills/robutler/kv/skill.py +80 -0
  52. webagents/agents/skills/robutler/message_history/__init__.py +9 -0
  53. webagents/agents/skills/robutler/message_history/skill.py +270 -0
  54. webagents/agents/skills/robutler/messages/__init__.py +0 -0
  55. webagents/agents/skills/robutler/nli/__init__.py +13 -0
  56. webagents/agents/skills/robutler/nli/skill.py +687 -0
  57. webagents/agents/skills/robutler/notifications/__init__.py +5 -0
  58. webagents/agents/skills/robutler/notifications/skill.py +141 -0
  59. webagents/agents/skills/robutler/payments/__init__.py +41 -0
  60. webagents/agents/skills/robutler/payments/exceptions.py +255 -0
  61. webagents/agents/skills/robutler/payments/skill.py +610 -0
  62. webagents/agents/skills/robutler/storage/__init__.py +10 -0
  63. webagents/agents/skills/robutler/storage/files/__init__.py +9 -0
  64. webagents/agents/skills/robutler/storage/files/skill.py +445 -0
  65. webagents/agents/skills/robutler/storage/json/__init__.py +9 -0
  66. webagents/agents/skills/robutler/storage/json/skill.py +336 -0
  67. webagents/agents/skills/robutler/storage/kv/skill.py +88 -0
  68. webagents/agents/skills/robutler/storage.py +389 -0
  69. webagents/agents/tools/__init__.py +0 -0
  70. webagents/agents/tools/decorators.py +426 -0
  71. webagents/agents/tracing/__init__.py +0 -0
  72. webagents/agents/workflows/__init__.py +0 -0
  73. webagents/api/__init__.py +17 -0
  74. webagents/api/client.py +1207 -0
  75. webagents/api/types.py +253 -0
  76. webagents/scripts/__init__.py +0 -0
  77. webagents/server/__init__.py +28 -0
  78. webagents/server/context/__init__.py +0 -0
  79. webagents/server/context/context_vars.py +121 -0
  80. webagents/server/core/__init__.py +0 -0
  81. webagents/server/core/app.py +843 -0
  82. webagents/server/core/middleware.py +69 -0
  83. webagents/server/core/models.py +98 -0
  84. webagents/server/core/monitoring.py +59 -0
  85. webagents/server/endpoints/__init__.py +0 -0
  86. webagents/server/interfaces/__init__.py +0 -0
  87. webagents/server/middleware.py +330 -0
  88. webagents/server/models.py +92 -0
  89. webagents/server/monitoring.py +659 -0
  90. webagents/utils/__init__.py +0 -0
  91. webagents/utils/logging.py +359 -0
  92. webagents-0.1.12.dist-info/METADATA +99 -0
  93. webagents-0.1.12.dist-info/RECORD +96 -0
  94. webagents-0.1.12.dist-info/WHEEL +4 -0
  95. webagents-0.1.12.dist-info/entry_points.txt +2 -0
  96. webagents-0.1.12.dist-info/licenses/LICENSE +1 -0
@@ -0,0 +1,5 @@
1
+ from .skill import NotificationsSkill
2
+
3
+ __all__ = ["NotificationsSkill"]
4
+
5
+
@@ -0,0 +1,141 @@
1
+ import os
2
+ from typing import Any, Dict, Optional, List
3
+
4
+ import httpx
5
+
6
+ from webagents.agents.skills.base import Skill
7
+ from webagents.agents.tools.decorators import tool, prompt
8
+ from webagents.server.context.context_vars import get_context
9
+ from webagents.utils.logging import get_logger, log_skill_event, log_tool_execution
10
+
11
+
12
+ def _resolve_portal_url() -> str:
13
+ return os.getenv("ROBUTLER_INTERNAL_API_URL") or os.getenv("ROBUTLER_API_URL", "http://localhost:3000")
14
+
15
+
16
+ class NotificationsSkill(Skill):
17
+ """Send push notifications to the owner of this agent (owner-only).
18
+
19
+ This skill exposes a single owner-scoped tool that sends a push notification via the
20
+ portal notifications API (POST /api/notifications/send). The notification will be
21
+ targeted to this agent's owner user ID.
22
+ """
23
+
24
+ def __init__(self, config: Optional[Dict[str, Any]] = None) -> None:
25
+ # Align with other skills (e.g., VectorMemorySkill) for consistent SDK behavior
26
+ super().__init__(config or {}, scope="all")
27
+ self.config = config or {}
28
+ self.logger = None
29
+
30
+ async def initialize(self, agent) -> None:
31
+ self.agent = agent
32
+ self.logger = get_logger('skill.notifications', agent.name)
33
+ # Log level is configured globally via setup_logging()
34
+ log_skill_event(agent.name, 'notifications', 'initialized', {})
35
+ # Cache agent API key for later use
36
+ try:
37
+ self.agent_api_key: Optional[str] = getattr(agent, 'api_key', None)
38
+ except Exception:
39
+ self.agent_api_key = None
40
+
41
+ @prompt(priority=60, scope=["owner"]) # Owner sees how to use it
42
+ def get_notifications_prompt(self) -> str:
43
+ return (
44
+ "You can use send_notification(title, body, tag?, type?, priority?, requireInteraction?, silent?, ttl?) to send a push notifications to the owner of this agent. Use it only when explicitly asked to do so."
45
+ )
46
+
47
+ def _get_owner_user_id(self) -> Optional[str]:
48
+ # Prefer agent metadata set by factory
49
+ try:
50
+ owner_id = getattr(self.agent, 'owner_user_id', None)
51
+ if owner_id:
52
+ return owner_id
53
+ except Exception:
54
+ pass
55
+ # Fallback to auth context if available
56
+ try:
57
+ ctx = get_context()
58
+ auth_ctx = getattr(ctx, 'auth', None) or (ctx and ctx.get('auth'))
59
+ if auth_ctx and getattr(auth_ctx, 'scope', '').upper() == 'OWNER':
60
+ # Not strictly the owner ID, but if missing we skip
61
+ return getattr(auth_ctx, 'user_id', None)
62
+ except Exception:
63
+ pass
64
+ return None
65
+
66
+ async def _post_notification(self, payload: Dict[str, Any]) -> Dict[str, Any]:
67
+ base_url = _resolve_portal_url()
68
+ url = f"{base_url}/api/notifications/send"
69
+ headers = {
70
+ 'Content-Type': 'application/json'
71
+ }
72
+ # Use agent API key only (no service tokens here)
73
+ api_key = getattr(self, 'agent_api_key', None) or getattr(self.agent, 'api_key', None)
74
+ if not api_key:
75
+ raise RuntimeError("Agent API key unavailable for notifications")
76
+ headers['X-API-Key'] = api_key
77
+
78
+ async with httpx.AsyncClient(timeout=20) as client:
79
+ resp = await client.post(url, json=payload, headers=headers)
80
+ try:
81
+ data = resp.json()
82
+ except Exception:
83
+ data = {'status': resp.status_code, 'text': resp.text}
84
+ if resp.status_code >= 400:
85
+ raise RuntimeError(f"Notification send failed: {resp.status_code} {data}")
86
+ return data
87
+
88
+ @tool(
89
+ name="send_notification",
90
+ description=(
91
+ "Send a push notification. "
92
+ "Parameters: title (string), body (string). Optional: tag (string), type (chat_message|agent_update|system_announcement|marketing), "
93
+ "priority (low|normal|high|urgent), requireInteraction (bool), silent (bool), ttl (seconds)."
94
+ ),
95
+ scope="owner",
96
+ )
97
+ async def send_notification(
98
+ self,
99
+ title: str,
100
+ body: str,
101
+ tag: Optional[str] = None,
102
+ type: Optional[str] = "agent_update",
103
+ priority: Optional[str] = "normal",
104
+ requireInteraction: Optional[bool] = False,
105
+ silent: Optional[bool] = False,
106
+ ttl: Optional[int] = 86400,
107
+ context: Any = None,
108
+ ) -> str:
109
+ owner_id = self._get_owner_user_id()
110
+ if not owner_id:
111
+ return "❌ Cannot resolve agent owner user ID"
112
+
113
+ payload: Dict[str, Any] = {
114
+ 'title': title,
115
+ 'body': body,
116
+ 'type': type or 'agent_update',
117
+ 'priority': priority or 'normal',
118
+ 'userIds': [owner_id],
119
+ 'requireInteraction': bool(requireInteraction),
120
+ 'silent': bool(silent),
121
+ 'ttl': int(ttl or 86400),
122
+ }
123
+ if tag:
124
+ payload['tag'] = tag
125
+
126
+ # Call API
127
+ try:
128
+ result = await self._post_notification(payload)
129
+ log_tool_execution(self.agent.name, 'notifications.send_notification', 'success', {
130
+ 'owner_id': owner_id,
131
+ 'title': title[:50]
132
+ })
133
+ return f"✅ Notification queued: {result.get('message', 'ok')}"
134
+ except Exception as e:
135
+ log_tool_execution(self.agent.name, 'notifications.send_notification', 'failure', {
136
+ 'owner_id': owner_id,
137
+ 'error': str(e)
138
+ })
139
+ return f"❌ Failed to send notification: {e}"
140
+
141
+
@@ -0,0 +1,41 @@
1
+ """
2
+ PaymentSkill Package - Robutler V2.0
3
+
4
+ Payment processing and billing skill for Robutler platform.
5
+ Validates payment tokens, calculates costs using LiteLLM, and charges on connection finalization.
6
+ Based on robutler_v1 implementation patterns.
7
+ """
8
+
9
+ from .skill import PaymentSkill, PaymentContext, PricingInfo, pricing
10
+ from .exceptions import (
11
+ PaymentError,
12
+ PaymentTokenRequiredError,
13
+ PaymentTokenInvalidError,
14
+ InsufficientBalanceError,
15
+ PaymentChargingError,
16
+ PaymentPlatformUnavailableError,
17
+ PaymentConfigurationError,
18
+ # Legacy compatibility
19
+ PaymentValidationError,
20
+ PaymentRequiredError
21
+ )
22
+
23
+ __all__ = [
24
+ # Main classes
25
+ "PaymentSkill",
26
+ "PaymentContext",
27
+ # Pricing decorators
28
+ "PricingInfo",
29
+ "pricing",
30
+ # New comprehensive error hierarchy
31
+ "PaymentError",
32
+ "PaymentTokenRequiredError",
33
+ "PaymentTokenInvalidError",
34
+ "InsufficientBalanceError",
35
+ "PaymentChargingError",
36
+ "PaymentPlatformUnavailableError",
37
+ "PaymentConfigurationError",
38
+ # Legacy compatibility
39
+ "PaymentValidationError",
40
+ "PaymentRequiredError"
41
+ ]
@@ -0,0 +1,255 @@
1
+ """
2
+ Payment Exceptions - Robutler V2.0 Platform Integration
3
+
4
+ Comprehensive payment error hierarchy for distinguishing between different 402 payment failure scenarios.
5
+ Provides specific error codes, subcodes, and context for better error handling and user experience.
6
+ """
7
+
8
+ from typing import Dict, Any, Optional
9
+ from decimal import Decimal
10
+
11
+
12
+ class PaymentError(Exception):
13
+ """Base payment error with 402 status code and detailed context"""
14
+
15
+ def __init__(self,
16
+ message: str,
17
+ error_code: str,
18
+ subcode: Optional[str] = None,
19
+ context: Optional[Dict[str, Any]] = None,
20
+ user_message: Optional[str] = None):
21
+ super().__init__(message)
22
+ self.status_code = 402 # Payment Required
23
+ self.error_code = error_code
24
+ self.subcode = subcode
25
+ self.context = context or {}
26
+ self.user_message = user_message or message
27
+
28
+ def to_dict(self) -> Dict[str, Any]:
29
+ """Convert error to dictionary for API responses"""
30
+ return {
31
+ 'error': self.error_code,
32
+ 'subcode': self.subcode,
33
+ 'message': str(self),
34
+ 'user_message': self.user_message,
35
+ 'status_code': self.status_code,
36
+ 'context': self.context
37
+ }
38
+
39
+ def __str__(self) -> str:
40
+ parts = [self.error_code]
41
+ if self.subcode:
42
+ parts.append(f"({self.subcode})")
43
+ parts.append(f": {super().__str__()}")
44
+ return " ".join(parts)
45
+
46
+ # FastAPI/Starlette HTTPException expects a `detail` field for structured errors.
47
+ # Our server checks for `hasattr(e, 'status_code') and hasattr(e, 'detail')` to
48
+ # map domain errors to HTTP responses. Expose detail dynamically from to_dict().
49
+ @property
50
+ def detail(self) -> Dict[str, Any]:
51
+ return self.to_dict()
52
+
53
+
54
+ class PaymentTokenRequiredError(PaymentError):
55
+ """Raised when payment token is required but not provided"""
56
+
57
+ def __init__(self, agent_name: Optional[str] = None):
58
+ context = {}
59
+ if agent_name:
60
+ context['agent_name'] = agent_name
61
+
62
+ super().__init__(
63
+ message="Payment token required for billing-enabled agent",
64
+ error_code="PAYMENT_TOKEN_REQUIRED",
65
+ context=context,
66
+ user_message="This agent requires payment. Please provide a valid payment token."
67
+ )
68
+
69
+
70
+ class PaymentTokenInvalidError(PaymentError):
71
+ """Raised when payment token is invalid or expired"""
72
+
73
+ def __init__(self,
74
+ token_prefix: Optional[str] = None,
75
+ reason: Optional[str] = None):
76
+ context = {}
77
+ if token_prefix:
78
+ context['token_prefix'] = token_prefix
79
+ if reason:
80
+ context['validation_error'] = reason
81
+
82
+ subcode = None
83
+ user_message = "Payment token is invalid or expired. Please check your token and try again."
84
+
85
+ if reason:
86
+ if "expired" in reason.lower():
87
+ subcode = "TOKEN_EXPIRED"
88
+ user_message = "Payment token has expired. Please obtain a new token."
89
+ elif "not found" in reason.lower():
90
+ subcode = "TOKEN_NOT_FOUND"
91
+ user_message = "Payment token not found. Please check your token and try again."
92
+ elif "malformed" in reason.lower():
93
+ subcode = "TOKEN_MALFORMED"
94
+ user_message = "Payment token format is invalid. Please check your token."
95
+
96
+ super().__init__(
97
+ message=f"Payment token validation failed: {reason}" if reason else "Payment token is invalid",
98
+ error_code="PAYMENT_TOKEN_INVALID",
99
+ subcode=subcode,
100
+ context=context,
101
+ user_message=user_message
102
+ )
103
+
104
+
105
+ class InsufficientBalanceError(PaymentError):
106
+ """Raised when payment token balance is insufficient"""
107
+
108
+ def __init__(self,
109
+ current_balance: float,
110
+ required_balance: float,
111
+ token_prefix: Optional[str] = None):
112
+ context = {
113
+ 'current_balance': current_balance,
114
+ 'required_balance': required_balance,
115
+ 'shortfall': required_balance - current_balance
116
+ }
117
+ if token_prefix:
118
+ context['token_prefix'] = token_prefix
119
+
120
+ super().__init__(
121
+ message=f"Insufficient balance: ${current_balance:.2f} < ${required_balance:.2f} required",
122
+ error_code="INSUFFICIENT_BALANCE",
123
+ context=context,
124
+ user_message=f"Insufficient credits. You have ${current_balance:.2f} but need ${required_balance:.2f}. Please add more credits to your account."
125
+ )
126
+
127
+
128
+ class PaymentChargingError(PaymentError):
129
+ """Raised when charging/redeeming payment token fails"""
130
+
131
+ def __init__(self,
132
+ amount: float,
133
+ token_prefix: Optional[str] = None,
134
+ reason: Optional[str] = None):
135
+ context = {
136
+ 'charge_amount': amount
137
+ }
138
+ if token_prefix:
139
+ context['token_prefix'] = token_prefix
140
+ if reason:
141
+ context['charge_error'] = reason
142
+
143
+ subcode = None
144
+ user_message = "Payment processing failed. Please try again or contact support."
145
+
146
+ if reason:
147
+ if "insufficient" in reason.lower():
148
+ subcode = "INSUFFICIENT_FUNDS"
149
+ user_message = "Insufficient funds for this transaction. Please add more credits."
150
+ elif "expired" in reason.lower():
151
+ subcode = "TOKEN_EXPIRED_DURING_CHARGE"
152
+ user_message = "Payment token expired during transaction. Please obtain a new token."
153
+ elif "limit" in reason.lower():
154
+ subcode = "SPENDING_LIMIT_EXCEEDED"
155
+ user_message = "Spending limit exceeded. Please check your account limits."
156
+
157
+ super().__init__(
158
+ message=f"Payment charging failed: {reason}" if reason else f"Failed to charge ${amount:.2f}",
159
+ error_code="PAYMENT_CHARGING_FAILED",
160
+ subcode=subcode,
161
+ context=context,
162
+ user_message=user_message
163
+ )
164
+
165
+
166
+ class PaymentPlatformUnavailableError(PaymentError):
167
+ """Raised when payment platform is unavailable"""
168
+
169
+ def __init__(self, operation: Optional[str] = None):
170
+ context = {}
171
+ if operation:
172
+ context['attempted_operation'] = operation
173
+
174
+ super().__init__(
175
+ message=f"Payment platform unavailable for {operation}" if operation else "Payment platform unavailable",
176
+ error_code="PAYMENT_PLATFORM_UNAVAILABLE",
177
+ context=context,
178
+ user_message="Payment system is temporarily unavailable. Please try again later."
179
+ )
180
+
181
+
182
+ class PaymentConfigurationError(PaymentError):
183
+ """Raised when payment configuration is invalid"""
184
+
185
+ def __init__(self,
186
+ config_issue: str,
187
+ details: Optional[str] = None):
188
+ context = {
189
+ 'config_issue': config_issue
190
+ }
191
+ if details:
192
+ context['details'] = details
193
+
194
+ super().__init__(
195
+ message=f"Payment configuration error: {config_issue}",
196
+ error_code="PAYMENT_CONFIG_ERROR",
197
+ context=context,
198
+ user_message="Payment system configuration error. Please contact support."
199
+ )
200
+
201
+
202
+ # Convenience functions for common error scenarios
203
+ def create_token_required_error(agent_name: Optional[str] = None) -> PaymentTokenRequiredError:
204
+ """Create a payment token required error"""
205
+ return PaymentTokenRequiredError(agent_name=agent_name)
206
+
207
+
208
+ def create_token_invalid_error(token_prefix: Optional[str] = None,
209
+ reason: Optional[str] = None) -> PaymentTokenInvalidError:
210
+ """Create a payment token invalid error"""
211
+ return PaymentTokenInvalidError(token_prefix=token_prefix, reason=reason)
212
+
213
+
214
+ def create_insufficient_balance_error(current_balance: float,
215
+ required_balance: float,
216
+ token_prefix: Optional[str] = None) -> InsufficientBalanceError:
217
+ """Create an insufficient balance error"""
218
+ return InsufficientBalanceError(
219
+ current_balance=current_balance,
220
+ required_balance=required_balance,
221
+ token_prefix=token_prefix
222
+ )
223
+
224
+
225
+ def create_charging_error(amount: float,
226
+ token_prefix: Optional[str] = None,
227
+ reason: Optional[str] = None) -> PaymentChargingError:
228
+ """Create a payment charging error"""
229
+ return PaymentChargingError(
230
+ amount=amount,
231
+ token_prefix=token_prefix,
232
+ reason=reason
233
+ )
234
+
235
+
236
+ def create_platform_unavailable_error(operation: Optional[str] = None) -> PaymentPlatformUnavailableError:
237
+ """Create a payment platform unavailable error"""
238
+ return PaymentPlatformUnavailableError(operation=operation)
239
+
240
+
241
+ def create_config_error(config_issue: str,
242
+ details: Optional[str] = None) -> PaymentConfigurationError:
243
+ """Create a payment configuration error"""
244
+ return PaymentConfigurationError(config_issue=config_issue, details=details)
245
+
246
+
247
+ # Legacy compatibility - keep existing exception names but inherit from new hierarchy
248
+ class PaymentValidationError(PaymentTokenInvalidError):
249
+ """Legacy compatibility - use PaymentTokenInvalidError instead"""
250
+ pass
251
+
252
+
253
+ class PaymentRequiredError(PaymentTokenRequiredError):
254
+ """Legacy compatibility - use PaymentTokenRequiredError instead"""
255
+ pass