Source code for tool_context

"""Shared context object passed to tools that need platform access.

Tools opt-in to receiving context by declaring a ``ctx`` parameter in
their ``run()`` signature.  The :class:`ToolRegistry` inspects the
handler at call-time and injects the context automatically -- the LLM
never sees or fills this parameter.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    from config import Config
    from conversation import ConversationManager
    from knowledge_graph import KnowledgeGraphManager
    from message_cache import MessageCache
    from platforms.base import PlatformAdapter
    from task_manager import TaskManager
    from threadweave import ThreadweaveManager
    from tools import ToolRegistry


[docs] @dataclass class ToolContext: """Runtime context injected into tool handlers that request it.""" platform: str = "" """Short identifier for the originating platform (``"discord"``, ``"matrix"``, ...).""" channel_id: str = "" """Platform-specific channel / room identifier.""" user_id: str = "" """Platform-specific sender identifier.""" user_name: str = "" """Human-readable display name of the sender.""" guild_id: str = "" """Discord-specific guild (server) ID. Empty on non-Discord platforms.""" adapter: PlatformAdapter | None = None """The :class:`PlatformAdapter` instance for the originating platform. For Discord this is a :class:`DiscordPlatform` whose ``.client`` property exposes the underlying :class:`discord.Client`. """ message_id: str = "" """Platform-specific ID of the message that triggered this response.""" config: Config | None = None """Global bot :class:`Config`. Gives tools access to ``redis_url``, ``model``, and other settings.""" redis: Any = None """Shared async Redis client (``redis.asyncio.Redis``). ``None`` when Redis is not configured. Tools should guard access with an ``if ctx.redis is not None`` check. """ message_cache: MessageCache | None = None """Shared :class:`MessageCache` instance for retrieving cached messages as :class:`~message_cache.CachedMessage` objects. ``None`` when Redis is not configured. """ kg_manager: KnowledgeGraphManager | None = None """Shared :class:`KnowledgeGraphManager` for the knowledge graph system. ``None`` when Redis is not configured. """ task_manager: TaskManager | None = None """Shared :class:`TaskManager` for checking background task results. Injected by the message processor so tools like ``check_task`` can access it. """ threadweave: ThreadweaveManager | None = None """Shared :class:`ThreadweaveManager` for the Threadweave persistent knowledge system (DNA Vault, Persistent Weave, Shadow Memory). ``None`` when Redis is not configured. """ tool_registry: ToolRegistry | None = None """The live :class:`ToolRegistry` that holds all loaded tools. Allows meta-tools like ``list_all_tools`` and ``reload_tools`` to inspect or modify the running tool set. """ conversation_manager: ConversationManager | None = None """Shared :class:`ConversationManager` for per-channel conversation histories. Allows tools to read or manipulate the in-memory context window for any channel. """ openrouter: Any = None """The :class:`OpenRouterClient` instance for the current session. Allows tools like ``extend_tool_loop`` to adjust the tool-calling loop parameters at runtime. """ all_adapters: list[Any] = field(default_factory=list) """All running :class:`PlatformAdapter` instances. Allows cross-platform tools (e.g. ``list_active_servers``) to query every connected platform, not just the one that triggered the current message. """ adapters_by_name: dict[str, Any] = field(default_factory=dict) """All platform adapters keyed by name (e.g. ``"discord"``, ``"discord-self"``, ``"matrix"``). Allows tools to look up a specific adapter directly:: selfbot = ctx.adapters_by_name.get("discord-self") """ injected_tools: list[str] | None = None """Tool names requested for injection by ``request_tool_injection``. Populated at runtime by tool handlers; the ``chat()`` loop in :class:`OpenRouterClient` checks this after each round and merges any new names into the active tool list. When several tools run in the same model round they share this :class:`ToolContext` and execute concurrently via ``asyncio.gather``. Use only atomic updates (e.g. ``list.append``); avoid read-modify-write races on the same list without coordination. """ sent_files: list[dict[str, Any]] = field(default_factory=list) """Media files sent to the channel during tool execution. Each entry has keys: ``data`` (raw ``bytes``), ``filename`` (``str``), ``mimetype`` (``str``), and optionally ``file_url`` (``str``). After the LLM call, the message processor converts these into real multimodal content parts (``image_url``, ``input_audio``, etc.) via :func:`~platforms.media_common.media_to_content_parts` and appends them to conversation history so the bot can see its own visual and audio outputs on subsequent turns. Parallel tools in one LLM round share this list; appends are safe, but do not assume ordering of entries matches tool-call order unless you serialize tool execution. """