coro_core/llm/
message.rs

1//! LLM message structures
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Represents a message in an LLM conversation
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct LlmMessage {
9    /// Role of the message sender
10    pub role: MessageRole,
11
12    /// Content of the message
13    pub content: MessageContent,
14
15    /// Optional metadata
16    pub metadata: Option<HashMap<String, serde_json::Value>>,
17}
18
19/// Role of the message sender
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21#[serde(rename_all = "lowercase")]
22pub enum MessageRole {
23    /// System message (instructions)
24    System,
25
26    /// User message (human input)
27    User,
28
29    /// Assistant message (AI response)
30    Assistant,
31
32    /// Tool message (tool execution result)
33    Tool,
34}
35
36/// Content of a message
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(untagged)]
39pub enum MessageContent {
40    /// Simple text content
41    Text(String),
42
43    /// Multi-modal content with text and other media
44    MultiModal(Vec<ContentBlock>),
45}
46
47/// A block of content within a message
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(tag = "type", rename_all = "snake_case")]
50pub enum ContentBlock {
51    /// Text content
52    Text { text: String },
53
54    /// Image content
55    Image {
56        /// Image data (base64 encoded)
57        data: String,
58        /// MIME type of the image
59        mime_type: String,
60    },
61
62    /// Tool use request
63    ToolUse {
64        /// Unique identifier for this tool use
65        id: String,
66        /// Name of the tool to use
67        name: String,
68        /// Input parameters for the tool
69        input: serde_json::Value,
70    },
71
72    /// Tool result
73    ToolResult {
74        /// ID of the tool use this is a result for
75        tool_use_id: String,
76        /// Whether the tool execution was successful
77        is_error: Option<bool>,
78        /// Result content
79        content: String,
80    },
81}
82
83impl LlmMessage {
84    /// Create a new system message
85    pub fn system<S: Into<String>>(content: S) -> Self {
86        Self {
87            role: MessageRole::System,
88            content: MessageContent::Text(content.into()),
89            metadata: None,
90        }
91    }
92
93    /// Create a new user message
94    pub fn user<S: Into<String>>(content: S) -> Self {
95        Self {
96            role: MessageRole::User,
97            content: MessageContent::Text(content.into()),
98            metadata: None,
99        }
100    }
101
102    /// Create a new assistant message
103    pub fn assistant<S: Into<String>>(content: S) -> Self {
104        Self {
105            role: MessageRole::Assistant,
106            content: MessageContent::Text(content.into()),
107            metadata: None,
108        }
109    }
110
111    /// Create a new tool message
112    pub fn tool<S: Into<String>>(content: S) -> Self {
113        Self {
114            role: MessageRole::Tool,
115            content: MessageContent::Text(content.into()),
116            metadata: None,
117        }
118    }
119
120    /// Get the text content of the message
121    pub fn get_text(&self) -> Option<String> {
122        match &self.content {
123            MessageContent::Text(text) => Some(text.clone()),
124            MessageContent::MultiModal(blocks) => {
125                let mut text_parts = Vec::new();
126                for block in blocks {
127                    if let ContentBlock::Text { text } = block {
128                        text_parts.push(text.clone());
129                    }
130                }
131                if text_parts.is_empty() {
132                    None
133                } else {
134                    Some(text_parts.join("\n"))
135                }
136            }
137        }
138    }
139
140    /// Check if the message contains tool use
141    pub fn has_tool_use(&self) -> bool {
142        match &self.content {
143            MessageContent::Text(_) => false,
144            MessageContent::MultiModal(blocks) => blocks
145                .iter()
146                .any(|block| matches!(block, ContentBlock::ToolUse { .. })),
147        }
148    }
149
150    /// Extract tool use blocks from the message
151    pub fn get_tool_uses(&self) -> Vec<&ContentBlock> {
152        match &self.content {
153            MessageContent::Text(_) => Vec::new(),
154            MessageContent::MultiModal(blocks) => blocks
155                .iter()
156                .filter(|block| matches!(block, ContentBlock::ToolUse { .. }))
157                .collect(),
158        }
159    }
160}
161
162impl From<String> for MessageContent {
163    fn from(text: String) -> Self {
164        MessageContent::Text(text)
165    }
166}
167
168impl From<&str> for MessageContent {
169    fn from(text: &str) -> Self {
170        MessageContent::Text(text.to_string())
171    }
172}