//! df-tunnel 隧道事件类型 //! //! 设计依据:设计文档「三、技术选型」指出 AiChatEvent 17 变体在两端同语言保证类型一致。 //! 但 AiChatEvent 定义在 src-tauri 非 crate 层,跨 crate path 引用会破坏分层边界 //! (lib crate 依赖 src-tauri 二进制不合理)。故此处**独立定义隧道透传子集**: //! - 仅保留跨端高频透传的 4 个变体(TextDelta/ToolCall/Approval/Completed), //! Phase2 可按需追加,不依赖 src-tauri。 //! - 携带 conv_id 字段做多会话路由(对齐 F-09 多会话 conv_id 契约)。 //! //! 向后兼容:新增变体只加不减,旧消费方忽略未知变体(serde 配 `#[serde(other)]` 捕获兜底)。 use serde::{Deserialize, Serialize}; /// 握手身份宣告帧(对齐 df-relay::relay::Hello) /// /// 桌面端 WS 连 `/ws/device` 后,首条消息发此 JSON,relay 校验 kind 与 token。 /// 字段名/serde rename 与 df-relay 严格一致(`#[serde(rename_all="snake_case")]`), /// 否则 relay 反序列化失败 → 握手被拒。 /// /// 注意:本 crate 不依赖 df-relay(避免 lib→server 反向依赖),自行镜像协议结构。 /// 协议字段如有变更须同步 df-relay::relay::Hello 与本类型。 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Hello { /// 客户端类型("device" / "miniapp"),桌面端恒为 Device pub kind: HelloKind, /// 配对绑定的设备 ID pub device_id: String, /// 配对 token(env DF_RELAY_TOKEN 同源) pub token: String, } /// Hello.kind 传输表示(serde 字符串,与 df-relay::ClientKindWire 一致) #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum HelloKind { Device, Miniapp, } /// relay 控制面错误帧(握手失败时 relay 发送) /// /// relay 在校验失败/连接异常时发 `{"kind":"control","error":""}` 文本帧。 /// 桌面端据此识别握手失败原因(不依赖 error 帧亦可:连接被 Close 即视为失败)。 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RelayControlError { /// 固定 "control"(用于与正常 BroadcastMessage 区分,relay 错误帧简化为两字段) pub kind: String, /// 错误原因(handshake_failed / kind_mismatch / auth_failed) pub error: String, } /// 隧道透传的事件类型(AiChatEvent 简化子集) /// /// 变体语义对齐桌面端 ai-chat-event: /// - `TextDelta`:流式增量文本块 /// - `ToolCall`:工具调用请求(含工具名/参数) /// - `Approval`:审批请求(用户授权确认) /// - `Completed`:单轮对话完成 /// /// Phase2 可扩展 Error/Stopped/Regenerated 等变体。 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum TunnelEvent { /// 流式文本增量 TextDelta { /// 会话 ID(多会话路由用,对齐 F-09) conv_id: String, /// 增量文本片段 delta: String, }, /// 工具调用请求 ToolCall { /// 会话 ID conv_id: String, /// 工具调用唯一标识(用于回填结果) call_id: String, /// 工具名 name: String, /// 工具参数(原始 JSON,由消费方按 name 解析) arguments: serde_json::Value, }, /// 审批请求(工具/操作需用户授权) Approval { /// 会话 ID conv_id: String, /// 审批项唯一标识 approval_id: String, /// 审批描述(展示给用户) description: String, }, /// 对话单轮完成 Completed { /// 会话 ID conv_id: String, /// 本轮产生的消息 ID message_id: String, }, } impl TunnelEvent { /// 取事件所属会话 ID(路由分发的便利方法) pub fn conv_id(&self) -> &str { match self { TunnelEvent::TextDelta { conv_id, .. } | TunnelEvent::ToolCall { conv_id, .. } | TunnelEvent::Approval { conv_id, .. } | TunnelEvent::Completed { conv_id, .. } => conv_id, } } } /// 云后端 → 桌面端的操作指令(小程序远程操作映射) /// /// 对齐设计文档「Layer1 指令路由」: /// send/stop/approve/regenerate/switch 五类操作。 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum TunnelCommand { /// 发送消息 Send { conv_id: String, content: String }, /// 停止当前生成 Stop { conv_id: String }, /// 审批通过/拒绝(approval_id + accepted) Approve { conv_id: String, approval_id: String, accepted: bool }, /// 重新生成 Regenerate { conv_id: String }, /// 切换对话 Switch { conv_id: String }, } /// 隧道传输的消息包装(双向) /// /// 区分「桌面端 → 云后端 → 小程序」的事件流(TunnelEvent) /// 与「小程序 → 云后端 → 桌面端」的指令流(TunnelCommand)。 /// 同一 WS 连接复用,靠 kind 标签区分。 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "dir", rename_all = "snake_case")] pub enum TunnelMessage { /// 桌面端发出的事件(下行) Event(TunnelEvent), /// 云后端转发的指令(上行,发往桌面端) Command(TunnelCommand), }