kailash 0.8.4__py3-none-any.whl → 0.8.6__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 +5 -11
- kailash/channels/__init__.py +2 -1
- kailash/channels/mcp_channel.py +23 -4
- kailash/cli/__init__.py +11 -1
- kailash/cli/validate_imports.py +202 -0
- kailash/cli/validation_audit.py +570 -0
- kailash/core/actors/supervisor.py +1 -1
- kailash/core/resilience/bulkhead.py +15 -5
- kailash/core/resilience/circuit_breaker.py +74 -1
- kailash/core/resilience/health_monitor.py +433 -33
- kailash/edge/compliance.py +33 -0
- kailash/edge/consistency.py +609 -0
- kailash/edge/coordination/__init__.py +30 -0
- kailash/edge/coordination/global_ordering.py +355 -0
- kailash/edge/coordination/leader_election.py +217 -0
- kailash/edge/coordination/partition_detector.py +296 -0
- kailash/edge/coordination/raft.py +485 -0
- kailash/edge/discovery.py +63 -1
- kailash/edge/migration/__init__.py +19 -0
- kailash/edge/migration/edge_migration_service.py +384 -0
- kailash/edge/migration/edge_migrator.py +832 -0
- kailash/edge/monitoring/__init__.py +21 -0
- kailash/edge/monitoring/edge_monitor.py +736 -0
- kailash/edge/prediction/__init__.py +10 -0
- kailash/edge/prediction/predictive_warmer.py +591 -0
- kailash/edge/resource/__init__.py +102 -0
- kailash/edge/resource/cloud_integration.py +796 -0
- kailash/edge/resource/cost_optimizer.py +949 -0
- kailash/edge/resource/docker_integration.py +919 -0
- kailash/edge/resource/kubernetes_integration.py +893 -0
- kailash/edge/resource/platform_integration.py +913 -0
- kailash/edge/resource/predictive_scaler.py +959 -0
- kailash/edge/resource/resource_analyzer.py +824 -0
- kailash/edge/resource/resource_pools.py +610 -0
- kailash/integrations/dataflow_edge.py +261 -0
- kailash/mcp_server/registry_integration.py +1 -1
- kailash/mcp_server/server.py +351 -8
- kailash/mcp_server/transports.py +305 -0
- kailash/middleware/gateway/event_store.py +1 -0
- kailash/monitoring/__init__.py +18 -0
- kailash/monitoring/alerts.py +646 -0
- kailash/monitoring/metrics.py +677 -0
- kailash/nodes/__init__.py +2 -0
- kailash/nodes/ai/semantic_memory.py +2 -2
- kailash/nodes/base.py +622 -1
- kailash/nodes/code/python.py +44 -3
- kailash/nodes/data/async_sql.py +42 -20
- kailash/nodes/edge/__init__.py +36 -0
- kailash/nodes/edge/base.py +240 -0
- kailash/nodes/edge/cloud_node.py +710 -0
- kailash/nodes/edge/coordination.py +239 -0
- kailash/nodes/edge/docker_node.py +825 -0
- kailash/nodes/edge/edge_data.py +582 -0
- kailash/nodes/edge/edge_migration_node.py +396 -0
- kailash/nodes/edge/edge_monitoring_node.py +421 -0
- kailash/nodes/edge/edge_state.py +673 -0
- kailash/nodes/edge/edge_warming_node.py +393 -0
- kailash/nodes/edge/kubernetes_node.py +652 -0
- kailash/nodes/edge/platform_node.py +766 -0
- kailash/nodes/edge/resource_analyzer_node.py +378 -0
- kailash/nodes/edge/resource_optimizer_node.py +501 -0
- kailash/nodes/edge/resource_scaler_node.py +397 -0
- kailash/nodes/governance.py +410 -0
- kailash/nodes/ports.py +676 -0
- kailash/nodes/rag/registry.py +1 -1
- kailash/nodes/transaction/distributed_transaction_manager.py +48 -1
- kailash/nodes/transaction/saga_state_storage.py +2 -1
- kailash/nodes/validation.py +8 -8
- kailash/runtime/local.py +374 -1
- kailash/runtime/validation/__init__.py +12 -0
- kailash/runtime/validation/connection_context.py +119 -0
- kailash/runtime/validation/enhanced_error_formatter.py +202 -0
- kailash/runtime/validation/error_categorizer.py +164 -0
- kailash/runtime/validation/import_validator.py +446 -0
- kailash/runtime/validation/metrics.py +380 -0
- kailash/runtime/validation/performance.py +615 -0
- kailash/runtime/validation/suggestion_engine.py +212 -0
- kailash/testing/fixtures.py +2 -2
- kailash/utils/data_paths.py +74 -0
- kailash/workflow/builder.py +413 -8
- kailash/workflow/contracts.py +418 -0
- kailash/workflow/edge_infrastructure.py +369 -0
- kailash/workflow/mermaid_visualizer.py +3 -1
- kailash/workflow/migration.py +3 -3
- kailash/workflow/templates.py +6 -6
- kailash/workflow/type_inference.py +669 -0
- kailash/workflow/validation.py +134 -3
- {kailash-0.8.4.dist-info → kailash-0.8.6.dist-info}/METADATA +52 -34
- {kailash-0.8.4.dist-info → kailash-0.8.6.dist-info}/RECORD +93 -42
- kailash/nexus/__init__.py +0 -21
- kailash/nexus/cli/__init__.py +0 -5
- kailash/nexus/cli/__main__.py +0 -6
- kailash/nexus/cli/main.py +0 -176
- kailash/nexus/factory.py +0 -413
- kailash/nexus/gateway.py +0 -545
- {kailash-0.8.4.dist-info → kailash-0.8.6.dist-info}/WHEEL +0 -0
- {kailash-0.8.4.dist-info → kailash-0.8.6.dist-info}/entry_points.txt +0 -0
- {kailash-0.8.4.dist-info → kailash-0.8.6.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.8.4.dist-info → kailash-0.8.6.dist-info}/top_level.txt +0 -0
kailash/mcp_server/transports.py
CHANGED
@@ -1084,6 +1084,310 @@ class WebSocketTransport(BaseTransport):
|
|
1084
1084
|
self.websocket = None
|
1085
1085
|
|
1086
1086
|
|
1087
|
+
class WebSocketServerTransport(BaseTransport):
|
1088
|
+
"""WebSocket server transport for accepting MCP connections."""
|
1089
|
+
|
1090
|
+
def __init__(
|
1091
|
+
self,
|
1092
|
+
host: str = "0.0.0.0",
|
1093
|
+
port: int = 3001,
|
1094
|
+
message_handler: Optional[
|
1095
|
+
Callable[[Dict[str, Any], str], Dict[str, Any]]
|
1096
|
+
] = None,
|
1097
|
+
ping_interval: float = 20.0,
|
1098
|
+
ping_timeout: float = 20.0,
|
1099
|
+
max_message_size: int = 10 * 1024 * 1024, # 10MB
|
1100
|
+
**kwargs,
|
1101
|
+
):
|
1102
|
+
"""Initialize WebSocket server transport.
|
1103
|
+
|
1104
|
+
Args:
|
1105
|
+
host: Host to bind to
|
1106
|
+
port: Port to listen on
|
1107
|
+
message_handler: Handler for incoming messages
|
1108
|
+
ping_interval: Ping interval in seconds
|
1109
|
+
ping_timeout: Ping timeout in seconds
|
1110
|
+
max_message_size: Maximum message size in bytes
|
1111
|
+
**kwargs: Base transport arguments
|
1112
|
+
"""
|
1113
|
+
super().__init__("websocket_server", **kwargs)
|
1114
|
+
|
1115
|
+
self.host = host
|
1116
|
+
self.port = port
|
1117
|
+
self.message_handler = message_handler
|
1118
|
+
self.ping_interval = ping_interval
|
1119
|
+
self.ping_timeout = ping_timeout
|
1120
|
+
self.max_message_size = max_message_size
|
1121
|
+
|
1122
|
+
# Server state
|
1123
|
+
self.server: Optional[websockets.WebSocketServer] = None
|
1124
|
+
self._clients: Dict[str, Any] = {} # websockets.WebSocketServerProtocol
|
1125
|
+
self._client_sessions: Dict[str, Dict[str, Any]] = {}
|
1126
|
+
self._server_task: Optional[asyncio.Task] = None
|
1127
|
+
|
1128
|
+
async def connect(self) -> None:
|
1129
|
+
"""Start the WebSocket server."""
|
1130
|
+
if self._connected:
|
1131
|
+
return
|
1132
|
+
|
1133
|
+
try:
|
1134
|
+
# Create handler that works with new websockets API
|
1135
|
+
async def connection_handler(websocket):
|
1136
|
+
# Get path from the websocket's request path
|
1137
|
+
path = websocket.path if hasattr(websocket, "path") else "/"
|
1138
|
+
await self.handle_client(websocket, path)
|
1139
|
+
|
1140
|
+
# Start WebSocket server
|
1141
|
+
self.server = await websockets.serve(
|
1142
|
+
connection_handler,
|
1143
|
+
self.host,
|
1144
|
+
self.port,
|
1145
|
+
ping_interval=self.ping_interval,
|
1146
|
+
ping_timeout=self.ping_timeout,
|
1147
|
+
max_size=self.max_message_size,
|
1148
|
+
)
|
1149
|
+
|
1150
|
+
self._connected = True
|
1151
|
+
self._update_metrics("connections_total")
|
1152
|
+
|
1153
|
+
logger.info(f"WebSocket server listening on {self.host}:{self.port}")
|
1154
|
+
|
1155
|
+
except Exception as e:
|
1156
|
+
self._update_metrics("connections_failed")
|
1157
|
+
raise TransportError(
|
1158
|
+
f"Failed to start WebSocket server: {e}",
|
1159
|
+
transport_type="websocket_server",
|
1160
|
+
)
|
1161
|
+
|
1162
|
+
async def disconnect(self) -> None:
|
1163
|
+
"""Stop the WebSocket server."""
|
1164
|
+
if not self._connected:
|
1165
|
+
return
|
1166
|
+
|
1167
|
+
self._connected = False
|
1168
|
+
|
1169
|
+
# Close all client connections
|
1170
|
+
clients = list(self._clients.values())
|
1171
|
+
for client in clients:
|
1172
|
+
await client.close()
|
1173
|
+
|
1174
|
+
# Stop server
|
1175
|
+
if self.server:
|
1176
|
+
self.server.close()
|
1177
|
+
await self.server.wait_closed()
|
1178
|
+
self.server = None
|
1179
|
+
|
1180
|
+
# Clear client tracking
|
1181
|
+
self._clients.clear()
|
1182
|
+
self._client_sessions.clear()
|
1183
|
+
|
1184
|
+
logger.info("WebSocket server stopped")
|
1185
|
+
|
1186
|
+
async def send_message(
|
1187
|
+
self, message: Dict[str, Any], client_id: Optional[str] = None
|
1188
|
+
) -> None:
|
1189
|
+
"""Send message to specific client or broadcast to all.
|
1190
|
+
|
1191
|
+
Args:
|
1192
|
+
message: Message to send
|
1193
|
+
client_id: Target client ID (None for broadcast)
|
1194
|
+
"""
|
1195
|
+
if not self._connected:
|
1196
|
+
raise TransportError(
|
1197
|
+
"Transport not connected", transport_type="websocket_server"
|
1198
|
+
)
|
1199
|
+
|
1200
|
+
message_data = json.dumps(message)
|
1201
|
+
|
1202
|
+
try:
|
1203
|
+
if client_id:
|
1204
|
+
# Send to specific client
|
1205
|
+
if client_id in self._clients:
|
1206
|
+
await self._clients[client_id].send(message_data)
|
1207
|
+
self._update_metrics("messages_sent")
|
1208
|
+
self._update_metrics("bytes_sent", len(message_data))
|
1209
|
+
else:
|
1210
|
+
raise TransportError(
|
1211
|
+
f"Client {client_id} not found",
|
1212
|
+
transport_type="websocket_server",
|
1213
|
+
)
|
1214
|
+
else:
|
1215
|
+
# Broadcast to all clients
|
1216
|
+
if self._clients:
|
1217
|
+
await asyncio.gather(
|
1218
|
+
*[
|
1219
|
+
client.send(message_data)
|
1220
|
+
for client in self._clients.values()
|
1221
|
+
],
|
1222
|
+
return_exceptions=True,
|
1223
|
+
)
|
1224
|
+
self._update_metrics("messages_sent", len(self._clients))
|
1225
|
+
self._update_metrics(
|
1226
|
+
"bytes_sent", len(message_data) * len(self._clients)
|
1227
|
+
)
|
1228
|
+
|
1229
|
+
except Exception as e:
|
1230
|
+
self._update_metrics("errors_total")
|
1231
|
+
raise TransportError(
|
1232
|
+
f"Failed to send message: {e}", transport_type="websocket_server"
|
1233
|
+
)
|
1234
|
+
|
1235
|
+
async def receive_message(self) -> Dict[str, Any]:
|
1236
|
+
"""Not implemented for server transport."""
|
1237
|
+
raise NotImplementedError(
|
1238
|
+
"Server transport doesn't support receive_message. "
|
1239
|
+
"Messages are handled via handle_client callback."
|
1240
|
+
)
|
1241
|
+
|
1242
|
+
async def handle_client(self, websocket, path: str):
|
1243
|
+
"""Handle a client connection.
|
1244
|
+
|
1245
|
+
Args:
|
1246
|
+
websocket: WebSocket connection
|
1247
|
+
path: Request path
|
1248
|
+
"""
|
1249
|
+
client_id = str(uuid.uuid4())
|
1250
|
+
self._clients[client_id] = websocket
|
1251
|
+
self._client_sessions[client_id] = {
|
1252
|
+
"connected_at": time.time(),
|
1253
|
+
"path": path,
|
1254
|
+
"remote_address": websocket.remote_address,
|
1255
|
+
}
|
1256
|
+
|
1257
|
+
logger.info(f"Client {client_id} connected from {websocket.remote_address}")
|
1258
|
+
self._update_metrics("connections_total")
|
1259
|
+
|
1260
|
+
try:
|
1261
|
+
async for message in websocket:
|
1262
|
+
try:
|
1263
|
+
# Parse message
|
1264
|
+
request = json.loads(message)
|
1265
|
+
|
1266
|
+
# Update metrics
|
1267
|
+
self._update_metrics("messages_received")
|
1268
|
+
self._update_metrics("bytes_received", len(message))
|
1269
|
+
|
1270
|
+
# Handle message
|
1271
|
+
if self.message_handler:
|
1272
|
+
response = await self._handle_message_safely(request, client_id)
|
1273
|
+
else:
|
1274
|
+
response = {
|
1275
|
+
"jsonrpc": "2.0",
|
1276
|
+
"error": {
|
1277
|
+
"code": -32601,
|
1278
|
+
"message": "No message handler configured",
|
1279
|
+
},
|
1280
|
+
"id": request.get("id"),
|
1281
|
+
}
|
1282
|
+
|
1283
|
+
# Send response
|
1284
|
+
await websocket.send(json.dumps(response))
|
1285
|
+
self._update_metrics("messages_sent")
|
1286
|
+
self._update_metrics("bytes_sent", len(json.dumps(response)))
|
1287
|
+
|
1288
|
+
except json.JSONDecodeError as e:
|
1289
|
+
logger.error(f"Invalid JSON from client {client_id}: {e}")
|
1290
|
+
self._update_metrics("errors_total")
|
1291
|
+
|
1292
|
+
error_response = {
|
1293
|
+
"jsonrpc": "2.0",
|
1294
|
+
"error": {
|
1295
|
+
"code": -32700,
|
1296
|
+
"message": "Parse error: Invalid JSON",
|
1297
|
+
},
|
1298
|
+
"id": None,
|
1299
|
+
}
|
1300
|
+
await websocket.send(json.dumps(error_response))
|
1301
|
+
|
1302
|
+
except Exception as e:
|
1303
|
+
logger.error(f"Error handling message from client {client_id}: {e}")
|
1304
|
+
self._update_metrics("errors_total")
|
1305
|
+
|
1306
|
+
except websockets.exceptions.ConnectionClosed:
|
1307
|
+
logger.info(f"Client {client_id} disconnected")
|
1308
|
+
except Exception as e:
|
1309
|
+
logger.error(f"Error in client handler for {client_id}: {e}")
|
1310
|
+
finally:
|
1311
|
+
# Clean up client
|
1312
|
+
del self._clients[client_id]
|
1313
|
+
del self._client_sessions[client_id]
|
1314
|
+
|
1315
|
+
async def _handle_message_safely(
|
1316
|
+
self, request: Dict[str, Any], client_id: str
|
1317
|
+
) -> Dict[str, Any]:
|
1318
|
+
"""Handle message with error handling.
|
1319
|
+
|
1320
|
+
Args:
|
1321
|
+
request: JSON-RPC request
|
1322
|
+
client_id: Client identifier
|
1323
|
+
|
1324
|
+
Returns:
|
1325
|
+
JSON-RPC response
|
1326
|
+
"""
|
1327
|
+
try:
|
1328
|
+
if asyncio.iscoroutinefunction(self.message_handler):
|
1329
|
+
return await self.message_handler(request, client_id)
|
1330
|
+
else:
|
1331
|
+
return self.message_handler(request, client_id)
|
1332
|
+
except Exception as e:
|
1333
|
+
logger.error(f"Message handler error: {e}")
|
1334
|
+
return {
|
1335
|
+
"jsonrpc": "2.0",
|
1336
|
+
"error": {
|
1337
|
+
"code": -32603,
|
1338
|
+
"message": f"Internal error: {str(e)}",
|
1339
|
+
},
|
1340
|
+
"id": request.get("id"),
|
1341
|
+
}
|
1342
|
+
|
1343
|
+
def get_client_info(self, client_id: str) -> Optional[Dict[str, Any]]:
|
1344
|
+
"""Get information about a connected client.
|
1345
|
+
|
1346
|
+
Args:
|
1347
|
+
client_id: Client identifier
|
1348
|
+
|
1349
|
+
Returns:
|
1350
|
+
Client information or None
|
1351
|
+
"""
|
1352
|
+
if client_id not in self._client_sessions:
|
1353
|
+
return None
|
1354
|
+
|
1355
|
+
session = self._client_sessions[client_id]
|
1356
|
+
return {
|
1357
|
+
"client_id": client_id,
|
1358
|
+
"connected_at": session["connected_at"],
|
1359
|
+
"connection_duration": time.time() - session["connected_at"],
|
1360
|
+
"path": session["path"],
|
1361
|
+
"remote_address": session["remote_address"],
|
1362
|
+
}
|
1363
|
+
|
1364
|
+
def list_clients(self) -> List[Dict[str, Any]]:
|
1365
|
+
"""List all connected clients.
|
1366
|
+
|
1367
|
+
Returns:
|
1368
|
+
List of client information
|
1369
|
+
"""
|
1370
|
+
return [self.get_client_info(client_id) for client_id in self._client_sessions]
|
1371
|
+
|
1372
|
+
async def close_client(
|
1373
|
+
self, client_id: str, code: int = 1000, reason: str = ""
|
1374
|
+
) -> bool:
|
1375
|
+
"""Close a specific client connection.
|
1376
|
+
|
1377
|
+
Args:
|
1378
|
+
client_id: Client to disconnect
|
1379
|
+
code: WebSocket close code
|
1380
|
+
reason: Close reason
|
1381
|
+
|
1382
|
+
Returns:
|
1383
|
+
True if client was closed
|
1384
|
+
"""
|
1385
|
+
if client_id in self._clients:
|
1386
|
+
await self._clients[client_id].close(code, reason)
|
1387
|
+
return True
|
1388
|
+
return False
|
1389
|
+
|
1390
|
+
|
1087
1391
|
class TransportManager:
|
1088
1392
|
"""Manager for MCP transport instances."""
|
1089
1393
|
|
@@ -1095,6 +1399,7 @@ class TransportManager:
|
|
1095
1399
|
"sse": SSETransport,
|
1096
1400
|
"streamable_http": StreamableHTTPTransport,
|
1097
1401
|
"websocket": WebSocketTransport,
|
1402
|
+
"websocket_server": WebSocketServerTransport,
|
1098
1403
|
}
|
1099
1404
|
|
1100
1405
|
def register_transport_factory(self, transport_type: str, factory: Callable):
|
@@ -125,6 +125,7 @@ class EventStore:
|
|
125
125
|
self._flush_task = asyncio.create_task(self._flush_loop())
|
126
126
|
except RuntimeError:
|
127
127
|
# If no event loop is running, defer task creation
|
128
|
+
# Don't create the coroutine here as it will never be awaited
|
128
129
|
self._flush_task = None
|
129
130
|
|
130
131
|
async def _ensure_flush_task(self):
|
@@ -0,0 +1,18 @@
|
|
1
|
+
"""
|
2
|
+
Monitoring and alerting system for Kailash SDK.
|
3
|
+
|
4
|
+
Provides comprehensive monitoring for validation failures, security violations,
|
5
|
+
performance metrics, and alerting for critical events.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .alerts import AlertManager, AlertRule, AlertSeverity
|
9
|
+
from .metrics import PerformanceMetrics, SecurityMetrics, ValidationMetrics
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
"ValidationMetrics",
|
13
|
+
"SecurityMetrics",
|
14
|
+
"PerformanceMetrics",
|
15
|
+
"AlertManager",
|
16
|
+
"AlertRule",
|
17
|
+
"AlertSeverity",
|
18
|
+
]
|