""" AIBOT 入站入口:桥进程把长连接消息转到本路由。 本文件职责:校验 bridge token、归一 InboundMessage、入 inbox 快回。 禁止:在本请求线程调 LLM / Graph / 出站企微。 路径:/internal/aibot/inbound(对齐 prompt/13)。 """ from __future__ import annotations import logging from typing import Any, Optional from fastapi import APIRouter, Header, HTTPException from pydantic import BaseModel, Field from agent.channel.queue import get_message_store from agent.channel.wecom.models import InboundMessage from agent.config import get_settings logger = logging.getLogger(__name__) router = APIRouter(prefix="/internal/aibot", tags=["aibot"]) class AibotInboundBody(BaseModel): """桥转发的归一字段(桥侧已解析,智能体不再调企微换 ID)。""" sender_id: str = Field(description="企微 userid,必须来自 AIBOT 报文") message_id: str content: str = "" msg_type: str = "text" chat_id: str = "" chat_type: str = "group" agent_id: str = "" create_time: int = 0 media: dict[str, Any] = Field(default_factory=dict) raw: dict[str, Any] = Field(default_factory=dict) def _check_bridge_token(x_token: Optional[str]) -> None: s = get_settings() expected = (getattr(s, "wecom_aibot_bridge_token", None) or "").strip() if not expected: if (s.ytd_env or "").lower() == "prod": raise HTTPException(status_code=503, detail="AIBOT bridge token 未配置") logger.warning("aibot inbound:未配置 bridge token,test 跳过校验") return if (x_token or "").strip() != expected: raise HTTPException(status_code=401, detail="unauthorized") @router.get("/ping") def aibot_ping() -> dict[str, str]: return {"status": "ok", "channel": "aibot"} @router.post("/inbound") def aibot_inbound( body: AibotInboundBody, x_aibot_bridge_token: Optional[str] = Header( default=None, alias="X-Aibot-Bridge-Token" ), ) -> dict[str, Any]: """ 接收桥转发入站:无 sender_id 拒绝;其余入 inbox。 """ _check_bridge_token(x_aibot_bridge_token) msg = InboundMessage( sender_id=body.sender_id.strip(), message_id=body.message_id.strip(), content=body.content or "", msg_type=body.msg_type or "text", chat_id=body.chat_id or "", chat_type=body.chat_type or "group", agent_id=body.agent_id or "", create_time=int(body.create_time or 0), media=body.media or {}, raw=body.raw or {}, ) if not msg.has_sender(): raise HTTPException(status_code=400, detail="missing_sender_id") if not msg.message_id: raise HTTPException(status_code=400, detail="missing_message_id") ok, info = get_message_store().enqueue_inbound(msg) logger.info( "aibot.inbound ok=%s info=%s sender=%s msg_id=%s", ok, info, msg.sender_id, msg.message_id, ) return {"status": "accepted" if ok else "duplicate", "detail": info}