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

267 lines
7.9 KiB
Python
Raw Normal View History

"""会话 DTO。
定义渠道会话相关的不可变值对象包括渠道会话 ID对端 ID会话类型
会话所有者所有者转移命令与临时会话模式所有 DTO 均为
``dataclass(frozen=True)``仅依赖标准库与契约层内部类型用于会话
解析所有者管理与临时会话识别
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from enum import StrEnum
from typing import Any, Literal
from yuxi.channels.contract.dtos.common import BatchOperationFailure, Operator
from yuxi.channels.contract.errors import ValidationError
@dataclass(frozen=True)
class ChannelSessionId:
"""渠道会话 ID。
标识渠道侧一次会话的唯一 ID用于会话定位与路由匹配
字段
value: 渠道会话 ID 字符串
"""
value: str
@dataclass(frozen=True)
class PeerId:
"""对端 ID。
标识渠道侧会话对端用户或群组的唯一 ID用于会话解析与身份关联
字段
value: 对端 ID 字符串
"""
value: str
class ChatType(StrEnum):
"""会话类型。
标识渠道侧会话的拓扑类型用于路由匹配与会话所有者策略继承
``str, Enum`` 以支持 JSON 序列化与字符串比较
取值
P2P: 单聊点对点
GROUP: 群聊
"""
P2P = "p2p"
GROUP = "group"
@dataclass(frozen=True)
class SessionOwner:
"""会话所有者。
描述会话的所有者信息用于 FR-26 所有者保护策略确保仅所有者可触发
关键操作
字段
conversation_id: 所属会话 ID
owner_peer_id: 所有者对端 ID
created_at: 所有者关系建立时间
"""
conversation_id: str
owner_peer_id: str
created_at: datetime
@dataclass(frozen=True)
class OwnerTransferCmd:
"""所有者转移命令FR-26
由端口方法 ``ConversationPort.transferSessionOwner`` 引用用于将会话
所有者从当前对端转移至新对端需记录操作人以满足审计要求FR-26
字段
conversation_id: 目标会话 ID
new_owner_id: 新所有者对端 ID
operator: 操作人审计用
"""
conversation_id: str
new_owner_id: str
operator: Operator
def __post_init__(self) -> None:
"""校验必填字段非空FR-26
``conversation_id`` ``new_owner_id`` 必须非空在构造时即抛出
``ValidationError``adapter 不再做该校验INV-8
"""
if not self.conversation_id:
raise ValidationError("conversation_id", "must not be empty")
if not self.new_owner_id:
raise ValidationError("new_owner_id", "must not be empty")
@dataclass(frozen=True)
class TemporarySessionPattern:
"""临时会话模式。
描述临时会话的匹配模式FR-27用于识别周期性任务产生的临时会话
支持 cron 等模式表达式
字段
pattern: 模式表达式 ``cron:<任务 ID>:<运行 ID>``
description: 模式描述
"""
pattern: str
description: str
@dataclass(frozen=True)
class CloseSessionCmd:
"""关闭会话命令FR-27
``SessionManagementPort.closeSession`` 端口方法引用用于关闭指定
渠道会话停止接收新消息需记录操作人以满足审计要求
字段
session_id: 要关闭的会话 ID
reason: 关闭原因审计用可选
operator: 操作人审计用
"""
session_id: str
operator: Operator
reason: str | None = None
def __post_init__(self) -> None:
"""校验必填字段非空。
``session_id`` 必须非空在构造时即抛出 ``ValidationError``
adapter 不再做该校验INV-8
"""
if not self.session_id:
raise ValidationError("session_id", "must not be empty")
@dataclass(frozen=True)
class SessionMessageItem:
"""会话消息列表项。
描述渠道会话消息列表中的单条消息包含消息核心字段与渠道侧状态
字段
message_id: 消息 ID
conversation_id: 会话 ID
channel_type: 渠道类型
role: 消息角色user | assistant | admin
content: 消息内容
channel_msg_id: 渠道侧消息 ID可选
channel_status: 渠道侧消息状态可选
created_at: 创建时间
"""
message_id: str
conversation_id: str
channel_type: str
role: Literal["user", "assistant", "admin"]
content: str
created_at: datetime
channel_msg_id: str | None = None
channel_status: str | None = None
@dataclass(frozen=True)
class SessionStatsResult:
"""会话统计结果SES-STATS-01
描述单会话的统计指标 dispatch handler 组合 ConversationPort 消息
查询后计算不在核心层引入统计聚合根
字段
message_count: 消息总数
user_message_count: 用户消息数
assistant_message_count: 助手消息数
started_at: 会话开始时间
last_activity_at: 最近活动时间
duration_seconds: 会话时长
first_response_seconds: 首条用户消息到首条助手消息的间隔
无助手消息时为 None
avg_response_seconds: 平均响应间隔无助手消息时为 None
"""
message_count: int
user_message_count: int
assistant_message_count: int
started_at: datetime
last_activity_at: datetime
duration_seconds: int
first_response_seconds: int | None = None
avg_response_seconds: float | None = None
@dataclass(frozen=True)
class BatchCloseSessionsCmd:
"""批量关闭会话命令SES-BATCH-CLOSE-01
``SessionManagementPort.batchCloseSessions`` 引用支持显式 ID 列表
或筛选条件两种模式二者不可同时空``session_ids`` ``filter``
同时为空``max_count`` 越界均在 ``__post_init__`` 中校验adapter 不再
重复校验INV-8
字段
session_ids: 显式会话 ID 元组默认空元组
filter: 筛选条件 channel_type / inactive_before / status
可选
max_count: 单次最大关闭数默认 1001-1000
reason: 关闭原因审计用可选
operator: 操作人审计用
"""
operator: Operator
session_ids: tuple[str, ...] = ()
filter: dict[str, Any] | None = None
max_count: int = 100
reason: str | None = None
def __post_init__(self) -> None:
"""校验必填字段与业务规则SES-BATCH-CLOSE-01
- ``session_ids`` ``filter`` 不可同时为空
- ``max_count`` [1, 1000]
在构造时即抛出 ``ValidationError``adapter 不再做该校验INV-8
"""
if not self.session_ids and not self.filter:
raise ValidationError(
"session_ids",
"either session_ids or filter must be provided",
)
if self.max_count < 1 or self.max_count > 1000:
raise ValidationError(
"max_count",
f"max_count must be in [1, 1000], got {self.max_count}",
)
@dataclass(frozen=True)
class BatchCloseResult:
"""批量关闭会话结果SES-BATCH-CLOSE-01
描述逐条独立事务关闭会话的执行结果``failed`` 使用通用
``BatchOperationFailure````id`` 字段承载 session_id
字段
total: 待关闭会话总数
closed: 成功关闭的会话 ID 元组
failed: 失败条目元组
"""
total: int
closed: tuple[str, ...]
failed: tuple[BatchOperationFailure, ...]