feat: rebuild AI commands and migrate config to TOML

This commit is contained in:
Azimkin
2026-09-14 13:03:04 +02:00
parent 01d3173254
commit 4421c9cd31
53 changed files with 1840 additions and 3464 deletions

113
app/agent_tools.py Normal file
View File

@@ -0,0 +1,113 @@
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 "")