2026-07-02 03:22:12 +08:00
|
|
|
|
"""可信注入边界 DTO。
|
|
|
|
|
|
|
|
|
|
|
|
定义可信注入边界(FR-25)的不可变值对象,包括可信上下文、可信消息上下文
|
|
|
|
|
|
与消息操作上下文。所有 DTO 均为 ``dataclass(frozen=True)``,仅依赖标准库
|
|
|
|
|
|
与契约层内部类型,用于服务端强制注入发送者身份与所有者标记,确保渠道侧
|
|
|
|
|
|
无法伪造发送者。
|
|
|
|
|
|
|
|
|
|
|
|
FR25-P0-2 可信上下文(``TrustedContext``):聚合入站管道解析的可信字段
|
|
|
|
|
|
(会话、发送者、渠道类型、对端等),由 ``ChannelToolExecutor`` 注入到
|
|
|
|
|
|
``_trusted_*`` 保留键,供 ``MessageOperationExecutor`` 提取构建可信消息
|
|
|
|
|
|
操作上下文。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
2026-07-03 19:18:13 +08:00
|
|
|
|
from typing import Any, Literal
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
from yuxi.channels.contract.dtos.channel import ChannelType
|
2026-07-03 19:18:13 +08:00
|
|
|
|
from yuxi.channels.contract.errors import ValidationError
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
# === 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
|
2026-07-03 19:18:13 +08:00
|
|
|
|
chat_type: Literal["p2p", "group"] | None = None
|
2026-07-02 03:22:12 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
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")
|
|
|
|
|
|
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
@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
|
2026-07-03 19:18:13 +08:00
|
|
|
|
|
|
|
|
|
|
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")
|