"""入站 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", ]