feat(telegram): stop generation from the client, questions survive a restart, images reach the model inline, albums become one turn

This commit is contained in:
hh committed 2026-09-02 02:11:44 +02:00
1 parent 7a8ee0f200
commit 85b14e2c2f
12 files changed
+452 -76

No files matched your search

+38 -3
View File
@@ -3,10 +3,12 @@
from __future__ import annotations
import asyncio
import base64
import contextlib
import inspect
import logging
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING, Any, cast
from uuid import uuid4
@@ -34,7 +36,7 @@ if TYPE_CHECKING:
from beaver_gateway.events.stream import MessageStreamEvent
__all__ = ["Turns"]
__all__ = ["Turns", "content_of", "image_block"]
_log = logging.getLogger(__name__)
@@ -125,13 +127,14 @@ class Turns(Seeds):
tools: bool = True,
turn_id: str | None = None,
item_origin: str | None = None,
attachments: Sequence[dict[str, Any]] | None = None,
) -> tuple[str, TurnCapture]:
capture = TurnCapture()
acc = StreamAccumulator()
agent = self._claude_agent(conv.agent_name)
async for event in self.turn(
conv,
messages=[{"role": "user", "content": text}],
messages=[{"role": "user", "content": content_of(text, attachments)}],
origin=origin,
capture=capture,
tools=tools,
@@ -224,7 +227,12 @@ class Turns(Seeds):
prompt = f"{seed}\n\n{prompt}"
try:
text, capture = await self.run_text_turn(
conv, prompt, origin=origin, turn_id=turn_id, item_origin=head.origin
conv,
prompt,
origin=origin,
turn_id=turn_id,
item_origin=head.origin,
attachments=head.attachments if origin == "user" else None,
)
except Exception: # noqa: BLE001
_log.exception("turn %s on %s failed", turn_id, conv.external_id)
@@ -402,6 +410,33 @@ class Turns(Seeds):
await self._update(conv, apply)
IMAGE_MAX_BYTES = 5 * 1024 * 1024
def content_of(
text: str, attachments: Sequence[dict[str, Any]] | None
) -> str | list[dict[str, Any]]:
"""Plain text, or text plus the images that ride with it."""
blocks = [b for a in attachments or () if (b := image_block(a)) is not None]
if not blocks:
return text
return [{"type": "text", "text": text}, *blocks]
def image_block(attachment: dict[str, Any]) -> dict[str, Any] | None:
media_type = str(attachment.get("media_type") or "")
path = Path(str(attachment.get("path") or ""))
if not media_type.startswith("image/") or not path.is_file():
return None
if path.stat().st_size > IMAGE_MAX_BYTES:
return None
data = base64.b64encode(path.read_bytes()).decode("ascii")
return {
"type": "image",
"source": {"type": "base64", "media_type": media_type, "data": data},
}
def _prompt_preview(messages: Sequence[Any], limit: int = 400) -> str | None:
if not messages:
return None