"""可信注入边界 DTO。 定义可信注入边界(FR-25)的不可变值对象,包括可信上下文、可信消息上下文 与消息操作上下文。所有 DTO 均为 ``dataclass(frozen=True)``,仅依赖标准库 与契约层内部类型,用于服务端强制注入发送者身份与所有者标记,确保渠道侧 无法伪造发送者。 FR25-P0-2 可信上下文(``TrustedContext``):聚合入站管道解析的可信字段 (会话、发送者、渠道类型、对端等),由 ``ChannelToolExecutor`` 注入到 ``_trusted_*`` 保留键,供 ``MessageOperationExecutor`` 提取构建可信消息 操作上下文。 """ from __future__ import annotations from dataclasses import dataclass from typing import Any, Literal from yuxi.channels.contract.dtos.channel import ChannelType from yuxi.channels.contract.errors import ValidationError # === FR-25 可信上下文保留键 === # Agent 运行时在调用渠道工具执行器前,将服务端可信上下文以这些保留键 # 注入到工具参数字典中。``MessageOperationExecutor`` 提取后从参数中移除, # 防止传递给插件方法。保留键使用 ``_trusted_`` 前缀,避免与 LLM 生成的 # 业务参数冲突。 TRUSTED_CONVERSATION_ID_KEY = "_trusted_conversation_id" TRUSTED_CLAIMED_SENDER_ID_KEY = "_trusted_claimed_sender_id" TRUSTED_CHANNEL_SESSION_ID_KEY = "_trusted_channel_session_id" TRUSTED_GATEWAY_CLIENT_INFO_KEY = "_trusted_gateway_client_info" TRUSTED_IS_DRY_RUN_KEY = "_trusted_is_dry_run" # 群组上下文保留键(FR25-P0-2 扩展):群组场景下消息操作(如置顶、卡片更新) # 需要群组 ID 定位会话,由服务端从入站管道解析的 ``channel_group_id`` 强制 # 注入,防止 LLM 伪造群组定位。 TRUSTED_GROUP_ID_KEY = "_trusted_group_id" # FR-25 可信上下文保留键集合,用于批量剥离。 # 由工具包装器(``wrap_channel_tool``)注入到工具参数字典中, # ``MessageOperationExecutor`` 提取后从参数中移除,``ChannelToolExecutor`` # 在普通工具路径中剥离以防止保留键泄露给插件方法。保留键使用 # ``_trusted_`` 前缀,避免与 LLM 生成的业务参数冲突。 TRUSTED_RESERVED_KEYS = frozenset( { TRUSTED_CONVERSATION_ID_KEY, TRUSTED_CLAIMED_SENDER_ID_KEY, TRUSTED_CHANNEL_SESSION_ID_KEY, TRUSTED_GATEWAY_CLIENT_INFO_KEY, TRUSTED_IS_DRY_RUN_KEY, TRUSTED_GROUP_ID_KEY, } ) @dataclass(frozen=True) class TrustedContext: """可信上下文(FR25-P0-2)。 聚合入站管道解析出的可信字段,由 ``ChannelToolExecutor`` 在工具调用前 注入到 ``_trusted_*`` 保留键,供 ``MessageOperationExecutor`` 提取并 构建可信消息操作上下文(FR-25)。字段与 ``MessageOperationExecutor`` 所需可信字段对齐,并扩展渠道定位字段(``channel_type`` / ``account_id`` / ``peer_id`` / ``chat_type`` / ``topic_id``)以支持更完整的上下文 注入。 所有字段均可空,适配非渠道 Agent Run(无可信会话)与未声明字段渠道的 降级场景;由消费方按 FR-25 规则校验必填性。 字段: conversation_id: 会话 ID(FR-25 核心可信字段,非渠道 Agent Run 为 None)。 claimed_sender_id: 客户端声明的发送者 ID(FR-25,由服务端校验)。 channel_session_id: 渠道会话 ID(FR-25)。 channel_type: 渠道类型(FR25-P0-2 扩展,定位渠道适配器)。 account_id: 渠道账户 ID(FR25-P0-2 扩展,定位渠道账户)。 peer_id: 对端 ID(FR25-P0-2 扩展,定位会话对端)。 chat_type: 会话类型(``"p2p"`` | ``"group"``,FR25-P0-2 扩展)。 topic_id: 话题 ID(FR25-P0-2 扩展,不支持话题的渠道为 None)。 group_id: 群组 ID(FR25-P0-2 扩展,群组场景下定位群会话)。``p2p`` 会话为 None;``group`` 会话由入站管道从 ``channel_group_id`` 解析后服务端强制注入,供消息操作(如置顶、卡片更新)定位群组。 """ conversation_id: str | None = None claimed_sender_id: str | None = None channel_session_id: str | None = None channel_type: ChannelType | None = None account_id: str | None = None peer_id: str | None = None chat_type: Literal["p2p", "group"] | None = None topic_id: str | None = None group_id: str | None = None @dataclass(frozen=True) class TrustedMessageContext: """可信消息上下文。 描述服务端强制注入的发送者身份与所有者标记,确保渠道侧无法伪造 发送者。同时携带网关客户端信息与 dry-run 标记,用于可信注入边界 (FR-25)的上下文传递。 字段: sender_id: 发送者 ID(服务端强制注入)。 is_owner: 是否为会话所有者(服务端强制注入,默认 False)。 gateway_client_info: 网关客户端信息(可选)。 is_dry_run: 是否为 dry-run 模式(默认 False)。 """ sender_id: str is_owner: bool = False gateway_client_info: dict[str, Any] | None = None is_dry_run: bool = False def __post_init__(self) -> None: """校验 sender_id 非空。 ``sender_id`` 必须非空,在构造时即抛出 ``ValidationError``,避免 空发送者身份被注入到渠道消息(FR-25 / INV-8)。 """ if not self.sender_id: raise ValidationError("sender_id", "must not be empty") @dataclass(frozen=True) class MessageOperationContext: """消息操作上下文。 描述消息操作的执行上下文,携带可信消息上下文、操作类型、渠道消息 ID 与渠道会话 ID,用于消息操作(edit / recall / pin 等)的可信注入与 上下文传递。 字段: trusted_context: 可信消息上下文。 operation: 操作类型(edit / recall / pin 等)。 channel_msg_id: 渠道消息 ID。 channel_session_id: 渠道会话 ID(可选)。 group_id: 群组 ID(可选,FR25-P0-2 扩展)。群组场景下的消息操作 (如置顶、卡片更新)按需消费以定位群会话;``p2p`` 会话为 None。 由服务端从入站管道解析的 ``channel_group_id`` 经可信注入链路 强制注入,防止 LLM 伪造群组定位。 """ trusted_context: TrustedMessageContext operation: str channel_msg_id: str channel_session_id: str | None = None group_id: str | None = None def __post_init__(self) -> None: """校验 operation 与 channel_msg_id 非空。 ``operation`` 与 ``channel_msg_id`` 必须非空,在构造时即抛出 ``ValidationError``,避免空操作类型或空消息 ID 导致操作无法定位 目标消息(FR-25 / INV-8)。 """ if not self.operation: raise ValidationError("operation", "must not be empty") if not self.channel_msg_id: raise ValidationError("channel_msg_id", "must not be empty")