ai_lib/types/
common.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value as JsonValue;
3
4/// Message content — moved to an enum to support multimodal and structured content
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub enum Content {
7    #[serde(rename = "text")]
8    Text(String),
9    /// Generic JSON content for structured payloads (e.g. function call args)
10    #[serde(rename = "json")]
11    Json(JsonValue),
12    /// Reference to an image (url) or metadata; adapters may upload or inline as needed
13    #[serde(rename = "image")]
14    Image {
15        url: Option<String>,
16        mime: Option<String>,
17        name: Option<String>,
18    },
19    /// Reference to audio content
20    #[serde(rename = "audio")]
21    Audio {
22        url: Option<String>,
23        mime: Option<String>,
24    },
25}
26
27impl Content {
28    /// Return a best-effort textual representation for legacy code paths
29    pub fn as_text(&self) -> String {
30        match self {
31            Content::Text(s) => s.clone(),
32            Content::Json(v) => v.to_string(),
33            Content::Image { url, .. } => url.clone().unwrap_or_default(),
34            Content::Audio { url, .. } => url.clone().unwrap_or_default(),
35        }
36    }
37
38    /// Convenience constructor for text content
39    pub fn new_text<S: Into<String>>(s: S) -> Self {
40        Content::Text(s.into())
41    }
42
43    /// Convenience constructor for JSON content
44    pub fn new_json(v: JsonValue) -> Self {
45        Content::Json(v)
46    }
47
48    /// Convenience constructor for image content
49    pub fn new_image(url: Option<String>, mime: Option<String>, name: Option<String>) -> Self {
50        Content::Image { url, mime, name }
51    }
52
53    /// Convenience constructor for audio content
54    pub fn new_audio(url: Option<String>, mime: Option<String>) -> Self {
55        Content::Audio { url, mime }
56    }
57
58    /// Create image content from a file path - the file will be automatically processed
59    /// (uploaded or inlined as data URL) by the AI client when used in a request
60    pub fn from_image_file<P: AsRef<std::path::Path>>(path: P) -> Self {
61        let path = path.as_ref();
62        let name = path
63            .file_name()
64            .and_then(|n| n.to_str())
65            .map(|s| s.to_string());
66        let mime = path
67            .extension()
68            .and_then(|ext| ext.to_str())
69            .and_then(|ext| match ext.to_lowercase().as_str() {
70                "png" => Some("image/png"),
71                "jpg" | "jpeg" => Some("image/jpeg"),
72                "gif" => Some("image/gif"),
73                "webp" => Some("image/webp"),
74                "svg" => Some("image/svg+xml"),
75                _ => None,
76            })
77            .map(|s| s.to_string());
78
79        Content::Image {
80            url: None, // Will be filled by the client
81            mime,
82            name,
83        }
84    }
85
86    /// Create audio content from a file path - the file will be automatically processed
87    /// (uploaded or inlined as data URL) by the AI client when used in a request
88    pub fn from_audio_file<P: AsRef<std::path::Path>>(path: P) -> Self {
89        let path = path.as_ref();
90        let mime = path
91            .extension()
92            .and_then(|ext| ext.to_str())
93            .and_then(|ext| match ext.to_lowercase().as_str() {
94                "mp3" => Some("audio/mpeg"),
95                "wav" => Some("audio/wav"),
96                "ogg" => Some("audio/ogg"),
97                "m4a" => Some("audio/mp4"),
98                "flac" => Some("audio/flac"),
99                _ => None,
100            })
101            .map(|s| s.to_string());
102
103        Content::Audio {
104            url: None, // Will be filled by the client
105            mime,
106        }
107    }
108
109    /// Create image content from a data URL (base64 encoded)
110    pub fn from_data_url(data_url: String, mime: Option<String>, name: Option<String>) -> Self {
111        Content::Image {
112            url: Some(data_url),
113            mime,
114            name,
115        }
116    }
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct Message {
121    pub role: Role,
122    pub content: Content,
123    /// Optional function call payload when assistant invokes a tool
124    pub function_call: Option<crate::types::function_call::FunctionCall>,
125}
126
127impl Message {
128    /// Create a new message with the specified role and content.
129    pub fn new(role: Role, content: impl Into<Content>) -> Self {
130        Self {
131            role,
132            content: content.into(),
133            function_call: None,
134        }
135    }
136
137    /// Create a user message with text content.
138    ///
139    /// # Example
140    /// ```rust
141    /// use ai_lib::prelude::*;
142    ///
143    /// let msg = Message::user("Hello, how are you?");
144    /// assert!(matches!(msg.role, Role::User));
145    /// ```
146    pub fn user<S: Into<String>>(text: S) -> Self {
147        Self {
148            role: Role::User,
149            content: Content::Text(text.into()),
150            function_call: None,
151        }
152    }
153
154    /// Create an assistant message with text content.
155    ///
156    /// # Example
157    /// ```rust
158    /// use ai_lib::prelude::*;
159    ///
160    /// let msg = Message::assistant("I'm doing well, thank you!");
161    /// assert!(matches!(msg.role, Role::Assistant));
162    /// ```
163    pub fn assistant<S: Into<String>>(text: S) -> Self {
164        Self {
165            role: Role::Assistant,
166            content: Content::Text(text.into()),
167            function_call: None,
168        }
169    }
170
171    /// Create a system message with text content.
172    ///
173    /// # Example
174    /// ```rust
175    /// use ai_lib::prelude::*;
176    ///
177    /// let msg = Message::system("You are a helpful assistant.");
178    /// assert!(matches!(msg.role, Role::System));
179    /// ```
180    pub fn system<S: Into<String>>(text: S) -> Self {
181        Self {
182            role: Role::System,
183            content: Content::Text(text.into()),
184            function_call: None,
185        }
186    }
187
188    /// Create a user message with custom content (text, image, audio, etc.).
189    ///
190    /// # Example
191    /// ```rust
192    /// use ai_lib::prelude::*;
193    ///
194    /// let msg = Message::user_with_content(Content::from_image_file("photo.jpg"));
195    /// ```
196    pub fn user_with_content(content: Content) -> Self {
197        Self {
198            role: Role::User,
199            content,
200            function_call: None,
201        }
202    }
203
204    /// Create an assistant message with a function call.
205    pub fn assistant_with_function_call(
206        text: impl Into<String>,
207        function_call: crate::types::function_call::FunctionCall,
208    ) -> Self {
209        Self {
210            role: Role::Assistant,
211            content: Content::Text(text.into()),
212            function_call: Some(function_call),
213        }
214    }
215}
216
217/// Allow `Into<Content>` for `String` to enable ergonomic `Message::new(Role::User, "hello")`
218impl From<String> for Content {
219    fn from(s: String) -> Self {
220        Content::Text(s)
221    }
222}
223
224/// Allow `Into<Content>` for `&str` to enable ergonomic `Message::new(Role::User, "hello")`
225impl From<&str> for Content {
226    fn from(s: &str) -> Self {
227        Content::Text(s.to_string())
228    }
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub enum Role {
233    #[serde(rename = "system")]
234    System,
235    #[serde(rename = "user")]
236    User,
237    #[serde(rename = "assistant")]
238    Assistant,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct Choice {
243    pub index: u32,
244    pub message: Message,
245    pub finish_reason: Option<String>,
246}
247
248// Note: Usage and UsageStatus have been moved to `types::response` as of 1.0.
249// Import from `ai_lib::Usage` / `ai_lib::UsageStatus` or `ai_lib::types::response::{Usage, UsageStatus}`.