""" Tool Registry Central registry for managing tools with dynamic registration, discovery, and statistics. """ from typing import Dict, List, Optional, Callable, Any from dataclasses import dataclass, field from datetime import datetime import asyncio @dataclass class ToolMetadata: """Tool metadata""" name: str display_name: str description: str version: str author: Optional[str] = None tags: List[str] = field(default_factory=list) dependencies: List[str] = field(default_factory=list) enabled: bool = True registered_at: datetime = field(default_factory=datetime.utcnow) # Statistics call_count: int = 0 error_count: int = 0 total_duration_ms: int = 0 @property def avg_duration_ms(self) -> int: if self.call_count == 0: return 0 return self.total_duration_ms // self.call_count @property def error_rate(self) -> float: if self.call_count == 0: return 0.0 return self.error_count / self.call_count class ToolRegistry: """Tool registry center for dynamic tool management""" def __init__(self): self._tools: Dict[str, ToolMetadata] = {} self._executors: Dict[str, Callable] = {} self._configs: Dict[str, dict] = {} self._lock = asyncio.Lock() # === Registration methods === async def register( self, manifest_path: str, executor: Callable, config: Optional[dict] = None, ) -> ToolMetadata: """Register a tool""" import yaml from pathlib import Path manifest_file = Path(manifest_path) if not manifest_file.exists(): raise FileNotFoundError(f"Manifest not found: {manifest_path}") with open(manifest_file, encoding="utf-8") as f: data = yaml.safe_load(f) from tools.schemas.validator import validate_manifest manifest = validate_manifest(data) metadata = ToolMetadata( name=manifest.name, display_name=manifest.display_name, description=manifest.description, version=manifest.version, author=manifest.author, tags=manifest.tags or [], dependencies=manifest.dependencies or [], enabled=manifest.enabled, ) async with self._lock: self._tools[manifest.name] = metadata self._executors[manifest.name] = executor if config: self._configs[manifest.name] = config return metadata async def register_from_dict( self, manifest_data: dict, executor: Callable, config: Optional[dict] = None, ) -> ToolMetadata: """Register a tool from manifest data dict""" from tools.schemas.validator import validate_manifest manifest = validate_manifest(manifest_data) metadata = ToolMetadata( name=manifest.name, display_name=manifest.display_name, description=manifest.description, version=manifest.version, author=manifest.author, tags=manifest.tags or [], dependencies=manifest.dependencies or [], enabled=manifest.enabled, ) async with self._lock: self._tools[manifest.name] = metadata self._executors[manifest.name] = executor if config: self._configs[manifest.name] = config return metadata async def unregister(self, name: str) -> bool: """Unregister a tool""" async with self._lock: if name in self._tools: del self._tools[name] del self._executors[name] self._configs.pop(name, None) return True return False async def enable(self, name: str) -> None: """Enable a tool""" async with self._lock: if name in self._tools: self._tools[name].enabled = True async def disable(self, name: str) -> None: """Disable a tool""" async with self._lock: if name in self._tools: self._tools[name].enabled = False # === Query methods === async def get(self, name: str) -> Optional[ToolMetadata]: """Get tool metadata""" return self._tools.get(name) async def get_executor(self, name: str) -> Optional[Callable]: """Get tool executor""" return self._executors.get(name) async def get_config(self, name: str) -> dict: """Get tool configuration""" return self._configs.get(name, {}) async def list_all(self) -> List[ToolMetadata]: """List all tools""" return list(self._tools.values()) async def list_enabled(self) -> List[ToolMetadata]: """List enabled tools""" return [t for t in self._tools.values() if t.enabled] async def list_by_tag(self, tag: str) -> List[ToolMetadata]: """List tools by tag""" return [t for t in self._tools.values() if tag in t.tags] async def search(self, query: str) -> List[ToolMetadata]: """Search tools""" query_lower = query.lower() return [ t for t in self._tools.values() if query_lower in t.name.lower() or query_lower in t.description.lower() or query_lower in t.display_name.lower() ] # === Statistics methods === async def record_call( self, name: str, duration_ms: int, error: bool = False, ) -> None: """Record a tool call""" async with self._lock: if name in self._tools: tool = self._tools[name] tool.call_count += 1 tool.total_duration_ms += duration_ms if error: tool.error_count += 1 async def get_stats(self) -> dict: """Get registry statistics""" tools = list(self._tools.values()) return { "total_tools": len(tools), "enabled_tools": sum(1 for t in tools if t.enabled), "total_calls": sum(t.call_count for t in tools), "total_errors": sum(t.error_count for t in tools), "avg_error_rate": sum(t.error_rate for t in tools) / len(tools) if tools else 0, } # Global registry instance _registry: Optional[ToolRegistry] = None def get_registry() -> ToolRegistry: """Get the global tool registry instance""" global _registry if _registry is None: _registry = ToolRegistry() return _registry