feat(ai): Harden tool access exclusion across all providers
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
4. [x] **Track: Codebase Audit and Cleanup**
|
4. [x] **Track: Codebase Audit and Cleanup**
|
||||||
*Link: [./tracks/codebase_audit_20260308/](./tracks/codebase_audit_20260308/)*
|
*Link: [./tracks/codebase_audit_20260308/](./tracks/codebase_audit_20260308/)*
|
||||||
|
|
||||||
5. [ ] **Track: Expanded Test Coverage and Stress Testing**
|
5. [~] **Track: Expanded Test Coverage and Stress Testing**
|
||||||
*Link: [./tracks/test_coverage_expansion_20260309/](./tracks/test_coverage_expansion_20260309/)*
|
*Link: [./tracks/test_coverage_expansion_20260309/](./tracks/test_coverage_expansion_20260309/)*
|
||||||
|
|
||||||
6. [ ] **Track: Beads Mode Integration**
|
6. [ ] **Track: Beads Mode Integration**
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Implementation Plan: Expanded Test Coverage and Stress Testing
|
# Implementation Plan: Expanded Test Coverage and Stress Testing
|
||||||
|
|
||||||
## Phase 1: Tool Accessibility and State Unit Tests
|
## Phase 1: Tool Accessibility and State Unit Tests
|
||||||
- [ ] Task: Review current tool registration and disabling logic in `src/mcp_client.py` and `src/api_hooks.py`.
|
- [x] Task: Review current tool registration and disabling logic in `src/mcp_client.py` and `src/api_hooks.py`.
|
||||||
- [ ] Task: Write Tests: Create unit tests in `tests/test_agent_tools_wiring.py` (or similar) to verify turning a tool off removes it from the agent's available tool list.
|
- [~] Task: Write Tests: Create unit tests in `tests/test_agent_tools_wiring.py` (or similar) to verify turning a tool off removes it from the agent's available tool list.
|
||||||
- [ ] Task: Implement: If tests fail due to missing logic, update the tool filtering implementation to ensure disabled tools are strictly excluded from the context sent to the provider.
|
- [ ] Task: Implement: If tests fail due to missing logic, update the tool filtering implementation to ensure disabled tools are strictly excluded from the context sent to the provider.
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Tool Accessibility and State Unit Tests' (Protocol in workflow.md)
|
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Tool Accessibility and State Unit Tests' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -396,7 +396,7 @@ def reset_session() -> None:
|
|||||||
global _anthropic_client, _anthropic_history
|
global _anthropic_client, _anthropic_history
|
||||||
global _deepseek_client, _deepseek_history
|
global _deepseek_client, _deepseek_history
|
||||||
global _minimax_client, _minimax_history
|
global _minimax_client, _minimax_history
|
||||||
global _CACHED_ANTHROPIC_TOOLS
|
global _CACHED_ANTHROPIC_TOOLS, _CACHED_DEEPSEEK_TOOLS
|
||||||
global _gemini_cli_adapter
|
global _gemini_cli_adapter
|
||||||
if _gemini_client and _gemini_cache:
|
if _gemini_client and _gemini_cache:
|
||||||
try:
|
try:
|
||||||
@@ -425,6 +425,7 @@ def reset_session() -> None:
|
|||||||
with _minimax_history_lock:
|
with _minimax_history_lock:
|
||||||
_minimax_history = []
|
_minimax_history = []
|
||||||
_CACHED_ANTHROPIC_TOOLS = None
|
_CACHED_ANTHROPIC_TOOLS = None
|
||||||
|
_CACHED_DEEPSEEK_TOOLS = None
|
||||||
file_cache.reset_client()
|
file_cache.reset_client()
|
||||||
|
|
||||||
def get_gemini_cache_stats() -> dict[str, Any]:
|
def get_gemini_cache_stats() -> dict[str, Any]:
|
||||||
@@ -501,13 +502,14 @@ _agent_tools: dict[str, bool] = {}
|
|||||||
|
|
||||||
def set_agent_tools(tools: dict[str, bool]) -> None:
|
def set_agent_tools(tools: dict[str, bool]) -> None:
|
||||||
"""Configures which tools are enabled for the AI agent."""
|
"""Configures which tools are enabled for the AI agent."""
|
||||||
global _agent_tools, _CACHED_ANTHROPIC_TOOLS
|
global _agent_tools, _CACHED_ANTHROPIC_TOOLS, _CACHED_DEEPSEEK_TOOLS
|
||||||
_agent_tools = tools
|
_agent_tools = tools
|
||||||
_CACHED_ANTHROPIC_TOOLS = None
|
_CACHED_ANTHROPIC_TOOLS = None
|
||||||
|
_CACHED_DEEPSEEK_TOOLS = None
|
||||||
|
|
||||||
def set_tool_preset(preset_name: Optional[str]) -> None:
|
def set_tool_preset(preset_name: Optional[str]) -> None:
|
||||||
"""Loads a tool preset and applies it via set_agent_tools."""
|
"""Loads a tool preset and applies it via set_agent_tools."""
|
||||||
global _agent_tools, _CACHED_ANTHROPIC_TOOLS, _tool_approval_modes, _active_tool_preset
|
global _agent_tools, _CACHED_ANTHROPIC_TOOLS, _CACHED_DEEPSEEK_TOOLS, _tool_approval_modes, _active_tool_preset
|
||||||
_tool_approval_modes = {}
|
_tool_approval_modes = {}
|
||||||
if not preset_name or preset_name == "None":
|
if not preset_name or preset_name == "None":
|
||||||
# Enable all tools if no preset
|
# Enable all tools if no preset
|
||||||
@@ -534,6 +536,7 @@ def set_tool_preset(preset_name: Optional[str]) -> None:
|
|||||||
sys.stderr.write(f"[ERROR] Failed to set tool preset '{preset_name}': {e}\n")
|
sys.stderr.write(f"[ERROR] Failed to set tool preset '{preset_name}': {e}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
_CACHED_ANTHROPIC_TOOLS = None
|
_CACHED_ANTHROPIC_TOOLS = None
|
||||||
|
_CACHED_DEEPSEEK_TOOLS = None
|
||||||
|
|
||||||
def set_bias_profile(profile_name: Optional[str]) -> None:
|
def set_bias_profile(profile_name: Optional[str]) -> None:
|
||||||
"""Sets the active tool bias profile for tuning model behavior."""
|
"""Sets the active tool bias profile for tuning model behavior."""
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import pytest
|
||||||
|
from src import ai_client
|
||||||
|
from src import mcp_client
|
||||||
|
|
||||||
|
def test_set_agent_tools_clears_caches():
|
||||||
|
ai_client._CACHED_ANTHROPIC_TOOLS = [{"dummy": "data"}]
|
||||||
|
ai_client._CACHED_DEEPSEEK_TOOLS = [{"dummy": "data"}]
|
||||||
|
|
||||||
|
ai_client.set_agent_tools({"read_file": True})
|
||||||
|
|
||||||
|
assert ai_client._CACHED_ANTHROPIC_TOOLS is None
|
||||||
|
assert ai_client._CACHED_DEEPSEEK_TOOLS is None
|
||||||
|
|
||||||
|
def test_gemini_tool_declaration_excludes_disabled():
|
||||||
|
# Test explicit disable
|
||||||
|
ai_client.set_agent_tools({"read_file": False})
|
||||||
|
tool = ai_client._gemini_tool_declaration()
|
||||||
|
names = [f.name for f in tool.function_declarations] if tool else []
|
||||||
|
assert "read_file" not in names
|
||||||
|
|
||||||
|
# Test enable only one
|
||||||
|
all_tools = {name: False for name in mcp_client.TOOL_NAMES}
|
||||||
|
all_tools[ai_client.TOOL_NAME] = False
|
||||||
|
all_tools["read_file"] = True
|
||||||
|
ai_client.set_agent_tools(all_tools)
|
||||||
|
tool = ai_client._gemini_tool_declaration()
|
||||||
|
names = [f.name for f in tool.function_declarations] if tool else []
|
||||||
|
assert "read_file" in names
|
||||||
|
assert "write_file" not in names
|
||||||
|
assert ai_client.TOOL_NAME not in names
|
||||||
|
|
||||||
|
def test_build_anthropic_tools_excludes_disabled():
|
||||||
|
# Test explicit disable
|
||||||
|
ai_client.set_agent_tools({"read_file": False})
|
||||||
|
tools = ai_client._build_anthropic_tools()
|
||||||
|
names = [t["name"] for t in tools]
|
||||||
|
assert "read_file" not in names
|
||||||
|
|
||||||
|
# Test enable only one
|
||||||
|
all_tools = {name: False for name in mcp_client.TOOL_NAMES}
|
||||||
|
all_tools[ai_client.TOOL_NAME] = False
|
||||||
|
all_tools["read_file"] = True
|
||||||
|
ai_client.set_agent_tools(all_tools)
|
||||||
|
tools = ai_client._build_anthropic_tools()
|
||||||
|
names = [t["name"] for t in tools]
|
||||||
|
assert "read_file" in names
|
||||||
|
assert "write_file" not in names
|
||||||
|
assert ai_client.TOOL_NAME not in names
|
||||||
|
|
||||||
|
def test_build_deepseek_tools_excludes_disabled():
|
||||||
|
# Test explicit disable
|
||||||
|
ai_client.set_agent_tools({"read_file": False})
|
||||||
|
tools = ai_client._build_deepseek_tools()
|
||||||
|
names = [t["function"]["name"] for t in tools]
|
||||||
|
assert "read_file" not in names
|
||||||
|
|
||||||
|
# Test enable only one
|
||||||
|
all_tools = {name: False for name in mcp_client.TOOL_NAMES}
|
||||||
|
all_tools[ai_client.TOOL_NAME] = False
|
||||||
|
all_tools["read_file"] = True
|
||||||
|
ai_client.set_agent_tools(all_tools)
|
||||||
|
tools = ai_client._build_deepseek_tools()
|
||||||
|
names = [t["function"]["name"] for t in tools]
|
||||||
|
assert "read_file" in names
|
||||||
|
assert "write_file" not in names
|
||||||
|
assert ai_client.TOOL_NAME not in names
|
||||||
Reference in New Issue
Block a user