refactor: nuke legacy harness, drop sdk_ prefixes

The SDK harness is the only path now; legacy host-side code is gone.
File names no longer carry the ``sdk_`` distinction.

Deleted legacy host-side modules:
- strix/agents/StrixAgent/ (template moved to strix/agents/prompts/)
- strix/agents/base_agent.py, state.py
- strix/llm/llm.py, config.py
- strix/runtime/docker_runtime.py, runtime.py
- strix/tools/executor.py, agents_graph/agents_graph_actions.py
- strix/interface/sdk_dispatch.py + the env-flag dispatch in cli.py

Renamed (drop ``sdk_`` prefix):
- strix/sdk_entry.py → strix/entry.py
- strix/agents/sdk_factory.py → strix/agents/factory.py
- strix/agents/sdk_prompt.py → strix/agents/prompt.py
- strix/tools/<x>/<x>_sdk_tool[s].py → strix/tools/<x>/tool[s].py
- strix/tools/_legacy_adapter.py → strix/tools/_state_adapter.py
- ``_legacy`` aliases inside the wrappers → ``_impl``

CLI + TUI now call ``run_strix_scan`` directly — they build the
sandbox image / sources_path locally and rely on
``session_manager.cleanup`` (called inside ``run_strix_scan``'s finally)
for teardown. Three TUI handlers that reached into legacy multi-agent
globals (``_agent_instances``, ``send_user_message_to_agent``,
``stop_agent``) are now no-ops with a TODO; reconnecting them to the
``AgentMessageBus`` is a follow-up.

Tracer.get_total_llm_stats no longer reaches into the deleted
``agents_graph_actions`` globals — the orchestration hooks now feed the
tracer via ``Tracer.record_llm_usage`` (live + completed buckets).
finish_scan's ``_check_active_agents`` and load_skill's runtime
``_agent_instances`` reach-in are no-op stubs; the
``AgentMessageBus`` is the source of truth post-migration.

llm/utils.py rewritten to keep only the streaming-parser helpers
(``normalize_tool_format``, ``parse_tool_invocations``,
``fix_incomplete_tool_call``, ``format_tool_call``, ``clean_content``).
``STRIX_MODEL_MAP`` moved to ``llm/multi_provider_setup.py`` (its only
remaining caller).

Per-file ruff ignores added for legacy interface modules (TUI / main /
CLI / utils / streaming_parser / tool_components) and tracer.py —
pre-existing PLC0415/BLE001/PLR0915 patterns are out of scope.

Tests: 287/287 passing. Renamed test files to drop ``sdk_`` prefix.
``test_tracer.py::test_get_total_llm_stats_aggregates_live_and_completed``
rewritten to feed ``Tracer.record_llm_usage`` instead of legacy globals.
Test file annotations added so pre-commit's strict mypy passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
0xallam
2026-04-25 09:30:23 -07:00
parent 4e0d0f35d9
commit d8881498ee
69 changed files with 646 additions and 4537 deletions
-291
View File
@@ -1,291 +0,0 @@
from types import SimpleNamespace
import strix.agents as agents_module
from strix.llm.config import LLMConfig
from strix.tools.agents_graph import agents_graph_actions
def _reset_agent_graph_state() -> None:
agents_graph_actions._agent_graph["nodes"].clear()
agents_graph_actions._agent_graph["edges"].clear()
agents_graph_actions._agent_messages.clear()
agents_graph_actions._running_agents.clear()
agents_graph_actions._agent_instances.clear()
agents_graph_actions._completed_agent_llm_totals.clear()
agents_graph_actions._completed_agent_llm_totals.update(
agents_graph_actions._empty_llm_stats_totals()
)
agents_graph_actions._agent_states.clear()
def test_create_agent_inherits_parent_whitebox_flag(monkeypatch) -> None:
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5")
_reset_agent_graph_state()
parent_id = "parent-agent"
parent_llm = LLMConfig(timeout=123, scan_mode="standard", is_whitebox=True)
agents_graph_actions._agent_instances[parent_id] = SimpleNamespace(
llm_config=parent_llm,
non_interactive=True,
)
captured_config: dict[str, object] = {}
class FakeStrixAgent:
def __init__(self, config: dict[str, object]):
captured_config["agent_config"] = config
class FakeThread:
def __init__(self, target, args, daemon, name):
self.target = target
self.args = args
self.daemon = daemon
self.name = name
def start(self) -> None:
return None
monkeypatch.setattr(agents_module, "StrixAgent", FakeStrixAgent)
monkeypatch.setattr(agents_graph_actions.threading, "Thread", FakeThread)
agent_state = SimpleNamespace(
agent_id=parent_id,
get_conversation_history=list,
)
result = agents_graph_actions.create_agent(
agent_state=agent_state,
task="source-aware child task",
name="SourceAwareChild",
inherit_context=False,
)
assert result["success"] is True
llm_config = captured_config["agent_config"]["llm_config"]
assert isinstance(llm_config, LLMConfig)
assert llm_config.timeout == 123
assert llm_config.scan_mode == "standard"
assert llm_config.is_whitebox is True
child_task = captured_config["agent_config"]["state"].task
assert "White-box execution guidance (recommended when source is available):" in child_task
assert "mandatory" not in child_task.lower()
def test_delegation_prompt_includes_wiki_memory_instruction_in_whitebox(monkeypatch) -> None:
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5")
_reset_agent_graph_state()
parent_id = "parent-1"
child_id = "child-1"
agents_graph_actions._agent_graph["nodes"][parent_id] = {"name": "Parent", "status": "running"}
agents_graph_actions._agent_graph["nodes"][child_id] = {"name": "Child", "status": "running"}
class FakeState:
def __init__(self) -> None:
self.agent_id = child_id
self.agent_name = "Child"
self.parent_id = parent_id
self.task = "analyze source risks"
self.stop_requested = False
self.messages: list[tuple[str, str]] = []
def add_message(self, role: str, content: str) -> None:
self.messages.append((role, content))
def model_dump(self) -> dict[str, str]:
return {"agent_id": self.agent_id}
class FakeAgent:
def __init__(self) -> None:
self.llm_config = LLMConfig(is_whitebox=True)
async def agent_loop(self, _task: str) -> dict[str, bool]:
return {"ok": True}
state = FakeState()
agent = FakeAgent()
agents_graph_actions._agent_instances[child_id] = agent
result = agents_graph_actions._run_agent_in_thread(agent, state, inherited_messages=[])
assert result["result"] == {"ok": True}
task_messages = [msg for role, msg in state.messages if role == "user"]
assert task_messages
assert 'list_notes(category="wiki")' in task_messages[-1]
assert "get_note(note_id=...)" in task_messages[-1]
assert "Before agent_finish" in task_messages[-1]
def test_agent_finish_appends_wiki_update_for_whitebox(monkeypatch) -> None:
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5")
_reset_agent_graph_state()
parent_id = "parent-2"
child_id = "child-2"
agents_graph_actions._agent_graph["nodes"][parent_id] = {
"name": "Parent",
"task": "parent task",
"status": "running",
"parent_id": None,
}
agents_graph_actions._agent_graph["nodes"][child_id] = {
"name": "Child",
"task": "child task",
"status": "running",
"parent_id": parent_id,
}
agents_graph_actions._agent_instances[child_id] = SimpleNamespace(
llm_config=LLMConfig(is_whitebox=True)
)
captured: dict[str, str] = {}
def fake_list_notes(category=None):
assert category == "wiki"
return {
"success": True,
"notes": [{"note_id": "wiki-note-1", "content": "Existing wiki content"}],
"total_count": 1,
}
captured_get: dict[str, str] = {}
def fake_get_note(note_id: str):
captured_get["note_id"] = note_id
return {
"success": True,
"note": {
"note_id": note_id,
"title": "Repo Wiki",
"content": "Existing wiki content",
},
}
def fake_append_note_content(note_id: str, delta: str):
captured["note_id"] = note_id
captured["delta"] = delta
return {"success": True, "note_id": note_id}
monkeypatch.setattr("strix.tools.notes.notes_actions.list_notes", fake_list_notes)
monkeypatch.setattr("strix.tools.notes.notes_actions.get_note", fake_get_note)
monkeypatch.setattr("strix.tools.notes.notes_actions.append_note_content", fake_append_note_content)
state = SimpleNamespace(agent_id=child_id, parent_id=parent_id)
result = agents_graph_actions.agent_finish(
agent_state=state,
result_summary="AST pass completed",
findings=["Found route sink candidate"],
success=True,
final_recommendations=["Validate sink with dynamic PoC"],
)
assert result["agent_completed"] is True
assert captured_get["note_id"] == "wiki-note-1"
assert captured["note_id"] == "wiki-note-1"
assert "Agent Update: Child" in captured["delta"]
assert "AST pass completed" in captured["delta"]
def test_run_agent_in_thread_injects_shared_wiki_context_in_whitebox(monkeypatch) -> None:
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5")
_reset_agent_graph_state()
parent_id = "parent-3"
child_id = "child-3"
agents_graph_actions._agent_graph["nodes"][parent_id] = {"name": "Parent", "status": "running"}
agents_graph_actions._agent_graph["nodes"][child_id] = {"name": "Child", "status": "running"}
class FakeState:
def __init__(self) -> None:
self.agent_id = child_id
self.agent_name = "Child"
self.parent_id = parent_id
self.task = "map source"
self.stop_requested = False
self.messages: list[tuple[str, str]] = []
def add_message(self, role: str, content: str) -> None:
self.messages.append((role, content))
def model_dump(self) -> dict[str, str]:
return {"agent_id": self.agent_id}
class FakeAgent:
def __init__(self) -> None:
self.llm_config = LLMConfig(is_whitebox=True)
async def agent_loop(self, _task: str) -> dict[str, bool]:
return {"ok": True}
captured_get: dict[str, str] = {}
def fake_list_notes(category=None):
assert category == "wiki"
return {
"success": True,
"notes": [{"note_id": "wiki-ctx-1"}],
"total_count": 1,
}
def fake_get_note(note_id: str):
captured_get["note_id"] = note_id
return {
"success": True,
"note": {
"note_id": note_id,
"title": "Shared Repo Wiki",
"content": "Architecture: server/client split",
},
}
monkeypatch.setattr("strix.tools.notes.notes_actions.list_notes", fake_list_notes)
monkeypatch.setattr("strix.tools.notes.notes_actions.get_note", fake_get_note)
state = FakeState()
agent = FakeAgent()
agents_graph_actions._agent_instances[child_id] = agent
result = agents_graph_actions._run_agent_in_thread(agent, state, inherited_messages=[])
assert result["result"] == {"ok": True}
assert captured_get["note_id"] == "wiki-ctx-1"
user_messages = [content for role, content in state.messages if role == "user"]
assert user_messages
assert "<shared_repo_wiki" in user_messages[0]
assert "Architecture: server/client split" in user_messages[0]
def test_load_primary_wiki_note_prefers_repo_tag_match(monkeypatch) -> None:
selected_note_ids: list[str] = []
def fake_list_notes(category=None):
assert category == "wiki"
return {
"success": True,
"notes": [
{"note_id": "wiki-other", "tags": ["repo:other"]},
{"note_id": "wiki-target", "tags": ["repo:appsmith"]},
],
"total_count": 2,
}
def fake_get_note(note_id: str):
selected_note_ids.append(note_id)
return {
"success": True,
"note": {"note_id": note_id, "title": "Repo Wiki", "content": "content"},
}
monkeypatch.setattr("strix.tools.notes.notes_actions.list_notes", fake_list_notes)
monkeypatch.setattr("strix.tools.notes.notes_actions.get_note", fake_get_note)
agent_state = SimpleNamespace(
task="analyze /workspace/appsmith",
context={"whitebox_repo_tags": ["repo:appsmith"]},
)
note = agents_graph_actions._load_primary_wiki_note(agent_state)
assert note is not None
assert note["note_id"] == "wiki-target"
assert selected_note_ids == ["wiki-target"]
@@ -29,7 +29,7 @@ import pytest
from agents.tool import FunctionTool
from strix.orchestration.bus import AgentMessageBus
from strix.tools.agents_graph.agents_graph_sdk_tools import (
from strix.tools.agents_graph.tools import (
agent_finish,
agent_status,
create_agent,
@@ -289,7 +289,7 @@ async def test_create_agent_spawns_and_registers_child() -> None:
)
with patch(
"strix.tools.agents_graph.agents_graph_sdk_tools.Runner.run",
"strix.tools.agents_graph.tools.Runner.run",
side_effect=fake_runner_run,
):
out = await _invoke(
@@ -361,7 +361,7 @@ async def test_create_agent_inherits_parent_history() -> None:
)
with patch(
"strix.tools.agents_graph.agents_graph_sdk_tools.Runner.run",
"strix.tools.agents_graph.tools.Runner.run",
side_effect=fake_runner_run,
):
await _invoke(
@@ -493,7 +493,7 @@ async def test_create_agent_spawn_is_cancelable_via_bus() -> None:
runner_mock = AsyncMock(side_effect=slow_runner_run)
with patch(
"strix.tools.agents_graph.agents_graph_sdk_tools.Runner.run",
"strix.tools.agents_graph.tools.Runner.run",
new=runner_mock,
):
out = await _invoke(
-139
View File
@@ -1,139 +0,0 @@
from typing import Any
from strix.tools.agents_graph import agents_graph_actions
from strix.tools.load_skill import load_skill_actions
class _DummyLLM:
def __init__(self, initial_skills: list[str] | None = None) -> None:
self.loaded: set[str] = set(initial_skills or [])
def add_skills(self, skill_names: list[str]) -> list[str]:
newly_loaded = [skill for skill in skill_names if skill not in self.loaded]
self.loaded.update(newly_loaded)
return newly_loaded
class _DummyAgent:
def __init__(self, initial_skills: list[str] | None = None) -> None:
self.llm = _DummyLLM(initial_skills)
class _DummyAgentState:
def __init__(self, agent_id: str) -> None:
self.agent_id = agent_id
self.context: dict[str, Any] = {}
def update_context(self, key: str, value: Any) -> None:
self.context[key] = value
def test_load_skill_success_and_context_update() -> None:
instances = agents_graph_actions.__dict__["_agent_instances"]
original_instances = dict(instances)
try:
state = _DummyAgentState("agent_test_load_skill_success")
instances.clear()
instances[state.agent_id] = _DummyAgent()
result = load_skill_actions.load_skill(state, "ffuf,xss")
assert result["success"] is True
assert result["loaded_skills"] == ["ffuf", "xss"]
assert result["newly_loaded_skills"] == ["ffuf", "xss"]
assert state.context["loaded_skills"] == ["ffuf", "xss"]
finally:
instances.clear()
instances.update(original_instances)
def test_load_skill_uses_same_plain_skill_format_as_create_agent() -> None:
instances = agents_graph_actions.__dict__["_agent_instances"]
original_instances = dict(instances)
try:
state = _DummyAgentState("agent_test_load_skill_short_name")
instances.clear()
instances[state.agent_id] = _DummyAgent()
result = load_skill_actions.load_skill(state, "nmap")
assert result["success"] is True
assert result["loaded_skills"] == ["nmap"]
assert result["newly_loaded_skills"] == ["nmap"]
assert state.context["loaded_skills"] == ["nmap"]
finally:
instances.clear()
instances.update(original_instances)
def test_load_skill_invalid_skill_returns_error() -> None:
instances = agents_graph_actions.__dict__["_agent_instances"]
original_instances = dict(instances)
try:
state = _DummyAgentState("agent_test_load_skill_invalid")
instances.clear()
instances[state.agent_id] = _DummyAgent()
result = load_skill_actions.load_skill(state, "definitely_not_a_real_skill")
assert result["success"] is False
assert "Invalid skills" in result["error"]
assert "Available skills" in result["error"]
finally:
instances.clear()
instances.update(original_instances)
def test_load_skill_rejects_more_than_five_skills() -> None:
instances = agents_graph_actions.__dict__["_agent_instances"]
original_instances = dict(instances)
try:
state = _DummyAgentState("agent_test_load_skill_too_many")
instances.clear()
instances[state.agent_id] = _DummyAgent()
result = load_skill_actions.load_skill(state, "a,b,c,d,e,f")
assert result["success"] is False
assert result["error"] == (
"Cannot specify more than 5 skills for an agent (use comma-separated format)"
)
finally:
instances.clear()
instances.update(original_instances)
def test_load_skill_missing_agent_instance_returns_error() -> None:
instances = agents_graph_actions.__dict__["_agent_instances"]
original_instances = dict(instances)
try:
state = _DummyAgentState("agent_test_load_skill_missing_instance")
instances.clear()
result = load_skill_actions.load_skill(state, "httpx")
assert result["success"] is False
assert "running agent instance" in result["error"]
finally:
instances.clear()
instances.update(original_instances)
def test_load_skill_does_not_reload_skill_already_present_from_agent_creation() -> None:
instances = agents_graph_actions.__dict__["_agent_instances"]
original_instances = dict(instances)
try:
state = _DummyAgentState("agent_test_load_skill_existing_config_skill")
instances.clear()
instances[state.agent_id] = _DummyAgent(["xss"])
result = load_skill_actions.load_skill(state, "xss,sql_injection")
assert result["success"] is True
assert result["loaded_skills"] == ["xss", "sql_injection"]
assert result["newly_loaded_skills"] == ["sql_injection"]
assert result["already_loaded_skills"] == ["xss"]
assert state.context["loaded_skills"] == ["sql_injection", "xss"]
finally:
instances.clear()
instances.update(original_instances)
@@ -20,16 +20,16 @@ from unittest.mock import patch
import pytest
from agents.tool import FunctionTool
from strix.tools.notes import notes_actions as _notes_legacy
from strix.tools.notes.notes_sdk_tools import (
from strix.tools.notes import notes_actions as _notes_impl
from strix.tools.notes.tools import (
create_note,
delete_note,
get_note,
list_notes,
update_note,
)
from strix.tools.thinking.thinking_sdk_tools import think
from strix.tools.todo.todo_sdk_tools import (
from strix.tools.thinking.tool import think
from strix.tools.todo.tools import (
create_todo,
delete_todo,
list_todos,
@@ -190,10 +190,10 @@ def notes_run_dir(tmp_path: Path) -> Iterator[Path]:
"""Point the legacy notes module at a fresh run dir per test."""
run_dir = tmp_path / "strix_runs" / "test"
run_dir.mkdir(parents=True)
_notes_legacy._notes_storage.clear()
_notes_legacy._loaded_notes_run_dir = None
_notes_impl._notes_storage.clear()
_notes_impl._loaded_notes_run_dir = None
with patch.object(_notes_legacy, "_get_run_dir", return_value=run_dir):
with patch.object(_notes_impl, "_get_run_dir", return_value=run_dir):
yield run_dir
@@ -12,7 +12,6 @@ sandbox-bound tools route through ``post_to_sandbox``.
from __future__ import annotations
import json
from collections.abc import Iterator
from dataclasses import dataclass, field
from typing import Any
from unittest.mock import patch
@@ -20,15 +19,15 @@ from unittest.mock import patch
import pytest
from agents.tool import FunctionTool
from strix.tools.file_edit.file_edit_sdk_tools import (
from strix.tools.file_edit.tools import (
list_files,
search_files,
str_replace_editor,
)
from strix.tools.finish.finish_sdk_tool import finish_scan
from strix.tools.load_skill.load_skill_sdk_tool import load_skill
from strix.tools.reporting.reporting_sdk_tools import create_vulnerability_report
from strix.tools.web_search.web_search_sdk_tool import web_search
from strix.tools.finish.tool import finish_scan
from strix.tools.load_skill.tool import load_skill
from strix.tools.reporting.tool import create_vulnerability_report
from strix.tools.web_search.tool import web_search
@dataclass
@@ -93,7 +92,7 @@ async def test_web_search_no_api_key_returns_structured_error(
@pytest.mark.asyncio
async def test_web_search_delegates_to_legacy(monkeypatch: pytest.MonkeyPatch) -> None:
async def test_web_search_delegates_to_impl(monkeypatch: pytest.MonkeyPatch) -> None:
"""Legacy ``web_search`` returns dict; wrapper JSON-encodes it."""
monkeypatch.setenv("PERPLEXITY_API_KEY", "fake-key")
@@ -104,7 +103,7 @@ async def test_web_search_delegates_to_legacy(monkeypatch: pytest.MonkeyPatch) -
"message": "Web search completed successfully",
}
with patch(
"strix.tools.web_search.web_search_sdk_tool._legacy.web_search",
"strix.tools.web_search.tool._impl.web_search",
return_value=fake_result,
) as legacy:
out = await _invoke(web_search, _ctx_for(), query="xss techniques")
@@ -121,7 +120,7 @@ async def test_str_replace_editor_routes_to_sandbox() -> None:
"""file_edit tools must POST to the in-sandbox tool server, not run locally."""
fake_response = {"result": {"content": "file viewed"}}
with patch(
"strix.tools.file_edit.file_edit_sdk_tools.post_to_sandbox",
"strix.tools.file_edit.tools.post_to_sandbox",
return_value=fake_response,
) as dispatch:
out = await _invoke(
@@ -147,7 +146,7 @@ async def test_str_replace_editor_routes_to_sandbox() -> None:
async def test_list_files_routes_to_sandbox() -> None:
fake_response = {"result": {"files": ["a.py"], "directories": []}}
with patch(
"strix.tools.file_edit.file_edit_sdk_tools.post_to_sandbox",
"strix.tools.file_edit.tools.post_to_sandbox",
return_value=fake_response,
) as dispatch:
out = await _invoke(list_files, _ctx_for(), path="src", recursive=True)
@@ -162,7 +161,7 @@ async def test_list_files_routes_to_sandbox() -> None:
async def test_search_files_routes_to_sandbox() -> None:
fake_response = {"result": {"output": "src/foo.py:1:match"}}
with patch(
"strix.tools.file_edit.file_edit_sdk_tools.post_to_sandbox",
"strix.tools.file_edit.tools.post_to_sandbox",
return_value=fake_response,
) as dispatch:
out = await _invoke(
@@ -204,7 +203,7 @@ async def test_create_vulnerability_report_validates_required_fields() -> None:
@pytest.mark.asyncio
async def test_create_vulnerability_report_delegates_to_legacy() -> None:
async def test_create_vulnerability_report_delegates_to_impl() -> None:
"""Verify the wrapper passes all params through to the legacy function."""
fake_result = {
"success": True,
@@ -214,7 +213,7 @@ async def test_create_vulnerability_report_delegates_to_legacy() -> None:
"cvss_score": 7.5,
}
with patch(
"strix.tools.reporting.reporting_sdk_tools._legacy.create_vulnerability_report",
"strix.tools.reporting.tool._impl.create_vulnerability_report",
return_value=fake_result,
) as legacy:
out = await _invoke(
@@ -255,7 +254,7 @@ async def test_load_skill_passes_adapter_with_agent_id() -> None:
return {"success": True, "loaded_skills": ["recon"]}
with patch(
"strix.tools.load_skill.load_skill_sdk_tool._legacy.load_skill",
"strix.tools.load_skill.tool._impl.load_skill",
side_effect=fake_legacy,
):
out = await _invoke(load_skill, _ctx_for("agent-XYZ"), skills="recon")
@@ -276,28 +275,7 @@ async def test_load_skill_with_empty_input() -> None:
# --- finish_scan ---------------------------------------------------------
@pytest.fixture
def isolated_agent_graph() -> Iterator[None]:
"""Clear the legacy agent-graph globals so finish_scan sees an empty world.
The legacy ``_check_active_agents`` reads ``_agent_graph["nodes"]`` and
returns an "agents still active" error if any non-self agent is in
state ``running`` or ``stopping``. Tests in other modules (legacy
multi-agent tests) populate this dict; without isolation they bleed
into our validation tests and mask the field-validation path.
"""
from strix.tools.agents_graph import agents_graph_actions
saved_nodes = agents_graph_actions._agent_graph.get("nodes", {}).copy()
agents_graph_actions._agent_graph["nodes"] = {}
try:
yield
finally:
agents_graph_actions._agent_graph["nodes"] = saved_nodes
@pytest.mark.asyncio
@pytest.mark.usefixtures("isolated_agent_graph")
async def test_finish_scan_validates_empty_fields() -> None:
"""Legacy validation: every section must be non-empty."""
out = await _invoke(
@@ -313,8 +291,7 @@ async def test_finish_scan_validates_empty_fields() -> None:
@pytest.mark.asyncio
@pytest.mark.usefixtures("isolated_agent_graph")
async def test_finish_scan_delegates_to_legacy() -> None:
async def test_finish_scan_delegates_to_impl() -> None:
"""Wrapper must pass the legacy adapter and the four sections through."""
fake_result = {
"success": True,
@@ -323,7 +300,7 @@ async def test_finish_scan_delegates_to_legacy() -> None:
"vulnerabilities_found": 3,
}
with patch(
"strix.tools.finish.finish_sdk_tool._legacy.finish_scan",
"strix.tools.finish.tool._impl.finish_scan",
return_value=fake_result,
) as legacy:
out = await _invoke(
@@ -27,8 +27,8 @@ from unittest.mock import patch
import pytest
from agents.tool import FunctionTool
from strix.tools.browser.browser_sdk_tool import browser_action
from strix.tools.proxy.proxy_sdk_tools import (
from strix.tools.browser.tool import browser_action
from strix.tools.proxy.tools import (
list_requests,
list_sitemap,
repeat_request,
@@ -37,8 +37,8 @@ from strix.tools.proxy.proxy_sdk_tools import (
view_request,
view_sitemap_entry,
)
from strix.tools.python.python_sdk_tool import python_action
from strix.tools.terminal.terminal_sdk_tool import terminal_execute
from strix.tools.python.tool import python_action
from strix.tools.terminal.tool import terminal_execute
_ALL_SANDBOX_TOOLS = (
@@ -119,7 +119,7 @@ async def test_browser_action_dispatches_full_payload() -> None:
(the in-container handler distinguishes ``None`` from missing)."""
fake = {"result": {"screenshot": "data:image/png;base64,..."}}
with patch(
"strix.tools.browser.browser_sdk_tool.post_to_sandbox",
"strix.tools.browser.tool.post_to_sandbox",
return_value=fake,
) as dispatch:
out = await _invoke(
@@ -158,7 +158,7 @@ async def test_browser_action_dispatches_full_payload() -> None:
async def test_terminal_execute_dispatches() -> None:
fake = {"result": {"content": "hello\n", "exit_code": 0}}
with patch(
"strix.tools.terminal.terminal_sdk_tool.post_to_sandbox",
"strix.tools.terminal.tool.post_to_sandbox",
return_value=fake,
) as dispatch:
out = await _invoke(
@@ -185,7 +185,7 @@ async def test_terminal_execute_dispatches() -> None:
async def test_python_action_dispatches() -> None:
fake = {"result": {"stdout": "42\n", "is_running": False}}
with patch(
"strix.tools.python.python_sdk_tool.post_to_sandbox",
"strix.tools.python.tool.post_to_sandbox",
return_value=fake,
) as dispatch:
out = await _invoke(
@@ -214,7 +214,7 @@ async def test_python_action_dispatches() -> None:
async def test_list_requests_forwards_full_query() -> None:
fake: dict[str, Any] = {"result": {"requests": []}}
with patch(
"strix.tools.proxy.proxy_sdk_tools.post_to_sandbox",
"strix.tools.proxy.tools.post_to_sandbox",
return_value=fake,
) as dispatch:
await _invoke(
@@ -241,7 +241,7 @@ async def test_list_requests_forwards_full_query() -> None:
async def test_view_request_dispatches() -> None:
fake = {"result": {"raw": "GET / HTTP/1.1..."}}
with patch(
"strix.tools.proxy.proxy_sdk_tools.post_to_sandbox",
"strix.tools.proxy.tools.post_to_sandbox",
return_value=fake,
) as dispatch:
await _invoke(
@@ -264,7 +264,7 @@ async def test_send_request_normalizes_missing_headers() -> None:
"""Legacy schema treats omitted ``headers`` as ``{}``; the wrapper must too."""
fake = {"result": {"status": 200}}
with patch(
"strix.tools.proxy.proxy_sdk_tools.post_to_sandbox",
"strix.tools.proxy.tools.post_to_sandbox",
return_value=fake,
) as dispatch:
await _invoke(
@@ -286,7 +286,7 @@ async def test_send_request_normalizes_missing_headers() -> None:
async def test_repeat_request_normalizes_missing_modifications() -> None:
fake = {"result": {"status": 200}}
with patch(
"strix.tools.proxy.proxy_sdk_tools.post_to_sandbox",
"strix.tools.proxy.tools.post_to_sandbox",
return_value=fake,
) as dispatch:
await _invoke(repeat_request, _ctx_for(), request_id="req-1")
@@ -300,7 +300,7 @@ async def test_repeat_request_normalizes_missing_modifications() -> None:
async def test_scope_rules_dispatches() -> None:
fake = {"result": {"scope_id": "s-1"}}
with patch(
"strix.tools.proxy.proxy_sdk_tools.post_to_sandbox",
"strix.tools.proxy.tools.post_to_sandbox",
return_value=fake,
) as dispatch:
await _invoke(
@@ -323,7 +323,7 @@ async def test_scope_rules_dispatches() -> None:
async def test_list_sitemap_defaults() -> None:
fake: dict[str, Any] = {"result": {"entries": []}}
with patch(
"strix.tools.proxy.proxy_sdk_tools.post_to_sandbox",
"strix.tools.proxy.tools.post_to_sandbox",
return_value=fake,
) as dispatch:
await _invoke(list_sitemap, _ctx_for())
@@ -338,7 +338,7 @@ async def test_list_sitemap_defaults() -> None:
async def test_view_sitemap_entry_dispatches() -> None:
fake = {"result": {"entry_id": "e-1"}}
with patch(
"strix.tools.proxy.proxy_sdk_tools.post_to_sandbox",
"strix.tools.proxy.tools.post_to_sandbox",
return_value=fake,
) as dispatch:
await _invoke(view_sitemap_entry, _ctx_for(), entry_id="e-1")
+7 -2
View File
@@ -21,9 +21,15 @@ def _reload_tools_module() -> ModuleType:
return importlib.import_module("strix.tools")
def test_non_sandbox_registers_agents_graph_but_not_browser_or_web_search_when_disabled(
def test_non_sandbox_skips_browser_and_web_search_when_disabled(
monkeypatch: Any,
) -> None:
"""Browser registration is gated on STRIX_DISABLE_BROWSER and
web_search on PERPLEXITY_API_KEY; both should stay out of the
in-container ``register_tool`` registry when their gates are off.
Agents_graph is no longer in this registry — those tools are SDK
function tools (host-side only), not in-container tools.
"""
monkeypatch.setenv("STRIX_SANDBOX_MODE", "false")
monkeypatch.setenv("STRIX_DISABLE_BROWSER", "true")
monkeypatch.delenv("PERPLEXITY_API_KEY", raising=False)
@@ -32,7 +38,6 @@ def test_non_sandbox_registers_agents_graph_but_not_browser_or_web_search_when_d
tools = _reload_tools_module()
names = set(tools.get_tool_names())
assert "create_agent" in names
assert "browser_action" not in names
assert "web_search" not in names