本次提交包含多项代码优化与规范修正: 1. 文档与注释优化:修正注释术语、补充注解与FR编号 2. 代码格式调整:统一空格、换行与缩进规范 3. 类型与接口完善:补充__all__导出、修正返回类型注解 4. 错误处理增强:新增领域错误类与校验逻辑 5. 依赖与导入调整:修复路径引用、统一时区导入 6. 协议与契约更新:完善接口文档与一致性注解
160 lines
7.0 KiB
Python
160 lines
7.0 KiB
Python
"""可信注入边界 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")
|