surfaces/adapter/matrix/routed_platform.py

110 lines
3.6 KiB
Python

from __future__ import annotations
from collections.abc import AsyncIterator, Mapping
from adapter.matrix.store import get_room_meta
from core.chat import ChatManager
from core.store import StateStore
from sdk.interface import (
Attachment,
MessageChunk,
MessageResponse,
PlatformClient,
PlatformError,
User,
UserSettings,
)
class RoutedPlatformClient(PlatformClient):
def __init__(
self,
*,
chat_mgr: ChatManager,
store: StateStore,
delegates: Mapping[str, PlatformClient],
) -> None:
if not delegates:
raise ValueError("RoutedPlatformClient requires at least one delegate")
self._chat_mgr = chat_mgr
self._store = store
self._delegates = dict(delegates)
self._default_client = next(iter(self._delegates.values()))
self._prototype_state = getattr(self._default_client, "_prototype_state", None)
async def get_or_create_user(
self,
external_id: str,
platform: str,
display_name: str | None = None,
) -> User:
return await self._default_client.get_or_create_user(
external_id=external_id,
platform=platform,
display_name=display_name,
)
async def send_message(
self,
user_id: str,
chat_id: str,
text: str,
attachments: list[Attachment] | None = None,
) -> MessageResponse:
delegate, platform_chat_id = await self._resolve_delegate(user_id, chat_id)
return await delegate.send_message(user_id, platform_chat_id, text, attachments)
async def stream_message(
self,
user_id: str,
chat_id: str,
text: str,
attachments: list[Attachment] | None = None,
) -> AsyncIterator[MessageChunk]:
delegate, platform_chat_id = await self._resolve_delegate(user_id, chat_id)
async for chunk in delegate.stream_message(user_id, platform_chat_id, text, attachments):
yield chunk
async def get_settings(self, user_id: str) -> UserSettings:
return await self._default_client.get_settings(user_id)
async def update_settings(self, user_id: str, action) -> None:
await self._default_client.update_settings(user_id, action)
async def close(self) -> None:
for delegate in self._delegates.values():
close = getattr(delegate, "close", None)
if callable(close):
await close()
async def _resolve_delegate(self, user_id: str, local_chat_id: str) -> tuple[PlatformClient, str]:
chat = await self._chat_mgr.get(local_chat_id, user_id)
if chat is None:
raise PlatformError(
f"unknown matrix chat id: {local_chat_id}",
code="MATRIX_CHAT_NOT_FOUND",
)
room_meta = await get_room_meta(self._store, chat.surface_ref)
if room_meta is None:
raise PlatformError(
f"matrix room is not bound: {chat.surface_ref}",
code="MATRIX_ROOM_NOT_BOUND",
)
agent_id = room_meta.get("agent_id")
platform_chat_id = room_meta.get("platform_chat_id")
if not agent_id or not platform_chat_id:
raise PlatformError(
f"matrix room routing is incomplete: {chat.surface_ref}",
code="MATRIX_ROUTE_INCOMPLETE",
)
delegate = self._delegates.get(str(agent_id))
if delegate is None:
raise PlatformError(
f"unknown matrix agent id: {agent_id}",
code="MATRIX_AGENT_NOT_FOUND",
)
return delegate, str(platform_chat_id)