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