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

160 lines
7.0 KiB
Python
Raw Normal View History

"""可信注入边界 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: 会话 IDFR-25 核心可信字段非渠道 Agent Run
None
claimed_sender_id: 客户端声明的发送者 IDFR-25由服务端校验
channel_session_id: 渠道会话 IDFR-25
channel_type: 渠道类型FR25-P0-2 扩展定位渠道适配器
account_id: 渠道账户 IDFR25-P0-2 扩展定位渠道账户
peer_id: 对端 IDFR25-P0-2 扩展定位会话对端
chat_type: 会话类型``"p2p"`` | ``"group"``FR25-P0-2 扩展
topic_id: 话题 IDFR25-P0-2 扩展不支持话题的渠道为 None
group_id: 群组 IDFR25-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")