from __future__ import annotations from typing import Any from nanobot.agent.tools.base import Tool from nanobot.agent.tools.registry import ToolRegistry class _FakeTool(Tool): def __init__(self, name: str): self._name = name @property def name(self) -> str: return self._name @property def description(self) -> str: return f"{self._name} tool" @property def parameters(self) -> dict[str, Any]: return {"type": "object", "properties": {}} async def execute(self, **kwargs: Any) -> Any: return kwargs def _tool_names(definitions: list[dict[str, Any]]) -> list[str]: names: list[str] = [] for definition in definitions: fn = definition.get("function", {}) names.append(fn.get("name", "")) return names def test_get_definitions_orders_builtins_then_mcp_tools() -> None: registry = ToolRegistry() registry.register(_FakeTool("mcp_git_status")) registry.register(_FakeTool("write_file")) registry.register(_FakeTool("mcp_fs_list")) registry.register(_FakeTool("read_file")) assert _tool_names(registry.get_definitions()) == [ "read_file", "write_file", "mcp_fs_list", "mcp_git_status", ] def test_prepare_call_read_file_rejects_non_object_params_with_actionable_hint() -> None: registry = ToolRegistry() registry.register(_FakeTool("read_file")) tool, params, error = registry.prepare_call("read_file", ["foo.txt"]) assert tool is None assert params == ["foo.txt"] assert error is not None assert "must be a JSON object" in error assert "Use named parameters" in error def test_prepare_call_other_tools_keep_generic_object_validation() -> None: registry = ToolRegistry() registry.register(_FakeTool("grep")) tool, params, error = registry.prepare_call("grep", ["TODO"]) assert tool is not None assert params == ["TODO"] assert error == "Error: Invalid parameters for tool 'grep': parameters must be an object, got list"