use serde::{Deserialize, Serialize}; #[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 SlashCommandInfo { pub name: String, pub description: String, pub aliases: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct UploadDescriptor { pub upload_id: String, pub name: String, pub media_type: String, pub mime_type: String, pub size: u64, pub expires_at: i64, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct MessageAttachment { pub index: u32, pub name: String, pub media_type: String, pub mime_type: String, } impl MessageAttachment { pub fn from_media_ref(index: usize, media_ref: &crate::bus::MediaRef) -> Self { let name = std::path::Path::new(&media_ref.path) .file_name() .map(|name| name.to_string_lossy().into_owned()) .filter(|name| !name.is_empty()) .unwrap_or_else(|| "attachment".to_string()); let mime_type = mime_guess::from_path(&name) .first_or_octet_stream() .to_string(); Self { index: u32::try_from(index).unwrap_or(u32::MAX), name, media_type: media_ref.media_type.clone(), mime_type, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HistoryMessage { pub id: String, pub seq: i64, pub role: String, pub content: String, pub created_at: i64, #[serde(default, skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub tool_name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub tool_calls: Option>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub attachments: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type")] pub enum WsInbound { #[serde(rename = "user_input")] UserInput { content: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] upload_ids: 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, }, #[serde(rename = "clear_history")] ClearHistory { #[serde(default, skip_serializing_if = "Option::is_none")] chat_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] session_id: Option, }, #[serde(rename = "create_session")] CreateSession { #[serde(default, skip_serializing_if = "Option::is_none")] title: Option, }, #[serde(rename = "list_sessions")] ListSessions { #[serde(default)] include_archived: bool, }, #[serde(rename = "load_session")] LoadSession { session_id: String }, #[serde(rename = "get_session_history")] GetSessionHistory { session_id: String, #[serde(default, skip_serializing_if = "Option::is_none")] limit: Option, }, #[serde(rename = "get_session_plan")] GetSessionPlan { session_id: String }, #[serde(rename = "rename_session")] RenameSession { #[serde(default, skip_serializing_if = "Option::is_none")] session_id: Option, title: String, }, #[serde(rename = "archive_session")] ArchiveSession { #[serde(default, skip_serializing_if = "Option::is_none")] session_id: Option, }, #[serde(rename = "delete_session")] DeleteSession { #[serde(default, skip_serializing_if = "Option::is_none")] session_id: Option, }, #[serde(rename = "get_slash_commands")] GetSlashCommands, #[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")] session_id: Option, }, #[serde(rename = "error")] Error { code: String, message: String }, #[serde(rename = "session_established")] SessionEstablished { session_id: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] capabilities: Vec, }, #[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(rename = "session_loaded")] SessionLoaded { session_id: String, title: String, message_count: i64, }, #[serde(rename = "session_history")] SessionHistory { session_id: String, messages: Vec, }, #[serde(rename = "session_plan")] SessionPlan { session_id: String, plan: Option, }, #[serde(rename = "plan_updated")] PlanUpdated { session_id: String, reason: String, changed_item_ids: Vec, plan: Option, }, #[serde(rename = "session_renamed")] SessionRenamed { session_id: String, title: String }, #[serde(rename = "session_archived")] SessionArchived { session_id: String }, #[serde(rename = "session_deleted")] SessionDeleted { session_id: String }, #[serde(rename = "history_cleared")] HistoryCleared { session_id: String }, #[serde(rename = "slash_commands_list")] SlashCommandsList { commands: Vec }, #[serde(rename = "pong")] Pong, #[serde(rename = "command_executed")] CommandExecuted { message: String }, #[serde(rename = "system_notification")] SystemNotification { content: String, #[serde(default, skip_serializing_if = "Option::is_none")] session_id: Option, }, } 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) }