Implement 6 new agent tools — write_file, make_dir, delete_file, str_replace, patch_apply, run_command — bringing the agent from read-only observer to active code modifier. All write/shell operations are gated through the existing permissions service. Also fix a bug where qwen3.5 thinking mode produces reasoning tokens but no content after tool results, causing the agent to silently exit. The loop now detects reasoning-only responses, retries twice, then injects a nudge message to break the model out of its thinking loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
2.5 KiB
Python
85 lines
2.5 KiB
Python
"""Tool registration and schema export."""
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from app.models.config import AppConfig
|
|
from app.tools.base import BaseTool
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ToolRegistry:
|
|
"""Registry of available tools, keyed by name."""
|
|
|
|
def __init__(self) -> None:
|
|
self._tools: dict[str, BaseTool] = {}
|
|
|
|
def register(self, tool: BaseTool) -> None:
|
|
"""Register a tool instance. Raises ValueError on duplicate name."""
|
|
if tool.name in self._tools:
|
|
raise ValueError(f"Duplicate tool name: '{tool.name}'")
|
|
self._tools[tool.name] = tool
|
|
logger.debug("Registered tool: %s", tool.name)
|
|
|
|
def get(self, name: str) -> BaseTool | None:
|
|
"""Look up a tool by name."""
|
|
return self._tools.get(name)
|
|
|
|
def get_all(self) -> dict[str, BaseTool]:
|
|
"""Return all registered tools."""
|
|
return dict(self._tools)
|
|
|
|
def get_openai_tools_schema(self) -> list[dict[str, Any]]:
|
|
"""Return OpenAI function-calling schemas for all registered tools."""
|
|
return [tool.get_openai_schema() for tool in self._tools.values()]
|
|
|
|
|
|
def create_default_registry(workspace_root: Path, config: AppConfig) -> ToolRegistry:
|
|
"""Create a ToolRegistry populated with all built-in tools."""
|
|
# Read tools
|
|
from app.tools.filesystem import ListDirTool, ReadFileTool
|
|
|
|
# Write tools
|
|
from app.tools.filesystem import DeleteFileTool, MakeDirTool, WriteFileTool
|
|
|
|
# Edit tools
|
|
from app.tools.edit import PatchApplyTool, StrReplaceTool
|
|
|
|
# Shell tools
|
|
from app.tools.shell import RunCommandTool
|
|
|
|
# Control flow
|
|
from app.tools.finish import FinishTool
|
|
|
|
# Search tools
|
|
from app.tools.search import FindFilesTool, GrepFilesTool
|
|
|
|
registry = ToolRegistry()
|
|
|
|
# Read
|
|
registry.register(ReadFileTool(workspace_root, config))
|
|
registry.register(ListDirTool(workspace_root, config))
|
|
|
|
# Search
|
|
registry.register(GrepFilesTool(workspace_root, config))
|
|
registry.register(FindFilesTool(workspace_root, config))
|
|
|
|
# Write
|
|
registry.register(WriteFileTool(workspace_root, config))
|
|
registry.register(MakeDirTool(workspace_root, config))
|
|
registry.register(DeleteFileTool(workspace_root, config))
|
|
|
|
# Edit
|
|
registry.register(StrReplaceTool(workspace_root, config))
|
|
registry.register(PatchApplyTool(workspace_root, config))
|
|
|
|
# Shell
|
|
registry.register(RunCommandTool(workspace_root, config))
|
|
|
|
# Control flow
|
|
registry.register(FinishTool(workspace_root, config))
|
|
|
|
return registry
|