"""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 .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}")