pub mod ws_adapter; use serde::{Deserialize, Serialize}; /// 当前时间戳(秒),用于填充消息的 timestamp 字段 pub fn now_timestamp() -> i64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_secs() as i64 } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SessionSummary { pub session_id: String, pub title: String, pub channel_name: String, pub chat_id: String, pub message_count: i64, pub last_active_at: i64, #[serde(default, skip_serializing_if = "Option::is_none")] pub archived_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Channel { pub id: String, pub name: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(rename = "isWritable")] pub is_writable: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TopicSummary { pub topic_id: String, pub session_id: String, pub title: String, pub message_count: i64, pub created_at: i64, pub last_active_at: i64, #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MediaSummary { pub path: String, pub media_type: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub mime_type: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub content_base64: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub file_name: Option, } /// 定时任务会话查找键(用于前端加载执行对话) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SchedulerJobSessionLookup { pub channel: String, pub chat_id: String, } /// Memory 摘要(发送给前端) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MemorySummary { pub id: String, pub namespace: String, pub memory_key: String, pub content: String, pub created_at: i64, pub updated_at: i64, } /// Skill 摘要(发送给前端) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SkillSummary { pub name: String, pub description: String, pub source: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SchedulerJobSummary { pub id: String, pub kind: String, pub schedule: serde_json::Value, pub enabled: bool, pub state: String, pub last_status: Option, pub last_error: Option, pub run_count: i64, pub max_runs: Option, pub last_fired_at: Option, pub next_fire_at: Option, pub created_at: i64, #[serde(default, skip_serializing_if = "Option::is_none")] pub session_lookup: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type")] pub enum WsInbound { /// 普通用户消息 #[serde(rename = "message")] Message { content: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] attachments: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] channel: Option, #[serde(default, skip_serializing_if = "Option::is_none")] chat_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] sender_id: Option, }, /// 命令(JSON 格式) #[serde(rename = "command")] Command { payload: String }, #[serde(rename = "ping")] Ping, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type")] pub enum WsOutbound { #[serde(rename = "assistant_response")] AssistantResponse { id: String, content: String, role: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] attachments: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] subagent_task_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] topic_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] timestamp: Option, #[serde(default, skip_serializing_if = "Option::is_none")] reasoning_content: Option, }, #[serde(rename = "tool_call")] ToolCall { id: String, tool_call_id: String, tool_name: String, arguments: serde_json::Value, content: String, role: String, #[serde(default, skip_serializing_if = "Option::is_none")] subagent_task_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] topic_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] timestamp: Option, #[serde(default, skip_serializing_if = "Option::is_none")] reasoning_content: Option, }, #[serde(rename = "tool_result")] ToolResult { id: String, tool_call_id: String, tool_name: String, content: String, role: String, #[serde(default, skip_serializing_if = "Option::is_none")] subagent_task_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] topic_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] duration_ms: Option, #[serde(default, skip_serializing_if = "Option::is_none")] timestamp: Option, }, #[serde(rename = "tool_pending")] ToolPending { id: String, tool_call_id: String, tool_name: String, content: String, role: String, resume_hint: String, #[serde(default, skip_serializing_if = "Option::is_none")] subagent_task_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] topic_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] timestamp: Option, }, #[serde(rename = "error")] Error { code: String, message: String, #[serde(default, skip_serializing_if = "Option::is_none")] timestamp: Option, }, #[serde(rename = "task_started")] TaskStarted { task_id: String, description: String, subagent_type: String, }, #[serde(rename = "session_established")] SessionEstablished { session_id: String }, #[serde(rename = "session_created")] SessionCreated { session_id: String, title: String }, #[serde(rename = "session_list")] SessionList { sessions: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] current_session_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] channel_name: Option, }, #[serde(rename = "channel_list")] ChannelList { channels: Vec, }, #[serde(rename = "topic_list")] TopicList { topics: Vec, session_id: String, }, #[serde(rename = "session_loaded")] SessionLoaded { session_id: String, title: String, message_count: i64, }, #[serde(rename = "session_saved")] SessionSaved { session_id: String, filepath: String }, #[serde(rename = "task_messages_loaded")] TaskMessagesLoaded { task_id: String, description: String, subagent_type: String, status: String, #[serde(default, skip_serializing_if = "Option::is_none")] summary: Option, }, #[serde(rename = "scheduler_job_list")] SchedulerJobList { jobs: Vec, }, #[serde(rename = "memory_list")] MemoryList { memories: Vec, }, #[serde(rename = "skill_list")] SkillList { skills: Vec, }, #[serde(rename = "execution_cancelled")] ExecutionCancelled { message: String }, #[serde(rename = "pong")] Pong, } pub fn parse_inbound(raw: &str) -> Result { serde_json::from_str(raw) } pub fn serialize_inbound(msg: &WsInbound) -> Result { serde_json::to_string(msg) } pub fn serialize_outbound(msg: &WsOutbound) -> Result { serde_json::to_string(msg) }