from __future__ import annotations import logging from yuxi.channel.domain.event.message_blocked import MessageBlocked from yuxi.channel.domain.event.message_received import MessageReceived from yuxi.channel.domain.service.message_context import MessageContext from yuxi.channel.domain.model.message.unified_message import UnifiedMessage from yuxi.channel.domain.port.event_publisher_port import EventPublisherPort from yuxi.channel.domain.repository.message_log_repository import MessageLogRepositoryPort from yuxi.channel.domain.service.pipeline import Pipeline logger = logging.getLogger(__name__) class InboundService: def __init__( self, pipeline: Pipeline, message_log_repo: MessageLogRepositoryPort | None = None, event_publisher: EventPublisherPort | None = None, ) -> None: self._pipeline = pipeline self._message_log_repo = message_log_repo self._events = event_publisher async def submit( self, message: UnifiedMessage, *, channel_type: str, trace_id: str = "", ) -> MessageContext: ctx = MessageContext(message=message, channel_type=channel_type) if trace_id: ctx.trace_id = trace_id await self._create_log(ctx) result = await self._pipeline.execute(ctx) await self._update_pipeline_log(result) await self._publish_event(result) return result async def _create_log(self, ctx: MessageContext) -> None: if not self._message_log_repo: return try: await self._message_log_repo.create_log( trace_id=ctx.trace_id, message_id=ctx.message.message_id, channel_type=ctx.channel_type, direction="inbound", sender_id=ctx.message.sender.id, content_summary=(ctx.message.content or "")[:500], agent_config_id=ctx.message.agent_config_id, ) except Exception: logger.debug("message log create failed for %s", ctx.trace_id) async def _update_pipeline_log(self, ctx: MessageContext) -> None: if not self._message_log_repo: return try: pipeline_result = "skipped" if ctx.is_skipped else ("aborted" if ctx.is_aborted else "passed") status = "processing" if pipeline_result == "passed" else "completed" await self._message_log_repo.update_pipeline_result( trace_id=ctx.trace_id, message_id=ctx.message.message_id, pipeline_result=pipeline_result, status=status, abort_reason=ctx.abort_reason if ctx.is_aborted else None, ) except Exception: logger.debug("pipeline log update failed for %s", ctx.trace_id) async def _publish_event(self, ctx: MessageContext) -> None: if not self._events: return session_id = ( ctx.message.metadata.get("group_id") or ctx.message.metadata.get("session_id") or ctx.message.sender.id or "" ) try: if ctx.is_aborted: await self._events.publish( MessageBlocked( message_id=ctx.message.message_id, channel_type=ctx.channel_type, session_id=session_id, reason=ctx.abort_reason, abort_code=ctx.abort_code, trace_id=ctx.trace_id, ) ) elif not ctx.is_skipped: await self._events.publish( MessageReceived( message_id=ctx.message.message_id, channel_type=ctx.channel_type, session_id=session_id, sender_id=ctx.message.sender.id, content_summary=(ctx.message.content or "")[:200], trace_id=ctx.trace_id, ) ) except Exception: logger.debug("event publish failed for %s", ctx.trace_id)