Compare commits

..

2 Commits

Author SHA1 Message Date
95cac39fcf Add send_file tool to send workspace files to Telegram chat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:59:40 +02:00
310ee4a266 Document python3 -m uv alternative for image_understand command in CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:53:38 +02:00
3 changed files with 48 additions and 2 deletions

View File

@@ -17,6 +17,8 @@ Copy `config.example.json` to `config.json` and fill in credentials, or use envi
Key env vars: `TG_API_ID`, `TG_API_HASH`, `SESSION_DIR`, `ACCOUNTS_JSON` (JSON list) or `ACCOUNT_SESSIONS` (comma-separated session names), `AI_CONFIG` (full AI block as JSON), `BOT_CONFIG` (path to config file), `BOT_DOTENV_PATH` (path to .env).
`ai.image_understand.command` defaults to `["uvx", "minimax-coding-plan-mcp", "-y"]`. If `uvx` is not on PATH but `uv` is available via Python, use `["python3", "-m", "uv", "tool", "run", "minimax-coding-plan-mcp", "-y"]` instead.
## Architecture
### Boot path

View File

@@ -332,9 +332,33 @@ class AIService:
def _get_allowed_roots(self) -> tuple[Path, ...]:
return (self.config.workspace_dir, self.config.prompt.root)
async def _execute_send_file(
self,
arguments: dict,
ctx: "CommandContext",
allowed_roots: tuple[Path, ...],
) -> str:
from app.ai.tools import _resolve_sandboxed_path
raw_path = str(arguments.get("path", "")).strip()
caption = arguments.get("caption") or None
if not raw_path:
raise ValueError("send_file: 'path' is required")
path = _resolve_sandboxed_path(raw_path, allowed_roots)
if not path.exists():
raise ValueError(f"send_file: file not found: {raw_path!r}")
if not path.is_file():
raise ValueError(f"send_file: not a file: {raw_path!r}")
await ctx.client.send_document(
ctx.message.chat.id,
document=str(path),
caption=caption,
)
return f"Sent {raw_path} to chat"
async def _execute_tool_call(
self,
tool_call: Any,
ctx: "CommandContext",
mcp_client: MinimaxImageUnderstandClient | None,
mcp_names: set[str],
allowed_roots: tuple[Path, ...],
@@ -349,7 +373,9 @@ class AIService:
return f"Tool {name!r}: invalid JSON arguments: {exc}"
try:
if name in FILE_TOOL_DISPATCH:
if name == "send_file":
result = await self._execute_send_file(arguments, ctx, allowed_roots)
elif name in FILE_TOOL_DISPATCH:
result = await FILE_TOOL_DISPATCH[name](arguments, allowed_roots)
elif name in mcp_names and mcp_client is not None:
result = await mcp_client.call_tool(name, arguments)
@@ -463,7 +489,7 @@ class AIService:
tc_name = getattr(getattr(tc, "function", None), "name", "?")
await self._maybe_call_progress(progress, f"🛠 Calling tool: {tc_name}")
result_text = await self._execute_tool_call(
tc, mcp_client, mcp_names, allowed_roots, progress
tc, ctx, mcp_client, mcp_names, allowed_roots, progress
)
messages.append({
"role": "tool",

View File

@@ -196,6 +196,24 @@ FILE_TOOL_DISPATCH: dict[str, ToolExecutor] = {
}
FILE_TOOL_SCHEMAS: list[dict[str, Any]] = [
{
"type": "function",
"function": {
"name": "send_file",
"description": (
"Send a file from workspace/ or prompts/ to the current Telegram chat as a document. "
"Use this when the user asks to receive, download, or share a file."
),
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Relative file path"},
"caption": {"type": "string", "description": "Optional caption for the file"},
},
"required": ["path"],
},
},
},
{
"type": "function",
"function": {