agent-mcp 0.1.4__py3-none-any.whl → 0.1.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.
agent_mcp/registry.py ADDED
@@ -0,0 +1,768 @@
1
+ """
2
+ Multi-Language Agent Registry
3
+ Enhanced agent discovery and registration system supporting multiple protocols
4
+
5
+ This module provides a comprehensive registry for:
6
+ - Multi-language agent support (Python, JavaScript, Go, Rust, etc.)
7
+ - Multiple protocols (MCP, A2A, OpenAPI, REST)
8
+ - Health monitoring and lifecycle management
9
+ - Capability discovery and matching
10
+ - Protocol auto-detection
11
+ """
12
+
13
+ import asyncio
14
+ import json
15
+ import uuid
16
+ import hashlib
17
+ import aiohttp
18
+ from datetime import datetime, timezone, timedelta
19
+ from typing import Dict, Any, List, Optional, Callable, Union, Set
20
+ from dataclasses import dataclass, asdict
21
+ from enum import Enum
22
+ import logging
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ class AgentProtocol(Enum):
27
+ """Supported agent protocols"""
28
+ MCP = "mcp"
29
+ A2A = "a2a"
30
+ OPENAPI = "openapi"
31
+ REST = "rest"
32
+ WEBHOOK = "webhook"
33
+ WEBSOCKET = "websocket"
34
+ GRPC = "grpc"
35
+
36
+ class AgentStatus(Enum):
37
+ """Agent registration status"""
38
+ ACTIVE = "active"
39
+ INACTIVE = "inactive"
40
+ PENDING = "pending"
41
+ SUSPENDED = "suspended"
42
+ DECOMMISSIONED = "decommissioned"
43
+
44
+ class AgentLanguage(Enum):
45
+ """Programming languages/frameworks"""
46
+ PYTHON = "python"
47
+ JAVASCRIPT = "javascript"
48
+ TYPESCRIPT = "typescript"
49
+ GO = "go"
50
+ RUST = "rust"
51
+ JAVA = "java"
52
+ CSHARP = "csharp"
53
+ RUBY = "ruby"
54
+ PHP = "php"
55
+ SWIFT = "swift"
56
+ KOTLIN = "kotlin"
57
+ UNKNOWN = "unknown"
58
+
59
+ @dataclass
60
+ class AgentRegistration:
61
+ """Comprehensive agent registration information"""
62
+ agent_id: str
63
+ name: str
64
+ description: str
65
+ language: AgentLanguage
66
+ frameworks: List[str]
67
+ protocols: List[AgentProtocol]
68
+ endpoint: str
69
+ webhook_url: Optional[str] = None
70
+ capabilities: List[str] = None
71
+ security_level: str = "medium"
72
+ owner: Optional[str] = None
73
+ version: str = "1.0.0"
74
+ metadata: Dict[str, Any] = None
75
+
76
+ # Runtime information
77
+ status: AgentStatus = AgentStatus.PENDING
78
+ registered_at: str = None
79
+ last_heartbeat: Optional[str] = None
80
+ health_status: Optional[str] = None
81
+ health_check_url: Optional[str] = None
82
+
83
+ # Network information
84
+ ip_address: Optional[str] = None
85
+ region: Optional[str] = None
86
+ latency_ms: Optional[float] = None
87
+
88
+ # Security information
89
+ auth_token: Optional[str] = None
90
+ public_key: Optional[str] = None
91
+ did: Optional[str] = None # Decentralized Identifier
92
+
93
+ def __post_init__(self):
94
+ if self.capabilities is None:
95
+ self.capabilities = []
96
+ if self.frameworks is None:
97
+ self.frameworks = []
98
+ if self.metadata is None:
99
+ self.metadata = {}
100
+ if self.registered_at is None:
101
+ self.registered_at = datetime.now(timezone.utc).isoformat()
102
+
103
+ @dataclass
104
+ class HealthCheckResult:
105
+ """Health check result for an agent"""
106
+ agent_id: str
107
+ status: str
108
+ response_time_ms: float
109
+ timestamp: str
110
+ error: Optional[str] = None
111
+ details: Dict[str, Any] = None
112
+
113
+ def __post_init__(self):
114
+ if self.details is None:
115
+ self.details = {}
116
+ if self.timestamp is None:
117
+ self.timestamp = datetime.now(timezone.utc).isoformat()
118
+
119
+ class ProtocolDetector:
120
+ """Auto-detection of agent protocols"""
121
+
122
+ def __init__(self):
123
+ self.detected_protocols = {}
124
+
125
+ async def detect_protocols(self, endpoint: str) -> List[AgentProtocol]:
126
+ """Detect which protocols an agent supports"""
127
+ detected = []
128
+
129
+ async with aiohttp.ClientSession() as session:
130
+ # Try MCP detection
131
+ if await self._detect_mcp(session, endpoint):
132
+ detected.append(AgentProtocol.MCP)
133
+
134
+ # Try A2A detection
135
+ if await self._detect_a2a(session, endpoint):
136
+ detected.append(AgentProtocol.A2A)
137
+
138
+ # Try OpenAPI detection
139
+ if await self._detect_openapi(session, endpoint):
140
+ detected.append(AgentProtocol.OPENAPI)
141
+
142
+ # Default to REST if endpoint responds
143
+ if not detected and await self._detect_rest(session, endpoint):
144
+ detected.append(AgentProtocol.REST)
145
+
146
+ return detected
147
+
148
+ async def _detect_mcp(self, session: aiohttp.ClientSession, endpoint: str) -> bool:
149
+ """Detect if endpoint supports MCP"""
150
+ try:
151
+ # Check for MCP endpoint
152
+ async with session.get(f"{endpoint}/.well-known/mcp", timeout=5) as response:
153
+ return response.status == 200
154
+ except:
155
+ try:
156
+ async with session.get(f"{endpoint}/mcp/info", timeout=5) as response:
157
+ return response.status == 200
158
+ except:
159
+ return False
160
+
161
+ async def _detect_a2a(self, session: aiohttp.ClientSession, endpoint: str) -> bool:
162
+ """Detect if endpoint supports A2A"""
163
+ try:
164
+ async with session.get(f"{endpoint}/.well-known/agent.json", timeout=5) as response:
165
+ return response.status == 200
166
+ except:
167
+ return False
168
+
169
+ async def _detect_openapi(self, session: aiohttp.ClientSession, endpoint: str) -> bool:
170
+ """Detect if endpoint supports OpenAPI"""
171
+ try:
172
+ async with session.get(f"{endpoint}/openapi.json", timeout=5) as response:
173
+ return response.status == 200
174
+ except:
175
+ try:
176
+ async with session.get(f"{endpoint}/api/docs", timeout=5) as response:
177
+ return response.status == 200
178
+ except:
179
+ return False
180
+
181
+ async def _detect_rest(self, session: aiohttp.ClientSession, endpoint: str) -> bool:
182
+ """Detect if endpoint supports basic REST"""
183
+ try:
184
+ # Simple ping to base endpoint
185
+ async with session.get(endpoint, timeout=5) as response:
186
+ return response.status < 500
187
+ except:
188
+ return False
189
+
190
+ class HealthMonitor:
191
+ """Health monitoring for registered agents"""
192
+
193
+ def __init__(self, check_interval: int = 60):
194
+ self.check_interval = check_interval
195
+ self.health_history = {}
196
+ self.monitoring_active = False
197
+ self.check_task = None
198
+
199
+ async def start_monitoring(self, agents: Dict[str, AgentRegistration]):
200
+ """Start health monitoring for agents"""
201
+ self.agents = agents
202
+ self.monitoring_active = True
203
+
204
+ if self.check_task:
205
+ self.check_task.cancel()
206
+
207
+ self.check_task = asyncio.create_task(self._monitoring_loop())
208
+ logger.info("Health monitoring started")
209
+
210
+ async def stop_monitoring(self):
211
+ """Stop health monitoring"""
212
+ self.monitoring_active = False
213
+ if self.check_task:
214
+ self.check_task.cancel()
215
+ self.check_task = None
216
+ logger.info("Health monitoring stopped")
217
+
218
+ async def _monitoring_loop(self):
219
+ """Main monitoring loop"""
220
+ while self.monitoring_active:
221
+ try:
222
+ await self._check_all_agents()
223
+ await asyncio.sleep(self.check_interval)
224
+ except asyncio.CancelledError:
225
+ break
226
+ except Exception as e:
227
+ logger.error(f"Error in health monitoring loop: {e}")
228
+ await asyncio.sleep(10) # Brief pause before retry
229
+
230
+ async def _check_all_agents(self):
231
+ """Check health of all registered agents"""
232
+ tasks = []
233
+
234
+ for agent_id, registration in self.agents.items():
235
+ if registration.status == AgentStatus.ACTIVE and registration.health_check_url:
236
+ tasks.append(self._check_agent_health(agent_id, registration))
237
+
238
+ if tasks:
239
+ await asyncio.gather(*tasks, return_exceptions=True)
240
+
241
+ async def _check_agent_health(self, agent_id: str, registration: AgentRegistration) -> HealthCheckResult:
242
+ """Check health of a single agent"""
243
+ start_time = datetime.now(timezone.utc)
244
+
245
+ try:
246
+ async with aiohttp.ClientSession() as session:
247
+ timeout = aiohttp.ClientTimeout(total=10)
248
+
249
+ # Use dedicated health check URL or default to agent endpoint
250
+ check_url = registration.health_check_url or f"{registration.endpoint}/health"
251
+
252
+ async with session.get(check_url, timeout=timeout) as response:
253
+ end_time = datetime.now(timezone.utc)
254
+ response_time = (end_time - start_time).total_seconds() * 1000
255
+
256
+ result = HealthCheckResult(
257
+ agent_id=agent_id,
258
+ status="healthy" if response.status == 200 else "unhealthy",
259
+ response_time_ms=response_time,
260
+ timestamp=end_time.isoformat(),
261
+ details={
262
+ "http_status": response.status,
263
+ "endpoint": check_url
264
+ }
265
+ )
266
+
267
+ if response.status == 200:
268
+ registration.last_heartbeat = result.timestamp
269
+ registration.health_status = "healthy"
270
+ else:
271
+ registration.health_status = "unhealthy"
272
+
273
+ # Store in history
274
+ if agent_id not in self.health_history:
275
+ self.health_history[agent_id] = []
276
+
277
+ self.health_history[agent_id].append(result)
278
+
279
+ # Keep only last 100 results per agent
280
+ if len(self.health_history[agent_id]) > 100:
281
+ self.health_history[agent_id] = self.health_history[agent_id][-100:]
282
+
283
+ return result
284
+
285
+ except Exception as e:
286
+ end_time = datetime.now(timezone.utc)
287
+ response_time = (end_time - start_time).total_seconds() * 1000
288
+
289
+ result = HealthCheckResult(
290
+ agent_id=agent_id,
291
+ status="error",
292
+ response_time_ms=response_time,
293
+ timestamp=end_time.isoformat(),
294
+ error=str(e),
295
+ details={"error_type": type(e).__name__}
296
+ )
297
+
298
+ registration.health_status = "error"
299
+
300
+ if agent_id not in self.health_history:
301
+ self.health_history[agent_id] = []
302
+
303
+ self.health_history[agent_id].append(result)
304
+
305
+ return result
306
+
307
+ class MultiLanguageAgentRegistry:
308
+ """Enhanced registry for multi-language agents"""
309
+
310
+ def __init__(
311
+ self,
312
+ storage_backend = None,
313
+ enable_health_monitoring: bool = True,
314
+ health_check_interval: int = 60,
315
+ require_approval: bool = False
316
+ ):
317
+ self.storage = storage_backend # Could be Firestore, PostgreSQL, etc.
318
+ self.agents = {}
319
+ self.capability_index = {} # capability -> [agent_ids]
320
+ self.protocol_index = {} # protocol -> [agent_ids]
321
+ self.language_index = {} # language -> [agent_ids]
322
+
323
+ # Components
324
+ self.protocol_detector = ProtocolDetector()
325
+ self.health_monitor = HealthMonitor(health_check_interval)
326
+ self.require_approval = require_approval
327
+
328
+ # Start health monitoring if enabled
329
+ if enable_health_monitoring:
330
+ asyncio.create_task(self._start_health_monitoring())
331
+
332
+ async def _start_health_monitoring(self):
333
+ """Start health monitoring after a short delay"""
334
+ await asyncio.sleep(5) # Wait for initial agents to be registered
335
+ await self.health_monitor.start_monitoring(self.agents)
336
+
337
+ async def register_agent(
338
+ self,
339
+ registration: AgentRegistration,
340
+ auto_detect_protocols: bool = True
341
+ ) -> Dict[str, Any]:
342
+ """Register a new agent with enhanced capabilities"""
343
+ try:
344
+ # Validate required fields
345
+ if not self._validate_registration(registration):
346
+ return {
347
+ "status": "error",
348
+ "message": "Invalid registration data",
349
+ "errors": self._get_validation_errors(registration)
350
+ }
351
+
352
+ # Auto-detect protocols if requested
353
+ if auto_detect_protocols and not registration.protocols:
354
+ detected_protocols = await self.protocol_detector.detect_protocols(registration.endpoint)
355
+ registration.protocols = detected_protocols
356
+ logger.info(f"Auto-detected protocols for {registration.agent_id}: {[p.value for p in detected_protocols]}")
357
+
358
+ # Generate authentication token
359
+ registration.auth_token = self._generate_agent_token(registration)
360
+
361
+ # Set initial status
362
+ registration.status = AgentStatus.PENDING if self.require_approval else AgentStatus.ACTIVE
363
+ registration.registered_at = datetime.now(timezone.utc).isoformat()
364
+
365
+ # Store in registry
366
+ self.agents[registration.agent_id] = registration
367
+
368
+ # Update indexes
369
+ self._update_indexes(registration)
370
+
371
+ # Store in persistent storage
372
+ await self.storage.write("agent_registrations", asdict(registration))
373
+
374
+ # Log registration
375
+ logger.info(f"Registered agent: {registration.agent_id} ({registration.language.value})")
376
+
377
+ # Start health check for new agent
378
+ if registration.health_check_url:
379
+ asyncio.create_task(self._immediate_health_check(registration))
380
+
381
+ return {
382
+ "status": "success",
383
+ "agent_id": registration.agent_id,
384
+ "auth_token": registration.auth_token,
385
+ "detected_protocols": [p.value for p in registration.protocols],
386
+ "message": "Agent registered successfully"
387
+ }
388
+
389
+ except Exception as e:
390
+ logger.error(f"Error registering agent {registration.agent_id}: {e}")
391
+ return {
392
+ "status": "error",
393
+ "message": str(e)
394
+ }
395
+
396
+ async def register_multi_protocol_agent(
397
+ self,
398
+ agent_id: str,
399
+ name: str,
400
+ description: str,
401
+ language: Union[str, AgentLanguage],
402
+ endpoint: str,
403
+ capabilities: List[str] = None,
404
+ frameworks: List[str] = None,
405
+ **kwargs
406
+ ) -> Dict[str, Any]:
407
+ """Register an agent with multiple protocol support"""
408
+
409
+ # Normalize language
410
+ if isinstance(language, str):
411
+ try:
412
+ language = AgentLanguage(language.lower())
413
+ except ValueError:
414
+ language = AgentLanguage.UNKNOWN
415
+
416
+ # Create registration
417
+ registration = AgentRegistration(
418
+ agent_id=agent_id,
419
+ name=name,
420
+ description=description,
421
+ language=language,
422
+ frameworks=frameworks or [],
423
+ protocols=[], # Will be auto-detected
424
+ endpoint=endpoint,
425
+ capabilities=capabilities or [],
426
+ **kwargs
427
+ )
428
+
429
+ return await self.register_agent(registration, auto_detect_protocols=True)
430
+
431
+ async def discover_agents(
432
+ self,
433
+ capability: str = None,
434
+ protocol: AgentProtocol = None,
435
+ language: AgentLanguage = None,
436
+ active_only: bool = True
437
+ ) -> List[Dict[str, Any]]:
438
+ """Discover agents based on various criteria"""
439
+ candidates = []
440
+
441
+ for agent_id, registration in self.agents.items():
442
+ # Skip inactive agents unless requested
443
+ if active_only and registration.status != AgentStatus.ACTIVE:
444
+ continue
445
+
446
+ # Filter by capability
447
+ if capability and capability not in registration.capabilities:
448
+ continue
449
+
450
+ # Filter by protocol
451
+ if protocol and protocol not in registration.protocols:
452
+ continue
453
+
454
+ # Filter by language
455
+ if language and registration.language != language:
456
+ continue
457
+
458
+ candidates.append({
459
+ "agent_id": agent_id,
460
+ "name": registration.name,
461
+ "description": registration.description,
462
+ "language": registration.language.value,
463
+ "protocols": [p.value for p in registration.protocols],
464
+ "capabilities": registration.capabilities,
465
+ "status": registration.status.value,
466
+ "endpoint": registration.endpoint,
467
+ "health_status": registration.health_status
468
+ })
469
+
470
+ return candidates
471
+
472
+ async def update_agent_status(
473
+ self,
474
+ agent_id: str,
475
+ status: AgentStatus,
476
+ metadata: Dict[str, Any] = None
477
+ ) -> Dict[str, Any]:
478
+ """Update agent registration status"""
479
+ if agent_id not in self.agents:
480
+ return {
481
+ "status": "error",
482
+ "message": f"Agent {agent_id} not found"
483
+ }
484
+
485
+ registration = self.agents[agent_id]
486
+ old_status = registration.status
487
+ registration.status = status
488
+
489
+ if metadata:
490
+ registration.metadata.update(metadata)
491
+
492
+ # Update storage
493
+ await self.storage.update(
494
+ "agent_registrations",
495
+ {"agent_id": agent_id},
496
+ {"status": status.value, "metadata": registration.metadata}
497
+ )
498
+
499
+ logger.info(f"Updated agent {agent_id} status: {old_status.value} -> {status.value}")
500
+
501
+ return {
502
+ "status": "success",
503
+ "agent_id": agent_id,
504
+ "old_status": old_status.value,
505
+ "new_status": status.value
506
+ }
507
+
508
+ async def get_agent_info(self, agent_id: str) -> Optional[Dict[str, Any]]:
509
+ """Get detailed information about an agent"""
510
+ registration = self.agents.get(agent_id)
511
+ if not registration:
512
+ return None
513
+
514
+ # Get health history
515
+ health_history = self.health_monitor.health_history.get(agent_id, [])
516
+
517
+ return {
518
+ "registration": asdict(registration),
519
+ "health_history": health_history[-10:], # Last 10 health checks
520
+ "uptime_percentage": self._calculate_uptime(health_history),
521
+ "average_response_time": self._calculate_avg_response_time(health_history)
522
+ }
523
+
524
+ async def create_agent_webhook(
525
+ self,
526
+ agent_id: str,
527
+ webhook_url: str,
528
+ events: List[str] = None
529
+ ) -> Dict[str, Any]:
530
+ """Create a webhook for agent events"""
531
+ if agent_id not in self.agents:
532
+ return {
533
+ "status": "error",
534
+ "message": "Agent not found"
535
+ }
536
+
537
+ registration = self.agents[agent_id]
538
+ registration.webhook_url = webhook_url
539
+
540
+ # Generate webhook secret
541
+ webhook_secret = self._generate_webhook_secret(agent_id)
542
+
543
+ # Store webhook info
544
+ webhook_info = {
545
+ "agent_id": agent_id,
546
+ "webhook_url": webhook_url,
547
+ "events": events or ["all"],
548
+ "secret": webhook_secret,
549
+ "created_at": datetime.now(timezone.utc).isoformat()
550
+ }
551
+
552
+ await self.storage.write("agent_webhooks", webhook_info)
553
+
554
+ return {
555
+ "status": "success",
556
+ "webhook_url": webhook_url,
557
+ "secret": webhook_secret,
558
+ "message": "Webhook created successfully"
559
+ }
560
+
561
+ async def handle_webhook(self, agent_id: str, payload: Dict[str, Any], signature: str) -> bool:
562
+ """Handle incoming webhook from an agent"""
563
+ try:
564
+ # Verify webhook signature
565
+ if not self._verify_webhook_signature(agent_id, payload, signature):
566
+ logger.warning(f"Invalid webhook signature for agent {agent_id}")
567
+ return False
568
+
569
+ # Process webhook based on event type
570
+ event_type = payload.get("event")
571
+
572
+ if event_type == "health.status":
573
+ await self._handle_health_webhook(agent_id, payload)
574
+ elif event_type == "capability.update":
575
+ await self._handle_capability_webhook(agent_id, payload)
576
+ elif event_type == "status.update":
577
+ await self._handle_status_webhook(agent_id, payload)
578
+
579
+ return True
580
+
581
+ except Exception as e:
582
+ logger.error(f"Error handling webhook for agent {agent_id}: {e}")
583
+ return False
584
+
585
+ async def _handle_health_webhook(self, agent_id: str, payload: Dict[str, Any]):
586
+ """Handle health status update webhook"""
587
+ if agent_id in self.agents:
588
+ registration = self.agents[agent_id]
589
+ registration.health_status = payload.get("status")
590
+ registration.last_heartbeat = datetime.now(timezone.utc).isoformat()
591
+
592
+ async def _handle_capability_webhook(self, agent_id: str, payload: Dict[str, Any]):
593
+ """Handle capability update webhook"""
594
+ if agent_id in self.agents:
595
+ registration = self.agents[agent_id]
596
+ old_capabilities = registration.capabilities.copy()
597
+ new_capabilities = payload.get("capabilities", [])
598
+
599
+ registration.capabilities = new_capabilities
600
+
601
+ # Update capability index
602
+ self._update_indexes(registration, old_capabilities)
603
+
604
+ async def _handle_status_webhook(self, agent_id: str, payload: Dict[str, Any]):
605
+ """Handle status update webhook"""
606
+ new_status = payload.get("status")
607
+ if new_status:
608
+ try:
609
+ status_enum = AgentStatus(new_status)
610
+ await self.update_agent_status(agent_id, status_enum)
611
+ except ValueError:
612
+ logger.error(f"Invalid status in webhook: {new_status}")
613
+
614
+ def _validate_registration(self, registration: AgentRegistration) -> bool:
615
+ """Validate agent registration data"""
616
+ required_fields = ["agent_id", "name", "description", "language", "endpoint"]
617
+
618
+ for field in required_fields:
619
+ if not getattr(registration, field):
620
+ return False
621
+
622
+ # Basic format validation
623
+ if not registration.endpoint.startswith(("http://", "https://")):
624
+ return False
625
+
626
+ return True
627
+
628
+ def _get_validation_errors(self, registration: AgentRegistration) -> List[str]:
629
+ """Get validation error messages"""
630
+ errors = []
631
+
632
+ required_fields = ["agent_id", "name", "description", "language", "endpoint"]
633
+ for field in required_fields:
634
+ if not getattr(registration, field):
635
+ errors.append(f"Missing required field: {field}")
636
+
637
+ if registration.endpoint and not registration.endpoint.startswith(("http://", "https://")):
638
+ errors.append("Invalid endpoint URL format")
639
+
640
+ return errors
641
+
642
+ def _generate_agent_token(self, registration: AgentRegistration) -> str:
643
+ """Generate authentication token for agent"""
644
+ data = f"{registration.agent_id}:{registration.endpoint}:{datetime.now(timezone.utc).isoformat()}"
645
+ return hashlib.sha256(data.encode()).hexdigest()
646
+
647
+ def _generate_webhook_secret(self, agent_id: str) -> str:
648
+ """Generate webhook secret for an agent"""
649
+ return secrets.token_urlsafe(32)
650
+
651
+ def _verify_webhook_signature(self, agent_id: str, payload: Dict[str, Any], signature: str) -> bool:
652
+ """Verify webhook signature"""
653
+ # In production, use proper HMAC verification
654
+ # This is a simplified implementation
655
+ expected_data = json.dumps(payload, sort_keys=True)
656
+ expected_signature = hashlib.sha256(f"{expected_data}:{agent_id}".encode()).hexdigest()
657
+ return secrets.compare_digest(signature, expected_signature)
658
+
659
+ def _update_indexes(self, registration: AgentRegistration, old_capabilities: List[str] = None):
660
+ """Update capability, protocol, and language indexes"""
661
+ agent_id = registration.agent_id
662
+
663
+ # Update capability index
664
+ if old_capabilities:
665
+ # Remove old capabilities
666
+ for cap in old_capabilities:
667
+ if cap in self.capability_index and agent_id in self.capability_index[cap]:
668
+ self.capability_index[cap].remove(agent_id)
669
+
670
+ for cap in registration.capabilities:
671
+ if cap not in self.capability_index:
672
+ self.capability_index[cap] = []
673
+ if agent_id not in self.capability_index[cap]:
674
+ self.capability_index[cap].append(agent_id)
675
+
676
+ # Update protocol index
677
+ for protocol in registration.protocols:
678
+ if protocol not in self.protocol_index:
679
+ self.protocol_index[protocol] = []
680
+ if agent_id not in self.protocol_index[protocol]:
681
+ self.protocol_index[protocol].append(agent_id)
682
+
683
+ # Update language index
684
+ language = registration.language
685
+ if language not in self.language_index:
686
+ self.language_index[language] = []
687
+ if agent_id not in self.language_index[language]:
688
+ self.language_index[language].append(agent_id)
689
+
690
+ async def _immediate_health_check(self, registration: AgentRegistration):
691
+ """Perform immediate health check on newly registered agent"""
692
+ try:
693
+ start_time = datetime.now(timezone.utc)
694
+
695
+ async with aiohttp.ClientSession() as session:
696
+ check_url = registration.health_check_url or f"{registration.endpoint}/health"
697
+ timeout = aiohttp.ClientTimeout(total=10)
698
+
699
+ async with session.get(check_url, timeout=timeout) as response:
700
+ end_time = datetime.now(timezone.utc)
701
+ response_time = (end_time - start_time).total_seconds() * 1000
702
+
703
+ registration.health_status = "healthy" if response.status == 200 else "unhealthy"
704
+ registration.last_heartbeat = end_time.isoformat()
705
+ registration.latency_ms = response_time
706
+
707
+ except Exception as e:
708
+ registration.health_status = "error"
709
+ registration.last_heartbeat = datetime.now(timezone.utc).isoformat()
710
+ logger.error(f"Immediate health check failed for {registration.agent_id}: {e}")
711
+
712
+ def _calculate_uptime(self, health_history: List[HealthCheckResult]) -> float:
713
+ """Calculate uptime percentage from health history"""
714
+ if not health_history:
715
+ return 0.0
716
+
717
+ healthy_checks = sum(1 for check in health_history if check.status == "healthy")
718
+ total_checks = len(health_history)
719
+
720
+ return (healthy_checks / total_checks) * 100 if total_checks > 0 else 0.0
721
+
722
+ def _calculate_avg_response_time(self, health_history: List[HealthCheckResult]) -> float:
723
+ """Calculate average response time from health history"""
724
+ if not health_history:
725
+ return 0.0
726
+
727
+ response_times = [check.response_time_ms for check in health_history if check.response_time_ms is not None]
728
+
729
+ return sum(response_times) / len(response_times) if response_times else 0.0
730
+
731
+ async def get_registry_stats(self) -> Dict[str, Any]:
732
+ """Get registry statistics"""
733
+ total_agents = len(self.agents)
734
+ active_agents = sum(1 for reg in self.agents.values() if reg.status == AgentStatus.ACTIVE)
735
+
736
+ language_stats = {}
737
+ for language in AgentLanguage:
738
+ count = sum(1 for reg in self.agents.values() if reg.language == language)
739
+ if count > 0:
740
+ language_stats[language.value] = count
741
+
742
+ protocol_stats = {}
743
+ for protocol in AgentProtocol:
744
+ count = sum(1 for reg in self.agents.values() if protocol in reg.protocols)
745
+ if count > 0:
746
+ protocol_stats[protocol.value] = count
747
+
748
+ return {
749
+ "total_agents": total_agents,
750
+ "active_agents": active_agents,
751
+ "inactive_agents": total_agents - active_agents,
752
+ "language_distribution": language_stats,
753
+ "protocol_distribution": protocol_stats,
754
+ "capabilities_available": list(self.capability_index.keys()),
755
+ "timestamp": datetime.now(timezone.utc).isoformat()
756
+ }
757
+
758
+ # Export classes for easy importing
759
+ __all__ = [
760
+ 'AgentProtocol',
761
+ 'AgentStatus',
762
+ 'AgentLanguage',
763
+ 'AgentRegistration',
764
+ 'HealthCheckResult',
765
+ 'ProtocolDetector',
766
+ 'HealthMonitor',
767
+ 'MultiLanguageAgentRegistry'
768
+ ]