133 lines
4.2 KiB
Python
133 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from collections.abc import AsyncIterator, Mapping
|
|
|
|
import structlog
|
|
|
|
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,
|
|
)
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
def _ws_debug_enabled() -> bool:
|
|
value = os.environ.get("SURFACES_DEBUG_WS", "")
|
|
return value.strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
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",
|
|
)
|
|
|
|
if _ws_debug_enabled():
|
|
logger.warning(
|
|
"matrix_route_resolved",
|
|
user_id=user_id,
|
|
local_chat_id=local_chat_id,
|
|
surface_ref=chat.surface_ref,
|
|
agent_id=str(agent_id),
|
|
platform_chat_id=str(platform_chat_id),
|
|
delegate_type=type(delegate).__name__,
|
|
)
|
|
|
|
return delegate, str(platform_chat_id)
|