fix max-bot, add tests

This commit is contained in:
Александра Пронина 2026-05-15 10:22:43 +03:00
parent 7abbaf7e7a
commit 2ad1438e1c
17 changed files with 1621 additions and 494 deletions

View file

@ -1,51 +1,88 @@
"""File handling for MAX surface."""
import os
import aiohttp
"""Incoming / outgoing file helpers for MAX (aligned with Matrix workspace layout)."""
from __future__ import annotations
import mimetypes
from pathlib import Path
import httpx
class FileHandler:
def __init__(self, workspace_root: str):
self.workspace_root = workspace_root
def _make_unique_filename(self, directory: str, filename: str) -> str:
base = Path(filename).stem
ext = Path(filename).suffix
candidate = filename
counter = 1
while os.path.exists(os.path.join(directory, candidate)):
candidate = f"{base} ({counter}){ext}"
counter += 1
return candidate
def guess_upload_type(mime_type: str | None, *, attachment_type: str) -> str:
if attachment_type == "image":
return "image"
if attachment_type == "video":
return "video"
if attachment_type == "audio":
return "audio"
mime = mime_type or ""
if mime.startswith("image/"):
return "image"
if mime.startswith("video/"):
return "video"
if mime.startswith("audio/"):
return "audio"
return "file"
async def download_attachment(
self,
download_url: str,
filename: str,
agent_workspace: str,
headers: dict = None,
) -> str:
full_dir = os.path.join(self.workspace_root, agent_workspace.strip("/"))
os.makedirs(full_dir, exist_ok=True)
unique_name = self._make_unique_filename(full_dir, filename)
filepath = os.path.join(full_dir, unique_name)
async def save_incoming_from_url(
*,
api: MaxBotApi,
workspace_root: Path,
filename: str,
url: str,
) -> str:
data = await api.download_file(url)
workspace_root.mkdir(parents=True, exist_ok=True)
relative_path, absolute_path = build_agent_workspace_path(
workspace_root=workspace_root,
filename=filename,
)
absolute_path.parent.mkdir(parents=True, exist_ok=True)
absolute_path.write_bytes(data)
return relative_path
async with aiohttp.ClientSession() as session:
async with session.get(download_url, headers=headers) as resp:
resp.raise_for_status()
with open(filepath, "wb") as f:
f.write(await resp.read())
return unique_name
async def upload_file_as_attachment(
api: MaxBotApi,
*,
filename: str,
content: bytes,
upload_type: str,
) -> dict:
meta = await api.get_upload_url(upload_type)
upload_url = meta.get("url")
token = meta.get("token")
if not isinstance(upload_url, str) or not upload_url:
raise RuntimeError("MAX uploads response missing url")
def read_outgoing_file(self, workspace_path: str, agent_workspace: str) -> bytes:
full_dir = os.path.join(self.workspace_root, agent_workspace.strip("/"))
filepath = os.path.join(full_dir, workspace_path.lstrip("/"))
with open(filepath, "rb") as f:
return f.read()
async with httpx.AsyncClient(timeout=httpx.Timeout(120.0)) as client:
response = await client.post(
upload_url,
files={"data": (filename, content, guess_mimetype(filename))},
)
response.raise_for_status()
def file_exists(self, workspace_path: str, agent_workspace: str) -> bool:
full_dir = os.path.join(self.workspace_root, agent_workspace.strip("/"))
filepath = os.path.join(full_dir, workspace_path.lstrip("/"))
return os.path.exists(filepath)
payload: dict = {}
if token:
payload["token"] = token
if upload_type == "image":
return {"type": "image", "payload": payload}
type_map = {
"file": "file",
"video": "video",
"audio": "audio",
}
mapped = type_map.get(upload_type, "file")
return {"type": mapped, "payload": payload}
def guess_mimetype(filename: str) -> str:
mime, _ = mimetypes.guess_type(filename)
return mime or "application/octet-stream"
def read_workspace_bytes(workspace_path: str | Path, *, agent_workspace: str) -> bytes:
root = Path(agent_workspace)
resolved = resolve_workspace_attachment_path(root, str(workspace_path))
return resolved.read_bytes()