Source code for mechaharness.tools.base
"""Tool protocol and in-process tool registry."""
from __future__ import annotations
import inspect
from collections.abc import Awaitable, Callable, Sequence
from typing import Any, Union
from mechaharness.core.access import Ability, grant_key
from mechaharness.core.types import ToolDefinition, ToolResult
ToolHandler = Callable[..., Union[str, Awaitable[str]]]
[docs]
class Tool:
"""A named callable exposed to harnesses as a ToolDefinition."""
[docs]
def __init__(
self,
name: str,
handler: ToolHandler,
*,
description: str = "",
parameters: dict[str, Any] | None = None,
grants: Sequence[object] | None = None,
ability: Ability = Ability.SIMPLE,
) -> None:
self.name = name
self.handler = handler
self.description = description
self.parameters = parameters or {"type": "object", "properties": {}}
self.grants = [grant_key(item) for item in (grants or [])]
self.ability = ability
[docs]
def definition(self) -> ToolDefinition:
return ToolDefinition(
name=self.name,
description=self.description,
parameters=self.parameters,
)
[docs]
async def invoke(self, arguments: dict[str, Any], tool_call_id: str) -> ToolResult:
try:
result = self.handler(**arguments)
if inspect.isawaitable(result):
result = await result
return ToolResult(tool_call_id=tool_call_id, content=str(result))
except TypeError as exc:
return ToolResult(
tool_call_id=tool_call_id,
content=f"Invalid arguments for tool {self.name}: {exc}",
is_error=True,
)
except Exception as exc: # noqa: BLE001 - surface tool failures to the model
return ToolResult(
tool_call_id=tool_call_id,
content=f"Tool {self.name} failed: {exc}",
is_error=True,
)
[docs]
class ToolRegistry:
"""In-process map of tool name → ``Tool`` for harness tool calling."""
[docs]
def register(self, tool: Tool) -> None:
"""Add or replace a tool by name."""
self._tools[tool.name] = tool
[docs]
def tool(
self,
name: str | None = None,
*,
description: str = "",
parameters: dict[str, Any] | None = None,
grants: Sequence[object] | None = None,
ability: Ability = Ability.SIMPLE,
) -> Callable[[ToolHandler], ToolHandler]:
"""Decorator that registers the wrapped function as a tool."""
def decorator(fn: ToolHandler) -> ToolHandler:
tool_name = name or fn.__name__
self.register(
Tool(
tool_name,
fn,
description=description or (fn.__doc__ or "").strip(),
parameters=parameters,
grants=grants,
ability=ability,
)
)
return fn
return decorator
[docs]
def definitions(self) -> list[ToolDefinition]:
"""Provider-agnostic schemas for the current tool set."""
return [t.definition() for t in self._tools.values()]
[docs]
def get(self, name: str) -> Tool:
"""Return a registered tool or raise ``KeyError``."""
try:
return self._tools[name]
except KeyError as exc:
raise KeyError(f"Unknown tool: {name}") from exc
[docs]
async def execute(self, name: str, arguments: dict[str, Any], tool_call_id: str) -> ToolResult:
return await self.get(name).invoke(arguments, tool_call_id)
def __contains__(self, name: str) -> bool:
return name in self._tools
def __len__(self) -> int:
return len(self._tools)