from __future__ import annotations from pathlib import Path from typing import Annotated, Any from micro_agents import Injected, SessionContext, Tool, ToolManager OWNER_ONLY_TOOL_NAMES = frozenset( { "list_files", "read_file", "attach_files", "write_file", "make_directory", "execute_python", } ) class AgentToolManager(ToolManager): async def is_allowed(self, tool: Tool, context: SessionContext) -> bool: if tool.name in OWNER_ONLY_TOOL_NAMES: return context.get("is_owner") is True return True class TelegramTools: def __init__( self, client: Any, chat_id: int | str, *, base_dir: Path, guest_base_dir: Path | None = None, reply_to_message_id: int | None = None, ) -> None: self._client = client self._chat_id = chat_id self._base_dir = base_dir.resolve() self._guest_base_dir = (guest_base_dir or base_dir).resolve() self._reply_to_message_id = reply_to_message_id def tools(self) -> list[Tool]: return [ Tool(self.send_message, name="send_message", strict=False), Tool(self.send_image, name="send_image", strict=False), Tool(self.send_file, name="send_file", strict=False), ] async def send_message(self, text: str) -> str: """Send a text message to the current Telegram chat.""" if not text.strip(): raise ValueError("text must not be empty") message = await self._client.send_message( self._chat_id, text, reply_to_message_id=self._reply_to_message_id, ) return self._result("message", message) async def send_image( self, path: str, caption: str = "", context: Annotated[SessionContext | None, Injected] = None, ) -> str: """Send a local image from the agent workspace to the current Telegram chat.""" target = self._resolve_file(path, context) message = await self._client.send_photo( self._chat_id, str(target), caption=caption or None, reply_to_message_id=self._reply_to_message_id, ) return self._result("image", message) async def send_file( self, path: str, caption: str = "", context: Annotated[SessionContext | None, Injected] = None, ) -> str: """Send a local file from the agent workspace to the current Telegram chat.""" target = self._resolve_file(path, context) message = await self._client.send_document( self._chat_id, str(target), caption=caption or None, reply_to_message_id=self._reply_to_message_id, ) return self._result("file", message) def _resolve_file(self, value: str, context: SessionContext | None) -> Path: base_dir = ( self._base_dir if context is not None and context.get("is_owner") is True else self._guest_base_dir ) path = Path(value) if path.is_absolute(): raise ValueError("Absolute paths are not allowed") target = (base_dir / path).resolve() if target != base_dir and base_dir not in target.parents: raise ValueError("Path escapes the agent workspace") if not target.is_file(): raise ValueError(f"File does not exist: {value}") return target @staticmethod def _result(kind: str, message: Any) -> str: message_id = getattr(message, "id", None) return f"Sent Telegram {kind}" + (f" (message_id={message_id})" if message_id else "")