genxai-framework 0.1.0__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 (156) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +6 -0
  3. cli/commands/approval.py +85 -0
  4. cli/commands/audit.py +127 -0
  5. cli/commands/metrics.py +25 -0
  6. cli/commands/tool.py +389 -0
  7. cli/main.py +32 -0
  8. genxai/__init__.py +81 -0
  9. genxai/api/__init__.py +5 -0
  10. genxai/api/app.py +21 -0
  11. genxai/config/__init__.py +5 -0
  12. genxai/config/settings.py +37 -0
  13. genxai/connectors/__init__.py +19 -0
  14. genxai/connectors/base.py +122 -0
  15. genxai/connectors/kafka.py +92 -0
  16. genxai/connectors/postgres_cdc.py +95 -0
  17. genxai/connectors/registry.py +44 -0
  18. genxai/connectors/sqs.py +94 -0
  19. genxai/connectors/webhook.py +73 -0
  20. genxai/core/__init__.py +37 -0
  21. genxai/core/agent/__init__.py +32 -0
  22. genxai/core/agent/base.py +206 -0
  23. genxai/core/agent/config_io.py +59 -0
  24. genxai/core/agent/registry.py +98 -0
  25. genxai/core/agent/runtime.py +970 -0
  26. genxai/core/communication/__init__.py +6 -0
  27. genxai/core/communication/collaboration.py +44 -0
  28. genxai/core/communication/message_bus.py +192 -0
  29. genxai/core/communication/protocols.py +35 -0
  30. genxai/core/execution/__init__.py +22 -0
  31. genxai/core/execution/metadata.py +181 -0
  32. genxai/core/execution/queue.py +201 -0
  33. genxai/core/graph/__init__.py +30 -0
  34. genxai/core/graph/checkpoints.py +77 -0
  35. genxai/core/graph/edges.py +131 -0
  36. genxai/core/graph/engine.py +813 -0
  37. genxai/core/graph/executor.py +516 -0
  38. genxai/core/graph/nodes.py +161 -0
  39. genxai/core/graph/trigger_runner.py +40 -0
  40. genxai/core/memory/__init__.py +19 -0
  41. genxai/core/memory/base.py +72 -0
  42. genxai/core/memory/embedding.py +327 -0
  43. genxai/core/memory/episodic.py +448 -0
  44. genxai/core/memory/long_term.py +467 -0
  45. genxai/core/memory/manager.py +543 -0
  46. genxai/core/memory/persistence.py +297 -0
  47. genxai/core/memory/procedural.py +461 -0
  48. genxai/core/memory/semantic.py +526 -0
  49. genxai/core/memory/shared.py +62 -0
  50. genxai/core/memory/short_term.py +303 -0
  51. genxai/core/memory/vector_store.py +508 -0
  52. genxai/core/memory/working.py +211 -0
  53. genxai/core/state/__init__.py +6 -0
  54. genxai/core/state/manager.py +293 -0
  55. genxai/core/state/schema.py +115 -0
  56. genxai/llm/__init__.py +14 -0
  57. genxai/llm/base.py +150 -0
  58. genxai/llm/factory.py +329 -0
  59. genxai/llm/providers/__init__.py +1 -0
  60. genxai/llm/providers/anthropic.py +249 -0
  61. genxai/llm/providers/cohere.py +274 -0
  62. genxai/llm/providers/google.py +334 -0
  63. genxai/llm/providers/ollama.py +147 -0
  64. genxai/llm/providers/openai.py +257 -0
  65. genxai/llm/routing.py +83 -0
  66. genxai/observability/__init__.py +6 -0
  67. genxai/observability/logging.py +327 -0
  68. genxai/observability/metrics.py +494 -0
  69. genxai/observability/tracing.py +372 -0
  70. genxai/performance/__init__.py +39 -0
  71. genxai/performance/cache.py +256 -0
  72. genxai/performance/pooling.py +289 -0
  73. genxai/security/audit.py +304 -0
  74. genxai/security/auth.py +315 -0
  75. genxai/security/cost_control.py +528 -0
  76. genxai/security/default_policies.py +44 -0
  77. genxai/security/jwt.py +142 -0
  78. genxai/security/oauth.py +226 -0
  79. genxai/security/pii.py +366 -0
  80. genxai/security/policy_engine.py +82 -0
  81. genxai/security/rate_limit.py +341 -0
  82. genxai/security/rbac.py +247 -0
  83. genxai/security/validation.py +218 -0
  84. genxai/tools/__init__.py +21 -0
  85. genxai/tools/base.py +383 -0
  86. genxai/tools/builtin/__init__.py +131 -0
  87. genxai/tools/builtin/communication/__init__.py +15 -0
  88. genxai/tools/builtin/communication/email_sender.py +159 -0
  89. genxai/tools/builtin/communication/notification_manager.py +167 -0
  90. genxai/tools/builtin/communication/slack_notifier.py +118 -0
  91. genxai/tools/builtin/communication/sms_sender.py +118 -0
  92. genxai/tools/builtin/communication/webhook_caller.py +136 -0
  93. genxai/tools/builtin/computation/__init__.py +15 -0
  94. genxai/tools/builtin/computation/calculator.py +101 -0
  95. genxai/tools/builtin/computation/code_executor.py +183 -0
  96. genxai/tools/builtin/computation/data_validator.py +259 -0
  97. genxai/tools/builtin/computation/hash_generator.py +129 -0
  98. genxai/tools/builtin/computation/regex_matcher.py +201 -0
  99. genxai/tools/builtin/data/__init__.py +15 -0
  100. genxai/tools/builtin/data/csv_processor.py +213 -0
  101. genxai/tools/builtin/data/data_transformer.py +299 -0
  102. genxai/tools/builtin/data/json_processor.py +233 -0
  103. genxai/tools/builtin/data/text_analyzer.py +288 -0
  104. genxai/tools/builtin/data/xml_processor.py +175 -0
  105. genxai/tools/builtin/database/__init__.py +15 -0
  106. genxai/tools/builtin/database/database_inspector.py +157 -0
  107. genxai/tools/builtin/database/mongodb_query.py +196 -0
  108. genxai/tools/builtin/database/redis_cache.py +167 -0
  109. genxai/tools/builtin/database/sql_query.py +145 -0
  110. genxai/tools/builtin/database/vector_search.py +163 -0
  111. genxai/tools/builtin/file/__init__.py +17 -0
  112. genxai/tools/builtin/file/directory_scanner.py +214 -0
  113. genxai/tools/builtin/file/file_compressor.py +237 -0
  114. genxai/tools/builtin/file/file_reader.py +102 -0
  115. genxai/tools/builtin/file/file_writer.py +122 -0
  116. genxai/tools/builtin/file/image_processor.py +186 -0
  117. genxai/tools/builtin/file/pdf_parser.py +144 -0
  118. genxai/tools/builtin/test/__init__.py +15 -0
  119. genxai/tools/builtin/test/async_simulator.py +62 -0
  120. genxai/tools/builtin/test/data_transformer.py +99 -0
  121. genxai/tools/builtin/test/error_generator.py +82 -0
  122. genxai/tools/builtin/test/simple_math.py +94 -0
  123. genxai/tools/builtin/test/string_processor.py +72 -0
  124. genxai/tools/builtin/web/__init__.py +15 -0
  125. genxai/tools/builtin/web/api_caller.py +161 -0
  126. genxai/tools/builtin/web/html_parser.py +330 -0
  127. genxai/tools/builtin/web/http_client.py +187 -0
  128. genxai/tools/builtin/web/url_validator.py +162 -0
  129. genxai/tools/builtin/web/web_scraper.py +170 -0
  130. genxai/tools/custom/my_test_tool_2.py +9 -0
  131. genxai/tools/dynamic.py +105 -0
  132. genxai/tools/mcp_server.py +167 -0
  133. genxai/tools/persistence/__init__.py +6 -0
  134. genxai/tools/persistence/models.py +55 -0
  135. genxai/tools/persistence/service.py +322 -0
  136. genxai/tools/registry.py +227 -0
  137. genxai/tools/security/__init__.py +11 -0
  138. genxai/tools/security/limits.py +214 -0
  139. genxai/tools/security/policy.py +20 -0
  140. genxai/tools/security/sandbox.py +248 -0
  141. genxai/tools/templates.py +435 -0
  142. genxai/triggers/__init__.py +19 -0
  143. genxai/triggers/base.py +104 -0
  144. genxai/triggers/file_watcher.py +75 -0
  145. genxai/triggers/queue.py +68 -0
  146. genxai/triggers/registry.py +82 -0
  147. genxai/triggers/schedule.py +66 -0
  148. genxai/triggers/webhook.py +68 -0
  149. genxai/utils/__init__.py +1 -0
  150. genxai/utils/tokens.py +295 -0
  151. genxai_framework-0.1.0.dist-info/METADATA +495 -0
  152. genxai_framework-0.1.0.dist-info/RECORD +156 -0
  153. genxai_framework-0.1.0.dist-info/WHEEL +5 -0
  154. genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
  155. genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
  156. genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,341 @@
1
+ """Rate limiting for GenXAI using token bucket algorithm."""
2
+
3
+ import time
4
+ import asyncio
5
+ from typing import Optional, Dict
6
+ from functools import wraps
7
+ from dataclasses import dataclass
8
+ import os
9
+
10
+
11
+ @dataclass
12
+ class RateLimitConfig:
13
+ """Rate limit configuration."""
14
+ requests_per_minute: int
15
+ requests_per_hour: int
16
+ requests_per_day: int
17
+
18
+
19
+ # Rate limit tiers
20
+ RATE_LIMITS = {
21
+ "free": RateLimitConfig(
22
+ requests_per_minute=10,
23
+ requests_per_hour=100,
24
+ requests_per_day=1000,
25
+ ),
26
+ "pro": RateLimitConfig(
27
+ requests_per_minute=60,
28
+ requests_per_hour=1000,
29
+ requests_per_day=10000,
30
+ ),
31
+ "enterprise": RateLimitConfig(
32
+ requests_per_minute=300,
33
+ requests_per_hour=10000,
34
+ requests_per_day=100000,
35
+ ),
36
+ }
37
+
38
+
39
+ class TokenBucket:
40
+ """Token bucket for rate limiting."""
41
+
42
+ def __init__(self, rate: float, capacity: int):
43
+ """Initialize token bucket.
44
+
45
+ Args:
46
+ rate: Tokens per second
47
+ capacity: Bucket capacity
48
+ """
49
+ self.rate = rate
50
+ self.capacity = capacity
51
+ self.tokens = capacity
52
+ self.last_update = time.time()
53
+ self.lock = asyncio.Lock()
54
+
55
+ async def consume(self, tokens: int = 1) -> bool:
56
+ """Consume tokens from bucket.
57
+
58
+ Args:
59
+ tokens: Number of tokens to consume
60
+
61
+ Returns:
62
+ True if tokens consumed, False if rate limited
63
+ """
64
+ async with self.lock:
65
+ now = time.time()
66
+ elapsed = now - self.last_update
67
+
68
+ # Add tokens based on elapsed time
69
+ self.tokens = min(
70
+ self.capacity,
71
+ self.tokens + elapsed * self.rate
72
+ )
73
+ self.last_update = now
74
+
75
+ # Check if enough tokens
76
+ if self.tokens >= tokens:
77
+ self.tokens -= tokens
78
+ return True
79
+
80
+ return False
81
+
82
+ async def get_remaining(self) -> int:
83
+ """Get remaining tokens.
84
+
85
+ Returns:
86
+ Number of remaining tokens
87
+ """
88
+ async with self.lock:
89
+ now = time.time()
90
+ elapsed = now - self.last_update
91
+
92
+ tokens = min(
93
+ self.capacity,
94
+ self.tokens + elapsed * self.rate
95
+ )
96
+
97
+ return int(tokens)
98
+
99
+
100
+ class RateLimiter:
101
+ """Rate limiter using token bucket algorithm."""
102
+
103
+ def __init__(self, storage: str = "memory"):
104
+ """Initialize rate limiter.
105
+
106
+ Args:
107
+ storage: Storage backend (memory or redis)
108
+ """
109
+ self.storage = storage
110
+ self.buckets: Dict[str, Dict[str, TokenBucket]] = {}
111
+
112
+ # Try to import Redis if using redis storage
113
+ if storage == "redis":
114
+ try:
115
+ import redis
116
+ redis_url = os.getenv("REDIS_URL", "redis://localhost:6379")
117
+ self.redis_client = redis.from_url(redis_url)
118
+ except ImportError:
119
+ print("Redis not available, falling back to memory storage")
120
+ self.storage = "memory"
121
+
122
+ async def check_rate_limit(
123
+ self,
124
+ key: str,
125
+ tier: str = "free",
126
+ cost: int = 1
127
+ ) -> bool:
128
+ """Check if request is within rate limit.
129
+
130
+ Args:
131
+ key: User ID or API key
132
+ tier: Rate limit tier (free, pro, enterprise)
133
+ cost: Cost in tokens (default: 1)
134
+
135
+ Returns:
136
+ True if within limit, False if rate limited
137
+ """
138
+ config = RATE_LIMITS.get(tier, RATE_LIMITS["free"])
139
+
140
+ # Check minute limit
141
+ minute_key = f"{key}:minute"
142
+ if not await self._check_bucket(
143
+ minute_key,
144
+ rate=config.requests_per_minute / 60,
145
+ capacity=config.requests_per_minute,
146
+ cost=cost
147
+ ):
148
+ return False
149
+
150
+ # Check hour limit
151
+ hour_key = f"{key}:hour"
152
+ if not await self._check_bucket(
153
+ hour_key,
154
+ rate=config.requests_per_hour / 3600,
155
+ capacity=config.requests_per_hour,
156
+ cost=cost
157
+ ):
158
+ return False
159
+
160
+ # Check day limit
161
+ day_key = f"{key}:day"
162
+ if not await self._check_bucket(
163
+ day_key,
164
+ rate=config.requests_per_day / 86400,
165
+ capacity=config.requests_per_day,
166
+ cost=cost
167
+ ):
168
+ return False
169
+
170
+ return True
171
+
172
+ async def _check_bucket(
173
+ self,
174
+ key: str,
175
+ rate: float,
176
+ capacity: int,
177
+ cost: int
178
+ ) -> bool:
179
+ """Check token bucket.
180
+
181
+ Args:
182
+ key: Bucket key
183
+ rate: Tokens per second
184
+ capacity: Bucket capacity
185
+ cost: Tokens to consume
186
+
187
+ Returns:
188
+ True if tokens consumed, False if rate limited
189
+ """
190
+ if self.storage == "memory":
191
+ # Get or create bucket
192
+ if key not in self.buckets:
193
+ self.buckets[key] = {}
194
+
195
+ if "bucket" not in self.buckets[key]:
196
+ self.buckets[key]["bucket"] = TokenBucket(rate, capacity)
197
+
198
+ bucket = self.buckets[key]["bucket"]
199
+ return await bucket.consume(cost)
200
+
201
+ elif self.storage == "redis":
202
+ # Redis-based rate limiting using Lua script
203
+ # This is a simplified version
204
+ return await self._check_redis_bucket(key, rate, capacity, cost)
205
+
206
+ return True
207
+
208
+ async def _check_redis_bucket(
209
+ self,
210
+ key: str,
211
+ rate: float,
212
+ capacity: int,
213
+ cost: int
214
+ ) -> bool:
215
+ """Check rate limit using Redis.
216
+
217
+ Args:
218
+ key: Bucket key
219
+ rate: Tokens per second
220
+ capacity: Bucket capacity
221
+ cost: Tokens to consume
222
+
223
+ Returns:
224
+ True if within limit, False if rate limited
225
+ """
226
+ # Simplified Redis implementation
227
+ # In production, use a Lua script for atomicity
228
+ try:
229
+ current = self.redis_client.get(key)
230
+ if current is None:
231
+ self.redis_client.setex(key, 60, capacity - cost)
232
+ return True
233
+
234
+ current = int(current)
235
+ if current >= cost:
236
+ self.redis_client.decrby(key, cost)
237
+ return True
238
+
239
+ return False
240
+ except Exception:
241
+ # Fallback to allowing request if Redis fails
242
+ return True
243
+
244
+ async def get_remaining(self, key: str, tier: str = "free") -> Dict[str, int]:
245
+ """Get remaining requests.
246
+
247
+ Args:
248
+ key: User ID or API key
249
+ tier: Rate limit tier
250
+
251
+ Returns:
252
+ Dictionary with remaining requests per period
253
+ """
254
+ config = RATE_LIMITS.get(tier, RATE_LIMITS["free"])
255
+
256
+ minute_key = f"{key}:minute"
257
+ hour_key = f"{key}:hour"
258
+ day_key = f"{key}:day"
259
+
260
+ return {
261
+ "minute": await self._get_bucket_remaining(minute_key),
262
+ "hour": await self._get_bucket_remaining(hour_key),
263
+ "day": await self._get_bucket_remaining(day_key),
264
+ }
265
+
266
+ async def _get_bucket_remaining(self, key: str) -> int:
267
+ """Get remaining tokens in bucket.
268
+
269
+ Args:
270
+ key: Bucket key
271
+
272
+ Returns:
273
+ Remaining tokens
274
+ """
275
+ if self.storage == "memory":
276
+ if key in self.buckets and "bucket" in self.buckets[key]:
277
+ return await self.buckets[key]["bucket"].get_remaining()
278
+ return 0
279
+
280
+ elif self.storage == "redis":
281
+ try:
282
+ current = self.redis_client.get(key)
283
+ return int(current) if current else 0
284
+ except Exception:
285
+ return 0
286
+
287
+ return 0
288
+
289
+
290
+ class RateLimitExceeded(Exception):
291
+ """Rate limit exceeded exception."""
292
+ pass
293
+
294
+
295
+ # Global rate limiter
296
+ _rate_limiter = None
297
+
298
+
299
+ def get_rate_limiter() -> RateLimiter:
300
+ """Get global rate limiter.
301
+
302
+ Returns:
303
+ RateLimiter instance
304
+ """
305
+ global _rate_limiter
306
+
307
+ if _rate_limiter is None:
308
+ storage = os.getenv("RATE_LIMIT_STORAGE", "memory")
309
+ _rate_limiter = RateLimiter(storage)
310
+
311
+ return _rate_limiter
312
+
313
+
314
+ def rate_limit(tier: str = "free", cost: int = 1):
315
+ """Decorator for rate limiting.
316
+
317
+ Args:
318
+ tier: Rate limit tier
319
+ cost: Cost in tokens
320
+
321
+ Usage:
322
+ @rate_limit(tier="pro", cost=1)
323
+ async def my_endpoint():
324
+ pass
325
+ """
326
+ def decorator(func):
327
+ @wraps(func)
328
+ async def wrapper(*args, **kwargs):
329
+ # Get user key from kwargs or context
330
+ user_id = kwargs.get("user_id", "anonymous")
331
+
332
+ # Check rate limit
333
+ limiter = get_rate_limiter()
334
+ if not await limiter.check_rate_limit(user_id, tier, cost):
335
+ raise RateLimitExceeded(f"Rate limit exceeded for tier: {tier}")
336
+
337
+ return await func(*args, **kwargs)
338
+
339
+ return wrapper
340
+
341
+ return decorator
@@ -0,0 +1,247 @@
1
+ """Role-Based Access Control (RBAC) for GenXAI."""
2
+
3
+ from enum import Enum
4
+ from typing import List, Set, Optional
5
+ from functools import wraps
6
+ from dataclasses import dataclass
7
+
8
+
9
+ class Role(Enum):
10
+ """User roles."""
11
+ ADMIN = "admin"
12
+ DEVELOPER = "developer"
13
+ OPERATOR = "operator"
14
+ VIEWER = "viewer"
15
+
16
+
17
+ class Permission(Enum):
18
+ """System permissions."""
19
+ # Agent permissions
20
+ AGENT_CREATE = "agent:create"
21
+ AGENT_READ = "agent:read"
22
+ AGENT_UPDATE = "agent:update"
23
+ AGENT_DELETE = "agent:delete"
24
+ AGENT_EXECUTE = "agent:execute"
25
+
26
+ # Workflow permissions
27
+ WORKFLOW_CREATE = "workflow:create"
28
+ WORKFLOW_READ = "workflow:read"
29
+ WORKFLOW_UPDATE = "workflow:update"
30
+ WORKFLOW_DELETE = "workflow:delete"
31
+ WORKFLOW_EXECUTE = "workflow:execute"
32
+
33
+ # Tool permissions
34
+ TOOL_CREATE = "tool:create"
35
+ TOOL_READ = "tool:read"
36
+ TOOL_UPDATE = "tool:update"
37
+ TOOL_DELETE = "tool:delete"
38
+ TOOL_EXECUTE = "tool:execute"
39
+
40
+ # Memory permissions
41
+ MEMORY_READ = "memory:read"
42
+ MEMORY_WRITE = "memory:write"
43
+ MEMORY_DELETE = "memory:delete"
44
+
45
+
46
+ # Role-Permission mapping
47
+ ROLE_PERMISSIONS: dict[Role, Set[Permission]] = {
48
+ Role.ADMIN: set(Permission), # All permissions
49
+
50
+ Role.DEVELOPER: {
51
+ Permission.AGENT_CREATE,
52
+ Permission.AGENT_READ,
53
+ Permission.AGENT_UPDATE,
54
+ Permission.AGENT_EXECUTE,
55
+ Permission.WORKFLOW_CREATE,
56
+ Permission.WORKFLOW_READ,
57
+ Permission.WORKFLOW_UPDATE,
58
+ Permission.WORKFLOW_EXECUTE,
59
+ Permission.TOOL_READ,
60
+ Permission.TOOL_EXECUTE,
61
+ Permission.MEMORY_READ,
62
+ Permission.MEMORY_WRITE,
63
+ },
64
+
65
+ Role.OPERATOR: {
66
+ Permission.AGENT_READ,
67
+ Permission.AGENT_EXECUTE,
68
+ Permission.WORKFLOW_READ,
69
+ Permission.WORKFLOW_EXECUTE,
70
+ Permission.TOOL_READ,
71
+ Permission.TOOL_EXECUTE,
72
+ Permission.MEMORY_READ,
73
+ },
74
+
75
+ Role.VIEWER: {
76
+ Permission.AGENT_READ,
77
+ Permission.WORKFLOW_READ,
78
+ Permission.TOOL_READ,
79
+ Permission.MEMORY_READ,
80
+ },
81
+ }
82
+
83
+
84
+ @dataclass
85
+ class User:
86
+ """User model."""
87
+ user_id: str
88
+ role: Role
89
+
90
+ def has_permission(self, permission: Permission) -> bool:
91
+ """Check if user has permission.
92
+
93
+ Args:
94
+ permission: Permission to check
95
+
96
+ Returns:
97
+ True if user has permission
98
+ """
99
+ return permission in ROLE_PERMISSIONS[self.role]
100
+
101
+ def has_any_permission(self, permissions: List[Permission]) -> bool:
102
+ """Check if user has any of the permissions.
103
+
104
+ Args:
105
+ permissions: List of permissions
106
+
107
+ Returns:
108
+ True if user has any permission
109
+ """
110
+ user_permissions = ROLE_PERMISSIONS[self.role]
111
+ return any(p in user_permissions for p in permissions)
112
+
113
+ def has_all_permissions(self, permissions: List[Permission]) -> bool:
114
+ """Check if user has all permissions.
115
+
116
+ Args:
117
+ permissions: List of permissions
118
+
119
+ Returns:
120
+ True if user has all permissions
121
+ """
122
+ user_permissions = ROLE_PERMISSIONS[self.role]
123
+ return all(p in user_permissions for p in permissions)
124
+
125
+
126
+ class PermissionDenied(Exception):
127
+ """Permission denied exception."""
128
+ pass
129
+
130
+
131
+ # Current user context (thread-local in production)
132
+ _current_user: Optional[User] = None
133
+
134
+
135
+ def set_current_user(user: User):
136
+ """Set current user.
137
+
138
+ Args:
139
+ user: User object
140
+ """
141
+ global _current_user
142
+ _current_user = user
143
+
144
+
145
+ def get_current_user() -> Optional[User]:
146
+ """Get current user.
147
+
148
+ Returns:
149
+ Current user or None
150
+ """
151
+ return _current_user
152
+
153
+
154
+ def require_permission(permission: Permission):
155
+ """Decorator to require permission.
156
+
157
+ Args:
158
+ permission: Required permission
159
+
160
+ Usage:
161
+ @require_permission(Permission.AGENT_EXECUTE)
162
+ async def execute_agent():
163
+ pass
164
+ """
165
+ def decorator(func):
166
+ @wraps(func)
167
+ async def async_wrapper(*args, **kwargs):
168
+ user = get_current_user()
169
+ if not user:
170
+ raise PermissionDenied("No user context")
171
+
172
+ if not user.has_permission(permission):
173
+ raise PermissionDenied(
174
+ f"User {user.user_id} missing permission: {permission.value}"
175
+ )
176
+
177
+ return await func(*args, **kwargs)
178
+
179
+ @wraps(func)
180
+ def sync_wrapper(*args, **kwargs):
181
+ user = get_current_user()
182
+ if not user:
183
+ raise PermissionDenied("No user context")
184
+
185
+ if not user.has_permission(permission):
186
+ raise PermissionDenied(
187
+ f"User {user.user_id} missing permission: {permission.value}"
188
+ )
189
+
190
+ return func(*args, **kwargs)
191
+
192
+ # Return appropriate wrapper
193
+ import asyncio
194
+ if asyncio.iscoroutinefunction(func):
195
+ return async_wrapper
196
+ else:
197
+ return sync_wrapper
198
+
199
+ return decorator
200
+
201
+
202
+ def require_role(role: Role):
203
+ """Decorator to require specific role.
204
+
205
+ Args:
206
+ role: Required role
207
+
208
+ Usage:
209
+ @require_role(Role.ADMIN)
210
+ async def admin_function():
211
+ pass
212
+ """
213
+ def decorator(func):
214
+ @wraps(func)
215
+ async def async_wrapper(*args, **kwargs):
216
+ user = get_current_user()
217
+ if not user:
218
+ raise PermissionDenied("No user context")
219
+
220
+ if user.role != role:
221
+ raise PermissionDenied(
222
+ f"User {user.user_id} requires role: {role.value}"
223
+ )
224
+
225
+ return await func(*args, **kwargs)
226
+
227
+ @wraps(func)
228
+ def sync_wrapper(*args, **kwargs):
229
+ user = get_current_user()
230
+ if not user:
231
+ raise PermissionDenied("No user context")
232
+
233
+ if user.role != role:
234
+ raise PermissionDenied(
235
+ f"User {user.user_id} requires role: {role.value}"
236
+ )
237
+
238
+ return func(*args, **kwargs)
239
+
240
+ # Return appropriate wrapper
241
+ import asyncio
242
+ if asyncio.iscoroutinefunction(func):
243
+ return async_wrapper
244
+ else:
245
+ return sync_wrapper
246
+
247
+ return decorator