Skip to main content

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}