kailash 0.3.1__py3-none-any.whl → 0.4.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 (146) hide show
  1. kailash/__init__.py +33 -1
  2. kailash/access_control/__init__.py +129 -0
  3. kailash/access_control/managers.py +461 -0
  4. kailash/access_control/rule_evaluators.py +467 -0
  5. kailash/access_control_abac.py +825 -0
  6. kailash/config/__init__.py +27 -0
  7. kailash/config/database_config.py +359 -0
  8. kailash/database/__init__.py +28 -0
  9. kailash/database/execution_pipeline.py +499 -0
  10. kailash/middleware/__init__.py +306 -0
  11. kailash/middleware/auth/__init__.py +33 -0
  12. kailash/middleware/auth/access_control.py +436 -0
  13. kailash/middleware/auth/auth_manager.py +422 -0
  14. kailash/middleware/auth/jwt_auth.py +477 -0
  15. kailash/middleware/auth/kailash_jwt_auth.py +616 -0
  16. kailash/middleware/communication/__init__.py +37 -0
  17. kailash/middleware/communication/ai_chat.py +989 -0
  18. kailash/middleware/communication/api_gateway.py +802 -0
  19. kailash/middleware/communication/events.py +470 -0
  20. kailash/middleware/communication/realtime.py +710 -0
  21. kailash/middleware/core/__init__.py +21 -0
  22. kailash/middleware/core/agent_ui.py +890 -0
  23. kailash/middleware/core/schema.py +643 -0
  24. kailash/middleware/core/workflows.py +396 -0
  25. kailash/middleware/database/__init__.py +63 -0
  26. kailash/middleware/database/base.py +113 -0
  27. kailash/middleware/database/base_models.py +525 -0
  28. kailash/middleware/database/enums.py +106 -0
  29. kailash/middleware/database/migrations.py +12 -0
  30. kailash/{api/database.py → middleware/database/models.py} +183 -291
  31. kailash/middleware/database/repositories.py +685 -0
  32. kailash/middleware/database/session_manager.py +19 -0
  33. kailash/middleware/mcp/__init__.py +38 -0
  34. kailash/middleware/mcp/client_integration.py +585 -0
  35. kailash/middleware/mcp/enhanced_server.py +576 -0
  36. kailash/nodes/__init__.py +25 -3
  37. kailash/nodes/admin/__init__.py +35 -0
  38. kailash/nodes/admin/audit_log.py +794 -0
  39. kailash/nodes/admin/permission_check.py +864 -0
  40. kailash/nodes/admin/role_management.py +823 -0
  41. kailash/nodes/admin/security_event.py +1519 -0
  42. kailash/nodes/admin/user_management.py +944 -0
  43. kailash/nodes/ai/a2a.py +24 -7
  44. kailash/nodes/ai/ai_providers.py +1 -0
  45. kailash/nodes/ai/embedding_generator.py +11 -11
  46. kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
  47. kailash/nodes/ai/llm_agent.py +407 -2
  48. kailash/nodes/ai/self_organizing.py +85 -10
  49. kailash/nodes/api/auth.py +287 -6
  50. kailash/nodes/api/rest.py +151 -0
  51. kailash/nodes/auth/__init__.py +17 -0
  52. kailash/nodes/auth/directory_integration.py +1228 -0
  53. kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
  54. kailash/nodes/auth/mfa.py +2338 -0
  55. kailash/nodes/auth/risk_assessment.py +872 -0
  56. kailash/nodes/auth/session_management.py +1093 -0
  57. kailash/nodes/auth/sso.py +1040 -0
  58. kailash/nodes/base.py +344 -13
  59. kailash/nodes/base_cycle_aware.py +4 -2
  60. kailash/nodes/base_with_acl.py +1 -1
  61. kailash/nodes/code/python.py +293 -12
  62. kailash/nodes/compliance/__init__.py +9 -0
  63. kailash/nodes/compliance/data_retention.py +1888 -0
  64. kailash/nodes/compliance/gdpr.py +2004 -0
  65. kailash/nodes/data/__init__.py +22 -2
  66. kailash/nodes/data/async_connection.py +469 -0
  67. kailash/nodes/data/async_sql.py +757 -0
  68. kailash/nodes/data/async_vector.py +598 -0
  69. kailash/nodes/data/readers.py +767 -0
  70. kailash/nodes/data/retrieval.py +360 -1
  71. kailash/nodes/data/sharepoint_graph.py +397 -21
  72. kailash/nodes/data/sql.py +94 -5
  73. kailash/nodes/data/streaming.py +68 -8
  74. kailash/nodes/data/vector_db.py +54 -4
  75. kailash/nodes/enterprise/__init__.py +13 -0
  76. kailash/nodes/enterprise/batch_processor.py +741 -0
  77. kailash/nodes/enterprise/data_lineage.py +497 -0
  78. kailash/nodes/logic/convergence.py +31 -9
  79. kailash/nodes/logic/operations.py +14 -3
  80. kailash/nodes/mixins/__init__.py +8 -0
  81. kailash/nodes/mixins/event_emitter.py +201 -0
  82. kailash/nodes/mixins/mcp.py +9 -4
  83. kailash/nodes/mixins/security.py +165 -0
  84. kailash/nodes/monitoring/__init__.py +7 -0
  85. kailash/nodes/monitoring/performance_benchmark.py +2497 -0
  86. kailash/nodes/rag/__init__.py +284 -0
  87. kailash/nodes/rag/advanced.py +1615 -0
  88. kailash/nodes/rag/agentic.py +773 -0
  89. kailash/nodes/rag/conversational.py +999 -0
  90. kailash/nodes/rag/evaluation.py +875 -0
  91. kailash/nodes/rag/federated.py +1188 -0
  92. kailash/nodes/rag/graph.py +721 -0
  93. kailash/nodes/rag/multimodal.py +671 -0
  94. kailash/nodes/rag/optimized.py +933 -0
  95. kailash/nodes/rag/privacy.py +1059 -0
  96. kailash/nodes/rag/query_processing.py +1335 -0
  97. kailash/nodes/rag/realtime.py +764 -0
  98. kailash/nodes/rag/registry.py +547 -0
  99. kailash/nodes/rag/router.py +837 -0
  100. kailash/nodes/rag/similarity.py +1854 -0
  101. kailash/nodes/rag/strategies.py +566 -0
  102. kailash/nodes/rag/workflows.py +575 -0
  103. kailash/nodes/security/__init__.py +19 -0
  104. kailash/nodes/security/abac_evaluator.py +1411 -0
  105. kailash/nodes/security/audit_log.py +91 -0
  106. kailash/nodes/security/behavior_analysis.py +1893 -0
  107. kailash/nodes/security/credential_manager.py +401 -0
  108. kailash/nodes/security/rotating_credentials.py +760 -0
  109. kailash/nodes/security/security_event.py +132 -0
  110. kailash/nodes/security/threat_detection.py +1103 -0
  111. kailash/nodes/testing/__init__.py +9 -0
  112. kailash/nodes/testing/credential_testing.py +499 -0
  113. kailash/nodes/transform/__init__.py +10 -2
  114. kailash/nodes/transform/chunkers.py +592 -1
  115. kailash/nodes/transform/processors.py +484 -14
  116. kailash/nodes/validation.py +321 -0
  117. kailash/runtime/access_controlled.py +1 -1
  118. kailash/runtime/async_local.py +41 -7
  119. kailash/runtime/docker.py +1 -1
  120. kailash/runtime/local.py +474 -55
  121. kailash/runtime/parallel.py +1 -1
  122. kailash/runtime/parallel_cyclic.py +1 -1
  123. kailash/runtime/testing.py +210 -2
  124. kailash/utils/migrations/__init__.py +25 -0
  125. kailash/utils/migrations/generator.py +433 -0
  126. kailash/utils/migrations/models.py +231 -0
  127. kailash/utils/migrations/runner.py +489 -0
  128. kailash/utils/secure_logging.py +342 -0
  129. kailash/workflow/__init__.py +16 -0
  130. kailash/workflow/cyclic_runner.py +3 -4
  131. kailash/workflow/graph.py +70 -2
  132. kailash/workflow/resilience.py +249 -0
  133. kailash/workflow/templates.py +726 -0
  134. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/METADATA +253 -20
  135. kailash-0.4.0.dist-info/RECORD +223 -0
  136. kailash/api/__init__.py +0 -17
  137. kailash/api/__main__.py +0 -6
  138. kailash/api/studio_secure.py +0 -893
  139. kailash/mcp/__main__.py +0 -13
  140. kailash/mcp/server_new.py +0 -336
  141. kailash/mcp/servers/__init__.py +0 -12
  142. kailash-0.3.1.dist-info/RECORD +0 -136
  143. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
  144. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
  145. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
  146. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,802 @@
1
+ """
2
+ Enhanced API Gateway for Kailash Middleware
3
+
4
+ Provides a comprehensive API gateway that integrates agent-UI middleware,
5
+ real-time communication, and dynamic workflow management with full
6
+ frontend support capabilities.
7
+ """
8
+
9
+ import asyncio
10
+ import logging
11
+ import time
12
+ import uuid
13
+ from contextlib import asynccontextmanager
14
+ from datetime import datetime, timezone
15
+ from typing import Any, Dict, List, Optional, Union
16
+ from urllib.parse import parse_qs
17
+
18
+ from fastapi import (
19
+ Depends,
20
+ FastAPI,
21
+ HTTPException,
22
+ Request,
23
+ WebSocket,
24
+ WebSocketDisconnect,
25
+ )
26
+ from fastapi.middleware.cors import CORSMiddleware
27
+ from fastapi.responses import JSONResponse, StreamingResponse
28
+ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
29
+ from pydantic import BaseModel, Field
30
+
31
+ from ...nodes.base import NodeRegistry
32
+ from ...nodes.security import CredentialManagerNode
33
+ from ...nodes.transform import DataTransformer
34
+ from ...workflow import Workflow
35
+ from ...workflow.builder import WorkflowBuilder
36
+ from ..auth import KailashJWTAuthManager
37
+ from ..core.agent_ui import AgentUIMiddleware
38
+ from ..core.schema import DynamicSchemaRegistry
39
+ from .events import EventFilter, EventType
40
+ from .realtime import RealtimeMiddleware
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+ # Use SDK Auth Manager instead of manual security
45
+ auth_manager = KailashJWTAuthManager()
46
+
47
+
48
+ # Pydantic Models
49
+ class SessionCreateRequest(BaseModel):
50
+ """Request model for creating a new session."""
51
+
52
+ user_id: Optional[str] = None
53
+ metadata: Dict[str, Any] = Field(default_factory=dict)
54
+
55
+
56
+ class SessionResponse(BaseModel):
57
+ """Response model for session operations."""
58
+
59
+ session_id: str
60
+ user_id: Optional[str] = None
61
+ created_at: datetime
62
+ active: bool = True
63
+
64
+
65
+ class WorkflowCreateRequest(BaseModel):
66
+ """Request model for creating a workflow."""
67
+
68
+ name: str
69
+ description: Optional[str] = None
70
+ nodes: List[Dict[str, Any]] = Field(default_factory=list)
71
+ connections: List[Dict[str, Any]] = Field(default_factory=list)
72
+ metadata: Dict[str, Any] = Field(default_factory=dict)
73
+
74
+
75
+ class WorkflowExecuteRequest(BaseModel):
76
+ """Request model for executing a workflow."""
77
+
78
+ workflow_id: str
79
+ inputs: Dict[str, Any] = Field(default_factory=dict)
80
+ config_overrides: Dict[str, Any] = Field(default_factory=dict)
81
+
82
+
83
+ class ExecutionResponse(BaseModel):
84
+ """Response model for workflow execution."""
85
+
86
+ execution_id: str
87
+ workflow_id: str
88
+ status: str
89
+ created_at: datetime
90
+ progress: float = 0.0
91
+
92
+
93
+ class NodeSchemaRequest(BaseModel):
94
+ """Request model for getting node schemas."""
95
+
96
+ node_types: Optional[List[str]] = None
97
+ include_examples: bool = False
98
+
99
+
100
+ class WebhookRegisterRequest(BaseModel):
101
+ """Request model for registering webhooks."""
102
+
103
+ url: str
104
+ secret: Optional[str] = None
105
+ event_types: List[str] = Field(default_factory=list)
106
+ headers: Dict[str, str] = Field(default_factory=dict)
107
+
108
+
109
+ class APIGateway:
110
+ """
111
+ Enhanced API Gateway for Kailash Middleware.
112
+
113
+ Now uses SDK components for:
114
+ - Authentication and authorization with SDKAuthManager
115
+ - Data transformation with DataTransformer nodes
116
+ - Audit logging with AuditLogNode
117
+ - Security event tracking with SecurityEventNode
118
+
119
+ Provides:
120
+ - Session management for frontend clients
121
+ - Real-time workflow execution and monitoring
122
+ - Dynamic workflow creation and modification
123
+ - Node discovery and schema generation
124
+ - Multi-transport real-time communication (WebSocket, SSE, Webhooks)
125
+ - AI chat integration for workflow assistance
126
+ - Comprehensive monitoring and statistics
127
+ """
128
+
129
+ def __init__(
130
+ self,
131
+ title: str = "Kailash Middleware Gateway",
132
+ description: str = "Enhanced API gateway for agent-frontend communication",
133
+ version: str = "1.0.0",
134
+ cors_origins: List[str] = None,
135
+ enable_docs: bool = True,
136
+ max_sessions: int = 1000,
137
+ enable_auth: bool = True,
138
+ database_url: str = None,
139
+ ):
140
+ self.title = title
141
+ self.version = version
142
+ self.enable_docs = enable_docs
143
+ self.enable_auth = enable_auth
144
+
145
+ # Initialize SDK nodes for gateway operations
146
+ self._init_sdk_nodes(database_url)
147
+
148
+ # Initialize core middleware components
149
+ self.agent_ui = AgentUIMiddleware(max_sessions=max_sessions)
150
+ self.realtime = RealtimeMiddleware(self.agent_ui)
151
+ self.schema_registry = DynamicSchemaRegistry()
152
+ self.node_registry = NodeRegistry()
153
+
154
+ # Initialize auth manager if enabled
155
+ if enable_auth:
156
+ self.auth_manager = KailashJWTAuthManager(secret_key="api-gateway-secret")
157
+
158
+ # Create FastAPI app with lifespan management
159
+ @asynccontextmanager
160
+ async def lifespan(app: FastAPI):
161
+ # Startup
162
+ logger.info(f"Starting {title} v{version}")
163
+ await self._log_startup()
164
+ yield
165
+ # Shutdown
166
+ logger.info("Shutting down gateway")
167
+ await self._cleanup()
168
+
169
+ self.app = FastAPI(
170
+ title=title,
171
+ description=description,
172
+ version=version,
173
+ docs_url="/docs" if enable_docs else None,
174
+ redoc_url="/redoc" if enable_docs else None,
175
+ lifespan=lifespan,
176
+ )
177
+
178
+ # Add CORS middleware
179
+ self.app.add_middleware(
180
+ CORSMiddleware,
181
+ allow_origins=cors_origins or ["*"],
182
+ allow_credentials=True,
183
+ allow_methods=["*"],
184
+ allow_headers=["*"],
185
+ )
186
+
187
+ # Setup routes
188
+ self._setup_routes()
189
+
190
+ # Performance tracking
191
+ self.start_time = time.time()
192
+ self.requests_processed = 0
193
+
194
+ def _init_sdk_nodes(self, database_url: str = None):
195
+ """Initialize SDK nodes for gateway operations."""
196
+
197
+ # Data transformer for request/response formatting
198
+ self.data_transformer = DataTransformer(
199
+ name="gateway_transformer",
200
+ transformations=[
201
+ {"type": "validate", "schema": "api_response"},
202
+ {"type": "add_field", "field": "timestamp", "value": "now()"},
203
+ ],
204
+ )
205
+
206
+ # Credential manager for gateway security
207
+ self.credential_manager = CredentialManagerNode(
208
+ name="gateway_credentials",
209
+ credential_name="gateway_secrets",
210
+ credential_type="custom",
211
+ )
212
+
213
+ async def _log_startup(self):
214
+ """Log gateway startup."""
215
+ logger.info(
216
+ f"API Gateway started: {self.title} v{self.version}, Auth: {self.enable_auth}"
217
+ )
218
+
219
+ async def _cleanup(self):
220
+ """Cleanup resources on shutdown."""
221
+ try:
222
+ # Close all sessions
223
+ for session_id in list(self.agent_ui.sessions.keys()):
224
+ await self.agent_ui.close_session(session_id)
225
+ except Exception as e:
226
+ logger.error(f"Error during cleanup: {e}")
227
+
228
+ def _setup_routes(self):
229
+ """Setup all API routes."""
230
+ self._setup_core_routes()
231
+ self._setup_session_routes()
232
+ self._setup_workflow_routes()
233
+ self._setup_execution_routes()
234
+ self._setup_schema_routes()
235
+ self._setup_realtime_routes()
236
+ self._setup_monitoring_routes()
237
+
238
+ def _setup_core_routes(self):
239
+ """Setup core gateway routes."""
240
+
241
+ @self.app.get("/")
242
+ async def root():
243
+ """Gateway information and status."""
244
+ return {
245
+ "name": self.title,
246
+ "version": self.version,
247
+ "status": "healthy",
248
+ "uptime_seconds": time.time() - self.start_time,
249
+ "features": {
250
+ "sessions": True,
251
+ "real_time": True,
252
+ "dynamic_workflows": True,
253
+ "ai_chat": True,
254
+ "webhooks": True,
255
+ },
256
+ "endpoints": {
257
+ "sessions": "/api/sessions",
258
+ "workflows": "/api/workflows",
259
+ "schemas": "/api/schemas",
260
+ "websocket": "/ws",
261
+ "sse": "/events",
262
+ "docs": "/docs" if self.enable_docs else None,
263
+ },
264
+ }
265
+
266
+ @self.app.get("/health")
267
+ async def health_check():
268
+ """Detailed health check."""
269
+ try:
270
+ agent_ui_stats = self.agent_ui.get_stats()
271
+ realtime_stats = self.realtime.get_stats()
272
+ schema_stats = self.schema_registry.get_stats()
273
+
274
+ return {
275
+ "status": "healthy",
276
+ "timestamp": datetime.now(timezone.utc).isoformat(),
277
+ "uptime_seconds": time.time() - self.start_time,
278
+ "requests_processed": self.requests_processed,
279
+ "components": {
280
+ "agent_ui": {
281
+ "status": "healthy",
282
+ "active_sessions": agent_ui_stats["active_sessions"],
283
+ "workflows_executed": agent_ui_stats["workflows_executed"],
284
+ },
285
+ "realtime": {
286
+ "status": "healthy",
287
+ "events_processed": realtime_stats["events_processed"],
288
+ "websocket_connections": realtime_stats.get(
289
+ "websocket_stats", {}
290
+ ).get("total_connections", 0),
291
+ },
292
+ "schema_registry": {
293
+ "status": "healthy",
294
+ "schemas_generated": schema_stats["schemas_generated"],
295
+ "cache_hit_rate": schema_stats["cache_hit_rate"],
296
+ },
297
+ },
298
+ }
299
+ except Exception as e:
300
+ logger.error(f"Health check failed: {e}")
301
+ return JSONResponse(
302
+ status_code=503, content={"status": "unhealthy", "error": str(e)}
303
+ )
304
+
305
+ def _setup_session_routes(self):
306
+ """Setup session management routes."""
307
+
308
+ @self.app.post("/api/sessions", response_model=SessionResponse)
309
+ async def create_session(
310
+ request: SessionCreateRequest, current_user: Dict[str, Any] = None
311
+ ):
312
+ """Create a new session for a frontend client."""
313
+ try:
314
+ # Use authenticated user ID if available
315
+ user_id = request.user_id
316
+ if self.enable_auth and current_user:
317
+ user_id = current_user.get("user_id", user_id)
318
+
319
+ session_id = await self.agent_ui.create_session(
320
+ user_id=user_id, metadata=request.metadata
321
+ )
322
+
323
+ session = await self.agent_ui.get_session(session_id)
324
+ self.requests_processed += 1
325
+
326
+ # Log session creation
327
+ logger.info(f"Session created: {session_id} for user {user_id}")
328
+
329
+ # Transform response using SDK node
330
+ response_data = {
331
+ "session_id": session_id,
332
+ "user_id": session.user_id,
333
+ "created_at": session.created_at.isoformat(),
334
+ "active": session.active,
335
+ }
336
+
337
+ transformed = await self.data_transformer.process(
338
+ {
339
+ "data": response_data,
340
+ "transformations": [
341
+ {
342
+ "type": "add_field",
343
+ "field": "api_version",
344
+ "value": self.version,
345
+ }
346
+ ],
347
+ }
348
+ )
349
+
350
+ return SessionResponse(**transformed["result"])
351
+ except Exception as e:
352
+ logger.error(f"Error creating session: {e}")
353
+
354
+ # Log security event for failed session creation
355
+ logger.warning(
356
+ f"Session creation failed for user {request.user_id}: {str(e)}"
357
+ )
358
+
359
+ raise HTTPException(status_code=500, detail=str(e))
360
+
361
+ @self.app.get("/api/sessions/{session_id}")
362
+ async def get_session(session_id: str):
363
+ """Get session information."""
364
+ session = await self.agent_ui.get_session(session_id)
365
+ if not session:
366
+ raise HTTPException(status_code=404, detail="Session not found")
367
+
368
+ return {
369
+ "session_id": session_id,
370
+ "user_id": session.user_id,
371
+ "created_at": session.created_at.isoformat(),
372
+ "active": session.active,
373
+ "workflows": list(session.workflows.keys()),
374
+ "active_executions": len(
375
+ [
376
+ exec_id
377
+ for exec_id, exec_data in session.executions.items()
378
+ if exec_data["status"] in ["started", "running"]
379
+ ]
380
+ ),
381
+ }
382
+
383
+ @self.app.delete("/api/sessions/{session_id}")
384
+ async def close_session(session_id: str):
385
+ """Close a session."""
386
+ await self.agent_ui.close_session(session_id)
387
+ return {"message": "Session closed"}
388
+
389
+ @self.app.get("/api/sessions")
390
+ async def list_sessions():
391
+ """List all active sessions."""
392
+ sessions = []
393
+ for session_id, session in self.agent_ui.sessions.items():
394
+ if session.active:
395
+ sessions.append(
396
+ {
397
+ "session_id": session_id,
398
+ "user_id": session.user_id,
399
+ "created_at": session.created_at.isoformat(),
400
+ "workflow_count": len(session.workflows),
401
+ "execution_count": len(session.executions),
402
+ }
403
+ )
404
+ return {"sessions": sessions, "total": len(sessions)}
405
+
406
+ def _setup_workflow_routes(self):
407
+ """Setup workflow management routes."""
408
+
409
+ @self.app.post("/api/workflows")
410
+ async def create_workflow(request: WorkflowCreateRequest, session_id: str):
411
+ """Create a new workflow dynamically."""
412
+ try:
413
+ workflow_config = {
414
+ "name": request.name,
415
+ "description": request.description,
416
+ "nodes": request.nodes,
417
+ "connections": request.connections,
418
+ "metadata": request.metadata,
419
+ }
420
+
421
+ workflow_id = await self.agent_ui.create_dynamic_workflow(
422
+ session_id=session_id, workflow_config=workflow_config
423
+ )
424
+
425
+ return {
426
+ "workflow_id": workflow_id,
427
+ "name": request.name,
428
+ "session_id": session_id,
429
+ "created_at": datetime.now(timezone.utc).isoformat(),
430
+ }
431
+ except Exception as e:
432
+ logger.error(f"Error creating workflow: {e}")
433
+ raise HTTPException(status_code=500, detail=str(e))
434
+
435
+ @self.app.get("/api/workflows/{workflow_id}")
436
+ async def get_workflow(workflow_id: str, session_id: str):
437
+ """Get workflow information and schema."""
438
+ session = await self.agent_ui.get_session(session_id)
439
+ if not session:
440
+ raise HTTPException(status_code=404, detail="Session not found")
441
+
442
+ workflow = None
443
+ if workflow_id in session.workflows:
444
+ workflow = session.workflows[workflow_id]
445
+ elif workflow_id in self.agent_ui.shared_workflows:
446
+ workflow = self.agent_ui.shared_workflows[workflow_id]
447
+ else:
448
+ raise HTTPException(status_code=404, detail="Workflow not found")
449
+
450
+ # Generate schema
451
+ schema = self.schema_registry.get_workflow_schema(workflow)
452
+
453
+ return {
454
+ "workflow_id": workflow_id,
455
+ "schema": schema,
456
+ "is_shared": workflow_id in self.agent_ui.shared_workflows,
457
+ }
458
+
459
+ @self.app.get("/api/workflows")
460
+ async def list_workflows(session_id: Optional[str] = None):
461
+ """List available workflows."""
462
+ workflows = []
463
+
464
+ # Add shared workflows
465
+ for workflow_id, workflow in self.agent_ui.shared_workflows.items():
466
+ workflows.append(
467
+ {
468
+ "workflow_id": workflow_id,
469
+ "name": workflow.name,
470
+ "description": workflow.description,
471
+ "is_shared": True,
472
+ "node_count": len(workflow.nodes),
473
+ }
474
+ )
475
+
476
+ # Add session workflows if session_id provided
477
+ if session_id:
478
+ session = await self.agent_ui.get_session(session_id)
479
+ if session:
480
+ for workflow_id, workflow in session.workflows.items():
481
+ workflows.append(
482
+ {
483
+ "workflow_id": workflow_id,
484
+ "name": workflow.name,
485
+ "description": workflow.description,
486
+ "is_shared": False,
487
+ "node_count": len(workflow.nodes),
488
+ }
489
+ )
490
+
491
+ return {"workflows": workflows, "total": len(workflows)}
492
+
493
+ def _setup_execution_routes(self):
494
+ """Setup workflow execution routes."""
495
+
496
+ @self.app.post("/api/executions", response_model=ExecutionResponse)
497
+ async def execute_workflow(request: WorkflowExecuteRequest, session_id: str):
498
+ """Execute a workflow."""
499
+ try:
500
+ execution_id = await self.agent_ui.execute_workflow(
501
+ session_id=session_id,
502
+ workflow_id=request.workflow_id,
503
+ inputs=request.inputs,
504
+ config_overrides=request.config_overrides,
505
+ )
506
+
507
+ return ExecutionResponse(
508
+ execution_id=execution_id,
509
+ workflow_id=request.workflow_id,
510
+ status="started",
511
+ created_at=datetime.now(timezone.utc),
512
+ )
513
+ except Exception as e:
514
+ logger.error(f"Error executing workflow: {e}")
515
+ raise HTTPException(status_code=500, detail=str(e))
516
+
517
+ @self.app.get("/api/executions/{execution_id}")
518
+ async def get_execution_status(execution_id: str, session_id: str):
519
+ """Get execution status."""
520
+ status = await self.agent_ui.get_execution_status(execution_id, session_id)
521
+ if not status:
522
+ raise HTTPException(status_code=404, detail="Execution not found")
523
+
524
+ return {
525
+ "execution_id": execution_id,
526
+ "status": status["status"],
527
+ "progress": status.get("progress", 0.0),
528
+ "created_at": status["created_at"].isoformat(),
529
+ "outputs": status.get("outputs", {}),
530
+ "error": status.get("error"),
531
+ }
532
+
533
+ @self.app.delete("/api/executions/{execution_id}")
534
+ async def cancel_execution(execution_id: str, session_id: str):
535
+ """Cancel a running execution."""
536
+ await self.agent_ui.cancel_execution(execution_id, session_id)
537
+ return {"message": "Execution cancelled"}
538
+
539
+ @self.app.get("/api/executions")
540
+ async def list_executions(session_id: str):
541
+ """List executions for a session."""
542
+ session = await self.agent_ui.get_session(session_id)
543
+ if not session:
544
+ raise HTTPException(status_code=404, detail="Session not found")
545
+
546
+ executions = []
547
+ for execution_id, execution in session.executions.items():
548
+ executions.append(
549
+ {
550
+ "execution_id": execution_id,
551
+ "workflow_id": execution["workflow_id"],
552
+ "status": execution["status"],
553
+ "progress": execution.get("progress", 0.0),
554
+ "created_at": execution["created_at"].isoformat(),
555
+ }
556
+ )
557
+
558
+ return {"executions": executions, "total": len(executions)}
559
+
560
+ def _setup_schema_routes(self):
561
+ """Setup schema and node discovery routes."""
562
+
563
+ @self.app.get("/api/schemas/nodes")
564
+ async def get_node_schemas(request: NodeSchemaRequest = Depends()):
565
+ """Get schemas for available node types."""
566
+ try:
567
+ # Get all registered nodes
568
+ available_nodes = self.node_registry.get_all_nodes()
569
+
570
+ # Filter by requested types if specified
571
+ if request.node_types:
572
+ available_nodes = {
573
+ name: node_class
574
+ for name, node_class in available_nodes.items()
575
+ if name in request.node_types
576
+ }
577
+
578
+ # Generate schemas
579
+ schemas = {}
580
+ for node_name, node_class in available_nodes.items():
581
+ schema = self.schema_registry.get_node_schema(node_class)
582
+ schemas[node_name] = schema
583
+
584
+ return {
585
+ "schemas": schemas,
586
+ "total": len(schemas),
587
+ "categories": self._get_node_categories(schemas),
588
+ }
589
+ except Exception as e:
590
+ logger.error(f"Error getting node schemas: {e}")
591
+ raise HTTPException(status_code=500, detail=str(e))
592
+
593
+ @self.app.get("/api/schemas/nodes/{node_type}")
594
+ async def get_node_schema(node_type: str):
595
+ """Get schema for a specific node type."""
596
+ node_class = self.node_registry.get_node(node_type)
597
+ if not node_class:
598
+ raise HTTPException(status_code=404, detail="Node type not found")
599
+
600
+ schema = self.schema_registry.get_node_schema(node_class)
601
+ return {"node_type": node_type, "schema": schema}
602
+
603
+ @self.app.get("/api/schemas/workflows/{workflow_id}")
604
+ async def get_workflow_schema(workflow_id: str, session_id: str):
605
+ """Get schema for a specific workflow."""
606
+ session = await self.agent_ui.get_session(session_id)
607
+ if not session:
608
+ raise HTTPException(status_code=404, detail="Session not found")
609
+
610
+ workflow = None
611
+ if workflow_id in session.workflows:
612
+ workflow = session.workflows[workflow_id]
613
+ elif workflow_id in self.agent_ui.shared_workflows:
614
+ workflow = self.agent_ui.shared_workflows[workflow_id]
615
+ else:
616
+ raise HTTPException(status_code=404, detail="Workflow not found")
617
+
618
+ schema = self.schema_registry.get_workflow_schema(workflow)
619
+ return {"workflow_id": workflow_id, "schema": schema}
620
+
621
+ def _get_node_categories(self, schemas: Dict[str, Any]) -> Dict[str, List[str]]:
622
+ """Group nodes by category."""
623
+ categories = {}
624
+ for node_name, schema in schemas.items():
625
+ category = schema.get("category", "general")
626
+ if category not in categories:
627
+ categories[category] = []
628
+ categories[category].append(node_name)
629
+ return categories
630
+
631
+ def _setup_realtime_routes(self):
632
+ """Setup real-time communication routes."""
633
+
634
+ @self.app.websocket("/ws")
635
+ async def websocket_endpoint(
636
+ websocket: WebSocket,
637
+ session_id: Optional[str] = None,
638
+ user_id: Optional[str] = None,
639
+ event_types: Optional[str] = None,
640
+ ):
641
+ """WebSocket endpoint for real-time communication."""
642
+ # Parse event types from query parameter
643
+ event_type_list = event_types.split(",") if event_types else None
644
+
645
+ await self.realtime.handle_websocket(
646
+ websocket, session_id, user_id, event_type_list
647
+ )
648
+
649
+ @self.app.get("/events")
650
+ async def sse_endpoint(
651
+ request: Request,
652
+ session_id: Optional[str] = None,
653
+ user_id: Optional[str] = None,
654
+ event_types: Optional[str] = None,
655
+ ):
656
+ """Server-Sent Events endpoint."""
657
+ event_type_list = event_types.split(",") if event_types else None
658
+
659
+ return self.realtime.create_sse_stream(
660
+ request, session_id, user_id, event_type_list
661
+ )
662
+
663
+ @self.app.post("/api/webhooks")
664
+ async def register_webhook(request: WebhookRegisterRequest):
665
+ """Register a webhook endpoint."""
666
+ webhook_id = str(uuid.uuid4())
667
+
668
+ self.realtime.register_webhook(
669
+ webhook_id=webhook_id,
670
+ url=request.url,
671
+ secret=request.secret,
672
+ event_types=request.event_types,
673
+ headers=request.headers,
674
+ )
675
+
676
+ return {
677
+ "webhook_id": webhook_id,
678
+ "url": request.url,
679
+ "event_types": request.event_types,
680
+ }
681
+
682
+ @self.app.delete("/api/webhooks/{webhook_id}")
683
+ async def unregister_webhook(webhook_id: str):
684
+ """Unregister a webhook endpoint."""
685
+ self.realtime.unregister_webhook(webhook_id)
686
+ return {"message": "Webhook unregistered"}
687
+
688
+ def _setup_monitoring_routes(self):
689
+ """Setup monitoring and statistics routes."""
690
+
691
+ @self.app.get("/api/stats")
692
+ async def get_stats():
693
+ """Get comprehensive system statistics."""
694
+ try:
695
+ return {
696
+ "gateway": {
697
+ "uptime_seconds": time.time() - self.start_time,
698
+ "requests_processed": self.requests_processed,
699
+ "title": self.title,
700
+ "version": self.version,
701
+ },
702
+ "agent_ui": self.agent_ui.get_stats(),
703
+ "realtime": self.realtime.get_stats(),
704
+ "schema_registry": self.schema_registry.get_stats(),
705
+ }
706
+ except Exception as e:
707
+ logger.error(f"Error getting stats: {e}")
708
+ raise HTTPException(status_code=500, detail=str(e))
709
+
710
+ @self.app.get("/api/events/recent")
711
+ async def get_recent_events(
712
+ count: int = 100,
713
+ event_types: Optional[str] = None,
714
+ session_id: Optional[str] = None,
715
+ ):
716
+ """Get recent events with filtering."""
717
+ try:
718
+ # Parse event types
719
+ event_type_list = None
720
+ if event_types:
721
+ event_type_list = [
722
+ EventType(t.strip()) for t in event_types.split(",")
723
+ ]
724
+
725
+ # Create filter
726
+ event_filter = EventFilter(
727
+ event_types=event_type_list, session_id=session_id
728
+ )
729
+
730
+ # Get events
731
+ events = await self.agent_ui.event_stream.get_recent_events(
732
+ count=count, event_filter=event_filter
733
+ )
734
+
735
+ return {
736
+ "events": [event.to_dict() for event in events],
737
+ "total": len(events),
738
+ "filters": {
739
+ "event_types": event_types,
740
+ "session_id": session_id,
741
+ "count": count,
742
+ },
743
+ }
744
+ except Exception as e:
745
+ logger.error(f"Error getting recent events: {e}")
746
+ raise HTTPException(status_code=500, detail=str(e))
747
+
748
+ # Public API methods
749
+ def run(
750
+ self, host: str = "0.0.0.0", port: int = 8000, reload: bool = False, **kwargs
751
+ ):
752
+ """Run the API gateway server."""
753
+ import uvicorn
754
+
755
+ logger.info(f"Starting {self.title} on {host}:{port}")
756
+ uvicorn.run(self.app, host=host, port=port, reload=reload, **kwargs)
757
+
758
+ def mount_existing_app(self, path: str, app: FastAPI):
759
+ """Mount an existing FastAPI app at a specific path."""
760
+ self.app.mount(path, app)
761
+ logger.info(f"Mounted existing app at {path}")
762
+
763
+ def register_shared_workflow(
764
+ self, workflow_id: str, workflow: Union[Workflow, WorkflowBuilder]
765
+ ):
766
+ """Register a workflow as shared across all sessions."""
767
+ asyncio.create_task(
768
+ self.agent_ui.register_workflow(
769
+ workflow_id=workflow_id, workflow=workflow, make_shared=True
770
+ )
771
+ )
772
+ logger.info(f"Registered shared workflow: {workflow_id}")
773
+
774
+
775
+ # Convenience function for quick setup
776
+ def create_gateway(
777
+ agent_ui_middleware: AgentUIMiddleware = None, **kwargs
778
+ ) -> APIGateway:
779
+ """
780
+ Create a configured API gateway instance.
781
+
782
+ Args:
783
+ agent_ui_middleware: Optional existing AgentUIMiddleware instance
784
+ **kwargs: Additional arguments for APIGateway initialization
785
+
786
+ Returns:
787
+ Configured APIGateway instance
788
+
789
+ Example:
790
+ >>> gateway = create_gateway(
791
+ ... title="My App Gateway",
792
+ ... cors_origins=["http://localhost:3000"]
793
+ ... )
794
+ >>> gateway.run(port=8000)
795
+ """
796
+ gateway = APIGateway(**kwargs)
797
+
798
+ if agent_ui_middleware:
799
+ gateway.agent_ui = agent_ui_middleware
800
+ gateway.realtime = RealtimeMiddleware(agent_ui_middleware)
801
+
802
+ return gateway