- WsTunnelClient: connect_async连df-relay /ws/device + Hello(HelloKind::Device) - 握手探测(HANDSHAKE_PROBE 3s):无负帧=relay静默通过 / error帧=Auth失败 / Close=Connect失败 - 收发循环: socket入帧→parse_command_from_broadcast提payload(kind=command)→on_command回调; mpsc出帧→socket写; heartbeat 25s Ping - cleanup_conn: Close帧+500ms等task+abort兜底; connected用Arc<AtomicBool>共享 - backoff_delay(1→2→4→8→16→30s cap)预留Phase3 supervisor自动重连 - 协议对齐 df-relay(Hello字段/方向Event↔Command/payload透传),cargo check零warning+4测试通过
147 lines
5.2 KiB
Rust
147 lines
5.2 KiB
Rust
//! 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":"<reason>"}` 文本帧。
|
|
/// 桌面端据此识别握手失败原因(不依赖 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),
|
|
}
|