190 lines
5.9 KiB
Rust
190 lines
5.9 KiB
Rust
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<String>) -> Self {
|
|
Self::Text { text: content.into() }
|
|
}
|
|
|
|
pub fn image_url(url: impl Into<String>) -> 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<String>,
|
|
pub original_key: Option<String>, // Feishu file_key for download
|
|
}
|
|
|
|
impl MediaItem {
|
|
pub fn new(path: impl Into<String>, media_type: impl Into<String>) -> 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<String>, // Paths to media files for context
|
|
pub timestamp: i64,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tool_call_id: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tool_name: Option<String>,
|
|
}
|
|
|
|
impl ChatMessage {
|
|
pub fn user(content: impl Into<String>) -> 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<String>, media_refs: Vec<String>) -> 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<String>) -> 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<String>) -> 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<String>, tool_name: impl Into<String>, content: impl Into<String>) -> 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<MediaItem>,
|
|
/// Channel-specific data used internally by the channel (not forwarded).
|
|
pub metadata: HashMap<String, String>,
|
|
/// Data forwarded from inbound to outbound (copied to OutboundMessage.metadata by gateway).
|
|
pub forwarded_metadata: HashMap<String, String>,
|
|
}
|
|
|
|
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<String>,
|
|
pub media: Vec<MediaItem>,
|
|
pub metadata: HashMap<String, String>,
|
|
}
|
|
|
|
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
|
|
}
|