kailash 0.3.2__py3-none-any.whl → 0.4.1__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.
- kailash/__init__.py +33 -1
- kailash/access_control/__init__.py +129 -0
- kailash/access_control/managers.py +461 -0
- kailash/access_control/rule_evaluators.py +467 -0
- kailash/access_control_abac.py +825 -0
- kailash/config/__init__.py +27 -0
- kailash/config/database_config.py +359 -0
- kailash/database/__init__.py +28 -0
- kailash/database/execution_pipeline.py +499 -0
- kailash/middleware/__init__.py +306 -0
- kailash/middleware/auth/__init__.py +33 -0
- kailash/middleware/auth/access_control.py +436 -0
- kailash/middleware/auth/auth_manager.py +422 -0
- kailash/middleware/auth/jwt_auth.py +477 -0
- kailash/middleware/auth/kailash_jwt_auth.py +616 -0
- kailash/middleware/communication/__init__.py +37 -0
- kailash/middleware/communication/ai_chat.py +989 -0
- kailash/middleware/communication/api_gateway.py +802 -0
- kailash/middleware/communication/events.py +470 -0
- kailash/middleware/communication/realtime.py +710 -0
- kailash/middleware/core/__init__.py +21 -0
- kailash/middleware/core/agent_ui.py +890 -0
- kailash/middleware/core/schema.py +643 -0
- kailash/middleware/core/workflows.py +396 -0
- kailash/middleware/database/__init__.py +63 -0
- kailash/middleware/database/base.py +113 -0
- kailash/middleware/database/base_models.py +525 -0
- kailash/middleware/database/enums.py +106 -0
- kailash/middleware/database/migrations.py +12 -0
- kailash/{api/database.py → middleware/database/models.py} +183 -291
- kailash/middleware/database/repositories.py +685 -0
- kailash/middleware/database/session_manager.py +19 -0
- kailash/middleware/mcp/__init__.py +38 -0
- kailash/middleware/mcp/client_integration.py +585 -0
- kailash/middleware/mcp/enhanced_server.py +576 -0
- kailash/nodes/__init__.py +27 -3
- kailash/nodes/admin/__init__.py +42 -0
- kailash/nodes/admin/audit_log.py +794 -0
- kailash/nodes/admin/permission_check.py +864 -0
- kailash/nodes/admin/role_management.py +823 -0
- kailash/nodes/admin/security_event.py +1523 -0
- kailash/nodes/admin/user_management.py +944 -0
- kailash/nodes/ai/a2a.py +24 -7
- kailash/nodes/ai/ai_providers.py +248 -40
- kailash/nodes/ai/embedding_generator.py +11 -11
- kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
- kailash/nodes/ai/llm_agent.py +436 -5
- kailash/nodes/ai/self_organizing.py +85 -10
- kailash/nodes/ai/vision_utils.py +148 -0
- kailash/nodes/alerts/__init__.py +26 -0
- kailash/nodes/alerts/base.py +234 -0
- kailash/nodes/alerts/discord.py +499 -0
- kailash/nodes/api/auth.py +287 -6
- kailash/nodes/api/rest.py +151 -0
- kailash/nodes/auth/__init__.py +17 -0
- kailash/nodes/auth/directory_integration.py +1228 -0
- kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
- kailash/nodes/auth/mfa.py +2338 -0
- kailash/nodes/auth/risk_assessment.py +872 -0
- kailash/nodes/auth/session_management.py +1093 -0
- kailash/nodes/auth/sso.py +1040 -0
- kailash/nodes/base.py +344 -13
- kailash/nodes/base_cycle_aware.py +4 -2
- kailash/nodes/base_with_acl.py +1 -1
- kailash/nodes/code/python.py +283 -10
- kailash/nodes/compliance/__init__.py +9 -0
- kailash/nodes/compliance/data_retention.py +1888 -0
- kailash/nodes/compliance/gdpr.py +2004 -0
- kailash/nodes/data/__init__.py +22 -2
- kailash/nodes/data/async_connection.py +469 -0
- kailash/nodes/data/async_sql.py +757 -0
- kailash/nodes/data/async_vector.py +598 -0
- kailash/nodes/data/readers.py +767 -0
- kailash/nodes/data/retrieval.py +360 -1
- kailash/nodes/data/sharepoint_graph.py +397 -21
- kailash/nodes/data/sql.py +94 -5
- kailash/nodes/data/streaming.py +68 -8
- kailash/nodes/data/vector_db.py +54 -4
- kailash/nodes/enterprise/__init__.py +13 -0
- kailash/nodes/enterprise/batch_processor.py +741 -0
- kailash/nodes/enterprise/data_lineage.py +497 -0
- kailash/nodes/logic/convergence.py +31 -9
- kailash/nodes/logic/operations.py +14 -3
- kailash/nodes/mixins/__init__.py +8 -0
- kailash/nodes/mixins/event_emitter.py +201 -0
- kailash/nodes/mixins/mcp.py +9 -4
- kailash/nodes/mixins/security.py +165 -0
- kailash/nodes/monitoring/__init__.py +7 -0
- kailash/nodes/monitoring/performance_benchmark.py +2497 -0
- kailash/nodes/rag/__init__.py +284 -0
- kailash/nodes/rag/advanced.py +1615 -0
- kailash/nodes/rag/agentic.py +773 -0
- kailash/nodes/rag/conversational.py +999 -0
- kailash/nodes/rag/evaluation.py +875 -0
- kailash/nodes/rag/federated.py +1188 -0
- kailash/nodes/rag/graph.py +721 -0
- kailash/nodes/rag/multimodal.py +671 -0
- kailash/nodes/rag/optimized.py +933 -0
- kailash/nodes/rag/privacy.py +1059 -0
- kailash/nodes/rag/query_processing.py +1335 -0
- kailash/nodes/rag/realtime.py +764 -0
- kailash/nodes/rag/registry.py +547 -0
- kailash/nodes/rag/router.py +837 -0
- kailash/nodes/rag/similarity.py +1854 -0
- kailash/nodes/rag/strategies.py +566 -0
- kailash/nodes/rag/workflows.py +575 -0
- kailash/nodes/security/__init__.py +19 -0
- kailash/nodes/security/abac_evaluator.py +1411 -0
- kailash/nodes/security/audit_log.py +103 -0
- kailash/nodes/security/behavior_analysis.py +1893 -0
- kailash/nodes/security/credential_manager.py +401 -0
- kailash/nodes/security/rotating_credentials.py +760 -0
- kailash/nodes/security/security_event.py +133 -0
- kailash/nodes/security/threat_detection.py +1103 -0
- kailash/nodes/testing/__init__.py +9 -0
- kailash/nodes/testing/credential_testing.py +499 -0
- kailash/nodes/transform/__init__.py +10 -2
- kailash/nodes/transform/chunkers.py +592 -1
- kailash/nodes/transform/processors.py +484 -14
- kailash/nodes/validation.py +321 -0
- kailash/runtime/access_controlled.py +1 -1
- kailash/runtime/async_local.py +41 -7
- kailash/runtime/docker.py +1 -1
- kailash/runtime/local.py +474 -55
- kailash/runtime/parallel.py +1 -1
- kailash/runtime/parallel_cyclic.py +1 -1
- kailash/runtime/testing.py +210 -2
- kailash/security.py +1 -1
- kailash/utils/migrations/__init__.py +25 -0
- kailash/utils/migrations/generator.py +433 -0
- kailash/utils/migrations/models.py +231 -0
- kailash/utils/migrations/runner.py +489 -0
- kailash/utils/secure_logging.py +342 -0
- kailash/workflow/__init__.py +16 -0
- kailash/workflow/cyclic_runner.py +3 -4
- kailash/workflow/graph.py +70 -2
- kailash/workflow/resilience.py +249 -0
- kailash/workflow/templates.py +726 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/METADATA +256 -20
- kailash-0.4.1.dist-info/RECORD +227 -0
- kailash/api/__init__.py +0 -17
- kailash/api/__main__.py +0 -6
- kailash/api/studio_secure.py +0 -893
- kailash/mcp/__main__.py +0 -13
- kailash/mcp/server_new.py +0 -336
- kailash/mcp/servers/__init__.py +0 -12
- kailash-0.3.2.dist-info/RECORD +0 -136
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/WHEEL +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.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
|