"""Test tool — run a user tool's execute() in isolation and return the result or traceback."""
import importlib
import importlib.util
import sys
import traceback
from pathlib import Path
from navi.config import settings
from ._internal.base import Tool, ToolResult
class TestToolTool(Tool):
name = "test_tool"
description = (
"Run a user tool's execute() function with given params and return the result or full traceback. "
"Always use this after writing or editing a tool file to verify it works before calling reload_tools."
)
parameters = {
"type": "object",
"properties": {
"tool_name": {
"type": "string",
"description": "Name of the tool to test (filename without .py, e.g. 'my_tool').",
},
"params": {
"type": "object",
"description": "Parameters dict to pass to execute(). Omit or pass {} for tools with no required params.",
},
},
"required": ["tool_name"],
}
async def execute(self, params: dict) -> ToolResult:
tool_name = (params.get("tool_name") or "").strip()
test_params: dict = params.get("params") or {}
if not tool_name:
return ToolResult(success=False, output="tool_name is required.", error="missing tool_name")
tool_path = Path(settings.tools_dir) / f"{tool_name}.py"
if not tool_path.exists():
return ToolResult(
success=False,
output=f"File not found: {tool_path}",
error="file not found",
)
# Force a fresh import from disk — bypasses any cached (possibly stale) module
module_key = f"_test_tool_run_{tool_name}"
spec = importlib.util.spec_from_file_location(module_key, tool_path)
if spec is None or spec.loader is None:
return ToolResult(success=False, output=f"Cannot load spec for {tool_path}", error="spec error")
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module) # type: ignore[union-attr]
except Exception:
tb = traceback.format_exc()
return ToolResult(success=False, output=f"Module load error:\n{tb}", error="load error")
execute_fn = getattr(module, "execute", None)
if execute_fn is None:
return ToolResult(success=False, output="Module has no execute() function.", error="missing execute")
try:
result = await execute_fn(test_params)
except Exception:
tb = traceback.format_exc()
return ToolResult(success=False, output=f"execute() raised an exception:\n{tb}", error="runtime error")
return ToolResult(success=True, output=f"OK\n\nResult: {result}")