"""流式 DTO。 定义流式输出相关的不可变值对象,包括分块模式枚举、流式分块、分块 结果、流式完成结果与流式能力。所有 DTO 均为 ``dataclass(frozen=True)``,仅依赖标准库与契约层内部类型,用于 流式输出的分块投递、结果同步与能力声明(FR-13)。 """ from __future__ import annotations from dataclasses import dataclass from enum import StrEnum from yuxi.channels.contract.errors import ValidationError class ChunkMode(StrEnum): """分块模式。 标识流式输出的分块模式,用于适配器按模式渲染分块。继承 ``str, Enum`` 以支持 JSON 序列化与字符串比较。 取值: NONE: 无流式(走完整消息)。 TEXT: 文本追加模式。 BLOCK: 块模式(每分块作为新消息块)。 FULL_UPDATE: 卡片整体更新。 PARTIAL_UPDATE: 卡片局部更新。 """ NONE = "none" TEXT = "text" BLOCK = "block" FULL_UPDATE = "full_update" # 卡片整体更新 PARTIAL_UPDATE = "partial_update" # 卡片局部更新 class RenderStrategy(StrEnum): """卡片渲染策略。 继承 ``str, Enum`` 以支持 JSON 序列化。用于 ``updatePartialCard`` 方法声明局部更新的渲染策略。 取值: APPEND: 追加到现有内容。 REPLACE: 替换现有内容。 MERGE: 合并到现有内容。 """ APPEND = "append" # 追加到现有内容 REPLACE = "replace" # 替换现有内容 MERGE = "merge" # 合并到现有内容 @dataclass(frozen=True) class StreamChunk: """流式分块。 描述流式输出的单个分块,包括内容、序号与是否末块标记,用于 适配器按序投递分块。 字段: content: 分块内容。 sequence: 序号。 is_final: 是否末块(默认 False)。 """ content: str sequence: int is_final: bool = False def __post_init__(self) -> None: """校验 content 非空与 sequence 非负。 ``content`` 必须非空,``sequence`` 必须非负,在构造时即抛出 ``ValidationError``,避免空分块或负序号破坏流式分块顺序(INV-8)。 """ if not self.content: raise ValidationError("content", "must not be empty") if self.sequence < 0: raise ValidationError("sequence", "must not be negative") @dataclass(frozen=True) class ChunkResult: """分块结果。 描述单个分块投递的结果,包括分块本身、是否成功与可选错误信息, 用于流式投递的状态同步与错误处理。 字段: chunk: 流式分块。 success: 是否成功。 error: 错误信息(可选)。 """ chunk: StreamChunk success: bool error: str | None = None @dataclass(frozen=True) class StreamingCompleted: """流式完成结果。 描述流式输出完成后的汇总结果,包括总分块数与耗时(毫秒),用于 流式投递的统计与审计。 字段: total_chunks: 总分块数。 duration_ms: 耗时(毫秒)。 """ total_chunks: int duration_ms: int def __post_init__(self) -> None: """校验 total_chunks 为正整数与 duration_ms 非负。 ``total_chunks`` 必须为正整数,``duration_ms`` 必须非负,在构造时 即抛出 ``ValidationError``,避免非正分块数或负耗时导致统计失真(INV-8)。 """ if self.total_chunks <= 0: raise ValidationError("total_chunks", "must be a positive integer") if self.duration_ms < 0: raise ValidationError("duration_ms", "must not be negative") @dataclass(frozen=True) class StreamingCapability: """流式能力。 描述渠道的流式能力声明,包括是否支持流式、是否支持输入指示、 分块模式、最小分块间隔与最大分块长度,用于 FR-13 流式能力协商 与降级决策。 字段: supports_streaming: 是否支持流式(默认 False)。 supports_typing_indicator: 是否支持输入指示(默认 False)。 chunk_mode: 分块模式(默认 NONE)。 min_chunk_interval_ms: 最小分块间隔(毫秒,默认 0)。 max_chunk_length: 最大分块长度(默认 0)。 """ supports_streaming: bool = False supports_typing_indicator: bool = False chunk_mode: ChunkMode = ChunkMode.NONE min_chunk_interval_ms: int = 0 max_chunk_length: int = 0 @dataclass(frozen=True) class StreamingConfig: """流式配置。 描述流式管道的运行时配置,由 ConfigPort.getStreamingConfig 读取, 用于控制流式管道是否激活、输入指示开关、最小分块间隔与 TTL 安全阀 (FR-13)。 字段: streaming_enabled: 是否启用流式管道(默认 False)。启用时 ``delivery_mode`` 设为 ``streaming``,激活 stream-chunk / truncation-check 等流式阶段。 enable_typing: 是否启用输入指示(默认 True)。typing-indicator 阶段 据此决定是否调用 ``startTypingIndicator`` / ``stopTypingIndicator`` (FR-13)。关闭时跳过输入指示动画,不影响流式分块投递。 min_chunk_interval_ms: 最小分块间隔(毫秒,默认 200)。stream-chunk 阶段据此在分批发送之间强制等待,避免对渠道侧造成压力(AC-22)。 streaming_ttl_ms: 流式 TTL 安全阀(毫秒,默认 60000)。stream-chunk 阶段累计耗时超过该值时停止发送分块,由 typing-stop 阶段强制 结束流式会话并记录告警日志(AC-62)。 typing_ttl_ms: 输入指示 TTL(毫秒,默认 10000)。插件可据此控制 输入指示动画的刷新周期,避免动画超时停留(FR-13)。 """ streaming_enabled: bool = False enable_typing: bool = True min_chunk_interval_ms: int = 200 streaming_ttl_ms: int = 60000 typing_ttl_ms: int = 10000