"""DB-backed gateway config resolution and message dispatch helpers. This module exists to keep `app.api.*` thin: APIs should call OpenClaw services, not directly orchestrate gateway RPC calls. """ from __future__ import annotations from uuid import uuid4 from fastapi import HTTPException, status from app.models.boards import Board from app.models.gateways import Gateway from app.services.openclaw.db_service import OpenClawDBService from app.services.openclaw.gateway_rpc import GatewayConfig as GatewayClientConfig from app.services.openclaw.gateway_rpc import OpenClawGatewayError, ensure_session, send_message class GatewayDispatchService(OpenClawDBService): """Resolve gateway config for boards and dispatch messages to agent sessions.""" async def optional_gateway_config_for_board( self, board: Board, ) -> GatewayClientConfig | None: if board.gateway_id is None: return None gateway = await Gateway.objects.by_id(board.gateway_id).first(self.session) if gateway is None or not gateway.url: return None return GatewayClientConfig(url=gateway.url, token=gateway.token) async def require_gateway_config_for_board( self, board: Board, ) -> tuple[Gateway, GatewayClientConfig]: if board.gateway_id is None: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Board is not attached to a gateway", ) gateway = await Gateway.objects.by_id(board.gateway_id).first(self.session) if gateway is None or not gateway.url: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Gateway is not configured for this board", ) return gateway, GatewayClientConfig(url=gateway.url, token=gateway.token) async def send_agent_message( self, *, session_key: str, config: GatewayClientConfig, agent_name: str, message: str, deliver: bool = False, ) -> None: await ensure_session(session_key, config=config, label=agent_name) await send_message(message, session_key=session_key, config=config, deliver=deliver) async def try_send_agent_message( self, *, session_key: str, config: GatewayClientConfig, agent_name: str, message: str, deliver: bool = False, ) -> OpenClawGatewayError | None: try: await self.send_agent_message( session_key=session_key, config=config, agent_name=agent_name, message=message, deliver=deliver, ) except OpenClawGatewayError as exc: return exc return None @staticmethod def resolve_trace_id(correlation_id: str | None, *, prefix: str) -> str: normalized = (correlation_id or "").strip() if normalized: return normalized return f"{prefix}:{uuid4().hex[:12]}"