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