Files
odysseus/src/tool_implementations.py
2026-06-28 14:07:23 +00:00

116 lines
5.1 KiB
Python

"""
tool_implementations.py
Extracted tool implementation functions (do_* and helpers) from agent_tools.py.
These handle the actual execution logic for each tool type.
"""
import logging
from typing import Dict, Optional
from src.tool_utils import get_mcp_manager # re-exported: tests patch src.tool_implementations.get_mcp_manager
# System-domain tools were extracted to src/tools/system.py (slice 1,
# #4082/#4071); the admin manage_* tools live in src/agent_tools/admin_tools
# after the upstream registry migration (#3629). Re-imported here so this
# module stays a working facade.
from src.tools.system import ( # noqa: F401
do_manage_skills, _skill_dump, do_manage_tasks,
do_api_call, do_app_api,
_APP_API_BLOCKLIST_PREFIXES, _APP_API_BLOCKLIST_METHOD_PATH,
)
# Admin manage_* tools (endpoints/mcp/webhooks/tokens/settings) live in
# src/agent_tools/admin_tools after the upstream registry migration (#3629).
# Re-exported lazily via __getattr__: src.agent_tools.__init__ imports this
# facade at top level, so a eager `from src.agent_tools.admin_tools import`
# here would re-enter the partially-initialized agent_tools package (circular).
_ADMIN_TOOL_SYMBOLS = (
"do_manage_endpoints", "do_manage_mcp", "do_manage_webhooks",
"do_manage_tokens", "do_manage_settings",
"_MCP_DENIED_COMMANDS", "_validate_mcp_command", "_mcp_allowed_commands",
)
def __getattr__(name):
if name in _ADMIN_TOOL_SYMBOLS:
from src.agent_tools import admin_tools
return getattr(admin_tools, name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# Cookbook (model serving) domain extracted to src/tools/cookbook.py
# (slice 1, #4082/#4071). Re-imported here so this module stays a working
# facade. cookbook.py pulls `_internal_headers` / `_INTERNAL_BASE` back
# function-locally from this facade (which re-exports them from _common).
from src.tools.cookbook import ( # noqa: F401
do_download_model, do_serve_model, do_list_served_models,
do_stop_served_model, do_tail_serve_output, do_list_downloads,
do_cancel_download, do_search_hf_models, do_adopt_served_model,
do_list_cookbook_servers, do_list_serve_presets, do_serve_preset,
do_list_cached_models,
_cookbook_servers, _resolve_cookbook_host, _cookbook_env_for_host,
_infer_serve_port, _infer_serve_host, _ensure_served_endpoint,
_cookbook_register_task, _cookbook_apply_retry_suggestion,
_scan_running_model_processes, _cookbook_kill_session,
_MODEL_PROCESS_PATTERNS,
_string_arg, _validate_cookbook_ssh_target,
)
# Search domain extracted to src/tools/search.py (slice 1, #4082/#4071).
# Re-imported here so this module stays a working facade.
from src.tools.search import do_search_chats # noqa: F401
# Notes domain extracted to src/tools/notes.py (slice 1, #4082/#4071).
from src.tools.notes import do_manage_notes # noqa: F401
# Calendar domain extracted to src/tools/calendar.py (slice 1, #4082/#4071).
from src.tools.calendar import do_manage_calendar # noqa: F401
# Image domain extracted to src/tools/image.py (slice 1, #4082/#4071).
from src.tools.image import do_edit_image # noqa: F401
# Research domain extracted to src/tools/research.py (slice 1, #4082/#4071).
from src.tools.research import do_manage_research, do_trigger_research # noqa: F401
# Contacts domain extracted to src/tools/contacts.py (slice 1, #4082/#4071).
from src.tools.contacts import do_resolve_contact, do_manage_contact # noqa: F401
# Vault domain extracted to src/tools/vault.py (slice 1, #4082/#4071).
from src.tools.vault import ( # noqa: F401
_load_vault_config, _run_bw,
do_vault_search, do_vault_get, do_vault_unlock,
)
# Shared helpers live in src/tools/_common.py. Re-exported here so the
# function-local `from src.tool_implementations import _INTERNAL_BASE` (and
# friends) used by domain files still resolve through this facade.
from src.tools._common import _parse_tool_args, _INTERNAL_BASE, _internal_headers # noqa: F401
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Active email state
# ---------------------------------------------------------------------------
# When the user has an email reader window open, the frontend tells the
# backend about it on each chat submit. Email tools can resolve "this email"
# without guessing a UID. Cleared between requests by chat_routes.
_active_email_ref: Optional[Dict[str, str]] = None
def set_active_email(uid: Optional[str], folder: Optional[str] = None, account: Optional[str] = None,
subject: Optional[str] = None, sender: Optional[str] = None) -> None:
"""Stash the email currently open in the UI. None clears it."""
global _active_email_ref
if not uid:
_active_email_ref = None
return
_active_email_ref = {
"uid": str(uid),
"folder": str(folder or "INBOX"),
"account": str(account or ""),
"subject": str(subject or ""),
"from": str(sender or ""),
}
def get_active_email() -> Optional[Dict[str, str]]:
return _active_email_ref
def clear_active_email() -> None:
global _active_email_ref
_active_email_ref = None