rig/completion/
message.rs

1use std::{convert::Infallible, str::FromStr};
2
3use crate::OneOrMany;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use super::CompletionError;
8
9// ================================================================
10// Message models
11// ================================================================
12
13/// A useful trait to help convert `rig::completion::Message` to your own message type.
14///
15/// Particularly useful if you don't want to create a free-standing function as
16/// when trying to use `TryFrom<T>`, you would normally run into the orphan rule as Vec is
17/// technically considered a foreign type (it's owned by stdlib).
18pub trait ConvertMessage: Sized + Send + Sync {
19    type Error: std::error::Error + Send;
20
21    fn convert_from_message(message: Message) -> Result<Vec<Self>, Self::Error>;
22}
23
24/// A message represents a run of input (user) and output (assistant).
25/// Each message type (based on it's `role`) can contain a atleast one bit of content such as text,
26///  images, audio, documents, or tool related information. While each message type can contain
27///  multiple content, most often, you'll only see one content type per message
28///  (an image w/ a description, etc).
29///
30/// Each provider is responsible with converting the generic message into it's provider specific
31///  type using `From` or `TryFrom` traits. Since not every provider supports every feature, the
32///  conversion can be lossy (providing an image might be discarded for a non-image supporting
33///  provider) though the message being converted back and forth should always be the same.
34#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
35#[serde(tag = "role", rename_all = "lowercase")]
36pub enum Message {
37    /// User message containing one or more content types defined by `UserContent`.
38    User { content: OneOrMany<UserContent> },
39
40    /// Assistant message containing one or more content types defined by `AssistantContent`.
41    Assistant {
42        id: Option<String>,
43        content: OneOrMany<AssistantContent>,
44    },
45}
46
47/// Describes the content of a message, which can be text, a tool result, an image, audio, or
48///  a document. Dependent on provider supporting the content type. Multimedia content is generally
49///  base64 (defined by it's format) encoded but additionally supports urls (for some providers).
50#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
51#[serde(tag = "type", rename_all = "lowercase")]
52pub enum UserContent {
53    Text(Text),
54    ToolResult(ToolResult),
55    Image(Image),
56    Audio(Audio),
57    Video(Video),
58    Document(Document),
59}
60
61/// Describes responses from a provider which is either text or a tool call.
62#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
63#[serde(untagged)]
64pub enum AssistantContent {
65    Text(Text),
66    ToolCall(ToolCall),
67    Reasoning(Reasoning),
68    Image(Image),
69}
70
71#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
72#[non_exhaustive]
73pub struct Reasoning {
74    pub id: Option<String>,
75    pub reasoning: Vec<String>,
76    /// Optional cryptographic signature for the reasoning content.
77    ///
78    /// This field is used by some providers (e.g., Anthropic) to provide a signature
79    /// that can verify the authenticity and integrity of the reasoning output. When present,
80    /// it allows verification that the reasoning content was actually generated by the model
81    /// and has not been tampered with.
82    ///
83    /// This is an optional, provider-specific feature and will be `None` for providers
84    /// that don't support reasoning signatures.
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub signature: Option<String>,
87}
88
89impl Reasoning {
90    /// Create a new reasoning item from a single item
91    pub fn new(input: &str) -> Self {
92        Self {
93            id: None,
94            reasoning: vec![input.to_string()],
95            signature: None,
96        }
97    }
98
99    pub fn optional_id(mut self, id: Option<String>) -> Self {
100        self.id = id;
101        self
102    }
103    pub fn with_id(mut self, id: String) -> Self {
104        self.id = Some(id);
105        self
106    }
107
108    pub fn with_signature(mut self, signature: Option<String>) -> Self {
109        self.signature = signature;
110        self
111    }
112
113    pub fn multi(input: Vec<String>) -> Self {
114        Self {
115            id: None,
116            reasoning: input,
117            signature: None,
118        }
119    }
120}
121
122/// Tool result content containing information about a tool call and it's resulting content.
123#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
124pub struct ToolResult {
125    pub id: String,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub call_id: Option<String>,
128    pub content: OneOrMany<ToolResultContent>,
129}
130
131/// Describes the content of a tool result, which can be text or an image.
132#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
133#[serde(tag = "type", rename_all = "lowercase")]
134pub enum ToolResultContent {
135    Text(Text),
136    Image(Image),
137}
138
139/// Describes a tool call with an id and function to call, generally produced by a provider.
140#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
141pub struct ToolCall {
142    pub id: String,
143    pub call_id: Option<String>,
144    pub function: ToolFunction,
145}
146
147/// Describes a tool function to call with a name and arguments, generally produced by a provider.
148#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
149pub struct ToolFunction {
150    pub name: String,
151    pub arguments: serde_json::Value,
152}
153
154// ================================================================
155// Base content models
156// ================================================================
157
158/// Basic text content.
159#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
160pub struct Text {
161    pub text: String,
162}
163
164impl Text {
165    pub fn text(&self) -> &str {
166        &self.text
167    }
168}
169
170impl std::fmt::Display for Text {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        let Self { text } = self;
173        write!(f, "{text}")
174    }
175}
176
177/// Image content containing image data and metadata about it.
178#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
179pub struct Image {
180    pub data: DocumentSourceKind,
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub media_type: Option<ImageMediaType>,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub detail: Option<ImageDetail>,
185    #[serde(flatten, skip_serializing_if = "Option::is_none")]
186    pub additional_params: Option<serde_json::Value>,
187}
188
189impl Image {
190    pub fn try_into_url(self) -> Result<String, MessageError> {
191        match self.data {
192            DocumentSourceKind::Url(url) => Ok(url),
193            DocumentSourceKind::Base64(data) => {
194                let Some(media_type) = self.media_type else {
195                    return Err(MessageError::ConversionError(
196                        "A media type is required to create a valid base64-encoded image URL"
197                            .to_string(),
198                    ));
199                };
200
201                Ok(format!(
202                    "data:image/{ty};base64,{data}",
203                    ty = media_type.to_mime_type()
204                ))
205            }
206            unknown => Err(MessageError::ConversionError(format!(
207                "Tried to convert unknown type to a URL: {unknown:?}"
208            ))),
209        }
210    }
211}
212
213/// The kind of image source (to be used).
214#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
215#[serde(tag = "type", content = "value", rename_all = "camelCase")]
216#[non_exhaustive]
217pub enum DocumentSourceKind {
218    /// A file URL/URI.
219    Url(String),
220    /// A base-64 encoded string.
221    Base64(String),
222    /// Raw bytes
223    Raw(Vec<u8>),
224    /// A string (or a string literal).
225    String(String),
226    #[default]
227    /// An unknown file source (there's nothing there).
228    Unknown,
229}
230
231impl DocumentSourceKind {
232    pub fn url(url: &str) -> Self {
233        Self::Url(url.to_string())
234    }
235
236    pub fn base64(base64_string: &str) -> Self {
237        Self::Base64(base64_string.to_string())
238    }
239
240    pub fn raw(bytes: impl Into<Vec<u8>>) -> Self {
241        Self::Raw(bytes.into())
242    }
243
244    pub fn string(input: &str) -> Self {
245        Self::String(input.into())
246    }
247
248    pub fn unknown() -> Self {
249        Self::Unknown
250    }
251
252    pub fn try_into_inner(self) -> Option<String> {
253        match self {
254            Self::Url(s) | Self::Base64(s) => Some(s),
255            _ => None,
256        }
257    }
258}
259
260impl std::fmt::Display for DocumentSourceKind {
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        match self {
263            Self::Url(string) => write!(f, "{string}"),
264            Self::Base64(string) => write!(f, "{string}"),
265            Self::String(string) => write!(f, "{string}"),
266            Self::Raw(_) => write!(f, "<binary data>"),
267            Self::Unknown => write!(f, "<unknown>"),
268        }
269    }
270}
271
272/// Audio content containing audio data and metadata about it.
273#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
274pub struct Audio {
275    pub data: DocumentSourceKind,
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub media_type: Option<AudioMediaType>,
278    #[serde(flatten, skip_serializing_if = "Option::is_none")]
279    pub additional_params: Option<serde_json::Value>,
280}
281
282/// Video content containing video data and metadata about it.
283#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
284pub struct Video {
285    pub data: DocumentSourceKind,
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub media_type: Option<VideoMediaType>,
288    #[serde(flatten, skip_serializing_if = "Option::is_none")]
289    pub additional_params: Option<serde_json::Value>,
290}
291
292/// Document content containing document data and metadata about it.
293#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
294pub struct Document {
295    pub data: DocumentSourceKind,
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub media_type: Option<DocumentMediaType>,
298    #[serde(flatten, skip_serializing_if = "Option::is_none")]
299    pub additional_params: Option<serde_json::Value>,
300}
301
302/// Describes the format of the content, which can be base64 or string.
303#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
304#[serde(rename_all = "lowercase")]
305pub enum ContentFormat {
306    #[default]
307    Base64,
308    String,
309    Url,
310}
311
312/// Helper enum that tracks the media type of the content.
313#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
314pub enum MediaType {
315    Image(ImageMediaType),
316    Audio(AudioMediaType),
317    Document(DocumentMediaType),
318    Video(VideoMediaType),
319}
320
321/// Describes the image media type of the content. Not every provider supports every media type.
322/// Convertible to and from MIME type strings.
323#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
324#[serde(rename_all = "lowercase")]
325pub enum ImageMediaType {
326    JPEG,
327    PNG,
328    GIF,
329    WEBP,
330    HEIC,
331    HEIF,
332    SVG,
333}
334
335/// Describes the document media type of the content. Not every provider supports every media type.
336/// Includes also programming languages as document types for providers who support code running.
337/// Convertible to and from MIME type strings.
338#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
339#[serde(rename_all = "lowercase")]
340pub enum DocumentMediaType {
341    PDF,
342    TXT,
343    RTF,
344    HTML,
345    CSS,
346    MARKDOWN,
347    CSV,
348    XML,
349    Javascript,
350    Python,
351}
352
353impl DocumentMediaType {
354    pub fn is_code(&self) -> bool {
355        matches!(self, Self::Javascript | Self::Python)
356    }
357}
358
359/// Describes the audio media type of the content. Not every provider supports every media type.
360/// Convertible to and from MIME type strings.
361#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
362#[serde(rename_all = "lowercase")]
363pub enum AudioMediaType {
364    WAV,
365    MP3,
366    AIFF,
367    AAC,
368    OGG,
369    FLAC,
370}
371
372/// Describes the video media type of the content. Not every provider supports every media type.
373/// Convertible to and from MIME type strings.
374#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
375#[serde(rename_all = "lowercase")]
376pub enum VideoMediaType {
377    AVI,
378    MP4,
379    MPEG,
380}
381
382/// Describes the detail of the image content, which can be low, high, or auto (open-ai specific).
383#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
384#[serde(rename_all = "lowercase")]
385pub enum ImageDetail {
386    Low,
387    High,
388    #[default]
389    Auto,
390}
391
392// ================================================================
393// Impl. for message models
394// ================================================================
395
396impl Message {
397    /// This helper method is primarily used to extract the first string prompt from a `Message`.
398    /// Since `Message` might have more than just text content, we need to find the first text.
399    pub(crate) fn rag_text(&self) -> Option<String> {
400        match self {
401            Message::User { content } => {
402                for item in content.iter() {
403                    if let UserContent::Text(Text { text }) = item {
404                        return Some(text.clone());
405                    }
406                }
407                None
408            }
409            _ => None,
410        }
411    }
412
413    /// Helper constructor to make creating user messages easier.
414    pub fn user(text: impl Into<String>) -> Self {
415        Message::User {
416            content: OneOrMany::one(UserContent::text(text)),
417        }
418    }
419
420    /// Helper constructor to make creating assistant messages easier.
421    pub fn assistant(text: impl Into<String>) -> Self {
422        Message::Assistant {
423            id: None,
424            content: OneOrMany::one(AssistantContent::text(text)),
425        }
426    }
427
428    /// Helper constructor to make creating assistant messages easier.
429    pub fn assistant_with_id(id: String, text: impl Into<String>) -> Self {
430        Message::Assistant {
431            id: Some(id),
432            content: OneOrMany::one(AssistantContent::text(text)),
433        }
434    }
435
436    /// Helper constructor to make creating tool result messages easier.
437    pub fn tool_result(id: impl Into<String>, content: impl Into<String>) -> Self {
438        Message::User {
439            content: OneOrMany::one(UserContent::ToolResult(ToolResult {
440                id: id.into(),
441                call_id: None,
442                content: OneOrMany::one(ToolResultContent::text(content)),
443            })),
444        }
445    }
446
447    pub fn tool_result_with_call_id(
448        id: impl Into<String>,
449        call_id: Option<String>,
450        content: impl Into<String>,
451    ) -> Self {
452        Message::User {
453            content: OneOrMany::one(UserContent::ToolResult(ToolResult {
454                id: id.into(),
455                call_id,
456                content: OneOrMany::one(ToolResultContent::text(content)),
457            })),
458        }
459    }
460}
461
462impl UserContent {
463    /// Helper constructor to make creating user text content easier.
464    pub fn text(text: impl Into<String>) -> Self {
465        UserContent::Text(text.into().into())
466    }
467
468    /// Helper constructor to make creating user image content easier.
469    pub fn image_base64(
470        data: impl Into<String>,
471        media_type: Option<ImageMediaType>,
472        detail: Option<ImageDetail>,
473    ) -> Self {
474        UserContent::Image(Image {
475            data: DocumentSourceKind::Base64(data.into()),
476            media_type,
477            detail,
478            additional_params: None,
479        })
480    }
481
482    /// Helper constructor to make creating user image content from raw unencoded bytes easier.
483    pub fn image_raw(
484        data: impl Into<Vec<u8>>,
485        media_type: Option<ImageMediaType>,
486        detail: Option<ImageDetail>,
487    ) -> Self {
488        UserContent::Image(Image {
489            data: DocumentSourceKind::Raw(data.into()),
490            media_type,
491            detail,
492            ..Default::default()
493        })
494    }
495
496    /// Helper constructor to make creating user image content easier.
497    pub fn image_url(
498        url: impl Into<String>,
499        media_type: Option<ImageMediaType>,
500        detail: Option<ImageDetail>,
501    ) -> Self {
502        UserContent::Image(Image {
503            data: DocumentSourceKind::Url(url.into()),
504            media_type,
505            detail,
506            additional_params: None,
507        })
508    }
509
510    /// Helper constructor to make creating user audio content easier.
511    pub fn audio(data: impl Into<String>, media_type: Option<AudioMediaType>) -> Self {
512        UserContent::Audio(Audio {
513            data: DocumentSourceKind::Base64(data.into()),
514            media_type,
515            additional_params: None,
516        })
517    }
518
519    /// Helper constructor to make creating user audio content from raw unencoded bytes easier.
520    pub fn audio_raw(data: impl Into<Vec<u8>>, media_type: Option<AudioMediaType>) -> Self {
521        UserContent::Audio(Audio {
522            data: DocumentSourceKind::Raw(data.into()),
523            media_type,
524            ..Default::default()
525        })
526    }
527
528    /// Helper to create an audio resource from a URL
529    pub fn audio_url(url: impl Into<String>, media_type: Option<AudioMediaType>) -> Self {
530        UserContent::Audio(Audio {
531            data: DocumentSourceKind::Url(url.into()),
532            media_type,
533            ..Default::default()
534        })
535    }
536
537    /// Helper constructor to make creating user document content easier.
538    /// This creates a document that assumes the data being passed in is a raw string.
539    pub fn document(data: impl Into<String>, media_type: Option<DocumentMediaType>) -> Self {
540        let data: String = data.into();
541        UserContent::Document(Document {
542            data: DocumentSourceKind::string(&data),
543            media_type,
544            additional_params: None,
545        })
546    }
547
548    /// Helper to create a document from raw unencoded bytes
549    pub fn document_raw(data: impl Into<Vec<u8>>, media_type: Option<DocumentMediaType>) -> Self {
550        UserContent::Document(Document {
551            data: DocumentSourceKind::Raw(data.into()),
552            media_type,
553            ..Default::default()
554        })
555    }
556
557    /// Helper to create a document from a URL
558    pub fn document_url(url: impl Into<String>, media_type: Option<DocumentMediaType>) -> Self {
559        UserContent::Document(Document {
560            data: DocumentSourceKind::Url(url.into()),
561            media_type,
562            ..Default::default()
563        })
564    }
565
566    /// Helper constructor to make creating user tool result content easier.
567    pub fn tool_result(id: impl Into<String>, content: OneOrMany<ToolResultContent>) -> Self {
568        UserContent::ToolResult(ToolResult {
569            id: id.into(),
570            call_id: None,
571            content,
572        })
573    }
574
575    /// Helper constructor to make creating user tool result content easier.
576    pub fn tool_result_with_call_id(
577        id: impl Into<String>,
578        call_id: String,
579        content: OneOrMany<ToolResultContent>,
580    ) -> Self {
581        UserContent::ToolResult(ToolResult {
582            id: id.into(),
583            call_id: Some(call_id),
584            content,
585        })
586    }
587}
588
589impl AssistantContent {
590    /// Helper constructor to make creating assistant text content easier.
591    pub fn text(text: impl Into<String>) -> Self {
592        AssistantContent::Text(text.into().into())
593    }
594
595    /// Helper constructor to make creating assistant image content easier.
596    pub fn image_base64(
597        data: impl Into<String>,
598        media_type: Option<ImageMediaType>,
599        detail: Option<ImageDetail>,
600    ) -> Self {
601        AssistantContent::Image(Image {
602            data: DocumentSourceKind::Base64(data.into()),
603            media_type,
604            detail,
605            additional_params: None,
606        })
607    }
608
609    /// Helper constructor to make creating assistant tool call content easier.
610    pub fn tool_call(
611        id: impl Into<String>,
612        name: impl Into<String>,
613        arguments: serde_json::Value,
614    ) -> Self {
615        AssistantContent::ToolCall(ToolCall {
616            id: id.into(),
617            call_id: None,
618            function: ToolFunction {
619                name: name.into(),
620                arguments,
621            },
622        })
623    }
624
625    pub fn tool_call_with_call_id(
626        id: impl Into<String>,
627        call_id: String,
628        name: impl Into<String>,
629        arguments: serde_json::Value,
630    ) -> Self {
631        AssistantContent::ToolCall(ToolCall {
632            id: id.into(),
633            call_id: Some(call_id),
634            function: ToolFunction {
635                name: name.into(),
636                arguments,
637            },
638        })
639    }
640
641    pub fn reasoning(reasoning: impl AsRef<str>) -> Self {
642        AssistantContent::Reasoning(Reasoning::new(reasoning.as_ref()))
643    }
644}
645
646impl ToolResultContent {
647    /// Helper constructor to make creating tool result text content easier.
648    pub fn text(text: impl Into<String>) -> Self {
649        ToolResultContent::Text(text.into().into())
650    }
651
652    /// Helper constructor to make tool result images from a base64-encoded string.
653    pub fn image_base64(
654        data: impl Into<String>,
655        media_type: Option<ImageMediaType>,
656        detail: Option<ImageDetail>,
657    ) -> Self {
658        ToolResultContent::Image(Image {
659            data: DocumentSourceKind::Base64(data.into()),
660            media_type,
661            detail,
662            additional_params: None,
663        })
664    }
665
666    /// Helper constructor to make tool result images from a base64-encoded string.
667    pub fn image_raw(
668        data: impl Into<Vec<u8>>,
669        media_type: Option<ImageMediaType>,
670        detail: Option<ImageDetail>,
671    ) -> Self {
672        ToolResultContent::Image(Image {
673            data: DocumentSourceKind::Raw(data.into()),
674            media_type,
675            detail,
676            ..Default::default()
677        })
678    }
679
680    /// Helper constructor to make tool result images from a URL.
681    pub fn image_url(
682        url: impl Into<String>,
683        media_type: Option<ImageMediaType>,
684        detail: Option<ImageDetail>,
685    ) -> Self {
686        ToolResultContent::Image(Image {
687            data: DocumentSourceKind::Url(url.into()),
688            media_type,
689            detail,
690            additional_params: None,
691        })
692    }
693}
694
695/// Trait for converting between MIME types and media types.
696pub trait MimeType {
697    fn from_mime_type(mime_type: &str) -> Option<Self>
698    where
699        Self: Sized;
700    fn to_mime_type(&self) -> &'static str;
701}
702
703impl MimeType for MediaType {
704    fn from_mime_type(mime_type: &str) -> Option<Self> {
705        ImageMediaType::from_mime_type(mime_type)
706            .map(MediaType::Image)
707            .or_else(|| {
708                DocumentMediaType::from_mime_type(mime_type)
709                    .map(MediaType::Document)
710                    .or_else(|| {
711                        AudioMediaType::from_mime_type(mime_type)
712                            .map(MediaType::Audio)
713                            .or_else(|| {
714                                VideoMediaType::from_mime_type(mime_type).map(MediaType::Video)
715                            })
716                    })
717            })
718    }
719
720    fn to_mime_type(&self) -> &'static str {
721        match self {
722            MediaType::Image(media_type) => media_type.to_mime_type(),
723            MediaType::Audio(media_type) => media_type.to_mime_type(),
724            MediaType::Document(media_type) => media_type.to_mime_type(),
725            MediaType::Video(media_type) => media_type.to_mime_type(),
726        }
727    }
728}
729
730impl MimeType for ImageMediaType {
731    fn from_mime_type(mime_type: &str) -> Option<Self> {
732        match mime_type {
733            "image/jpeg" => Some(ImageMediaType::JPEG),
734            "image/png" => Some(ImageMediaType::PNG),
735            "image/gif" => Some(ImageMediaType::GIF),
736            "image/webp" => Some(ImageMediaType::WEBP),
737            "image/heic" => Some(ImageMediaType::HEIC),
738            "image/heif" => Some(ImageMediaType::HEIF),
739            "image/svg+xml" => Some(ImageMediaType::SVG),
740            _ => None,
741        }
742    }
743
744    fn to_mime_type(&self) -> &'static str {
745        match self {
746            ImageMediaType::JPEG => "image/jpeg",
747            ImageMediaType::PNG => "image/png",
748            ImageMediaType::GIF => "image/gif",
749            ImageMediaType::WEBP => "image/webp",
750            ImageMediaType::HEIC => "image/heic",
751            ImageMediaType::HEIF => "image/heif",
752            ImageMediaType::SVG => "image/svg+xml",
753        }
754    }
755}
756
757impl MimeType for DocumentMediaType {
758    fn from_mime_type(mime_type: &str) -> Option<Self> {
759        match mime_type {
760            "application/pdf" => Some(DocumentMediaType::PDF),
761            "text/plain" => Some(DocumentMediaType::TXT),
762            "text/rtf" => Some(DocumentMediaType::RTF),
763            "text/html" => Some(DocumentMediaType::HTML),
764            "text/css" => Some(DocumentMediaType::CSS),
765            "text/md" | "text/markdown" => Some(DocumentMediaType::MARKDOWN),
766            "text/csv" => Some(DocumentMediaType::CSV),
767            "text/xml" => Some(DocumentMediaType::XML),
768            "application/x-javascript" | "text/x-javascript" => Some(DocumentMediaType::Javascript),
769            "application/x-python" | "text/x-python" => Some(DocumentMediaType::Python),
770            _ => None,
771        }
772    }
773
774    fn to_mime_type(&self) -> &'static str {
775        match self {
776            DocumentMediaType::PDF => "application/pdf",
777            DocumentMediaType::TXT => "text/plain",
778            DocumentMediaType::RTF => "text/rtf",
779            DocumentMediaType::HTML => "text/html",
780            DocumentMediaType::CSS => "text/css",
781            DocumentMediaType::MARKDOWN => "text/markdown",
782            DocumentMediaType::CSV => "text/csv",
783            DocumentMediaType::XML => "text/xml",
784            DocumentMediaType::Javascript => "application/x-javascript",
785            DocumentMediaType::Python => "application/x-python",
786        }
787    }
788}
789
790impl MimeType for AudioMediaType {
791    fn from_mime_type(mime_type: &str) -> Option<Self> {
792        match mime_type {
793            "audio/wav" => Some(AudioMediaType::WAV),
794            "audio/mp3" => Some(AudioMediaType::MP3),
795            "audio/aiff" => Some(AudioMediaType::AIFF),
796            "audio/aac" => Some(AudioMediaType::AAC),
797            "audio/ogg" => Some(AudioMediaType::OGG),
798            "audio/flac" => Some(AudioMediaType::FLAC),
799            _ => None,
800        }
801    }
802
803    fn to_mime_type(&self) -> &'static str {
804        match self {
805            AudioMediaType::WAV => "audio/wav",
806            AudioMediaType::MP3 => "audio/mp3",
807            AudioMediaType::AIFF => "audio/aiff",
808            AudioMediaType::AAC => "audio/aac",
809            AudioMediaType::OGG => "audio/ogg",
810            AudioMediaType::FLAC => "audio/flac",
811        }
812    }
813}
814
815impl MimeType for VideoMediaType {
816    fn from_mime_type(mime_type: &str) -> Option<Self>
817    where
818        Self: Sized,
819    {
820        match mime_type {
821            "video/avi" => Some(VideoMediaType::AVI),
822            "video/mp4" => Some(VideoMediaType::MP4),
823            "video/mpeg" => Some(VideoMediaType::MPEG),
824            &_ => None,
825        }
826    }
827
828    fn to_mime_type(&self) -> &'static str {
829        match self {
830            VideoMediaType::AVI => "video/avi",
831            VideoMediaType::MP4 => "video/mp4",
832            VideoMediaType::MPEG => "video/mpeg",
833        }
834    }
835}
836
837impl std::str::FromStr for ImageDetail {
838    type Err = ();
839
840    fn from_str(s: &str) -> Result<Self, Self::Err> {
841        match s.to_lowercase().as_str() {
842            "low" => Ok(ImageDetail::Low),
843            "high" => Ok(ImageDetail::High),
844            "auto" => Ok(ImageDetail::Auto),
845            _ => Err(()),
846        }
847    }
848}
849
850// ================================================================
851// FromStr, From<String>, and From<&str> impls
852// ================================================================
853
854impl From<String> for Text {
855    fn from(text: String) -> Self {
856        Text { text }
857    }
858}
859
860impl From<&String> for Text {
861    fn from(text: &String) -> Self {
862        text.to_owned().into()
863    }
864}
865
866impl From<&str> for Text {
867    fn from(text: &str) -> Self {
868        text.to_owned().into()
869    }
870}
871
872impl FromStr for Text {
873    type Err = Infallible;
874
875    fn from_str(s: &str) -> Result<Self, Self::Err> {
876        Ok(s.into())
877    }
878}
879
880impl From<String> for Message {
881    fn from(text: String) -> Self {
882        Message::User {
883            content: OneOrMany::one(UserContent::Text(text.into())),
884        }
885    }
886}
887
888impl From<&str> for Message {
889    fn from(text: &str) -> Self {
890        Message::User {
891            content: OneOrMany::one(UserContent::Text(text.into())),
892        }
893    }
894}
895
896impl From<&String> for Message {
897    fn from(text: &String) -> Self {
898        Message::User {
899            content: OneOrMany::one(UserContent::Text(text.into())),
900        }
901    }
902}
903
904impl From<Text> for Message {
905    fn from(text: Text) -> Self {
906        Message::User {
907            content: OneOrMany::one(UserContent::Text(text)),
908        }
909    }
910}
911
912impl From<Image> for Message {
913    fn from(image: Image) -> Self {
914        Message::User {
915            content: OneOrMany::one(UserContent::Image(image)),
916        }
917    }
918}
919
920impl From<Audio> for Message {
921    fn from(audio: Audio) -> Self {
922        Message::User {
923            content: OneOrMany::one(UserContent::Audio(audio)),
924        }
925    }
926}
927
928impl From<Document> for Message {
929    fn from(document: Document) -> Self {
930        Message::User {
931            content: OneOrMany::one(UserContent::Document(document)),
932        }
933    }
934}
935
936impl From<String> for ToolResultContent {
937    fn from(text: String) -> Self {
938        ToolResultContent::text(text)
939    }
940}
941
942impl From<String> for AssistantContent {
943    fn from(text: String) -> Self {
944        AssistantContent::text(text)
945    }
946}
947
948impl From<String> for UserContent {
949    fn from(text: String) -> Self {
950        UserContent::text(text)
951    }
952}
953
954impl From<AssistantContent> for Message {
955    fn from(content: AssistantContent) -> Self {
956        Message::Assistant {
957            id: None,
958            content: OneOrMany::one(content),
959        }
960    }
961}
962
963impl From<UserContent> for Message {
964    fn from(content: UserContent) -> Self {
965        Message::User {
966            content: OneOrMany::one(content),
967        }
968    }
969}
970
971impl From<OneOrMany<AssistantContent>> for Message {
972    fn from(content: OneOrMany<AssistantContent>) -> Self {
973        Message::Assistant { id: None, content }
974    }
975}
976
977impl From<OneOrMany<UserContent>> for Message {
978    fn from(content: OneOrMany<UserContent>) -> Self {
979        Message::User { content }
980    }
981}
982
983impl From<ToolCall> for Message {
984    fn from(tool_call: ToolCall) -> Self {
985        Message::Assistant {
986            id: None,
987            content: OneOrMany::one(AssistantContent::ToolCall(tool_call)),
988        }
989    }
990}
991
992impl From<ToolResult> for Message {
993    fn from(tool_result: ToolResult) -> Self {
994        Message::User {
995            content: OneOrMany::one(UserContent::ToolResult(tool_result)),
996        }
997    }
998}
999
1000impl From<ToolResultContent> for Message {
1001    fn from(tool_result_content: ToolResultContent) -> Self {
1002        Message::User {
1003            content: OneOrMany::one(UserContent::ToolResult(ToolResult {
1004                id: String::new(),
1005                call_id: None,
1006                content: OneOrMany::one(tool_result_content),
1007            })),
1008        }
1009    }
1010}
1011
1012#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
1013#[serde(rename_all = "snake_case")]
1014pub enum ToolChoice {
1015    #[default]
1016    Auto,
1017    None,
1018    Required,
1019    Specific {
1020        function_names: Vec<String>,
1021    },
1022}
1023
1024// ================================================================
1025// Error types
1026// ================================================================
1027
1028/// Error type to represent issues with converting messages to and from specific provider messages.
1029#[derive(Debug, Error)]
1030pub enum MessageError {
1031    #[error("Message conversion error: {0}")]
1032    ConversionError(String),
1033}
1034
1035impl From<MessageError> for CompletionError {
1036    fn from(error: MessageError) -> Self {
1037        CompletionError::RequestError(error.into())
1038    }
1039}