131 lines
4.1 KiB
Python
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 []
|