mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-28 23:52:09 -04:00
refactor(tools): migrate config/integration admin tools to the registry (#4742)
Part of #3629 (the `admin_tools.py` bullet). Moves the config/integration admin tools off the legacy elif dispatch chain in tool_implementations.py onto the agent_tools registry: manage_endpoints, manage_mcp, manage_webhooks, manage_tokens, manage_settings The do_* implementations (and manage_mcp's command-allowlist / RCE guard: _validate_mcp_command, _mcp_allowed_commands, and the _MCP_* constants) move verbatim into the new src/agent_tools/admin_tools.py. They register through a single ADMIN_TOOL_HANDLERS map that TOOL_HANDLERS.update()s, and the five elif branches plus their imports are dropped from tool_execution.py, so these tools now flow through _direct_fallback like the other migrated clusters. The names are re-exported from src.agent_tools for back-compat. Dedup: - _parse_tool_args was duplicated in tool_implementations.py and document_tools.py. It now lives once in src.tool_utils (which imports nothing from the project beyond src.constants, so this introduces no cycle) and both call sites import it from there. The orphaned `import json` in document_tools is removed with it. - The five tools share one _owner_adapter(fn) factory that threads ctx["owner"] into the owner-taking do_* signature, instead of five near-identical wrappers. Tests: new tests/test_admin_tools_registry.py pins the registration, the re-export back-compat, the owner-threading adapter, and the single-source _parse_tool_args (across admin_tools and document_tools). Existing MCP / settings / webhook suites are repointed at the new module.
This commit is contained in:
committed by
GitHub
parent
e0ccf250a4
commit
5ce2056521
@@ -0,0 +1,69 @@
|
||||
"""Registry wiring for the config/integration admin tools (#3629).
|
||||
|
||||
manage_endpoints/mcp/webhooks/tokens/settings moved from tool_implementations
|
||||
into agent_tools.admin_tools. These pin the registration + the single
|
||||
owner-threading adapter factory, without touching the DB (the do_* impls
|
||||
themselves are exercised by their own suites).
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from src.agent_tools import TOOL_HANDLERS
|
||||
from src.agent_tools.admin_tools import (
|
||||
ADMIN_TOOL_HANDLERS, _owner_adapter,
|
||||
do_manage_endpoints, do_manage_mcp, do_manage_webhooks,
|
||||
do_manage_tokens, do_manage_settings,
|
||||
)
|
||||
|
||||
_NAMES = ["manage_endpoints", "manage_mcp", "manage_webhooks", "manage_tokens", "manage_settings"]
|
||||
|
||||
|
||||
def test_all_registered_in_tool_handlers():
|
||||
for n in _NAMES:
|
||||
assert n in TOOL_HANDLERS, f"{n} missing from TOOL_HANDLERS"
|
||||
assert n in ADMIN_TOOL_HANDLERS
|
||||
|
||||
|
||||
def test_re_exported_from_agent_tools():
|
||||
# Back-compat: importers that used `from src.agent_tools import do_manage_*`
|
||||
# keep working after the move.
|
||||
from src.agent_tools import ( # noqa: F401
|
||||
do_manage_endpoints, do_manage_mcp, do_manage_webhooks,
|
||||
do_manage_tokens, do_manage_settings,
|
||||
)
|
||||
|
||||
|
||||
def test_owner_adapter_threads_owner_from_ctx():
|
||||
seen = {}
|
||||
|
||||
async def _spy(content, owner):
|
||||
seen["content"] = content
|
||||
seen["owner"] = owner
|
||||
return {"response": "ok", "exit_code": 0}
|
||||
|
||||
handler = _owner_adapter(_spy)
|
||||
res = asyncio.run(handler('{"action":"list"}', {"owner": "alice", "session_id": "s1"}))
|
||||
assert res["exit_code"] == 0
|
||||
assert seen == {"content": '{"action":"list"}', "owner": "alice"}
|
||||
|
||||
|
||||
def test_owner_adapter_defaults_owner_to_none():
|
||||
captured = {}
|
||||
|
||||
async def _spy(content, owner):
|
||||
captured["owner"] = owner
|
||||
return {"exit_code": 0}
|
||||
|
||||
asyncio.run(_owner_adapter(_spy)("{}", {})) # ctx without owner
|
||||
assert captured["owner"] is None
|
||||
|
||||
|
||||
def test_parse_tool_args_lives_in_tool_utils_single_source():
|
||||
# The helper was de-duplicated into tool_utils; admin_tools imports it
|
||||
# from there rather than carrying its own copy.
|
||||
from src.tool_utils import _parse_tool_args
|
||||
from src.agent_tools import admin_tools, document_tools
|
||||
assert admin_tools._parse_tool_args is _parse_tool_args
|
||||
assert document_tools._parse_tool_args is _parse_tool_args
|
||||
assert _parse_tool_args('{"action":"add"}') == {"action": "add"}
|
||||
# body-envelope unwrap still works
|
||||
assert _parse_tool_args('{"body":{"action":"x"}}') == {"action": "x"}
|
||||
@@ -86,7 +86,8 @@ def test_default_settings_registers_hard_max_key():
|
||||
def test_alias_map_registers_friendly_names():
|
||||
"""`manage_settings` should accept 'hard max' and friends."""
|
||||
from pathlib import Path
|
||||
src = Path("src/tool_implementations.py").read_text()
|
||||
# manage_settings (and its alias map) moved to agent_tools/admin_tools.py in #3629.
|
||||
src = Path("src/agent_tools/admin_tools.py").read_text()
|
||||
assert '"hard max": "agent_input_token_hard_max"' in src
|
||||
assert '"token budget cap": "agent_input_token_hard_max"' in src
|
||||
assert '"input budget cap": "agent_input_token_hard_max"' in src
|
||||
|
||||
@@ -26,8 +26,8 @@ clear_fake_database_modules()
|
||||
|
||||
import core.database as cdb
|
||||
from core.database import McpServer
|
||||
import src.tool_implementations as ti
|
||||
from src.tool_implementations import _validate_mcp_command
|
||||
import src.agent_tools.admin_tools as ti # do_manage_mcp/get_mcp_manager moved here in the registry migration
|
||||
from src.agent_tools.admin_tools import _validate_mcp_command
|
||||
|
||||
_TS, _ENGINE, _TMPDB = make_temp_sqlite(cdb.Base.metadata)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import asyncio
|
||||
import json
|
||||
|
||||
import src.settings as settings_mod
|
||||
from src.tool_implementations import do_manage_settings
|
||||
from src.agent_tools.admin_tools import do_manage_settings
|
||||
|
||||
|
||||
def test_set_token_budget_is_not_refused_as_secret(monkeypatch):
|
||||
|
||||
@@ -8,7 +8,7 @@ from types import SimpleNamespace
|
||||
|
||||
def test_reconnect_passes_full_server_config():
|
||||
"""do_manage_mcp reconnect must pass name/transport/command/args/env/url."""
|
||||
from src.tool_implementations import do_manage_mcp
|
||||
from src.agent_tools.admin_tools import do_manage_mcp
|
||||
|
||||
fake_mcp = MagicMock()
|
||||
fake_mcp.disconnect_server = AsyncMock()
|
||||
@@ -28,7 +28,7 @@ def test_reconnect_passes_full_server_config():
|
||||
fake_db = MagicMock()
|
||||
fake_db.query.return_value.filter.return_value.first.return_value = fake_srv
|
||||
|
||||
with patch("src.tool_implementations.get_mcp_manager", return_value=fake_mcp), \
|
||||
with patch("src.agent_tools.admin_tools.get_mcp_manager", return_value=fake_mcp), \
|
||||
patch("core.database.SessionLocal", return_value=fake_db):
|
||||
result = asyncio.run(do_manage_mcp(
|
||||
json.dumps({"action": "reconnect", "server_id": "srv-123"})
|
||||
|
||||
@@ -821,7 +821,7 @@ async def test_webhook_tool_reuses_private_url_validation():
|
||||
monkeypatch.setitem(sys.modules, "core.database", fake_core_db)
|
||||
monkeypatch.setitem(sys.modules, "src.database", fake_src_db)
|
||||
|
||||
from src.tool_implementations import do_manage_webhooks
|
||||
from src.agent_tools.admin_tools import do_manage_webhooks
|
||||
|
||||
try:
|
||||
result = await do_manage_webhooks(
|
||||
|
||||
Reference in New Issue
Block a user