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}