"""Tool registry: OpenAI schemas plus dispatch to sync implementations.""" from __future__ import annotations from collections.abc import Callable from typing import TYPE_CHECKING, Any, cast if TYPE_CHECKING: from pydantic import BaseModel ToolImpl = Callable[..., str] class _RegisteredTool: """A registered tool: its OpenAI schema plus its synchronous impl.""" __slots__ = ("args_schema", "description", "impl", "name") def __init__( self, name: str, description: str, args_schema: dict[str, object], impl: ToolImpl, ) -> None: self.name = name self.description = description self.args_schema = args_schema self.impl = impl class ToolRegistry: """Holds tool schemas and dispatches tool calls to their implementations.""" def __init__(self) -> None: self._tools: dict[str, _RegisteredTool] = {} def register( self, name: str, description: str, args_schema: dict[str, object], impl: ToolImpl, ) -> None: """Register a tool under ``name`` with its OpenAI args schema.""" self._tools[name] = _RegisteredTool(name, description, args_schema, impl) def to_openai_tools(self) -> list[dict[str, object]]: """Return the registered tools in OpenAI function-calling format.""" return [ { "type": "function", "function": { "name": tool.name, "description": tool.description, "parameters": tool.args_schema, }, } for tool in self._tools.values() ] def execute(self, name: str, args: dict[str, str], **impl_kwargs: Any) -> str: """Dispatch a tool call; unknown tools yield a friendly message. ``impl_kwargs`` (e.g. ``channel``) are forwarded to the impl so tools can access per-invocation context. """ tool = self._tools.get(name) if tool is None: return f"Unknown tool: {name}" return tool.impl(name, args, **impl_kwargs) _default_registry: ToolRegistry | None = None def get_tool_registry() -> ToolRegistry: """Return the shared tool registry, seeded with the channel-members tool.""" global _default_registry if _default_registry is None: from vibe_bot.tools import get_channel_members raw_schema = get_channel_members.args_schema if isinstance(raw_schema, dict): args_schema: dict[str, object] = raw_schema else: # A LangChain @tool exposes args_schema as a pydantic model class. args_schema = cast("type[BaseModel]", raw_schema).model_json_schema() registry = ToolRegistry() registry.register( get_channel_members.name, get_channel_members.description or "", args_schema, _channel_members_tool, ) _default_registry = registry return _default_registry def _channel_members_tool(name: str, args: dict[str, str], **kwargs: Any) -> str: """Adapt the registry dispatch to ``get_channel_members_impl(channel)``.""" from vibe_bot.tools import get_channel_members_impl channel = kwargs.get("channel") return get_channel_members_impl(channel)