open_agent/types/message.rs
1/// A complete message in a conversation.
2///
3/// Each message has a role and a sequence of text, image, tool-call, or tool-result
4/// blocks.
5///
6/// # Message Patterns
7///
8/// ## Simple Text Message
9/// ```
10/// use open_agent::Message;
11///
12/// let msg = Message::user("What's the weather?");
13/// ```
14///
15/// ## Assistant Response with Tool Call
16/// ```
17/// use open_agent::{Message, ContentBlock, TextBlock, ToolUseBlock};
18/// use serde_json::json;
19///
20/// let msg = Message::assistant(vec![
21/// ContentBlock::Text(TextBlock::new("Let me check that for you.")),
22/// ContentBlock::ToolUse(ToolUseBlock::new(
23/// "call_123",
24/// "get_weather",
25/// json!({"location": "San Francisco"})
26/// ))
27/// ]);
28/// ```
29///
30/// ## Tool Result
31/// ```
32/// use open_agent::{Message, ContentBlock, ToolResultBlock};
33/// use serde_json::json;
34///
35/// let msg = Message::user_with_blocks(vec![
36/// ContentBlock::ToolResult(ToolResultBlock::new(
37/// "call_123",
38/// json!({"temp": 72, "conditions": "sunny"})
39/// ))
40/// ]);
41/// ```
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Message {
44 /// The role/sender of this message.
45 pub role: MessageRole,
46
47 /// The content blocks that make up this message.
48 ///
49 /// A message can contain multiple blocks of different types. For example,
50 /// an assistant message might have both text and tool use blocks.
51 pub content: Vec<ContentBlock>,
52}
53
54impl Message {
55 /// Creates a message with the supplied role and content blocks.
56 pub fn new(role: MessageRole, content: Vec<ContentBlock>) -> Self {
57 Self { role, content }
58 }
59
60 /// Creates a user message containing one text block.
61 pub fn user(text: impl Into<String>) -> Self {
62 Self {
63 role: MessageRole::User,
64 content: vec![ContentBlock::Text(TextBlock::new(text))],
65 }
66 }
67
68 /// Creates an assistant message with the supplied text and/or tool-call blocks.
69 pub fn assistant(content: Vec<ContentBlock>) -> Self {
70 Self {
71 role: MessageRole::Assistant,
72 content,
73 }
74 }
75
76 /// Creates a system message containing one text block.
77 pub fn system(text: impl Into<String>) -> Self {
78 Self {
79 role: MessageRole::System,
80 content: vec![ContentBlock::Text(TextBlock::new(text))],
81 }
82 }
83
84 /// Creates a user message with arbitrary blocks, including tool results.
85 ///
86 /// See [`Message`] for a tool-result example.
87 pub fn user_with_blocks(content: Vec<ContentBlock>) -> Self {
88 Self {
89 role: MessageRole::User,
90 content,
91 }
92 }
93
94 /// Creates a user message with text and an image from a URL.
95 ///
96 /// This is a convenience method for the common pattern of sending text with
97 /// an image. The image uses `ImageDetail::Auto` by default. For more control
98 /// over detail level, use [`user_with_image_detail()`](Message::user_with_image_detail).
99 ///
100 /// # Arguments
101 ///
102 /// * `text` - The text prompt
103 /// * `image_url` - URL of the image (http/https or data URI)
104 ///
105 /// # Errors
106 ///
107 /// Returns `Error::InvalidInput` if the image URL is invalid (empty, wrong scheme, etc.)
108 ///
109 /// # Example
110 ///
111 /// ```
112 /// use open_agent::Message;
113 ///
114 /// let msg = Message::user_with_image(
115 /// "What's in this image?",
116 /// "https://example.com/photo.jpg"
117 /// )?;
118 /// # Ok::<(), open_agent::Error>(())
119 /// ```
120 pub fn user_with_image(
121 text: impl Into<String>,
122 image_url: impl Into<String>,
123 ) -> crate::Result<Self> {
124 Ok(Self {
125 role: MessageRole::User,
126 content: vec![
127 ContentBlock::Text(TextBlock::new(text)),
128 ContentBlock::Image(ImageBlock::from_url(image_url)?),
129 ],
130 })
131 }
132
133 /// Creates a user message with text and an image with specified detail level.
134 ///
135 /// Detail is a provider hint; its effect on quality and token costs depends on
136 /// the endpoint and model.
137 ///
138 /// # Arguments
139 ///
140 /// * `text` - The text prompt
141 /// * `image_url` - URL of the image (http/https or data URI)
142 /// * `detail` - Detail level (Low, High, or Auto)
143 ///
144 /// # Errors
145 ///
146 /// Returns `Error::InvalidInput` if the image URL is invalid (empty, wrong scheme, etc.)
147 ///
148 /// # Example
149 ///
150 /// ```
151 /// use open_agent::{Message, ImageDetail};
152 ///
153 /// let msg = Message::user_with_image_detail(
154 /// "Analyze this diagram in detail",
155 /// "https://example.com/diagram.png",
156 /// ImageDetail::High
157 /// )?;
158 /// # Ok::<(), open_agent::Error>(())
159 /// ```
160 pub fn user_with_image_detail(
161 text: impl Into<String>,
162 image_url: impl Into<String>,
163 detail: ImageDetail,
164 ) -> crate::Result<Self> {
165 Ok(Self {
166 role: MessageRole::User,
167 content: vec![
168 ContentBlock::Text(TextBlock::new(text)),
169 ContentBlock::Image(ImageBlock::from_url(image_url)?.with_detail(detail)),
170 ],
171 })
172 }
173
174 /// Creates a user message with text and a base64-encoded image.
175 ///
176 /// This is useful when you have image data in memory and want to send it
177 /// without uploading to a URL first. The image will be encoded as a data URI.
178 ///
179 /// # Arguments
180 ///
181 /// * `text` - The text prompt
182 /// * `base64_data` - Base64-encoded image data
183 /// * `mime_type` - MIME type (e.g., "image/png", "image/jpeg")
184 ///
185 /// # Errors
186 ///
187 /// Returns `Error::InvalidInput` if the base64 data or MIME type is invalid
188 ///
189 /// # Example
190 ///
191 /// ```
192 /// use open_agent::Message;
193 ///
194 /// // Use properly formatted base64 (length divisible by 4, valid chars)
195 /// let base64_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
196 /// let msg = Message::user_with_base64_image(
197 /// "What's this image?",
198 /// base64_data,
199 /// "image/png"
200 /// )?;
201 /// # Ok::<(), open_agent::Error>(())
202 /// ```
203 pub fn user_with_base64_image(
204 text: impl Into<String>,
205 base64_data: impl AsRef<str>,
206 mime_type: impl AsRef<str>,
207 ) -> crate::Result<Self> {
208 Ok(Self {
209 role: MessageRole::User,
210 content: vec![
211 ContentBlock::Text(TextBlock::new(text)),
212 ContentBlock::Image(ImageBlock::from_base64(base64_data, mime_type)?),
213 ],
214 })
215 }
216}