1use alloc::{string::String, vec::Vec};
7use mime::Mime;
8use url::Url;
9
10use super::event::ToolCall;
11use super::reasoning::ReasoningState;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct Attachment {
20 url: Url,
21 #[cfg_attr(feature = "serde", serde(with = "mime_serde"))]
22 media_type: Mime,
23}
24
25impl Attachment {
26 #[must_use]
28 pub const fn new(url: Url, media_type: Mime) -> Self {
29 Self { url, media_type }
30 }
31
32 #[must_use]
34 pub const fn url(&self) -> &Url {
35 &self.url
36 }
37
38 #[must_use]
40 pub const fn media_type(&self) -> &Mime {
41 &self.media_type
42 }
43
44 #[must_use]
46 pub fn with_url(self, url: Url) -> Self {
47 Self {
48 url,
49 media_type: self.media_type,
50 }
51 }
52
53 #[must_use]
55 pub fn into_parts(self) -> (Url, Mime) {
56 (self.url, self.media_type)
57 }
58}
59
60#[cfg(feature = "serde")]
61mod mime_serde {
62 use alloc::string::String;
63 use core::str::FromStr;
64 use mime::Mime;
65 use serde::{Deserialize, Deserializer, Serializer, de::Error as _};
66
67 pub fn serialize<S>(media_type: &Mime, serializer: S) -> Result<S::Ok, S::Error>
68 where
69 S: Serializer,
70 {
71 serializer.serialize_str(media_type.as_ref())
72 }
73
74 pub fn deserialize<'de, D>(deserializer: D) -> Result<Mime, D::Error>
75 where
76 D: Deserializer<'de>,
77 {
78 let raw = String::deserialize(deserializer)?;
79 Mime::from_str(&raw).map_err(D::Error::custom)
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
86pub enum Role {
87 User,
89 Assistant,
91 System,
93 Tool,
95}
96
97impl core::fmt::Display for Role {
98 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
99 f.write_str(match self {
100 Self::User => "User",
101 Self::Assistant => "Assistant",
102 Self::System => "System",
103 Self::Tool => "Tool",
104 })
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
115#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
116#[cfg_attr(feature = "serde", serde(tag = "role", rename_all = "snake_case"))]
117pub enum Message {
118 User {
120 content: String,
122 #[cfg_attr(
124 feature = "serde",
125 serde(default, skip_serializing_if = "Vec::is_empty")
126 )]
127 attachments: Vec<Attachment>,
128 },
129 Assistant {
131 content: String,
133 #[cfg_attr(
135 feature = "serde",
136 serde(default, skip_serializing_if = "Vec::is_empty")
137 )]
138 tool_calls: Vec<ToolCall>,
139 #[cfg_attr(
145 feature = "serde",
146 serde(default, skip_serializing_if = "Vec::is_empty")
147 )]
148 reasoning: Vec<ReasoningState>,
149 },
150 System {
152 content: String,
154 },
155 Tool {
157 content: String,
159 tool_call_id: String,
161 },
162}
163
164impl Message {
165 #[must_use]
167 pub const fn role(&self) -> Role {
168 match self {
169 Self::User { .. } => Role::User,
170 Self::Assistant { .. } => Role::Assistant,
171 Self::System { .. } => Role::System,
172 Self::Tool { .. } => Role::Tool,
173 }
174 }
175
176 #[must_use]
178 pub fn content(&self) -> &str {
179 match self {
180 Self::User { content, .. }
181 | Self::Assistant { content, .. }
182 | Self::System { content }
183 | Self::Tool { content, .. } => content,
184 }
185 }
186
187 #[must_use]
189 pub fn attachments(&self) -> &[Attachment] {
190 match self {
191 Self::User { attachments, .. } => attachments,
192 _ => &[],
193 }
194 }
195
196 #[must_use]
198 pub fn tool_calls(&self) -> &[ToolCall] {
199 match self {
200 Self::Assistant { tool_calls, .. } => tool_calls,
201 _ => &[],
202 }
203 }
204
205 #[must_use]
207 pub fn tool_call_id(&self) -> Option<&str> {
208 match self {
209 Self::Tool { tool_call_id, .. } => Some(tool_call_id),
210 _ => None,
211 }
212 }
213
214 pub fn user(content: impl Into<String>) -> Self {
216 Self::User {
217 content: content.into(),
218 attachments: Vec::new(),
219 }
220 }
221
222 pub fn assistant(content: impl Into<String>) -> Self {
224 Self::Assistant {
225 content: content.into(),
226 tool_calls: Vec::new(),
227 reasoning: Vec::new(),
228 }
229 }
230
231 pub fn assistant_with_tool_calls(
233 content: impl Into<String>,
234 tool_calls: Vec<ToolCall>,
235 ) -> Self {
236 Self::Assistant {
237 content: content.into(),
238 tool_calls,
239 reasoning: Vec::new(),
240 }
241 }
242
243 pub fn assistant_with_reasoning(
249 content: impl Into<String>,
250 tool_calls: Vec<ToolCall>,
251 reasoning: Vec<ReasoningState>,
252 ) -> Self {
253 Self::Assistant {
254 content: content.into(),
255 tool_calls,
256 reasoning,
257 }
258 }
259
260 #[must_use]
264 pub fn reasoning(&self) -> &[ReasoningState] {
265 match self {
266 Self::Assistant { reasoning, .. } => reasoning,
267 _ => &[],
268 }
269 }
270
271 pub fn system(content: impl Into<String>) -> Self {
273 Self::System {
274 content: content.into(),
275 }
276 }
277
278 pub fn tool(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
280 Self::Tool {
281 content: content.into(),
282 tool_call_id: tool_call_id.into(),
283 }
284 }
285
286 #[must_use]
288 pub fn with_attachment(mut self, attachment: Attachment) -> Self {
289 if let Self::User { attachments, .. } = &mut self {
290 attachments.push(attachment);
291 }
292 self
293 }
294
295 #[must_use]
297 pub fn with_attachments(mut self, values: impl IntoIterator<Item = Attachment>) -> Self {
298 if let Self::User { attachments, .. } = &mut self {
299 attachments.extend(values);
300 }
301 self
302 }
303
304 #[must_use]
306 pub fn with_tool_calls(mut self, calls: Vec<ToolCall>) -> Self {
307 if let Self::Assistant { tool_calls, .. } = &mut self {
308 *tool_calls = calls;
309 }
310 self
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use alloc::vec;
317
318 use super::*;
319
320 #[test]
321 fn role_equality() {
322 assert_eq!(Role::User, Role::User);
323 assert_eq!(Role::Assistant, Role::Assistant);
324 assert_eq!(Role::System, Role::System);
325 assert_eq!(Role::Tool, Role::Tool);
326 assert_ne!(Role::User, Role::Assistant);
327 }
328
329 #[test]
330 fn message_creation() {
331 let user = Message::user("Hello");
332 assert_eq!(user.role(), Role::User);
333 assert_eq!(user.content(), "Hello");
334
335 let assistant = Message::assistant("Hi there!");
336 assert_eq!(assistant.role(), Role::Assistant);
337 assert_eq!(assistant.content(), "Hi there!");
338
339 let system = Message::system("Be helpful");
340 assert_eq!(system.role(), Role::System);
341 assert_eq!(system.content(), "Be helpful");
342
343 let tool = Message::tool("call_123", "Success");
344 assert_eq!(tool.role(), Role::Tool);
345 assert_eq!(tool.content(), "Success");
346 assert_eq!(tool.tool_call_id(), Some("call_123"));
347 }
348
349 #[test]
350 fn assistant_with_tool_calls() {
351 let tool_calls = vec![ToolCall::new(
352 "call_1",
353 "get_weather",
354 serde_json::json!({"city": "NYC"}),
355 )];
356
357 let msg = Message::assistant_with_tool_calls("", tool_calls);
358 assert_eq!(msg.tool_calls().len(), 1);
359 assert_eq!(msg.tool_calls()[0].name, "get_weather");
360 }
361
362 #[test]
363 fn message_with_attachment() {
364 let attachment = Attachment::new(
365 "https://example.com/image.png".parse::<Url>().unwrap(),
366 mime::IMAGE_PNG,
367 );
368 let message = Message::user("Hello").with_attachment(attachment.clone());
369 assert_eq!(message.attachments(), &[attachment]);
370 }
371
372 #[test]
373 fn message_with_attachments() {
374 let attachments = vec![
375 Attachment::new(
376 "https://example.com/a.png".parse::<Url>().unwrap(),
377 mime::IMAGE_PNG,
378 ),
379 Attachment::new(
380 "https://example.com/b.pdf".parse::<Url>().unwrap(),
381 mime::APPLICATION_PDF,
382 ),
383 ];
384 let message = Message::user("Hello").with_attachments(attachments.clone());
385 assert_eq!(message.attachments(), attachments.as_slice());
386 }
387
388 #[test]
389 fn attachments_are_ignored_for_non_user_messages() {
390 let attachment = Attachment::new(
391 "https://example.com/a.png".parse::<Url>().unwrap(),
392 mime::IMAGE_PNG,
393 );
394 let message = Message::assistant("Hello").with_attachment(attachment);
395 assert!(
396 message.attachments().is_empty(),
397 "expected no attachments, got {:?}",
398 message.attachments()
399 );
400 }
401
402 #[test]
403 fn message_clone() {
404 let original = Message::user("Original");
405 let cloned = original.clone();
406 assert_eq!(original.content(), cloned.content());
407 }
408}