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.
- webagents/__init__.py +18 -0
- webagents/agents/__init__.py +13 -0
- webagents/agents/core/__init__.py +19 -0
- webagents/agents/core/base_agent.py +1834 -0
- webagents/agents/core/handoffs.py +293 -0
- webagents/agents/handoffs/__init__.py +0 -0
- webagents/agents/interfaces/__init__.py +0 -0
- webagents/agents/lifecycle/__init__.py +0 -0
- webagents/agents/skills/__init__.py +109 -0
- webagents/agents/skills/base.py +136 -0
- webagents/agents/skills/core/__init__.py +8 -0
- webagents/agents/skills/core/guardrails/__init__.py +0 -0
- webagents/agents/skills/core/llm/__init__.py +0 -0
- webagents/agents/skills/core/llm/anthropic/__init__.py +1 -0
- webagents/agents/skills/core/llm/litellm/__init__.py +10 -0
- webagents/agents/skills/core/llm/litellm/skill.py +538 -0
- webagents/agents/skills/core/llm/openai/__init__.py +1 -0
- webagents/agents/skills/core/llm/xai/__init__.py +1 -0
- webagents/agents/skills/core/mcp/README.md +375 -0
- webagents/agents/skills/core/mcp/__init__.py +15 -0
- webagents/agents/skills/core/mcp/skill.py +731 -0
- webagents/agents/skills/core/memory/__init__.py +11 -0
- webagents/agents/skills/core/memory/long_term_memory/__init__.py +10 -0
- webagents/agents/skills/core/memory/long_term_memory/memory_skill.py +639 -0
- webagents/agents/skills/core/memory/short_term_memory/__init__.py +9 -0
- webagents/agents/skills/core/memory/short_term_memory/skill.py +341 -0
- webagents/agents/skills/core/memory/vector_memory/skill.py +447 -0
- webagents/agents/skills/core/planning/__init__.py +9 -0
- webagents/agents/skills/core/planning/planner.py +343 -0
- webagents/agents/skills/ecosystem/__init__.py +0 -0
- webagents/agents/skills/ecosystem/crewai/__init__.py +1 -0
- webagents/agents/skills/ecosystem/database/__init__.py +1 -0
- webagents/agents/skills/ecosystem/filesystem/__init__.py +0 -0
- webagents/agents/skills/ecosystem/google/__init__.py +0 -0
- webagents/agents/skills/ecosystem/google/calendar/__init__.py +6 -0
- webagents/agents/skills/ecosystem/google/calendar/skill.py +306 -0
- webagents/agents/skills/ecosystem/n8n/__init__.py +0 -0
- webagents/agents/skills/ecosystem/openai_agents/__init__.py +0 -0
- webagents/agents/skills/ecosystem/web/__init__.py +0 -0
- webagents/agents/skills/ecosystem/zapier/__init__.py +0 -0
- webagents/agents/skills/robutler/__init__.py +11 -0
- webagents/agents/skills/robutler/auth/README.md +63 -0
- webagents/agents/skills/robutler/auth/__init__.py +17 -0
- webagents/agents/skills/robutler/auth/skill.py +354 -0
- webagents/agents/skills/robutler/crm/__init__.py +18 -0
- webagents/agents/skills/robutler/crm/skill.py +368 -0
- webagents/agents/skills/robutler/discovery/README.md +281 -0
- webagents/agents/skills/robutler/discovery/__init__.py +16 -0
- webagents/agents/skills/robutler/discovery/skill.py +230 -0
- webagents/agents/skills/robutler/kv/__init__.py +6 -0
- webagents/agents/skills/robutler/kv/skill.py +80 -0
- webagents/agents/skills/robutler/message_history/__init__.py +9 -0
- webagents/agents/skills/robutler/message_history/skill.py +270 -0
- webagents/agents/skills/robutler/messages/__init__.py +0 -0
- webagents/agents/skills/robutler/nli/__init__.py +13 -0
- webagents/agents/skills/robutler/nli/skill.py +687 -0
- webagents/agents/skills/robutler/notifications/__init__.py +5 -0
- webagents/agents/skills/robutler/notifications/skill.py +141 -0
- webagents/agents/skills/robutler/payments/__init__.py +41 -0
- webagents/agents/skills/robutler/payments/exceptions.py +255 -0
- webagents/agents/skills/robutler/payments/skill.py +610 -0
- webagents/agents/skills/robutler/storage/__init__.py +10 -0
- webagents/agents/skills/robutler/storage/files/__init__.py +9 -0
- webagents/agents/skills/robutler/storage/files/skill.py +445 -0
- webagents/agents/skills/robutler/storage/json/__init__.py +9 -0
- webagents/agents/skills/robutler/storage/json/skill.py +336 -0
- webagents/agents/skills/robutler/storage/kv/skill.py +88 -0
- webagents/agents/skills/robutler/storage.py +389 -0
- webagents/agents/tools/__init__.py +0 -0
- webagents/agents/tools/decorators.py +426 -0
- webagents/agents/tracing/__init__.py +0 -0
- webagents/agents/workflows/__init__.py +0 -0
- webagents/api/__init__.py +17 -0
- webagents/api/client.py +1207 -0
- webagents/api/types.py +253 -0
- webagents/scripts/__init__.py +0 -0
- webagents/server/__init__.py +28 -0
- webagents/server/context/__init__.py +0 -0
- webagents/server/context/context_vars.py +121 -0
- webagents/server/core/__init__.py +0 -0
- webagents/server/core/app.py +843 -0
- webagents/server/core/middleware.py +69 -0
- webagents/server/core/models.py +98 -0
- webagents/server/core/monitoring.py +59 -0
- webagents/server/endpoints/__init__.py +0 -0
- webagents/server/interfaces/__init__.py +0 -0
- webagents/server/middleware.py +330 -0
- webagents/server/models.py +92 -0
- webagents/server/monitoring.py +659 -0
- webagents/utils/__init__.py +0 -0
- webagents/utils/logging.py +359 -0
- webagents-0.1.12.dist-info/METADATA +99 -0
- webagents-0.1.12.dist-info/RECORD +96 -0
- webagents-0.1.12.dist-info/WHEEL +4 -0
- webagents-0.1.12.dist-info/entry_points.txt +2 -0
- 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
|
+
]
|