CHKIM79/scalable-ai-agent-system
0
1"""2Integration Layer3Provides RESTful APIs, webhooks, cloud connectors, and external service integrations4"""5import asyncio6import logging7import json8import aiohttp9import hmac10import hashlib11from typing import Dict, List, Any, Optional, Callable, Union12from dataclasses import dataclass, field13from enum import Enum14from datetime import datetime, timedelta15from urllib.parse import urljoin, urlparse16import sqlite317from fastapi import FastAPI, HTTPException, Request, BackgroundTasks18from fastapi.middleware.cors import CORSMiddleware19from pydantic import BaseModel, Field20import uvicorn21 22 23class IntegrationType(Enum):24 REST_API = "rest_api"25 WEBHOOK = "webhook"26 CLOUD_CONNECTOR = "cloud_connector"27 MESSAGE_QUEUE = "message_queue"28 DATABASE = "database"29 FILE_SYSTEM = "file_system"30 31 32class AuthType(Enum):33 NONE = "none"34 API_KEY = "api_key"35 BEARER_TOKEN = "bearer_token"36 OAUTH2 = "oauth2"37 BASIC_AUTH = "basic_auth"38 HMAC = "hmac"39 40 41@dataclass42class IntegrationConfig:43 id: str44 name: str45 integration_type: IntegrationType46 endpoint_url: str47 auth_type: AuthType = AuthType.NONE48 auth_config: Dict[str, Any] = field(default_factory=dict)49 headers: Dict[str, str] = field(default_factory=dict)50 timeout: int = 3051 retry_attempts: int = 352 rate_limit: Optional[int] = None53 enabled: bool = True54 metadata: Dict[str, Any] = field(default_factory=dict)55 56 57@dataclass58class WebhookConfig:59 id: str60 name: str61 endpoint: str62 secret: Optional[str] = None63 events: List[str] = field(default_factory=list)64 filters: Dict[str, Any] = field(default_factory=dict)65 retry_policy: Dict[str, Any] = field(default_factory=dict)66 enabled: bool = True67 68 69class APIRequest(BaseModel):70 endpoint: str71 method: str = "GET"72 headers: Optional[Dict[str, str]] = None73 params: Optional[Dict[str, Any]] = None74 data: Optional[Dict[str, Any]] = None75 timeout: Optional[int] = 3076 77 78class APIResponse(BaseModel):79 status_code: int80 headers: Dict[str, str]81 data: Any82 execution_time: float83 success: bool84 error: Optional[str] = None85 86 87class WebhookPayload(BaseModel):88 event: str89 data: Dict[str, Any]90 timestamp: datetime = Field(default_factory=datetime.now)91 source: str92 signature: Optional[str] = None93 94 95class CloudConnector:96 """Base class for cloud service connectors"""97 98 def __init__(self, config: IntegrationConfig):99 self.config = config100 self.session: Optional[aiohttp.ClientSession] = None101 self.logger = logging.getLogger(__name__)102 103 async def initialize(self):104 """Initialize connector"""105 self.session = aiohttp.ClientSession(106 timeout=aiohttp.ClientTimeout(total=self.config.timeout),107 headers=self.config.headers108 )109 110 async def authenticate(self) -> Dict[str, str]:111 """Perform authentication and return headers"""112 auth_headers = {}113 114 if self.config.auth_type == AuthType.API_KEY:115 key_name = self.config.auth_config.get('key_name', 'X-API-Key')116 api_key = self.config.auth_config.get('api_key')117 if api_key:118 auth_headers[key_name] = api_key119 120 elif self.config.auth_type == AuthType.BEARER_TOKEN:121 token = self.config.auth_config.get('token')122 if token:123 auth_headers['Authorization'] = f'Bearer {token}'124 125 elif self.config.auth_type == AuthType.BASIC_AUTH:126 username = self.config.auth_config.get('username')127 password = self.config.auth_config.get('password')128 if username and password:129 import base64130 credentials = base64.b64encode(f'{username}:{password}'.encode()).decode()131 auth_headers['Authorization'] = f'Basic {credentials}'132 133 return auth_headers134 135 async def make_request(self, 136 method: str, 137 endpoint: str, 138 data: Optional[Dict] = None,139 params: Optional[Dict] = None) -> APIResponse:140 """Make HTTP request to external service"""141 142 if not self.session:143 await self.initialize()144 145 # Prepare request146 url = urljoin(self.config.endpoint_url, endpoint)147 auth_headers = await self.authenticate()148 headers = {**self.config.headers, **auth_headers}149 150 start_time = datetime.now()151 152 try:153 async with self.session.request(154 method=method,155 url=url,156 json=data,157 params=params,158 headers=headers159 ) as response:160 161 execution_time = (datetime.now() - start_time).total_seconds()162 response_data = await response.json() if response.content_type == 'application/json' else await response.text()163 164 return APIResponse(165 status_code=response.status,166 headers=dict(response.headers),167 data=response_data,168 execution_time=execution_time,169 success=200 <= response.status < 300170 )171 172 except Exception as e:173 execution_time = (datetime.now() - start_time).total_seconds()174 self.logger.error(f"Request failed: {e}")175 176 return APIResponse(177 status_code=500,178 headers={},179 data=None,180 execution_time=execution_time,181 success=False,182 error=str(e)183 )184 185 async def shutdown(self):186 """Shutdown connector"""187 if self.session:188 await self.session.close()189 190 191class AWSConnector(CloudConnector):192 """AWS services connector"""193 194 async def authenticate(self) -> Dict[str, str]:195 """AWS authentication using access keys"""196 # Simplified AWS auth - in production use boto3197 access_key = self.config.auth_config.get('access_key_id')198 secret_key = self.config.auth_config.get('secret_access_key')199 200 if access_key and secret_key:201 # This is a simplified version - real AWS auth is more complex202 return {203 'Authorization': f'AWS {access_key}:{secret_key}',204 'X-Amz-Date': datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')205 }206 207 return await super().authenticate()208 209 async def invoke_lambda(self, function_name: str, payload: Dict) -> APIResponse:210 """Invoke AWS Lambda function"""211 return await self.make_request(212 method='POST',213 endpoint=f'/2015-03-31/functions/{function_name}/invocations',214 data=payload215 )216 217 async def put_s3_object(self, bucket: str, key: str, data: bytes) -> APIResponse:218 """Put object to S3"""219 # Simplified S3 put - real implementation would use proper AWS SDK220 return await self.make_request(221 method='PUT',222 endpoint=f'/{bucket}/{key}',223 data={'content': data.decode() if isinstance(data, bytes) else data}224 )225 226 227class GCPConnector(CloudConnector):228 """Google Cloud Platform connector"""229 230 async def authenticate(self) -> Dict[str, str]:231 """GCP authentication using service account"""232 service_account_key = self.config.auth_config.get('service_account_key')233 234 if service_account_key:235 # Simplified GCP auth - in production use google-auth236 return {237 'Authorization': f'Bearer {service_account_key}',238 'Content-Type': 'application/json'239 }240 241 return await super().authenticate()242 243 async def call_cloud_function(self, function_name: str, data: Dict) -> APIResponse:244 """Call Google Cloud Function"""245 return await self.make_request(246 method='POST',247 endpoint=f'/v1/projects/{self.config.auth_config.get("project_id")}/locations/{self.config.auth_config.get("region", "us-central1")}/functions/{function_name}:call',248 data=data249 )250 251 252class AzureConnector(CloudConnector):253 """Microsoft Azure connector"""254 255 async def authenticate(self) -> Dict[str, str]:256 """Azure authentication"""257 tenant_id = self.config.auth_config.get('tenant_id')258 client_id = self.config.auth_config.get('client_id')259 client_secret = self.config.auth_config.get('client_secret')260 261 if tenant_id and client_id and client_secret:262 # Simplified Azure auth - in production use azure-identity263 return {264 'Authorization': f'Bearer {client_secret}',265 'Content-Type': 'application/json'266 }267 268 return await super().authenticate()269 270 async def call_function_app(self, function_name: str, data: Dict) -> APIResponse:271 """Call Azure Function"""272 return await self.make_request(273 method='POST',274 endpoint=f'/api/{function_name}',275 data=data276 )277 278 279class WebhookManager:280 """Manages incoming and outgoing webhooks"""281 282 def __init__(self, db_path: str = "webhooks.db"):283 self.db_path = db_path284 self.webhooks: Dict[str, WebhookConfig] = {}285 self.event_handlers: Dict[str, List[Callable]] = {}286 self.db_connection = None287 self.logger = logging.getLogger(__name__)288 289 async def initialize(self):290 """Initialize webhook manager"""291 self.db_connection = sqlite3.connect(self.db_path)292 await self._create_tables()293 await self._load_webhooks()294 295 async def _create_tables(self):296 """Create webhook tables"""297 cursor = self.db_connection.cursor()298 299 cursor.execute('''300 CREATE TABLE IF NOT EXISTS webhooks (301 id TEXT PRIMARY KEY,302 name TEXT,303 endpoint TEXT,304 secret TEXT,305 events TEXT,306 filters TEXT,307 retry_policy TEXT,308 enabled BOOLEAN309 )310 ''')311 312 cursor.execute('''313 CREATE TABLE IF NOT EXISTS webhook_logs (314 id INTEGER PRIMARY KEY AUTOINCREMENT,315 webhook_id TEXT,316 event TEXT,317 payload TEXT,318 status TEXT,319 response TEXT,320 timestamp DATETIME,321 execution_time REAL322 )323 ''')324 325 self.db_connection.commit()326 327 async def _load_webhooks(self):328 """Load webhooks from database"""329 cursor = self.db_connection.cursor()330 cursor.execute('SELECT * FROM webhooks WHERE enabled = 1')331 332 for row in cursor.fetchall():333 webhook = WebhookConfig(334 id=row[0],335 name=row[1],336 endpoint=row[2],337 secret=row[3],338 events=json.loads(row[4]) if row[4] else [],339 filters=json.loads(row[5]) if row[5] else {},340 retry_policy=json.loads(row[6]) if row[6] else {},341 enabled=bool(row[7])342 )343 self.webhooks[webhook.id] = webhook344 345 def register_webhook(self, webhook: WebhookConfig):346 """Register a new webhook"""347 self.webhooks[webhook.id] = webhook348 349 # Persist to database350 cursor = self.db_connection.cursor()351 cursor.execute('''352 INSERT OR REPLACE INTO webhooks 353 (id, name, endpoint, secret, events, filters, retry_policy, enabled)354 VALUES (?, ?, ?, ?, ?, ?, ?, ?)355 ''', (356 webhook.id,357 webhook.name,358 webhook.endpoint,359 webhook.secret,360 json.dumps(webhook.events),361 json.dumps(webhook.filters),362 json.dumps(webhook.retry_policy),363 webhook.enabled364 ))365 self.db_connection.commit()366 367 self.logger.info(f"Registered webhook: {webhook.name}")368 369 def register_event_handler(self, event: str, handler: Callable):370 """Register event handler"""371 if event not in self.event_handlers:372 self.event_handlers[event] = []373 self.event_handlers[event].append(handler)374 375 async def process_incoming_webhook(self, request: Request) -> Dict[str, Any]:376 """Process incoming webhook request"""377 378 # Get request data379 body = await request.body()380 headers = dict(request.headers)381 382 # Verify signature if secret is provided383 webhook_id = request.path_params.get('webhook_id')384 if webhook_id and webhook_id in self.webhooks:385 webhook = self.webhooks[webhook_id]386 387 if webhook.secret:388 signature = headers.get('x-hub-signature-256', '')389 expected_signature = self._calculate_signature(body, webhook.secret)390 391 if not hmac.compare_digest(signature, expected_signature):392 raise HTTPException(status_code=401, detail="Invalid signature")393 394 # Parse payload395 try:396 payload_data = json.loads(body.decode())397 except json.JSONDecodeError:398 payload_data = {'raw_body': body.decode()}399 400 # Create webhook payload401 payload = WebhookPayload(402 event=payload_data.get('event', 'unknown'),403 data=payload_data,404 source=headers.get('user-agent', 'unknown'),405 signature=headers.get('x-hub-signature-256')406 )407 408 # Process event409 result = await self._process_event(payload)410 411 # Log webhook412 await self._log_webhook_event(webhook_id or 'unknown', payload, result)413 414 return result415 416 async def send_webhook(self, webhook_id: str, payload: WebhookPayload) -> APIResponse:417 """Send outgoing webhook"""418 419 if webhook_id not in self.webhooks:420 raise ValueError(f"Webhook {webhook_id} not found")421 422 webhook = self.webhooks[webhook_id]423 424 # Prepare request425 headers = {'Content-Type': 'application/json'}426 427 if webhook.secret:428 payload_json = json.dumps(payload.dict(), default=str)429 signature = self._calculate_signature(payload_json.encode(), webhook.secret)430 headers['X-Hub-Signature-256'] = signature431 432 # Send request433 start_time = datetime.now()434 435 try:436 async with aiohttp.ClientSession() as session:437 async with session.post(438 webhook.endpoint,439 json=payload.dict(),440 headers=headers,441 timeout=aiohttp.ClientTimeout(total=30)442 ) as response:443 444 execution_time = (datetime.now() - start_time).total_seconds()445 response_data = await response.text()446 447 result = APIResponse(448 status_code=response.status,449 headers=dict(response.headers),450 data=response_data,451 execution_time=execution_time,452 success=200 <= response.status < 300453 )454 455 # Log webhook456 await self._log_webhook_event(webhook_id, payload, result.dict())457 458 return result459 460 except Exception as e:461 execution_time = (datetime.now() - start_time).total_seconds()462 result = APIResponse(463 status_code=500,464 headers={},465 data=None,466 execution_time=execution_time,467 success=False,468 error=str(e)469 )470 471 await self._log_webhook_event(webhook_id, payload, result.dict())472 return result473 474 def _calculate_signature(self, payload: bytes, secret: str) -> str:475 """Calculate HMAC signature"""476 signature = hmac.new(477 secret.encode(),478 payload,479 hashlib.sha256480 ).hexdigest()481 return f'sha256={signature}'482 483 async def _process_event(self, payload: WebhookPayload) -> Dict[str, Any]:484 """Process webhook event"""485 486 results = []487 488 # Call registered handlers489 if payload.event in self.event_handlers:490 for handler in self.event_handlers[payload.event]:491 try:492 result = await handler(payload)493 results.append({494 'handler': handler.__name__,495 'success': True,496 'result': result497 })498 except Exception as e:499 results.append({500 'handler': handler.__name__,501 'success': False,502 'error': str(e)503 })504 505 return {506 'event': payload.event,507 'processed': len(results),508 'results': results509 }510 511 async def _log_webhook_event(self, webhook_id: str, payload: WebhookPayload, result: Dict):512 """Log webhook event"""513 cursor = self.db_connection.cursor()514 515 cursor.execute('''516 INSERT INTO webhook_logs 517 (webhook_id, event, payload, status, response, timestamp, execution_time)518 VALUES (?, ?, ?, ?, ?, ?, ?)519 ''', (520 webhook_id,521 payload.event,522 json.dumps(payload.dict(), default=str),523 'success' if result.get('success', False) else 'failed',524 json.dumps(result),525 datetime.now(),526 result.get('execution_time', 0.0)527 ))528 529 self.db_connection.commit()530 531 532class IntegrationLayer:533 """Main integration layer coordinating all external integrations"""534 535 def __init__(self, db_path: str = "integrations.db"):536 self.db_path = db_path537 self.integrations: Dict[str, IntegrationConfig] = {}538 self.connectors: Dict[str, CloudConnector] = {}539 self.webhook_manager = WebhookManager()540 541 self.db_connection = None542 self.logger = logging.getLogger(__name__)543 544 # Rate limiting545 self.rate_limits: Dict[str, List[datetime]] = {}546 547 async def initialize(self):548 """Initialize integration layer"""549 self.db_connection = sqlite3.connect(self.db_path)550 await self._create_tables()551 await self._load_integrations()552 await self.webhook_manager.initialize()553 554 self.logger.info("Integration layer initialized")555 556 async def _create_tables(self):557 """Create integration tables"""558 cursor = self.db_connection.cursor()559 560 cursor.execute('''561 CREATE TABLE IF NOT EXISTS integrations (562 id TEXT PRIMARY KEY,563 name TEXT,564 integration_type TEXT,565 endpoint_url TEXT,566 auth_type TEXT,567 auth_config TEXT,568 headers TEXT,569 timeout INTEGER,570 retry_attempts INTEGER,571 rate_limit INTEGER,572 enabled BOOLEAN,573 metadata TEXT574 )575 ''')576 577 cursor.execute('''578 CREATE TABLE IF NOT EXISTS integration_logs (579 id INTEGER PRIMARY KEY AUTOINCREMENT,580 integration_id TEXT,581 method TEXT,582 endpoint TEXT,583 status_code INTEGER,584 execution_time REAL,585 success BOOLEAN,586 error TEXT,587 timestamp DATETIME588 )589 ''')590 591 self.db_connection.commit()592 593 async def _load_integrations(self):594 """Load integrations from database"""595 cursor = self.db_connection.cursor()596 cursor.execute('SELECT * FROM integrations WHERE enabled = 1')597 598 for row in cursor.fetchall():599 config = IntegrationConfig(600 id=row[0],601 name=row[1],602 integration_type=IntegrationType(row[2]),603 endpoint_url=row[3],604 auth_type=AuthType(row[4]),605 auth_config=json.loads(row[5]) if row[5] else {},606 headers=json.loads(row[6]) if row[6] else {},607 timeout=row[7],608 retry_attempts=row[8],609 rate_limit=row[9],610 enabled=bool(row[10]),611 metadata=json.loads(row[11]) if row[11] else {}612 )613 614 await self.register_integration(config)615 616 async def register_integration(self, config: IntegrationConfig):617 """Register a new integration"""618 self.integrations[config.id] = config619 620 # Create appropriate connector621 if config.integration_type == IntegrationType.CLOUD_CONNECTOR:622 if 'aws' in config.endpoint_url.lower():623 connector = AWSConnector(config)624 elif 'googleapis' in config.endpoint_url.lower():625 connector = GCPConnector(config)626 elif 'azure' in config.endpoint_url.lower():627 connector = AzureConnector(config)628 else:629 connector = CloudConnector(config)630 631 await connector.initialize()632 self.connectors[config.id] = connector633 634 # Persist to database635 cursor = self.db_connection.cursor()636 cursor.execute('''637 INSERT OR REPLACE INTO integrations 638 (id, name, integration_type, endpoint_url, auth_type, auth_config, 639 headers, timeout, retry_attempts, rate_limit, enabled, metadata)640 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)641 ''', (642 config.id,643 config.name,644 config.integration_type.value,645 config.endpoint_url,646 config.auth_type.value,647 json.dumps(config.auth_config),648 json.dumps(config.headers),649 config.timeout,650 config.retry_attempts,651 config.rate_limit,652 config.enabled,653 json.dumps(config.metadata)654 ))655 self.db_connection.commit()656 657 self.logger.info(f"Registered integration: {config.name}")658 659 async def make_api_call(self, integration_id: str, request: APIRequest) -> APIResponse:660 """Make API call through integration"""661 662 if integration_id not in self.integrations:663 raise ValueError(f"Integration {integration_id} not found")664 665 config = self.integrations[integration_id]666 667 # Check rate limit668 if not await self._check_rate_limit(integration_id, config.rate_limit):669 return APIResponse(670 status_code=429,671 headers={},672 data=None,673 execution_time=0.0,674 success=False,675 error="Rate limit exceeded"676 )677 678 # Use connector if available679 if integration_id in self.connectors:680 connector = self.connectors[integration_id]681 response = await connector.make_request(682 method=request.method,683 endpoint=request.endpoint,684 data=request.data,685 params=request.params686 )687 else:688 # Direct HTTP call689 response = await self._make_direct_request(config, request)690 691 # Log the call692 await self._log_api_call(integration_id, request, response)693 694 return response695 696 async def _make_direct_request(self, config: IntegrationConfig, request: APIRequest) -> APIResponse:697 """Make direct HTTP request"""698 699 url = urljoin(config.endpoint_url, request.endpoint)700 headers = {**config.headers, **(request.headers or {})}701 702 # Add authentication703 if config.auth_type == AuthType.API_KEY:704 key_name = config.auth_config.get('key_name', 'X-API-Key')705 api_key = config.auth_config.get('api_key')706 if api_key:707 headers[key_name] = api_key708 709 start_time = datetime.now()710 711 try:712 async with aiohttp.ClientSession() as session:713 async with session.request(714 method=request.method,715 url=url,716 json=request.data,717 params=request.params,718 headers=headers,719 timeout=aiohttp.ClientTimeout(total=request.timeout or config.timeout)720 ) as response:721 722 execution_time = (datetime.now() - start_time).total_seconds()723 response_data = await response.json() if response.content_type == 'application/json' else await response.text()724 725 return APIResponse(726 status_code=response.status,727 headers=dict(response.headers),728 data=response_data,729 execution_time=execution_time,730 success=200 <= response.status < 300731 )732 733 except Exception as e:734 execution_time = (datetime.now() - start_time).total_seconds()735 return APIResponse(736 status_code=500,737 headers={},738 data=None,739 execution_time=execution_time,740 success=False,741 error=str(e)742 )743 744 async def _check_rate_limit(self, integration_id: str, rate_limit: Optional[int]) -> bool:745 """Check if request is within rate limit"""746 747 if not rate_limit:748 return True749 750 now = datetime.now()751 minute_ago = now - timedelta(minutes=1)752 753 # Clean old entries754 if integration_id in self.rate_limits:755 self.rate_limits[integration_id] = [756 timestamp for timestamp in self.rate_limits[integration_id]757 if timestamp > minute_ago758 ]759 else:760 self.rate_limits[integration_id] = []761 762 # Check limit763 if len(self.rate_limits[integration_id]) >= rate_limit:764 return False765 766 # Add current request767 self.rate_limits[integration_id].append(now)768 return True769 770 async def _log_api_call(self, integration_id: str, request: APIRequest, response: APIResponse):771 """Log API call"""772 cursor = self.db_connection.cursor()773 774 cursor.execute('''775 INSERT INTO integration_logs 776 (integration_id, method, endpoint, status_code, execution_time, success, error, timestamp)777 VALUES (?, ?, ?, ?, ?, ?, ?, ?)778 ''', (779 integration_id,780 request.method,781 request.endpoint,782 response.status_code,783 response.execution_time,784 response.success,785 response.error,786 datetime.now()787 ))788 789 self.db_connection.commit()790 791 def get_integration_stats(self, integration_id: str) -> Dict[str, Any]:792 """Get integration statistics"""793 cursor = self.db_connection.cursor()794 795 # Get recent calls796 cursor.execute('''797 SELECT COUNT(*), AVG(execution_time), 798 SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success_count799 FROM integration_logs 800 WHERE integration_id = ? AND timestamp > datetime('now', '-1 hour')801 ''', (integration_id,))802 803 row = cursor.fetchone()804 total_calls = row[0] or 0805 avg_time = row[1] or 0.0806 success_count = row[2] or 0807 808 success_rate = (success_count / total_calls) if total_calls > 0 else 0.0809 810 return {811 'integration_id': integration_id,812 'total_calls_last_hour': total_calls,813 'average_response_time': avg_time,814 'success_rate': success_rate,815 'current_rate_limit_usage': len(self.rate_limits.get(integration_id, []))816 }817 818 async def shutdown(self):819 """Shutdown integration layer"""820 # Shutdown connectors821 for connector in self.connectors.values():822 await connector.shutdown()823 824 if self.db_connection:825 self.db_connection.close()826 827 self.logger.info("Integration layer shutdown")828 829 830# FastAPI integration endpoints831def create_integration_api(integration_layer: IntegrationLayer) -> FastAPI:832 """Create FastAPI app for integration endpoints"""833 834 app = FastAPI(title="AI Agent Integration API", version="1.0.0")835 836 app.add_middleware(837 CORSMiddleware,838 allow_origins=["*"],839 allow_credentials=True,840 allow_methods=["*"],841 allow_headers=["*"],842 )843 844 @app.post("/api/call/{integration_id}")845 async def make_api_call(integration_id: str, request: APIRequest):846 """Make API call through integration"""847 try:848 response = await integration_layer.make_api_call(integration_id, request)849 return response.dict()850 except Exception as e:851 raise HTTPException(status_code=500, detail=str(e))852 853 @app.post("/webhook/{webhook_id}")854 async def receive_webhook(webhook_id: str, request: Request):855 """Receive incoming webhook"""856 try:857 result = await integration_layer.webhook_manager.process_incoming_webhook(request)858 return result859 except Exception as e:860 raise HTTPException(status_code=500, detail=str(e))861 862 @app.get("/api/integrations")863 async def list_integrations():864 """List all integrations"""865 return {866 integration_id: {867 'name': config.name,868 'type': config.integration_type.value,869 'enabled': config.enabled870 }871 for integration_id, config in integration_layer.integrations.items()872 }873 874 @app.get("/api/integrations/{integration_id}/stats")875 async def get_integration_stats(integration_id: str):876 """Get integration statistics"""877 try:878 stats = integration_layer.get_integration_stats(integration_id)879 return stats880 except Exception as e:881 raise HTTPException(status_code=500, detail=str(e))882 883 return app884 