mirror of
https://github.com/usestrix/strix.git
synced 2026-08-20 18:38:57 +02:00
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.
121 lines
3.3 KiB
Python
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()
|