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,426 @@
1
+ """
2
+ Tool, Hook, Handoff, and HTTP Decorators - Robutler V2.0
3
+
4
+ Decorators for automatic registration of tools, hooks, handoffs, and HTTP handlers with BaseAgent.
5
+ Supports context injection and scope-based access control.
6
+ """
7
+
8
+ import inspect
9
+ import functools
10
+ from typing import Dict, Any, List, Optional, Callable, Union
11
+ from dataclasses import dataclass
12
+
13
+
14
+ def tool(func: Optional[Callable] = None, *, name: Optional[str] = None, description: Optional[str] = None, scope: Union[str, List[str]] = "all"):
15
+ """Decorator to mark functions as tools for automatic registration
16
+
17
+ Can be used as:
18
+ @tool
19
+ def my_tool(param: str) -> str: ...
20
+
21
+ Or:
22
+ @tool(name="custom", description="Custom tool", scope="owner")
23
+ def my_tool(param: str) -> str: ...
24
+
25
+ Args:
26
+ name: Optional override for tool name (defaults to function name)
27
+ description: Tool description (defaults to function docstring)
28
+ scope: Access scope - "all", "owner", "admin", or list of scopes
29
+
30
+ The decorated function can optionally receive Context via dependency injection:
31
+
32
+ @tool(scope="owner")
33
+ def my_tool(self, param: str, context: Context = None) -> str:
34
+ # Context automatically injected if parameter exists
35
+ if context:
36
+ user_id = context.peer_user_id
37
+ # ... use context
38
+ return result
39
+ """
40
+ def decorator(f: Callable) -> Callable:
41
+ # Generate OpenAI-compatible tool schema
42
+ sig = inspect.signature(f)
43
+ parameters = {}
44
+ required = []
45
+
46
+ for param_name, param in sig.parameters.items():
47
+ # Skip 'self' and 'context' parameters from schema
48
+ if param_name in ('self', 'context'):
49
+ continue
50
+
51
+ param_type = "string" # Default type
52
+ param_desc = f"Parameter {param_name}"
53
+
54
+ # Try to infer type from annotation
55
+ if param.annotation != inspect.Parameter.empty:
56
+ if param.annotation == int:
57
+ param_type = "integer"
58
+ elif param.annotation == float:
59
+ param_type = "number"
60
+ elif param.annotation == bool:
61
+ param_type = "boolean"
62
+ elif param.annotation == list:
63
+ param_type = "array"
64
+ elif param.annotation == dict:
65
+ param_type = "object"
66
+
67
+ parameters[param_name] = {
68
+ "type": param_type,
69
+ "description": param_desc
70
+ }
71
+
72
+ # Mark as required if no default value
73
+ if param.default == inspect.Parameter.empty:
74
+ required.append(param_name)
75
+
76
+ # Create OpenAI tool schema
77
+ tool_schema = {
78
+ "type": "function",
79
+ "function": {
80
+ "name": name or f.__name__,
81
+ "description": description or f.__doc__ or f"Tool: {f.__name__}",
82
+ "parameters": {
83
+ "type": "object",
84
+ "properties": parameters,
85
+ "required": required
86
+ }
87
+ }
88
+ }
89
+
90
+ # Mark function with metadata for BaseAgent discovery
91
+ f._robutler_is_tool = True
92
+ f._robutler_tool_definition = tool_schema
93
+ f._tool_scope = scope
94
+ f._tool_scope_was_set = func is None # If func is None, decorator was called with params
95
+ f._tool_name = name or f.__name__
96
+ f._tool_description = description or f.__doc__ or f"Tool: {f.__name__}"
97
+
98
+ # Check if function expects context injection
99
+ has_context_param = 'context' in sig.parameters
100
+
101
+ if has_context_param:
102
+ @functools.wraps(f)
103
+ async def async_wrapper(*args, **kwargs):
104
+ # Inject context if requested and not provided
105
+ if 'context' not in kwargs:
106
+ from ...server.context.context_vars import get_context
107
+ context = get_context()
108
+ kwargs['context'] = context
109
+ # Call original function and return directly; BaseAgent will log usage
110
+ return await f(*args, **kwargs) if inspect.iscoroutinefunction(f) else f(*args, **kwargs)
111
+
112
+ @functools.wraps(f)
113
+ def sync_wrapper(*args, **kwargs):
114
+ # Inject context if requested and not provided
115
+ if 'context' not in kwargs:
116
+ from ...server.context.context_vars import get_context
117
+ context = get_context()
118
+ kwargs['context'] = context
119
+ # Call original function and return directly; BaseAgent will log usage
120
+ return f(*args, **kwargs)
121
+
122
+ # Return appropriate wrapper based on function type
123
+ if inspect.iscoroutinefunction(f):
124
+ wrapper = async_wrapper
125
+ else:
126
+ wrapper = sync_wrapper
127
+ else:
128
+ # No context injection needed; return function directly
129
+ wrapper = f
130
+
131
+ # Copy metadata to wrapper
132
+ wrapper._robutler_is_tool = True
133
+ wrapper._robutler_tool_definition = tool_schema
134
+ wrapper._tool_scope = scope
135
+ wrapper._tool_scope_was_set = func is None # If func is None, decorator was called with params
136
+ wrapper._tool_name = name or f.__name__
137
+ wrapper._tool_description = description or f.__doc__ or f"Tool: {f.__name__}"
138
+
139
+ return wrapper
140
+
141
+ if func is None:
142
+ # Called with arguments: @tool(name="...", ...)
143
+ return decorator
144
+ else:
145
+ # Called without arguments: @tool
146
+ return decorator(func)
147
+
148
+
149
+ def hook(event: str, priority: int = 50, scope: Union[str, List[str]] = "all"):
150
+ """Decorator to mark functions as lifecycle hooks for automatic registration
151
+
152
+ Args:
153
+ event: Lifecycle event name (on_connection, on_chunk, on_message, etc.)
154
+ priority: Execution priority (lower numbers execute first)
155
+ scope: Access scope - "all", "owner", "admin", or list of scopes
156
+
157
+ Hook functions should accept and return Context:
158
+
159
+ @hook("on_message", priority=10, scope="owner")
160
+ async def my_hook(self, context: Context) -> Context:
161
+ # Process context
162
+ return context
163
+ """
164
+ def decorator(func: Callable) -> Callable:
165
+ # Mark function with metadata for BaseAgent discovery
166
+ func._robutler_is_hook = True
167
+ func._hook_event_type = event
168
+ func._hook_priority = priority
169
+ func._hook_scope = scope
170
+
171
+ return func
172
+
173
+ return decorator
174
+
175
+
176
+ def prompt(priority: int = 50, scope: Union[str, List[str]] = "all"):
177
+ """Decorator to mark functions as system prompt providers for automatic registration
178
+
179
+ Args:
180
+ priority: Execution priority (lower numbers execute first)
181
+ scope: Access scope - "all", "owner", "admin", or list of scopes
182
+
183
+ Prompt functions should accept context and return a string to be added to the system prompt:
184
+
185
+ @prompt(priority=10, scope="owner")
186
+ def my_prompt(self, context: Context) -> str:
187
+ # Generate dynamic prompt content
188
+ return f"Current user: {context.user_id}"
189
+
190
+ @prompt(priority=20)
191
+ async def async_prompt(self, context: Context) -> str:
192
+ # Async prompt generation
193
+ data = await some_async_call()
194
+ return f"Dynamic data: {data}"
195
+ """
196
+ def decorator(func: Callable) -> Callable:
197
+ # Mark function with metadata for BaseAgent discovery
198
+ func._robutler_is_prompt = True
199
+ func._prompt_priority = priority
200
+ func._prompt_scope = scope
201
+
202
+ # Check if function expects context injection
203
+ sig = inspect.signature(func)
204
+ has_context_param = 'context' in sig.parameters
205
+
206
+ if has_context_param:
207
+ @functools.wraps(func)
208
+ async def async_wrapper(*args, **kwargs):
209
+ # Inject context if requested and not provided
210
+ if 'context' not in kwargs:
211
+ from ...server.context.context_vars import get_context
212
+ context = get_context()
213
+ kwargs['context'] = context
214
+
215
+ # Call original function
216
+ if inspect.iscoroutinefunction(func):
217
+ return await func(*args, **kwargs)
218
+ else:
219
+ return func(*args, **kwargs)
220
+
221
+ @functools.wraps(func)
222
+ def sync_wrapper(*args, **kwargs):
223
+ # Inject context if requested and not provided
224
+ if 'context' not in kwargs:
225
+ from ...server.context.context_vars import get_context
226
+ context = get_context()
227
+ kwargs['context'] = context
228
+
229
+ return func(*args, **kwargs)
230
+
231
+ # Return appropriate wrapper based on function type
232
+ if inspect.iscoroutinefunction(func):
233
+ wrapper = async_wrapper
234
+ else:
235
+ wrapper = sync_wrapper
236
+ else:
237
+ # No context injection needed
238
+ wrapper = func
239
+
240
+ # Copy metadata to wrapper
241
+ wrapper._robutler_is_prompt = True
242
+ wrapper._prompt_priority = priority
243
+ wrapper._prompt_scope = scope
244
+
245
+ return wrapper
246
+
247
+ return decorator
248
+
249
+
250
+ def handoff(name: Optional[str] = None, handoff_type: str = "agent", description: Optional[str] = None,
251
+ scope: Union[str, List[str]] = "all"):
252
+ """Decorator to mark functions as handoffs for automatic registration
253
+
254
+ Args:
255
+ name: Optional override for handoff name (defaults to function name)
256
+ handoff_type: Type of handoff - "agent", "llm", "pipeline", etc.
257
+ description: Handoff description (defaults to function docstring)
258
+ scope: Access scope - "all", "owner", "admin", or list of scopes
259
+
260
+ Handoff functions should return HandoffResult:
261
+
262
+ @handoff(handoff_type="agent", scope=["admin"])
263
+ async def escalate_to_admin(self, issue: str, context: Context = None) -> HandoffResult:
264
+ # Process handoff
265
+ return HandoffResult(result="escalated", handoff_type="agent")
266
+ """
267
+ def decorator(func: Callable) -> Callable:
268
+ # Mark function with metadata for BaseAgent discovery
269
+ func._robutler_is_handoff = True
270
+ func._handoff_type = handoff_type
271
+ func._handoff_scope = scope
272
+ func._handoff_name = name or func.__name__
273
+ func._handoff_description = description or func.__doc__ or f"Handoff: {func.__name__}"
274
+
275
+ # Check if function expects context injection
276
+ sig = inspect.signature(func)
277
+ has_context_param = 'context' in sig.parameters
278
+
279
+ if has_context_param:
280
+ @functools.wraps(func)
281
+ async def async_wrapper(*args, **kwargs):
282
+ # Inject context if requested and not provided
283
+ if 'context' not in kwargs:
284
+ from ...server.context.context_vars import get_context
285
+ context = get_context()
286
+ kwargs['context'] = context
287
+
288
+ # Call original function
289
+ if inspect.iscoroutinefunction(func):
290
+ return await func(*args, **kwargs)
291
+ else:
292
+ return func(*args, **kwargs)
293
+
294
+ @functools.wraps(func)
295
+ def sync_wrapper(*args, **kwargs):
296
+ # Inject context if requested and not provided
297
+ if 'context' not in kwargs:
298
+ from ...server.context.context_vars import get_context
299
+ context = get_context()
300
+ kwargs['context'] = context
301
+
302
+ return func(*args, **kwargs)
303
+
304
+ # Return appropriate wrapper
305
+ if inspect.iscoroutinefunction(func):
306
+ wrapper = async_wrapper
307
+ else:
308
+ wrapper = sync_wrapper
309
+ else:
310
+ wrapper = func
311
+
312
+ # Copy metadata to wrapper
313
+ wrapper._robutler_is_handoff = True
314
+ wrapper._handoff_type = handoff_type
315
+ wrapper._handoff_scope = scope
316
+ wrapper._handoff_name = name or func.__name__
317
+ wrapper._handoff_description = description or func.__doc__ or f"Handoff: {func.__name__}"
318
+
319
+ return wrapper
320
+
321
+ return decorator
322
+
323
+
324
+ def http(subpath: str, method: str = "get", scope: Union[str, List[str]] = "all"):
325
+ """Decorator to mark functions as HTTP handlers for automatic registration
326
+
327
+ Args:
328
+ subpath: URL path after agent name (e.g., "/myapi" -> /{agentname}/myapi)
329
+ Supports dynamic parameters: "/users/{user_id}/posts/{post_id}"
330
+ method: HTTP method - "get", "post", "put", "delete", etc. (default: "get")
331
+ scope: Access scope - "all", "owner", "admin", or list of scopes
332
+
333
+ HTTP handler functions receive FastAPI request arguments directly:
334
+
335
+ @http("/weather", method="get", scope="owner")
336
+ def get_weather(location: str, units: str = "celsius") -> dict:
337
+ # Function receives query parameters as arguments
338
+ return {"location": location, "temperature": 25, "units": units}
339
+
340
+ @http("/data", method="post")
341
+ async def post_data(request: Request, data: dict) -> dict:
342
+ # Function can receive Request object and body data
343
+ return {"received": data, "status": "success"}
344
+
345
+ @http("/users/{user_id}", method="get")
346
+ def get_user(user_id: str) -> dict:
347
+ # Function receives path parameters as arguments
348
+ return {"user_id": user_id, "name": f"User {user_id}"}
349
+
350
+ @http("/users/{user_id}/posts/{post_id}", method="get")
351
+ def get_user_post(user_id: str, post_id: str, include_comments: bool = False) -> dict:
352
+ # Function receives both path parameters and query parameters
353
+ return {
354
+ "user_id": user_id,
355
+ "post_id": post_id,
356
+ "include_comments": include_comments
357
+ }
358
+
359
+ Dynamic path parameters are automatically extracted by FastAPI and passed
360
+ to the handler function. Query parameters and JSON body data are also
361
+ automatically passed as function arguments.
362
+ """
363
+ def decorator(func: Callable) -> Callable:
364
+ # Validate HTTP method
365
+ valid_methods = ["get", "post", "put", "delete", "patch", "head", "options"]
366
+ if method.lower() not in valid_methods:
367
+ raise ValueError(f"Invalid HTTP method '{method}'. Must be one of: {valid_methods}")
368
+
369
+ # Ensure subpath starts with /
370
+ normalized_subpath = subpath if subpath.startswith('/') else f'/{subpath}'
371
+
372
+ # Mark function with metadata for BaseAgent discovery
373
+ func._robutler_is_http = True
374
+ func._http_subpath = normalized_subpath
375
+ func._http_method = method.lower()
376
+ func._http_scope = scope
377
+ func._http_description = func.__doc__ or f"HTTP {method.upper()} handler for {normalized_subpath}"
378
+
379
+ # Check if function expects context injection
380
+ sig = inspect.signature(func)
381
+ has_context_param = 'context' in sig.parameters
382
+
383
+ if has_context_param:
384
+ @functools.wraps(func)
385
+ async def async_wrapper(*args, **kwargs):
386
+ # Inject context if requested and not provided
387
+ if 'context' not in kwargs:
388
+ from ...server.context.context_vars import get_context
389
+ context = get_context()
390
+ kwargs['context'] = context
391
+
392
+ # Call original function
393
+ if inspect.iscoroutinefunction(func):
394
+ return await func(*args, **kwargs)
395
+ else:
396
+ return func(*args, **kwargs)
397
+
398
+ @functools.wraps(func)
399
+ def sync_wrapper(*args, **kwargs):
400
+ # Inject context if requested and not provided
401
+ if 'context' not in kwargs:
402
+ from ...server.context.context_vars import get_context
403
+ context = get_context()
404
+ kwargs['context'] = context
405
+
406
+ return func(*args, **kwargs)
407
+
408
+ # Return appropriate wrapper based on function type
409
+ if inspect.iscoroutinefunction(func):
410
+ wrapper = async_wrapper
411
+ else:
412
+ wrapper = sync_wrapper
413
+ else:
414
+ # No context injection needed
415
+ wrapper = func
416
+
417
+ # Copy metadata to wrapper
418
+ wrapper._robutler_is_http = True
419
+ wrapper._http_subpath = normalized_subpath
420
+ wrapper._http_method = method.lower()
421
+ wrapper._http_scope = scope
422
+ wrapper._http_description = func.__doc__ or f"HTTP {method.upper()} handler for {normalized_subpath}"
423
+
424
+ return wrapper
425
+
426
+ return decorator
File without changes
File without changes
@@ -0,0 +1,17 @@
1
+ """
2
+ Robutler API Client Package - Robutler V2.0
3
+
4
+ Client library for integrating with Robutler Platform services.
5
+ Provides authentication, user management, payment, and other platform APIs.
6
+ """
7
+
8
+ from .client import RobutlerClient
9
+ from .types import User, ApiKey, Integration, CreditTransaction
10
+
11
+ __all__ = [
12
+ "RobutlerClient",
13
+ "User",
14
+ "ApiKey",
15
+ "Integration",
16
+ "CreditTransaction"
17
+ ]