"""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.
"""