use std::collections::HashMap; use serde::{Deserialize, Serialize}; // ============================================================================ // ContentBlock - Multimodal content representation (OpenAI-style) // ============================================================================ #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ContentBlock { #[serde(rename = "text")] Text { text: String }, #[serde(rename = "image_url")] ImageUrl { image_url: ImageUrlBlock }, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ImageUrlBlock { pub url: String, } impl ContentBlock { pub fn text(content: impl Into) -> Self { Self::Text { text: content.into() } } pub fn image_url(url: impl Into) -> Self { Self::ImageUrl { image_url: ImageUrlBlock { url: url.into() }, } } } // ============================================================================ // MediaItem - Media metadata for messages // ============================================================================ #[derive(Debug, Clone)] pub struct MediaItem { pub path: String, // Local file path pub media_type: String, // "image", "audio", "file", "video" pub mime_type: Option, pub original_key: Option, // Feishu file_key for download } impl MediaItem { pub fn new(path: impl Into, media_type: impl Into) -> Self { Self { path: path.into(), media_type: media_type.into(), mime_type: None, original_key: None, } } } // ============================================================================ // ChatMessage - Used by AgentLoop for LLM conversation history // ============================================================================ #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatMessage { pub id: String, pub role: String, pub content: String, pub media_refs: Vec, // Paths to media files for context pub timestamp: i64, #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub tool_name: Option, } impl ChatMessage { pub fn user(content: impl Into) -> Self { Self { id: uuid::Uuid::new_v4().to_string(), role: "user".to_string(), content: content.into(), media_refs: Vec::new(), timestamp: current_timestamp(), tool_call_id: None, tool_name: None, } } pub fn user_with_media(content: impl Into, media_refs: Vec) -> Self { Self { id: uuid::Uuid::new_v4().to_string(), role: "user".to_string(), content: content.into(), media_refs, timestamp: current_timestamp(), tool_call_id: None, tool_name: None, } } pub fn assistant(content: impl Into) -> Self { Self { id: uuid::Uuid::new_v4().to_string(), role: "assistant".to_string(), content: content.into(), media_refs: Vec::new(), timestamp: current_timestamp(), tool_call_id: None, tool_name: None, } } pub fn system(content: impl Into) -> Self { Self { id: uuid::Uuid::new_v4().to_string(), role: "system".to_string(), content: content.into(), media_refs: Vec::new(), timestamp: current_timestamp(), tool_call_id: None, tool_name: None, } } pub fn tool(tool_call_id: impl Into, tool_name: impl Into, content: impl Into) -> Self { Self { id: uuid::Uuid::new_v4().to_string(), role: "tool".to_string(), content: content.into(), media_refs: Vec::new(), timestamp: current_timestamp(), tool_call_id: Some(tool_call_id.into()), tool_name: Some(tool_name.into()), } } } // ============================================================================ // InboundMessage - Message from Channel to Bus (user input) // ============================================================================ #[derive(Debug, Clone)] pub struct InboundMessage { pub channel: String, pub sender_id: String, pub chat_id: String, pub content: String, pub timestamp: i64, pub media: Vec, /// Channel-specific data used internally by the channel (not forwarded). pub metadata: HashMap, /// Data forwarded from inbound to outbound (copied to OutboundMessage.metadata by gateway). pub forwarded_metadata: HashMap, } impl InboundMessage { pub fn session_key(&self) -> String { format!("{}:{}", self.channel, self.chat_id) } } // ============================================================================ // OutboundMessage - Message from Agent to Channel (bot response) // ============================================================================ #[derive(Debug, Clone)] pub struct OutboundMessage { pub channel: String, pub chat_id: String, pub content: String, pub reply_to: Option, pub media: Vec, pub metadata: HashMap, } impl OutboundMessage { pub fn is_stream_delta(&self) -> bool { self.metadata.get("_stream_delta").is_some() } } // ============================================================================ // Helpers // ============================================================================ fn current_timestamp() -> i64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis() as i64 }