From 92aa33c6d3a4fc1dfc935f6bd850ba04146a5766 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 23 Feb 2026 11:30:36 -0500 Subject: [PATCH] feat(core): Wire tool toggles to AI provider tool declaration payload --- ai_client.py | 111 ++++++++++++++++++------------- gui.py | 1 + tests/test_agent_tools_wiring.py | 23 +++++++ 3 files changed, 89 insertions(+), 46 deletions(-) create mode 100644 tests/test_agent_tools_wiring.py diff --git a/ai_client.py b/ai_client.py index e38a0cf..3de4e62 100644 --- a/ai_client.py +++ b/ai_client.py @@ -286,37 +286,53 @@ def _list_anthropic_models() -> list[str]: TOOL_NAME = "run_powershell" +_agent_tools: dict = {} + +def set_agent_tools(tools: dict): + global _agent_tools, _CACHED_ANTHROPIC_TOOLS + _agent_tools = tools + _CACHED_ANTHROPIC_TOOLS = None + def _build_anthropic_tools() -> list[dict]: """Build the full Anthropic tools list: run_powershell + MCP file tools.""" mcp_tools = [] for spec in mcp_client.MCP_TOOL_SPECS: - mcp_tools.append({ - "name": spec["name"], - "description": spec["description"], - "input_schema": spec["parameters"], - }) - powershell_tool = { - "name": TOOL_NAME, - "description": ( - "Run a PowerShell script within the project base_dir. " - "Use this to create, edit, rename, or delete files and directories. " - "The working directory is set to base_dir automatically. " - "Always prefer targeted edits over full rewrites where possible. " - "stdout and stderr are returned to you as the result." - ), - "input_schema": { - "type": "object", - "properties": { - "script": { - "type": "string", - "description": "The PowerShell script to execute." - } + if _agent_tools.get(spec["name"], True): + mcp_tools.append({ + "name": spec["name"], + "description": spec["description"], + "input_schema": spec["parameters"], + }) + + tools_list = mcp_tools + if _agent_tools.get(TOOL_NAME, True): + powershell_tool = { + "name": TOOL_NAME, + "description": ( + "Run a PowerShell script within the project base_dir. " + "Use this to create, edit, rename, or delete files and directories. " + "The working directory is set to base_dir automatically. " + "Always prefer targeted edits over full rewrites where possible. " + "stdout and stderr are returned to you as the result." + ), + "input_schema": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The PowerShell script to execute." + } + }, + "required": ["script"] }, - "required": ["script"] - }, - "cache_control": {"type": "ephemeral"}, - } - return mcp_tools + [powershell_tool] + "cache_control": {"type": "ephemeral"}, + } + tools_list.append(powershell_tool) + elif tools_list: + # Anthropic requires the LAST tool to have cache_control for the prefix caching to work optimally on tools + tools_list[-1]["cache_control"] = {"type": "ephemeral"} + + return tools_list _ANTHROPIC_TOOLS = _build_anthropic_tools() @@ -338,6 +354,8 @@ def _gemini_tool_declaration(): # MCP file tools for spec in mcp_client.MCP_TOOL_SPECS: + if not _agent_tools.get(spec["name"], True): + continue props = {} for pname, pdef in spec["parameters"].get("properties", {}).items(): props[pname] = types.Schema( @@ -355,27 +373,28 @@ def _gemini_tool_declaration(): )) # PowerShell tool - declarations.append(types.FunctionDeclaration( - name=TOOL_NAME, - description=( - "Run a PowerShell script within the project base_dir. " - "Use this to create, edit, rename, or delete files and directories. " - "The working directory is set to base_dir automatically. " - "stdout and stderr are returned to you as the result." - ), - parameters=types.Schema( - type=types.Type.OBJECT, - properties={ - "script": types.Schema( - type=types.Type.STRING, - description="The PowerShell script to execute." - ) - }, - required=["script"] - ), - )) + if _agent_tools.get(TOOL_NAME, True): + declarations.append(types.FunctionDeclaration( + name=TOOL_NAME, + description=( + "Run a PowerShell script within the project base_dir. " + "Use this to create, edit, rename, or delete files and directories. " + "The working directory is set to base_dir automatically. " + "stdout and stderr are returned to you as the result." + ), + parameters=types.Schema( + type=types.Type.OBJECT, + properties={ + "script": types.Schema( + type=types.Type.STRING, + description="The PowerShell script to execute." + ) + }, + required=["script"] + ), + )) - return types.Tool(function_declarations=declarations) + return types.Tool(function_declarations=declarations) if declarations else None def _run_script(script: str, base_dir: str) -> str: diff --git a/gui.py b/gui.py index 68390b9..2d5fec1 100644 --- a/gui.py +++ b/gui.py @@ -1203,6 +1203,7 @@ class App: if global_sp: combined_sp.append(global_sp.strip()) if project_sp: combined_sp.append(project_sp.strip()) ai_client.set_custom_system_prompt("\n\n".join(combined_sp)) + ai_client.set_agent_tools(self.project.get("agent", {}).get("tools", {})) temp = dpg.get_value("ai_temperature") if dpg.does_item_exist("ai_temperature") else 0.0 max_tok = dpg.get_value("ai_max_tokens") if dpg.does_item_exist("ai_max_tokens") else 8192 trunc = dpg.get_value("ai_history_trunc") if dpg.does_item_exist("ai_history_trunc") else 8000 diff --git a/tests/test_agent_tools_wiring.py b/tests/test_agent_tools_wiring.py new file mode 100644 index 0000000..fdc0780 --- /dev/null +++ b/tests/test_agent_tools_wiring.py @@ -0,0 +1,23 @@ +import pytest +from ai_client import set_agent_tools, _build_anthropic_tools + +def test_agent_tools_wiring(): + # Only enable read_file and run_powershell + agent_tools = { + "run_powershell": True, + "read_file": True, + "list_directory": False, + "search_files": False, + "get_file_summary": False, + "web_search": False, + "fetch_url": False + } + set_agent_tools(agent_tools) + + anth_tools = _build_anthropic_tools() + tool_names = [t["name"] for t in anth_tools] + + assert "read_file" in tool_names + assert "run_powershell" in tool_names + assert "list_directory" not in tool_names + assert "web_search" not in tool_names