"""出站管道上下文。 定义出站管道的可变局部上下文 ``OutboundContext``,携带请求从 sse-consume 到 deliver 各阶段产生的状态。上下文为 ``@dataclass``(不 frozen),阶段直接 修改字段以推进管道状态。 """ from __future__ import annotations from dataclasses import dataclass, field from typing import Any from yuxi.channels.contract.dtos.capability import ( CapabilityResult, ChannelCapabilities, ) from yuxi.channels.contract.dtos.channel import ChannelType from yuxi.channels.contract.dtos.common import Attachment from yuxi.channels.contract.dtos.fence import FenceGeneration from yuxi.channels.contract.dtos.outbound import ( FinalMessage, FormattedMessage, OutboundPayload, RichMessage, TrustedMessage, ) from yuxi.channels.contract.dtos.outbox import MultiPartReceipt, OutboxEntry from yuxi.channels.contract.dtos.streaming import StreamingCompleted from yuxi.channels.contract.dtos.truncation import TruncationResult from yuxi.channels.contract.errors import Error @dataclass class OutboundContext: """出站管道可变局部上下文。 携带出站请求从 sse-consume 到 deliver 各阶段产生的状态,阶段按顺序填充 字段。字段按阶段分组,便于追溯状态来源。 """ # ---- 基础字段 ---- trace_id: str request_id: str channel_type: ChannelType account_id: str channel_session_id: str agent_run_id: str delivery_mode: str trusted_sender_id: str is_owner: bool = False # 消息发送者角色(user | assistant | admin),outbox-persist 阶段据此 # 设置 Message 表 role 字段,管理员消息为 "admin"(FR-19) sender_role: str = "assistant" # 持久化策略(required | best_effort | none),由上游阶段(如 # AdminMessageService)按消息类型设置,outbox-persist 阶段据此决定 # 持久化行为(FR-22)。为 None 时回退到配置 durability_policy_default。 durability_policy: str | None = None # 出站投递幂等键(可选)。跨重试稳定,供 deliver 阶段调用适配器 # ``sendMessage`` / ``sendMessageContinuation`` 时传入,实现渠道侧去重。 idempotency_key: str | None = None # ---- 会话定位字段 ---- conversation_id: str | None = None peer_id: str | None = None message_id: str | None = None channel_capabilities: ChannelCapabilities | None = None # ---- trusted-inject 阶段请求上下文字段(FR-25) ---- # gateway_client_info 与 is_dry_run 由上游从实际请求上下文提取后填充, # trusted-inject 阶段透传给 TrustedMessageGuard.buildContext,避免领域 # 服务硬编码(FR-25)。 gateway_client_info: dict[str, Any] | None = None is_dry_run: bool = False # ---- sse-consume 阶段字段 ---- stream_chunks: list[str] = field(default_factory=list) chunk_results: list[Any] = field(default_factory=list) # ---- load-build 阶段输入字段 ---- # 富消息源数据(FR-08),由上游阶段(如 AdminMessageService)填充, # load-build 阶段将其包装为 RichMessageFields 装入 OutboundPayload。 rich_message: RichMessage | None = None # 附件列表(FR-19),由上游阶段(如 AdminMessageService)填充, # load-build / format 阶段据此装入出站负载供适配器渲染。 attachments: tuple[Attachment, ...] = () # ---- fence-check 阶段输入字段(FR-23,由入站管道捕获并透传) ---- fence_inbound_generation: int | None = None # ---- fence-check 阶段字段 ---- fence_generation: FenceGeneration | None = None fence_visible: bool = False # ---- load-build 阶段字段 ---- outbound_payload: OutboundPayload | None = None # ---- capability-verify 阶段字段 ---- capability_result: CapabilityResult | None = None # 富消息能力校验结果(FR-08 增强),声明+证明双层校验。 rich_message_capability_result: CapabilityResult | None = None # ---- format 阶段字段 ---- formatted_message: FormattedMessage | None = None # ---- trusted-inject 阶段字段 ---- trusted_message: TrustedMessage | None = None # ---- prefix 阶段字段 ---- final_message: FinalMessage | None = None # ---- streaming 阶段字段 ---- streaming_started: bool = False # 输入指示是否已启动(FR-13)。typing-indicator 阶段调用 # ``startTypingIndicator`` 成功后置 True,typing-stop 阶段据此 # 决定是否调用 ``stopTypingIndicator`` 释放输入指示资源。 typing_started: bool = False streaming_completed: StreamingCompleted | None = None # 流式中断时分片序号(可选)。流式投递被中断时记录已发送分片序号, # 供 ``sendMessageContinuation`` 续发定位续发起点。 stream_aborted_at_chunk: int | None = None # ---- truncation-check 阶段字段 ---- truncation_detected: bool = False truncation_info: TruncationResult | None = None # ---- outbox-persist 阶段字段 ---- outbox_entry: OutboxEntry | None = None # ---- deliver 阶段字段 ---- delivered: bool = False channel_msg_id: str | None = None # 多部分消息回执列表(FR-22 多部分模型)。适配器实现 ``sendMessageMultiPart`` # 时由 deliver 阶段填充,``channel_msg_id`` 取首分片 ``platform_msg_id``。 # 单分片投递(``sendMessage``)时为 None。部分失败时仅含成功分片,由 # 下游阶段据长度判定部分失败。 multi_part_receipts: list[MultiPartReceipt] | None = None # ---- 降级标志字段 ---- degraded: bool = False degraded_reason: Error | None = None