Skip to main content

open_agent/types/
message.rs

1/// A complete message in a conversation.
2///
3/// Messages are the primary unit of communication in the agent system. Each
4/// message has a role (who sent it) and content (what it contains). Content
5/// is structured as a vector of blocks to support multi-modal communication.
6///
7/// # Structure
8///
9/// - `role`: Who sent the message ([`MessageRole`])
10/// - `content`: What the message contains (one or more [`ContentBlock`]s)
11///
12/// # Message Patterns
13///
14/// ## Simple Text Message
15/// ```
16/// use open_agent::Message;
17///
18/// let msg = Message::user("What's the weather?");
19/// ```
20///
21/// ## Assistant Response with Tool Call
22/// ```
23/// use open_agent::{Message, ContentBlock, TextBlock, ToolUseBlock};
24/// use serde_json::json;
25///
26/// let msg = Message::assistant(vec![
27///     ContentBlock::Text(TextBlock::new("Let me check that for you.")),
28///     ContentBlock::ToolUse(ToolUseBlock::new(
29///         "call_123",
30///         "get_weather",
31///         json!({"location": "San Francisco"})
32///     ))
33/// ]);
34/// ```
35///
36/// ## Tool Result
37/// ```
38/// use open_agent::{Message, ContentBlock, ToolResultBlock};
39/// use serde_json::json;
40///
41/// let msg = Message::user_with_blocks(vec![
42///     ContentBlock::ToolResult(ToolResultBlock::new(
43///         "call_123",
44///         json!({"temp": 72, "conditions": "sunny"})
45///     ))
46/// ]);
47/// ```
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Message {
50    /// The role/sender of this message.
51    pub role: MessageRole,
52
53    /// The content blocks that make up this message.
54    ///
55    /// A message can contain multiple blocks of different types. For example,
56    /// an assistant message might have both text and tool use blocks.
57    pub content: Vec<ContentBlock>,
58}
59
60impl Message {
61    /// Creates a new message with the specified role and content.
62    ///
63    /// This is the most general constructor. For convenience, use the
64    /// role-specific constructors like [`user()`](Message::user),
65    /// [`assistant()`](Message::assistant), etc.
66    ///
67    /// # Example
68    ///
69    /// ```
70    /// use open_agent::{Message, MessageRole, ContentBlock, TextBlock};
71    ///
72    /// let msg = Message::new(
73    ///     MessageRole::User,
74    ///     vec![ContentBlock::Text(TextBlock::new("Hello"))]
75    /// );
76    /// ```
77    pub fn new(role: MessageRole, content: Vec<ContentBlock>) -> Self {
78        Self { role, content }
79    }
80
81    /// Creates a user message with simple text content.
82    ///
83    /// This is the most common way to create user messages. For more complex
84    /// content with multiple blocks, use [`user_with_blocks()`](Message::user_with_blocks).
85    ///
86    /// # Example
87    ///
88    /// ```
89    /// use open_agent::Message;
90    ///
91    /// let msg = Message::user("What is 2+2?");
92    /// ```
93    pub fn user(text: impl Into<String>) -> Self {
94        Self {
95            role: MessageRole::User,
96            content: vec![ContentBlock::Text(TextBlock::new(text))],
97        }
98    }
99
100    /// Creates an assistant message with the specified content blocks.
101    ///
102    /// Assistant messages often contain multiple content blocks (text + tool use).
103    /// This method takes a vector of blocks for maximum flexibility.
104    ///
105    /// # Example
106    ///
107    /// ```
108    /// use open_agent::{Message, ContentBlock, TextBlock};
109    ///
110    /// let msg = Message::assistant(vec![
111    ///     ContentBlock::Text(TextBlock::new("The answer is 4"))
112    /// ]);
113    /// ```
114    pub fn assistant(content: Vec<ContentBlock>) -> Self {
115        Self {
116            role: MessageRole::Assistant,
117            content,
118        }
119    }
120
121    /// Creates a system message with simple text content.
122    ///
123    /// System messages establish the agent's behavior and context. They're
124    /// typically sent at the start of a conversation.
125    ///
126    /// # Example
127    ///
128    /// ```
129    /// use open_agent::Message;
130    ///
131    /// let msg = Message::system("You are a helpful assistant. Be concise.");
132    /// ```
133    pub fn system(text: impl Into<String>) -> Self {
134        Self {
135            role: MessageRole::System,
136            content: vec![ContentBlock::Text(TextBlock::new(text))],
137        }
138    }
139
140    /// Creates a user message with custom content blocks.
141    ///
142    /// Use this when you need to send structured content beyond simple text,
143    /// such as tool results. For simple text messages, prefer
144    /// [`user()`](Message::user).
145    ///
146    /// # Example
147    ///
148    /// ```
149    /// use open_agent::{Message, ContentBlock, ToolResultBlock};
150    /// use serde_json::json;
151    ///
152    /// let msg = Message::user_with_blocks(vec![
153    ///     ContentBlock::ToolResult(ToolResultBlock::new(
154    ///         "call_123",
155    ///         json!({"result": "success"})
156    ///     ))
157    /// ]);
158    /// ```
159    pub fn user_with_blocks(content: Vec<ContentBlock>) -> Self {
160        Self {
161            role: MessageRole::User,
162            content,
163        }
164    }
165
166    /// Creates a user message with text and an image from a URL.
167    ///
168    /// This is a convenience method for the common pattern of sending text with
169    /// an image. The image uses `ImageDetail::Auto` by default. For more control
170    /// over detail level, use [`user_with_image_detail()`](Message::user_with_image_detail).
171    ///
172    /// # Arguments
173    ///
174    /// * `text` - The text prompt
175    /// * `image_url` - URL of the image (http/https or data URI)
176    ///
177    /// # Errors
178    ///
179    /// Returns `Error::InvalidInput` if the image URL is invalid (empty, wrong scheme, etc.)
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// use open_agent::Message;
185    ///
186    /// let msg = Message::user_with_image(
187    ///     "What's in this image?",
188    ///     "https://example.com/photo.jpg"
189    /// )?;
190    /// # Ok::<(), open_agent::Error>(())
191    /// ```
192    pub fn user_with_image(
193        text: impl Into<String>,
194        image_url: impl Into<String>,
195    ) -> crate::Result<Self> {
196        Ok(Self {
197            role: MessageRole::User,
198            content: vec![
199                ContentBlock::Text(TextBlock::new(text)),
200                ContentBlock::Image(ImageBlock::from_url(image_url)?),
201            ],
202        })
203    }
204
205    /// Creates a user message with text and an image with specified detail level.
206    ///
207    /// Use this when you need control over the image detail level for token cost
208    /// management. On OpenAI's Vision API: `ImageDetail::Low` uses ~85 tokens,
209    /// `ImageDetail::High` uses more tokens based on image dimensions, and
210    /// `ImageDetail::Auto` lets the model decide. Local models may have very different token costs.
211    ///
212    /// # Arguments
213    ///
214    /// * `text` - The text prompt
215    /// * `image_url` - URL of the image (http/https or data URI)
216    /// * `detail` - Detail level (Low, High, or Auto)
217    ///
218    /// # Errors
219    ///
220    /// Returns `Error::InvalidInput` if the image URL is invalid (empty, wrong scheme, etc.)
221    ///
222    /// # Example
223    ///
224    /// ```
225    /// use open_agent::{Message, ImageDetail};
226    ///
227    /// let msg = Message::user_with_image_detail(
228    ///     "Analyze this diagram in detail",
229    ///     "https://example.com/diagram.png",
230    ///     ImageDetail::High
231    /// )?;
232    /// # Ok::<(), open_agent::Error>(())
233    /// ```
234    pub fn user_with_image_detail(
235        text: impl Into<String>,
236        image_url: impl Into<String>,
237        detail: ImageDetail,
238    ) -> crate::Result<Self> {
239        Ok(Self {
240            role: MessageRole::User,
241            content: vec![
242                ContentBlock::Text(TextBlock::new(text)),
243                ContentBlock::Image(ImageBlock::from_url(image_url)?.with_detail(detail)),
244            ],
245        })
246    }
247
248    /// Creates a user message with text and a base64-encoded image.
249    ///
250    /// This is useful when you have image data in memory and want to send it
251    /// without uploading to a URL first. The image will be encoded as a data URI.
252    ///
253    /// # Arguments
254    ///
255    /// * `text` - The text prompt
256    /// * `base64_data` - Base64-encoded image data
257    /// * `mime_type` - MIME type (e.g., "image/png", "image/jpeg")
258    ///
259    /// # Errors
260    ///
261    /// Returns `Error::InvalidInput` if the base64 data or MIME type is invalid
262    ///
263    /// # Example
264    ///
265    /// ```
266    /// use open_agent::Message;
267    ///
268    /// // Use properly formatted base64 (length divisible by 4, valid chars)
269    /// let base64_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
270    /// let msg = Message::user_with_base64_image(
271    ///     "What's this image?",
272    ///     base64_data,
273    ///     "image/png"
274    /// )?;
275    /// # Ok::<(), open_agent::Error>(())
276    /// ```
277    pub fn user_with_base64_image(
278        text: impl Into<String>,
279        base64_data: impl AsRef<str>,
280        mime_type: impl AsRef<str>,
281    ) -> crate::Result<Self> {
282        Ok(Self {
283            role: MessageRole::User,
284            content: vec![
285                ContentBlock::Text(TextBlock::new(text)),
286                ContentBlock::Image(ImageBlock::from_base64(base64_data, mime_type)?),
287            ],
288        })
289    }
290}