"""Global tool registry for AI Assistant plugin tools. Plugins can register tools that AI agents can call during chat sessions. Each tool declares a name, description, JSON schema for parameters, and an async handler. Tools can optionally require specific RBAC permissions. """ from __future__ import annotations import logging from dataclasses import dataclass, field from typing import Any, Awaitable, Callable, Protocol logger = logging.getLogger(__name__) class ToolHandler(Protocol): async def __call__( self, arguments: dict[str, Any], context: dict[str, Any], ) -> str: ... @dataclass class AITool: """Represents a tool that an AI agent can call.""" name: str description: str parameters: dict[str, Any] # JSON Schema for parameters handler: ToolHandler plugin_name: str = "" required_permission: str | None = None # e.g. "mail:send" category: str = "general" def to_openai_schema(self) -> dict[str, Any]: """Convert to OpenAI function-calling tool schema.""" return { "type": "function", "function": { "name": self.name, "description": self.description, "parameters": self.parameters, }, } class ToolRegistry: """Singleton registry for AI tools.""" _instance: ToolRegistry | None = None def __new__(cls) -> ToolRegistry: if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._tools: dict[str, AITool] = {} return cls._instance def register( self, name: str, description: str, parameters: dict[str, Any], handler: ToolHandler, plugin_name: str = "", required_permission: str | None = None, category: str = "general", ) -> None: """Register a tool.""" tool = AITool( name=name, description=description, parameters=parameters, handler=handler, plugin_name=plugin_name, required_permission=required_permission, category=category, ) self._tools[name] = tool logger.info("AI tool registered: %s (plugin=%s)", name, plugin_name) def unregister(self, name: str) -> None: """Unregister a tool by name.""" self._tools.pop(name, None) def unregister_plugin(self, plugin_name: str) -> None: """Unregister all tools from a plugin.""" to_remove = [ name for name, tool in self._tools.items() if tool.plugin_name == plugin_name ] for name in to_remove: self._tools.pop(name, None) def get(self, name: str) -> AITool | None: return self._tools.get(name) def get_all(self) -> list[AITool]: return list(self._tools.values()) def get_by_names(self, names: list[str]) -> list[AITool]: return [self._tools[name] for name in names if name in self._tools] def list_for_api(self) -> list[dict[str, Any]]: """Return tool list for API response.""" return [ { "name": tool.name, "description": tool.description, "parameters": tool.parameters, "plugin_name": tool.plugin_name, "required_permission": tool.required_permission, "category": tool.category, } for tool in self._tools.values() ] def get_tool_registry() -> ToolRegistry: """Get the global tool registry singleton.""" return ToolRegistry()