Files
strix/strix/tools/registry.py
T
0xallam 7970271d4f refactor: inline non-sandbox actions, strip registry, drop schemas
Cleanup pass after the migration:

#1 Inline ``*_actions.py`` into wrapper ``tool[s].py`` for the
non-sandbox tools (think, todo, notes, reporting, web_search,
finish_scan). One file per tool family now. Helpers + public
function bodies live alongside the ``@strix_tool``-decorated
wrappers that call them.

For notes, the sync helpers are renamed to ``_create_note_impl`` /
``_list_notes_impl`` / etc. so the public names ``create_note`` /
``list_notes`` / etc. can be the FunctionTool instances the agent
factory imports. ``append_note_content`` (used by the agents-graph
wiki-update hook) calls the impl helpers directly.

#2 Delete ``strix/tools/_state_adapter.py``. The ``AgentStateAdapter``
shim only existed to feed legacy ``*_actions.py`` functions a
``state.agent_id`` they could read. With the actions inlined, the
wrappers read ``ctx.context['agent_id']`` directly.

#3 Strip ``strix/tools/registry.py`` from ~250 LOC to ~110.
Deleted: XML schema loading, ``_parse_param_schema``,
``get_tools_prompt``, ``get_tool_param_schema``, ``needs_agent_state``,
``should_execute_in_sandbox``, ``validate_tool_availability`` — all
for the host-side legacy dispatcher path. Kept the ``register_tool``
decorator (sandbox side), ``get_tool_by_name``, ``get_tool_names``,
``tools`` list, ``clear_registry``.

The Jinja prompt template's ``{{ get_tools_prompt() }}`` injection
is dropped — the SDK auto-generates tool descriptions from function
signatures, so the legacy XML tool block was redundant and stale.

#4 Delete every ``*_actions_schema.xml`` (12 files). They were read
by the now-removed ``_load_xml_schema`` to build the legacy prompt's
tool descriptions. No consumer remains.

Side fixes:
- ``reporting_renderer.py`` updated to import ``_parse_*_xml`` from
  the new location with leading underscore.
- ``test_local_tools.py``, ``test_notes_jsonl_concurrency.py``,
  ``test_notes_wiki.py`` updated to point at the new module paths
  and call the ``_*_impl`` sync helpers.

Tests: 279/279 passing. ~1500 LOC of action files moved into the
tool wrappers; ~140 LOC of registry boilerplate removed; ~400 lines
of dead XML deleted.
2026-04-25 11:26:02 -07:00

121 lines
3.3 KiB
Python

"""Minimal in-container tool registry.
Used inside the sandbox container by ``strix.runtime.tool_server`` to
look up `@register_tool`-decorated functions by name. Sandbox-bound
tools (browser, terminal, python, file_edit, proxy) live as legacy
``*_actions.py`` modules with this decoration; the host POSTs to
:func:`tool_server.execute_tool` which dispatches via
:func:`get_tool_by_name`.
Host-side tools are pure SDK function tools wired through
:mod:`strix.agents.factory` and don't touch this registry at all.
"""
import logging
import os
from collections.abc import Callable
from functools import wraps
from typing import Any
logger = logging.getLogger(__name__)
tools: list[dict[str, Any]] = []
_tools_by_name: dict[str, Callable[..., Any]] = {}
class ImplementedInClientSideOnlyError(Exception):
"""Raised by sandbox-side stubs whose real implementation lives host-side."""
def __init__(
self,
message: str = "This tool is implemented in the client side only",
) -> None:
self.message = message
super().__init__(self.message)
def _is_sandbox_mode() -> bool:
return os.getenv("STRIX_SANDBOX_MODE", "false").lower() == "true"
def _is_browser_disabled() -> bool:
return os.getenv("STRIX_DISABLE_BROWSER", "").lower() == "true"
def _has_perplexity_api() -> bool:
return bool(os.getenv("PERPLEXITY_API_KEY"))
def _should_register_tool(
*,
sandbox_execution: bool,
requires_browser_mode: bool,
requires_web_search_mode: bool,
) -> bool:
"""In-container side only registers sandbox-execution tools."""
sandbox_mode = _is_sandbox_mode()
if sandbox_mode and not sandbox_execution:
return False
if requires_browser_mode and _is_browser_disabled():
return False
return not (requires_web_search_mode and not _has_perplexity_api())
def register_tool(
func: Callable[..., Any] | None = None,
*,
sandbox_execution: bool = True,
requires_browser_mode: bool = False,
requires_web_search_mode: bool = False,
) -> Callable[..., Any]:
"""Register a tool function for in-container dispatch.
Decorations are conditional on the env (``STRIX_SANDBOX_MODE``,
``STRIX_DISABLE_BROWSER``, ``PERPLEXITY_API_KEY``) so the host
side, which imports these modules but doesn't run sandbox-bound
tools locally, doesn't accumulate dead registrations.
"""
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
if not _should_register_tool(
sandbox_execution=sandbox_execution,
requires_browser_mode=requires_browser_mode,
requires_web_search_mode=requires_web_search_mode,
):
return f
tools.append(
{
"name": f.__name__,
"function": f,
"sandbox_execution": sandbox_execution,
},
)
_tools_by_name[f.__name__] = f
@wraps(f)
def wrapper(*args: Any, **kwargs: Any) -> Any:
return f(*args, **kwargs)
return wrapper
if func is None:
return decorator
return decorator(func)
def get_tool_by_name(name: str) -> Callable[..., Any] | None:
return _tools_by_name.get(name)
def get_tool_names() -> list[str]:
return list(_tools_by_name.keys())
def clear_registry() -> None:
tools.clear()
_tools_by_name.clear()