ForcePilot/backend/package/yuxi/channels/contract/dtos/message_ops.py
Kris 00092c818e chore: 批量代码优化与规范完善
本次提交包含多项代码优化与规范修正:
1. 文档与注释优化:修正注释术语、补充注解与FR编号
2. 代码格式调整:统一空格、换行与缩进规范
3. 类型与接口完善:补充__all__导出、修正返回类型注解
4. 错误处理增强:新增领域错误类与校验逻辑
5. 依赖与导入调整:修复路径引用、统一时区导入
6. 协议与契约更新:完善接口文档与一致性注解
2026-07-03 19:18:13 +08:00

264 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""消息操作 DTO。
定义消息操作的枚举与不可变值对象,包括消息操作类型、消息操作能力、
消息操作定义与消息操作结果。所有 DTO 均为 ``dataclass(frozen=True)``
仅依赖标准库与契约层内部类型,用于 FR-12 消息操作(编辑 / 撤回 / 反应 /
置顶 / 卡片更新等)的能力声明、操作定义、工具转换与结果回传。
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from enum import StrEnum
from typing import Any
from yuxi.channels.contract.dtos.channel import ChannelType
from yuxi.channels.contract.dtos.common import BatchOperationFailure, Operator
from yuxi.channels.contract.dtos.tools import ToolParameter
from yuxi.channels.contract.errors import ValidationError
class MessageOperation(StrEnum):
"""消息操作类型。
标识渠道侧支持的消息操作,用于消息操作能力声明与结果回传。继承
``str, Enum`` 以支持 JSON 序列化与字符串比较。
取值:
SEND: 发送消息。
EDIT: 编辑消息。
REACT: 添加反应。
UNREACT: 移除反应。
PIN: 置顶消息。
UNPIN: 取消置顶。
UPDATE_CARD: 更新卡片。
DELETE_CARD: 删除卡片。
"""
SEND = "send"
EDIT = "edit"
REACT = "react"
UNREACT = "unreact"
PIN = "pin"
UNPIN = "unpin"
UPDATE_CARD = "update_card"
DELETE_CARD = "delete_card"
@dataclass(frozen=True)
class MessageOperationCapability:
"""消息操作能力。
描述渠道适配器支持的消息操作能力,包括是否支持反应、置顶与卡片更新,
用于能力声明与降级决策。
字段:
supports_reaction: 是否支持反应(默认 False
supports_pin: 是否支持置顶(默认 False
supports_card_update: 是否支持卡片更新(默认 False
has_side_effects: 操作是否产生不可逆副作用(编辑/撤回/置顶等修改
渠道侧状态的操作为 True查询类操作为 False默认 False
用于工具调用前置可信注入阶段决定是否强制校验可信发送者
FR-25
requires_trusted_sender: 操作是否要求可信发送者声明。为 True 时,
非渠道 Agent Run无可信发送者**不得** 执行该操作,由
pre-tool-call 可信注入阶段强制校验FR-25。默认 True对齐
PRD §FR-25 业务规则第 4 条"默认所有消息操作都需要可信发送者"
"""
supports_reaction: bool = False
supports_pin: bool = False
supports_card_update: bool = False
has_side_effects: bool = False
requires_trusted_sender: bool = True
@dataclass(frozen=True)
class MessageOperationDefinition:
"""消息操作定义。
描述单个消息操作的完整元信息,包括操作类型、工具名(不含统一前缀)、
描述、参数列表与操作粒度能力声明(副作用、可信发送者要求),由插件
的 ``getMessageOperations`` 返回,供消息操作执行器与工具转换机制
消费FR-12
工具转换规则PRD §FR-12 业务规则第 3 条):
- 定义中的 ``tool_name`` 在合并到 ``ChannelTool`` 时附加统一前缀
``MESSAGE_OPERATION_TOOL_PREFIX``(默认 ``渠道操作_``)。
- Agent 调用工具时通过工具名前缀路由到消息操作执行器,而非插件
的 ``executeTool``。
字段:
operation: 消息操作类型。
tool_name: 工具名(不含统一前缀,如 ``表情反应``)。
description: 工具描述,供 Agent 理解操作用途。
parameters: 参数列表(默认空 tuple
has_side_effects: 操作是否产生不可逆副作用(默认 False。用于
工具调用前置可信注入阶段决定是否强制校验可信发送者FR-25
requires_trusted_sender: 操作是否要求可信发送者声明(默认 True
为 True 时,非渠道 Agent Run无可信发送者**不得** 执行该
操作,由 pre-tool-call 可信注入阶段强制校验FR-25。默认
True 对齐 PRD §FR-25 业务规则第 4 条"默认所有消息操作都需要
可信发送者"
"""
operation: MessageOperation
tool_name: str
description: str
parameters: tuple[ToolParameter, ...] = ()
has_side_effects: bool = False
requires_trusted_sender: bool = True
@dataclass(frozen=True)
class MessageOperationResult:
"""消息操作结果FR-12
描述一次消息操作的执行结果,包括操作类型、是否成功、结果数据与错误
信息,用于消息操作结果回传与审计。
字段:
operation: 消息操作类型。
success: 是否成功。
result_data: 结果数据(可选)。
error: 错误信息(可选)。
"""
operation: MessageOperation
success: bool
result_data: dict[str, Any] | None = None
error: str | None = None
@dataclass(frozen=True)
class ResendMessageCmd:
"""消息重发命令MSG-RESEND
由 ``MessageManagementPort.resendMessage`` 引用,基于原消息重新构造并发送
一条新消息需记录操作人以满足审计要求FR-12
字段:
channel_type: 渠道类型。
message_id: 原消息 ID。
operator: 操作人(审计用)。
target: 重发目标(可选,缺省时复用原消息目标)。
content_overrides: 内容覆盖(可选,按字段覆盖原消息内容)。
reason: 重发原因(审计用,可选)。
"""
channel_type: ChannelType
message_id: str
operator: Operator
target: str | None = None
content_overrides: dict[str, Any] | None = None
reason: str | None = None
def __post_init__(self) -> None:
"""校验必填字段非空MSG-RESEND
``message_id`` 必须非空,在构造时即抛出 ``ValidationError``
adapter 不再做该校验INV-8
"""
if not self.message_id:
raise ValidationError("message_id", "must not be empty")
@dataclass(frozen=True)
class ResendMessageResult:
"""消息重发结果MSG-RESEND
描述重发操作的新旧消息映射关系,``new_message_id`` 为新生成消息的 ID。
字段:
original_message_id: 原消息 ID。
new_message_id: 新消息 ID。
sent_at: 发送时间戳。
"""
original_message_id: str
new_message_id: str
sent_at: datetime
@dataclass(frozen=True)
class BatchRecallCmd:
"""批量撤回命令MSG-BATCH-RECALL
由 ``MessageManagementPort.batchRecallMessages`` 引用,逐条独立事务撤回
消息(模式 D``message_ids`` 长度限制 1-500。
字段:
message_ids: 待撤回消息 ID 元组必填1-500
operator: 操作人(审计用)。
reason: 撤回原因(审计用,可选)。
"""
message_ids: tuple[str, ...]
operator: Operator
reason: str | None = None
def __post_init__(self) -> None:
"""校验必填字段非空与 ``message_ids`` 长度范围MSG-BATCH-RECALL
``message_ids`` 必须非空且长度不超过 500批量操作上限
在构造时即抛出 ``ValidationError``adapter 不再做该校验INV-8
"""
if not self.message_ids:
raise ValidationError("message_ids", "must not be empty")
if len(self.message_ids) > 500:
raise ValidationError(
"message_ids",
"must not exceed 500 items",
)
@dataclass(frozen=True)
class BatchRecallResult:
"""批量撤回结果MSG-BATCH-RECALL
描述逐条独立事务撤回消息的执行结果,``failed`` 使用通用
``BatchOperationFailure````id`` 字段承载 message_id
``succeeded`` 为每条撤回结果 dict含 message_id / channel_msg_id /
channel_recalled_at / im_side_recall_ack供前端直接展示撤回详情。
字段:
total: 待撤回消息总数。
succeeded: 成功撤回的结果 dict 元组。
failed: 失败条目元组。
"""
total: int
succeeded: tuple[dict[str, Any], ...]
failed: tuple[BatchOperationFailure, ...]
@dataclass(frozen=True)
class ReadStatus:
"""消息已读状态。
描述 ``MessageOpsAdapter.getReadStatus`` 返回的消息已读回执信息,用于
业务方判断用户是否已读 AI 发送的重要通知。集合字段使用 tuple 以保证
frozen dataclass 的不可变语义。
字段:
channel_msg_id: 渠道侧消息 ID。
read_count: 已读用户数。
total_count: 目标用户总数。
read_user_ids: 已读用户 ID 元组(渠道支持返回明细时填充,
不支持时为空 tuple调用方据 ``read_count`` 判定)。
read_at: 最近一次已读时间戳(可选,渠道不支持时为 ``None``)。
"""
channel_msg_id: str
read_count: int
total_count: int
read_user_ids: tuple[str, ...] = ()
read_at: datetime | None = None
# 消息操作工具统一前缀PRD §FR-12 业务规则第 3 条)。
# 消息操作定义转换为渠道工具时附加本前缀,``ChannelToolExecutor`` 检测
# 前缀后路由到 ``MessageOperationExecutor`` 而非插件 ``executeTool``。
MESSAGE_OPERATION_TOOL_PREFIX = "渠道操作_"