Files
beaver-gateway/src/beaver_gateway/conversations/messaging.py
T

131 lines
4.1 KiB
Python

"""Putting words into a conversation: a message, an inject, ``say``, ``schedule``."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any, cast
from beaver_gateway.conversations.turns import Turns
if TYPE_CHECKING:
from datetime import datetime
from beaver_gateway.conversations.injects import Priority
from beaver_gateway.storage.models import Conversation, InjectQueueItem
__all__ = ["Messaging"]
_log = logging.getLogger(__name__)
class Messaging(Turns):
async def post(
self,
conv: Conversation,
text: str,
*,
origin: str = "user",
attachments: list[dict[str, Any]] | None = None,
) -> InjectQueueItem:
item = await self._queue.push(
conversation_id=cast("int", conv.id),
priority="user",
origin=origin,
text=text,
attachments=attachments,
)
await self.touch_user(conv)
self._bus.publish(
"message.queued",
conversation_id=conv.external_id,
item=item.id,
origin=origin,
)
self._ensure_worker(cast("int", conv.id))
return item
async def inject(
self,
conv: Conversation,
text: str,
*,
urgency: Priority = "normal",
origin: str = "system",
interrupt: bool = True,
) -> InjectQueueItem:
if conv.kind == "master" and conv.status != "open":
live = await self.open_master()
if live is None:
_log.error(
"inject (%s) for closed master %s: no open master, it stays there",
origin,
conv.external_id,
)
else:
_log.info(
"inject (%s) for closed master %s goes to %s",
origin,
conv.external_id,
live.external_id,
)
conv = live
item = await self._queue.push(
conversation_id=cast("int", conv.id),
priority=urgency,
origin=origin,
text=text,
)
self._bus.publish(
"inject.queued",
conversation_id=conv.external_id,
item=item.id,
priority=urgency,
origin=origin,
)
if urgency == "urgent" and interrupt:
backend = self._backend(conv.agent_name)
if await backend.interrupt(conv.external_id):
_log.info(
"conversation %s: interrupted for urgent inject", conv.external_id
)
await self._queue.mark_interrupting(item)
self._ensure_worker(cast("int", conv.id))
return item
async def interrupt(self, conv: Conversation) -> bool:
"""Cut the running turn; the reply so far still lands."""
try:
backend = self._backend(conv.agent_name)
except LookupError:
return False
return await backend.interrupt(conv.external_id)
async def say(self, conv: Conversation, text: str) -> dict[str, Any]:
runner = self._runners.get(cast("int", conv.id))
_log.info("say[%s]: %s", conv.external_id, text[:200])
return self._bus.publish(
"say",
conversation_id=conv.external_id,
text=text,
turn_id=runner.turn_id if runner is not None else None,
)
async def schedule(
self,
conv: Conversation,
at: str,
text: str,
*,
urgency: Priority = "wake",
dedupe_key: str | None = None,
) -> tuple[int | None, datetime]:
if self.scheduler is None:
msg = "no scheduler; `schedule` is unavailable"
raise RuntimeError(msg)
return await self.scheduler.schedule(
conv, at, text, urgency=urgency, dedupe_key=dedupe_key
)
async def schedules(self, conv: Conversation | None = None) -> list[dict[str, Any]]:
return await self.scheduler.scheduled(conv) if self.scheduler else []