ForcePilot/backend/package/yuxi/channels/contract/dtos/inbound.py

142 lines
5.3 KiB
Python
Raw Normal View History

"""入站 DTO。
定义入站管道入口的命令与结果值对象包括入站消息命令与入站结果所有
DTO 均为 ``dataclass(frozen=True)``仅依赖标准库与契约层内部类型用于
渠道适配器向核心层传递原始事件并返回 ACK 决策
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Literal
from yuxi.channels.contract.dtos.channel import ChannelType
from yuxi.channels.contract.dtos.command import CommandResponse
from yuxi.channels.contract.dtos.common import RawEvent
from yuxi.channels.contract.errors import ValidationError
@dataclass(frozen=True)
class InboundMessageCmd:
"""入站消息命令FR-01
由入站端口方法引用封装渠道适配器接收到的原始事件携带渠道类型
账户 ID原始事件与可选追踪 ID作为入站管道的统一输入
字段
channel_type: 渠道类型
account_id: 渠道账户 ID
raw_event: 原始事件
trace_id: 追踪 ID可选
"""
channel_type: ChannelType
account_id: str
raw_event: RawEvent
trace_id: str | None = None
def __post_init__(self) -> None:
"""校验必填字段非空。
``account_id`` 必须非空在构造时即抛出 ``ValidationError``
adapter 不再做该校验INV-8
"""
if not self.account_id:
raise ValidationError("account_id", "must not be empty")
@dataclass(frozen=True)
class InboundResult:
"""入站结果FR-24
描述入站管道处理后的 ACK 决策与后续 Agent 运行信息用于适配器按
决策向渠道侧返回确认或保持静默
字段
ack_decision: ACK 决策ack | nack | pending
agent_run_id: 关联的 Agent 运行 ID可选
is_silent: 是否静默处理默认 False
error: NACK 错误消息FR-24 显式 NACK ACK 互斥
``ack_decision="nack"`` 时填充
command_response: 命令响应FR-15命令命中且需回显时由
command-check 阶段填充 reply 阶段向渠道侧回送命令响应内容
非命令路径或静默命令为 None
error_code: 错误码用例服务捕获异常时填充默认空字符串
trace_id: 追踪 ID用例服务捕获异常时填充默认空字符串
raw_response: 渠道侧契约回包可选用于 Webhook 配置验证等
需要向渠道侧返回特定格式响应的场景如飞书 challenge 握手
``InboundAdapter.extractWebhookResponse`` 填充 None
Router 直接将其作为 HTTP 响应体返回跳过标准 ``{"success":...}``
包装确保渠道平台能正确解析握手回包
"""
ack_decision: Literal["ack", "nack", "pending"]
agent_run_id: str | None = None
is_silent: bool = False
error: str | None = None
command_response: CommandResponse | None = None
error_code: str = ""
trace_id: str = ""
raw_response: dict[str, Any] | None = None
@dataclass(frozen=True)
class ReceiveInboundCmd:
"""Webhook 入站接收命令FR-01
``InboundMessagePort.receiveWebhook`` 调用封装 webhook 路由解析
后的原始事件负载与请求头 ``InboundMessageCmd`` 的差异不要求
``account_id``账户身份解析下沉入站管道 webhook 适配器仅传递
渠道类型原始事件字符串与请求头FR-01
字段
channel_type: 渠道类型
raw_event: 原始事件负载字符串webhook body 原文
headers: 请求头含签名时间戳等默认空 dict
trace_id: 链路追踪 ID可选 Router ContextVar 注入
确保全链路 trace_id 一致缺省时由用例服务生成新值
"""
channel_type: ChannelType
raw_event: str
headers: dict[str, str] = field(default_factory=dict)
trace_id: str | None = None
def __post_init__(self) -> None:
"""校验必填字段非空。
``channel_type`` 不可为 ``None````raw_event`` 必须为非空字符串
违规抛 ``ValidationError``与同文件 ``InboundMessageCmd`` 风格一致
INV-7 异常协议一致性
"""
if self.channel_type is None:
raise ValidationError("channel_type", "must not be None")
if not self.raw_event:
raise ValidationError("raw_event", "must be a non-empty string")
@dataclass(frozen=True)
class SignatureVerifyResult:
"""Webhook 签名校验结果WHK-09
``InboundAdapter.verifySignature`` 返回携带校验结论与失败原因
Router 与入站管道 signature-verify 阶段消费``reason`` 在校验通过
时为 ``None``失败时填充可读原因 ``"timestamp out of window"``
/ ``"signature mismatch"``辅助接入调试与日志排查
字段
valid: 签名校验是否通过
reason: 校验失败原因通过时为 ``None``
"""
valid: bool
reason: str | None = None
__all__ = [
"InboundMessageCmd",
"InboundResult",
"ReceiveInboundCmd",
"SignatureVerifyResult",
]