1use std::{convert::Infallible, str::FromStr};
2
3use crate::OneOrMany;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use super::CompletionError;
8
9pub 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#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
35#[serde(tag = "role", rename_all = "lowercase")]
36pub enum Message {
37 User { content: OneOrMany<UserContent> },
39
40 Assistant {
42 id: Option<String>,
43 content: OneOrMany<AssistantContent>,
44 },
45}
46
47#[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#[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#[serde(tag = "type", content = "content", rename_all = "snake_case")]
73#[non_exhaustive]
74pub enum ReasoningContent {
76 Text {
78 text: String,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 signature: Option<String>,
81 },
82 Encrypted(String),
84 Redacted { data: String },
86 Summary(String),
88}
89
90#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
91#[non_exhaustive]
92pub struct Reasoning {
94 pub id: Option<String>,
96 pub content: Vec<ReasoningContent>,
98}
99
100impl Reasoning {
101 pub fn new(input: &str) -> Self {
103 Self::new_with_signature(input, None)
104 }
105
106 pub fn new_with_signature(input: &str, signature: Option<String>) -> Self {
108 Self {
109 id: None,
110 content: vec![ReasoningContent::Text {
111 text: input.to_string(),
112 signature,
113 }],
114 }
115 }
116
117 pub fn optional_id(mut self, id: Option<String>) -> Self {
119 self.id = id;
120 self
121 }
122
123 pub fn with_id(mut self, id: String) -> Self {
125 self.id = Some(id);
126 self
127 }
128
129 #[deprecated(note = "Use `new_with_signature` or content-level signature handling instead")]
130 pub fn with_signature(mut self, signature: Option<String>) -> Self {
131 if let Some(existing_signature) =
132 self.content.iter_mut().find_map(|content| match content {
133 ReasoningContent::Text { signature, .. } => Some(signature),
134 _ => None,
135 })
136 {
137 *existing_signature = signature;
138 }
139 self
140 }
141
142 pub fn multi(input: Vec<String>) -> Self {
144 Self {
145 id: None,
146 content: input
147 .into_iter()
148 .map(|text| ReasoningContent::Text {
149 text,
150 signature: None,
151 })
152 .collect(),
153 }
154 }
155
156 pub fn redacted(data: impl Into<String>) -> Self {
158 Self {
159 id: None,
160 content: vec![ReasoningContent::Redacted { data: data.into() }],
161 }
162 }
163
164 pub fn encrypted(data: impl Into<String>) -> Self {
166 Self {
167 id: None,
168 content: vec![ReasoningContent::Encrypted(data.into())],
169 }
170 }
171
172 pub fn summaries(input: Vec<String>) -> Self {
174 Self {
175 id: None,
176 content: input.into_iter().map(ReasoningContent::Summary).collect(),
177 }
178 }
179
180 pub fn display_text(&self) -> String {
182 self.content
183 .iter()
184 .filter_map(|content| match content {
185 ReasoningContent::Text { text, .. } => Some(text.as_str()),
186 ReasoningContent::Summary(summary) => Some(summary.as_str()),
187 ReasoningContent::Redacted { data } => Some(data.as_str()),
188 ReasoningContent::Encrypted(_) => None,
189 })
190 .collect::<Vec<_>>()
191 .join("\n")
192 }
193
194 pub fn first_text(&self) -> Option<&str> {
196 self.content.iter().find_map(|content| match content {
197 ReasoningContent::Text { text, .. } => Some(text.as_str()),
198 _ => None,
199 })
200 }
201
202 pub fn first_signature(&self) -> Option<&str> {
204 self.content.iter().find_map(|content| match content {
205 ReasoningContent::Text {
206 signature: Some(signature),
207 ..
208 } => Some(signature.as_str()),
209 _ => None,
210 })
211 }
212
213 pub fn encrypted_content(&self) -> Option<&str> {
215 self.content.iter().find_map(|content| match content {
216 ReasoningContent::Encrypted(data) => Some(data.as_str()),
217 _ => None,
218 })
219 }
220}
221
222#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
224pub struct ToolResult {
225 pub id: String,
226 #[serde(skip_serializing_if = "Option::is_none")]
227 pub call_id: Option<String>,
228 pub content: OneOrMany<ToolResultContent>,
229}
230
231#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
233#[serde(tag = "type", rename_all = "lowercase")]
234pub enum ToolResultContent {
235 Text(Text),
236 Image(Image),
237}
238
239#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
241pub struct ToolCall {
242 pub id: String,
243 pub call_id: Option<String>,
244 pub function: ToolFunction,
245 pub signature: Option<String>,
255 pub additional_params: Option<serde_json::Value>,
257}
258
259impl ToolCall {
260 pub fn new(id: String, function: ToolFunction) -> Self {
261 Self {
262 id,
263 call_id: None,
264 function,
265 signature: None,
266 additional_params: None,
267 }
268 }
269
270 pub fn with_call_id(mut self, call_id: String) -> Self {
271 self.call_id = Some(call_id);
272 self
273 }
274
275 pub fn with_signature(mut self, signature: Option<String>) -> Self {
276 self.signature = signature;
277 self
278 }
279
280 pub fn with_additional_params(mut self, additional_params: Option<serde_json::Value>) -> Self {
281 self.additional_params = additional_params;
282 self
283 }
284}
285
286#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
288pub struct ToolFunction {
289 pub name: String,
290 pub arguments: serde_json::Value,
291}
292
293impl ToolFunction {
294 pub fn new(name: String, arguments: serde_json::Value) -> Self {
295 Self { name, arguments }
296 }
297}
298
299#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
305pub struct Text {
306 pub text: String,
307}
308
309impl Text {
310 pub fn text(&self) -> &str {
311 &self.text
312 }
313}
314
315impl std::fmt::Display for Text {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 let Self { text } = self;
318 write!(f, "{text}")
319 }
320}
321
322#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
324pub struct Image {
325 pub data: DocumentSourceKind,
326 #[serde(skip_serializing_if = "Option::is_none")]
327 pub media_type: Option<ImageMediaType>,
328 #[serde(skip_serializing_if = "Option::is_none")]
329 pub detail: Option<ImageDetail>,
330 #[serde(flatten, skip_serializing_if = "Option::is_none")]
331 pub additional_params: Option<serde_json::Value>,
332}
333
334impl Image {
335 pub fn try_into_url(self) -> Result<String, MessageError> {
336 match self.data {
337 DocumentSourceKind::Url(url) => Ok(url),
338 DocumentSourceKind::Base64(data) => {
339 let Some(media_type) = self.media_type else {
340 return Err(MessageError::ConversionError(
341 "A media type is required to create a valid base64-encoded image URL"
342 .to_string(),
343 ));
344 };
345
346 Ok(format!(
347 "data:image/{ty};base64,{data}",
348 ty = media_type.to_mime_type()
349 ))
350 }
351 unknown => Err(MessageError::ConversionError(format!(
352 "Tried to convert unknown type to a URL: {unknown:?}"
353 ))),
354 }
355 }
356}
357
358#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
360#[serde(tag = "type", content = "value", rename_all = "camelCase")]
361#[non_exhaustive]
362pub enum DocumentSourceKind {
363 Url(String),
365 Base64(String),
367 Raw(Vec<u8>),
369 String(String),
371 #[default]
372 Unknown,
374}
375
376impl DocumentSourceKind {
377 pub fn url(url: &str) -> Self {
378 Self::Url(url.to_string())
379 }
380
381 pub fn base64(base64_string: &str) -> Self {
382 Self::Base64(base64_string.to_string())
383 }
384
385 pub fn raw(bytes: impl Into<Vec<u8>>) -> Self {
386 Self::Raw(bytes.into())
387 }
388
389 pub fn string(input: &str) -> Self {
390 Self::String(input.into())
391 }
392
393 pub fn unknown() -> Self {
394 Self::Unknown
395 }
396
397 pub fn try_into_inner(self) -> Option<String> {
398 match self {
399 Self::Url(s) | Self::Base64(s) => Some(s),
400 _ => None,
401 }
402 }
403}
404
405impl std::fmt::Display for DocumentSourceKind {
406 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407 match self {
408 Self::Url(string) => write!(f, "{string}"),
409 Self::Base64(string) => write!(f, "{string}"),
410 Self::String(string) => write!(f, "{string}"),
411 Self::Raw(_) => write!(f, "<binary data>"),
412 Self::Unknown => write!(f, "<unknown>"),
413 }
414 }
415}
416
417#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
419pub struct Audio {
420 pub data: DocumentSourceKind,
421 #[serde(skip_serializing_if = "Option::is_none")]
422 pub media_type: Option<AudioMediaType>,
423 #[serde(flatten, skip_serializing_if = "Option::is_none")]
424 pub additional_params: Option<serde_json::Value>,
425}
426
427#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
429pub struct Video {
430 pub data: DocumentSourceKind,
431 #[serde(skip_serializing_if = "Option::is_none")]
432 pub media_type: Option<VideoMediaType>,
433 #[serde(flatten, skip_serializing_if = "Option::is_none")]
434 pub additional_params: Option<serde_json::Value>,
435}
436
437#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
439pub struct Document {
440 pub data: DocumentSourceKind,
441 #[serde(skip_serializing_if = "Option::is_none")]
442 pub media_type: Option<DocumentMediaType>,
443 #[serde(flatten, skip_serializing_if = "Option::is_none")]
444 pub additional_params: Option<serde_json::Value>,
445}
446
447#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
449#[serde(rename_all = "lowercase")]
450pub enum ContentFormat {
451 #[default]
452 Base64,
453 String,
454 Url,
455}
456
457#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
459pub enum MediaType {
460 Image(ImageMediaType),
461 Audio(AudioMediaType),
462 Document(DocumentMediaType),
463 Video(VideoMediaType),
464}
465
466#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
469#[serde(rename_all = "lowercase")]
470pub enum ImageMediaType {
471 JPEG,
472 PNG,
473 GIF,
474 WEBP,
475 HEIC,
476 HEIF,
477 SVG,
478}
479
480#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
484#[serde(rename_all = "lowercase")]
485pub enum DocumentMediaType {
486 PDF,
487 TXT,
488 RTF,
489 HTML,
490 CSS,
491 MARKDOWN,
492 CSV,
493 XML,
494 Javascript,
495 Python,
496}
497
498impl DocumentMediaType {
499 pub fn is_code(&self) -> bool {
500 matches!(self, Self::Javascript | Self::Python)
501 }
502}
503
504#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
507#[serde(rename_all = "lowercase")]
508pub enum AudioMediaType {
509 WAV,
510 MP3,
511 AIFF,
512 AAC,
513 OGG,
514 FLAC,
515 M4A,
516 PCM16,
517 PCM24,
518}
519
520#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
523#[serde(rename_all = "lowercase")]
524pub enum VideoMediaType {
525 AVI,
526 MP4,
527 MPEG,
528 MOV,
529 WEBM,
530}
531
532#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
534#[serde(rename_all = "lowercase")]
535pub enum ImageDetail {
536 Low,
537 High,
538 #[default]
539 Auto,
540}
541
542impl Message {
547 pub(crate) fn rag_text(&self) -> Option<String> {
550 match self {
551 Message::User { content } => {
552 for item in content.iter() {
553 if let UserContent::Text(Text { text }) = item {
554 return Some(text.clone());
555 }
556 }
557 None
558 }
559 _ => None,
560 }
561 }
562
563 pub fn user(text: impl Into<String>) -> Self {
565 Message::User {
566 content: OneOrMany::one(UserContent::text(text)),
567 }
568 }
569
570 pub fn assistant(text: impl Into<String>) -> Self {
572 Message::Assistant {
573 id: None,
574 content: OneOrMany::one(AssistantContent::text(text)),
575 }
576 }
577
578 pub fn assistant_with_id(id: String, text: impl Into<String>) -> Self {
580 Message::Assistant {
581 id: Some(id),
582 content: OneOrMany::one(AssistantContent::text(text)),
583 }
584 }
585
586 pub fn tool_result(id: impl Into<String>, content: impl Into<String>) -> Self {
588 Message::User {
589 content: OneOrMany::one(UserContent::ToolResult(ToolResult {
590 id: id.into(),
591 call_id: None,
592 content: OneOrMany::one(ToolResultContent::text(content)),
593 })),
594 }
595 }
596
597 pub fn tool_result_with_call_id(
598 id: impl Into<String>,
599 call_id: Option<String>,
600 content: impl Into<String>,
601 ) -> Self {
602 Message::User {
603 content: OneOrMany::one(UserContent::ToolResult(ToolResult {
604 id: id.into(),
605 call_id,
606 content: OneOrMany::one(ToolResultContent::text(content)),
607 })),
608 }
609 }
610}
611
612impl UserContent {
613 pub fn text(text: impl Into<String>) -> Self {
615 UserContent::Text(text.into().into())
616 }
617
618 pub fn image_base64(
620 data: impl Into<String>,
621 media_type: Option<ImageMediaType>,
622 detail: Option<ImageDetail>,
623 ) -> Self {
624 UserContent::Image(Image {
625 data: DocumentSourceKind::Base64(data.into()),
626 media_type,
627 detail,
628 additional_params: None,
629 })
630 }
631
632 pub fn image_raw(
634 data: impl Into<Vec<u8>>,
635 media_type: Option<ImageMediaType>,
636 detail: Option<ImageDetail>,
637 ) -> Self {
638 UserContent::Image(Image {
639 data: DocumentSourceKind::Raw(data.into()),
640 media_type,
641 detail,
642 ..Default::default()
643 })
644 }
645
646 pub fn image_url(
648 url: impl Into<String>,
649 media_type: Option<ImageMediaType>,
650 detail: Option<ImageDetail>,
651 ) -> Self {
652 UserContent::Image(Image {
653 data: DocumentSourceKind::Url(url.into()),
654 media_type,
655 detail,
656 additional_params: None,
657 })
658 }
659
660 pub fn audio(data: impl Into<String>, media_type: Option<AudioMediaType>) -> Self {
662 UserContent::Audio(Audio {
663 data: DocumentSourceKind::Base64(data.into()),
664 media_type,
665 additional_params: None,
666 })
667 }
668
669 pub fn audio_raw(data: impl Into<Vec<u8>>, media_type: Option<AudioMediaType>) -> Self {
671 UserContent::Audio(Audio {
672 data: DocumentSourceKind::Raw(data.into()),
673 media_type,
674 ..Default::default()
675 })
676 }
677
678 pub fn audio_url(url: impl Into<String>, media_type: Option<AudioMediaType>) -> Self {
680 UserContent::Audio(Audio {
681 data: DocumentSourceKind::Url(url.into()),
682 media_type,
683 ..Default::default()
684 })
685 }
686
687 pub fn document(data: impl Into<String>, media_type: Option<DocumentMediaType>) -> Self {
690 let data: String = data.into();
691 UserContent::Document(Document {
692 data: DocumentSourceKind::string(&data),
693 media_type,
694 additional_params: None,
695 })
696 }
697
698 pub fn document_raw(data: impl Into<Vec<u8>>, media_type: Option<DocumentMediaType>) -> Self {
700 UserContent::Document(Document {
701 data: DocumentSourceKind::Raw(data.into()),
702 media_type,
703 ..Default::default()
704 })
705 }
706
707 pub fn document_url(url: impl Into<String>, media_type: Option<DocumentMediaType>) -> Self {
709 UserContent::Document(Document {
710 data: DocumentSourceKind::Url(url.into()),
711 media_type,
712 ..Default::default()
713 })
714 }
715
716 pub fn tool_result(id: impl Into<String>, content: OneOrMany<ToolResultContent>) -> Self {
718 UserContent::ToolResult(ToolResult {
719 id: id.into(),
720 call_id: None,
721 content,
722 })
723 }
724
725 pub fn tool_result_with_call_id(
727 id: impl Into<String>,
728 call_id: String,
729 content: OneOrMany<ToolResultContent>,
730 ) -> Self {
731 UserContent::ToolResult(ToolResult {
732 id: id.into(),
733 call_id: Some(call_id),
734 content,
735 })
736 }
737}
738
739impl AssistantContent {
740 pub fn text(text: impl Into<String>) -> Self {
742 AssistantContent::Text(text.into().into())
743 }
744
745 pub fn image_base64(
747 data: impl Into<String>,
748 media_type: Option<ImageMediaType>,
749 detail: Option<ImageDetail>,
750 ) -> Self {
751 AssistantContent::Image(Image {
752 data: DocumentSourceKind::Base64(data.into()),
753 media_type,
754 detail,
755 additional_params: None,
756 })
757 }
758
759 pub fn tool_call(
761 id: impl Into<String>,
762 name: impl Into<String>,
763 arguments: serde_json::Value,
764 ) -> Self {
765 AssistantContent::ToolCall(ToolCall::new(
766 id.into(),
767 ToolFunction {
768 name: name.into(),
769 arguments,
770 },
771 ))
772 }
773
774 pub fn tool_call_with_call_id(
775 id: impl Into<String>,
776 call_id: String,
777 name: impl Into<String>,
778 arguments: serde_json::Value,
779 ) -> Self {
780 AssistantContent::ToolCall(
781 ToolCall::new(
782 id.into(),
783 ToolFunction {
784 name: name.into(),
785 arguments,
786 },
787 )
788 .with_call_id(call_id),
789 )
790 }
791
792 pub fn reasoning(reasoning: impl AsRef<str>) -> Self {
793 AssistantContent::Reasoning(Reasoning::new(reasoning.as_ref()))
794 }
795}
796
797impl ToolResultContent {
798 pub fn text(text: impl Into<String>) -> Self {
800 ToolResultContent::Text(text.into().into())
801 }
802
803 pub fn image_base64(
805 data: impl Into<String>,
806 media_type: Option<ImageMediaType>,
807 detail: Option<ImageDetail>,
808 ) -> Self {
809 ToolResultContent::Image(Image {
810 data: DocumentSourceKind::Base64(data.into()),
811 media_type,
812 detail,
813 additional_params: None,
814 })
815 }
816
817 pub fn image_raw(
819 data: impl Into<Vec<u8>>,
820 media_type: Option<ImageMediaType>,
821 detail: Option<ImageDetail>,
822 ) -> Self {
823 ToolResultContent::Image(Image {
824 data: DocumentSourceKind::Raw(data.into()),
825 media_type,
826 detail,
827 ..Default::default()
828 })
829 }
830
831 pub fn image_url(
833 url: impl Into<String>,
834 media_type: Option<ImageMediaType>,
835 detail: Option<ImageDetail>,
836 ) -> Self {
837 ToolResultContent::Image(Image {
838 data: DocumentSourceKind::Url(url.into()),
839 media_type,
840 detail,
841 additional_params: None,
842 })
843 }
844
845 pub fn from_tool_output(output: impl Into<String>) -> OneOrMany<ToolResultContent> {
854 let output_str = output.into();
855
856 if let Ok(json) = serde_json::from_str::<serde_json::Value>(&output_str) {
857 if json.get("response").is_some() || json.get("parts").is_some() {
858 let mut results: Vec<ToolResultContent> = Vec::new();
859
860 if let Some(response) = json.get("response") {
861 results.push(ToolResultContent::Text(Text {
862 text: response.to_string(),
863 }));
864 }
865
866 if let Some(parts) = json.get("parts").and_then(|p| p.as_array()) {
867 for part in parts {
868 let is_image = part
869 .get("type")
870 .and_then(|t| t.as_str())
871 .is_some_and(|t| t == "image");
872
873 if !is_image {
874 continue;
875 }
876
877 if let (Some(data), Some(mime_type)) = (
878 part.get("data").and_then(|v| v.as_str()),
879 part.get("mimeType").and_then(|v| v.as_str()),
880 ) {
881 let data_kind =
882 if data.starts_with("http://") || data.starts_with("https://") {
883 DocumentSourceKind::Url(data.to_string())
884 } else {
885 DocumentSourceKind::Base64(data.to_string())
886 };
887
888 results.push(ToolResultContent::Image(Image {
889 data: data_kind,
890 media_type: ImageMediaType::from_mime_type(mime_type),
891 detail: None,
892 additional_params: None,
893 }));
894 }
895 }
896 }
897
898 if !results.is_empty() {
899 return OneOrMany::many(results).unwrap_or_else(|_| {
900 OneOrMany::one(ToolResultContent::Text(output_str.into()))
901 });
902 }
903 }
904
905 let is_image = json
906 .get("type")
907 .and_then(|v| v.as_str())
908 .is_some_and(|t| t == "image");
909
910 if is_image
911 && let (Some(data), Some(mime_type)) = (
912 json.get("data").and_then(|v| v.as_str()),
913 json.get("mimeType").and_then(|v| v.as_str()),
914 )
915 {
916 let data_kind = if data.starts_with("http://") || data.starts_with("https://") {
917 DocumentSourceKind::Url(data.to_string())
918 } else {
919 DocumentSourceKind::Base64(data.to_string())
920 };
921
922 return OneOrMany::one(ToolResultContent::Image(Image {
923 data: data_kind,
924 media_type: ImageMediaType::from_mime_type(mime_type),
925 detail: None,
926 additional_params: None,
927 }));
928 }
929 }
930
931 OneOrMany::one(ToolResultContent::Text(output_str.into()))
932 }
933}
934
935pub trait MimeType {
937 fn from_mime_type(mime_type: &str) -> Option<Self>
938 where
939 Self: Sized;
940 fn to_mime_type(&self) -> &'static str;
941}
942
943impl MimeType for MediaType {
944 fn from_mime_type(mime_type: &str) -> Option<Self> {
945 ImageMediaType::from_mime_type(mime_type)
946 .map(MediaType::Image)
947 .or_else(|| {
948 DocumentMediaType::from_mime_type(mime_type)
949 .map(MediaType::Document)
950 .or_else(|| {
951 AudioMediaType::from_mime_type(mime_type)
952 .map(MediaType::Audio)
953 .or_else(|| {
954 VideoMediaType::from_mime_type(mime_type).map(MediaType::Video)
955 })
956 })
957 })
958 }
959
960 fn to_mime_type(&self) -> &'static str {
961 match self {
962 MediaType::Image(media_type) => media_type.to_mime_type(),
963 MediaType::Audio(media_type) => media_type.to_mime_type(),
964 MediaType::Document(media_type) => media_type.to_mime_type(),
965 MediaType::Video(media_type) => media_type.to_mime_type(),
966 }
967 }
968}
969
970impl MimeType for ImageMediaType {
971 fn from_mime_type(mime_type: &str) -> Option<Self> {
972 match mime_type {
973 "image/jpeg" => Some(ImageMediaType::JPEG),
974 "image/png" => Some(ImageMediaType::PNG),
975 "image/gif" => Some(ImageMediaType::GIF),
976 "image/webp" => Some(ImageMediaType::WEBP),
977 "image/heic" => Some(ImageMediaType::HEIC),
978 "image/heif" => Some(ImageMediaType::HEIF),
979 "image/svg+xml" => Some(ImageMediaType::SVG),
980 _ => None,
981 }
982 }
983
984 fn to_mime_type(&self) -> &'static str {
985 match self {
986 ImageMediaType::JPEG => "image/jpeg",
987 ImageMediaType::PNG => "image/png",
988 ImageMediaType::GIF => "image/gif",
989 ImageMediaType::WEBP => "image/webp",
990 ImageMediaType::HEIC => "image/heic",
991 ImageMediaType::HEIF => "image/heif",
992 ImageMediaType::SVG => "image/svg+xml",
993 }
994 }
995}
996
997impl MimeType for DocumentMediaType {
998 fn from_mime_type(mime_type: &str) -> Option<Self> {
999 match mime_type {
1000 "application/pdf" => Some(DocumentMediaType::PDF),
1001 "text/plain" => Some(DocumentMediaType::TXT),
1002 "text/rtf" => Some(DocumentMediaType::RTF),
1003 "text/html" => Some(DocumentMediaType::HTML),
1004 "text/css" => Some(DocumentMediaType::CSS),
1005 "text/md" | "text/markdown" => Some(DocumentMediaType::MARKDOWN),
1006 "text/csv" => Some(DocumentMediaType::CSV),
1007 "text/xml" => Some(DocumentMediaType::XML),
1008 "application/x-javascript" | "text/x-javascript" => Some(DocumentMediaType::Javascript),
1009 "application/x-python" | "text/x-python" => Some(DocumentMediaType::Python),
1010 _ => None,
1011 }
1012 }
1013
1014 fn to_mime_type(&self) -> &'static str {
1015 match self {
1016 DocumentMediaType::PDF => "application/pdf",
1017 DocumentMediaType::TXT => "text/plain",
1018 DocumentMediaType::RTF => "text/rtf",
1019 DocumentMediaType::HTML => "text/html",
1020 DocumentMediaType::CSS => "text/css",
1021 DocumentMediaType::MARKDOWN => "text/markdown",
1022 DocumentMediaType::CSV => "text/csv",
1023 DocumentMediaType::XML => "text/xml",
1024 DocumentMediaType::Javascript => "application/x-javascript",
1025 DocumentMediaType::Python => "application/x-python",
1026 }
1027 }
1028}
1029
1030impl MimeType for AudioMediaType {
1031 fn from_mime_type(mime_type: &str) -> Option<Self> {
1032 match mime_type {
1033 "audio/wav" => Some(AudioMediaType::WAV),
1034 "audio/mp3" => Some(AudioMediaType::MP3),
1035 "audio/aiff" => Some(AudioMediaType::AIFF),
1036 "audio/aac" => Some(AudioMediaType::AAC),
1037 "audio/ogg" => Some(AudioMediaType::OGG),
1038 "audio/flac" => Some(AudioMediaType::FLAC),
1039 "audio/m4a" => Some(AudioMediaType::M4A),
1040 "audio/pcm16" => Some(AudioMediaType::PCM16),
1041 "audio/pcm24" => Some(AudioMediaType::PCM24),
1042 _ => None,
1043 }
1044 }
1045
1046 fn to_mime_type(&self) -> &'static str {
1047 match self {
1048 AudioMediaType::WAV => "audio/wav",
1049 AudioMediaType::MP3 => "audio/mp3",
1050 AudioMediaType::AIFF => "audio/aiff",
1051 AudioMediaType::AAC => "audio/aac",
1052 AudioMediaType::OGG => "audio/ogg",
1053 AudioMediaType::FLAC => "audio/flac",
1054 AudioMediaType::M4A => "audio/m4a",
1055 AudioMediaType::PCM16 => "audio/pcm16",
1056 AudioMediaType::PCM24 => "audio/pcm24",
1057 }
1058 }
1059}
1060
1061impl MimeType for VideoMediaType {
1062 fn from_mime_type(mime_type: &str) -> Option<Self>
1063 where
1064 Self: Sized,
1065 {
1066 match mime_type {
1067 "video/avi" => Some(VideoMediaType::AVI),
1068 "video/mp4" => Some(VideoMediaType::MP4),
1069 "video/mpeg" => Some(VideoMediaType::MPEG),
1070 "video/mov" => Some(VideoMediaType::MOV),
1071 "video/webm" => Some(VideoMediaType::WEBM),
1072 &_ => None,
1073 }
1074 }
1075
1076 fn to_mime_type(&self) -> &'static str {
1077 match self {
1078 VideoMediaType::AVI => "video/avi",
1079 VideoMediaType::MP4 => "video/mp4",
1080 VideoMediaType::MPEG => "video/mpeg",
1081 VideoMediaType::MOV => "video/mov",
1082 VideoMediaType::WEBM => "video/webm",
1083 }
1084 }
1085}
1086
1087impl std::str::FromStr for ImageDetail {
1088 type Err = ();
1089
1090 fn from_str(s: &str) -> Result<Self, Self::Err> {
1091 match s.to_lowercase().as_str() {
1092 "low" => Ok(ImageDetail::Low),
1093 "high" => Ok(ImageDetail::High),
1094 "auto" => Ok(ImageDetail::Auto),
1095 _ => Err(()),
1096 }
1097 }
1098}
1099
1100impl From<String> for Text {
1105 fn from(text: String) -> Self {
1106 Text { text }
1107 }
1108}
1109
1110impl From<&String> for Text {
1111 fn from(text: &String) -> Self {
1112 text.to_owned().into()
1113 }
1114}
1115
1116impl From<&str> for Text {
1117 fn from(text: &str) -> Self {
1118 text.to_owned().into()
1119 }
1120}
1121
1122impl FromStr for Text {
1123 type Err = Infallible;
1124
1125 fn from_str(s: &str) -> Result<Self, Self::Err> {
1126 Ok(s.into())
1127 }
1128}
1129
1130impl From<String> for Message {
1131 fn from(text: String) -> Self {
1132 Message::User {
1133 content: OneOrMany::one(UserContent::Text(text.into())),
1134 }
1135 }
1136}
1137
1138impl From<&str> for Message {
1139 fn from(text: &str) -> Self {
1140 Message::User {
1141 content: OneOrMany::one(UserContent::Text(text.into())),
1142 }
1143 }
1144}
1145
1146impl From<&String> for Message {
1147 fn from(text: &String) -> Self {
1148 Message::User {
1149 content: OneOrMany::one(UserContent::Text(text.into())),
1150 }
1151 }
1152}
1153
1154impl From<Text> for Message {
1155 fn from(text: Text) -> Self {
1156 Message::User {
1157 content: OneOrMany::one(UserContent::Text(text)),
1158 }
1159 }
1160}
1161
1162impl From<Image> for Message {
1163 fn from(image: Image) -> Self {
1164 Message::User {
1165 content: OneOrMany::one(UserContent::Image(image)),
1166 }
1167 }
1168}
1169
1170impl From<Audio> for Message {
1171 fn from(audio: Audio) -> Self {
1172 Message::User {
1173 content: OneOrMany::one(UserContent::Audio(audio)),
1174 }
1175 }
1176}
1177
1178impl From<Document> for Message {
1179 fn from(document: Document) -> Self {
1180 Message::User {
1181 content: OneOrMany::one(UserContent::Document(document)),
1182 }
1183 }
1184}
1185
1186impl From<String> for ToolResultContent {
1187 fn from(text: String) -> Self {
1188 ToolResultContent::text(text)
1189 }
1190}
1191
1192impl From<String> for AssistantContent {
1193 fn from(text: String) -> Self {
1194 AssistantContent::text(text)
1195 }
1196}
1197
1198impl From<String> for UserContent {
1199 fn from(text: String) -> Self {
1200 UserContent::text(text)
1201 }
1202}
1203
1204impl From<AssistantContent> for Message {
1205 fn from(content: AssistantContent) -> Self {
1206 Message::Assistant {
1207 id: None,
1208 content: OneOrMany::one(content),
1209 }
1210 }
1211}
1212
1213impl From<UserContent> for Message {
1214 fn from(content: UserContent) -> Self {
1215 Message::User {
1216 content: OneOrMany::one(content),
1217 }
1218 }
1219}
1220
1221impl From<OneOrMany<AssistantContent>> for Message {
1222 fn from(content: OneOrMany<AssistantContent>) -> Self {
1223 Message::Assistant { id: None, content }
1224 }
1225}
1226
1227impl From<OneOrMany<UserContent>> for Message {
1228 fn from(content: OneOrMany<UserContent>) -> Self {
1229 Message::User { content }
1230 }
1231}
1232
1233impl From<ToolCall> for Message {
1234 fn from(tool_call: ToolCall) -> Self {
1235 Message::Assistant {
1236 id: None,
1237 content: OneOrMany::one(AssistantContent::ToolCall(tool_call)),
1238 }
1239 }
1240}
1241
1242impl From<ToolResult> for Message {
1243 fn from(tool_result: ToolResult) -> Self {
1244 Message::User {
1245 content: OneOrMany::one(UserContent::ToolResult(tool_result)),
1246 }
1247 }
1248}
1249
1250impl From<ToolResultContent> for Message {
1251 fn from(tool_result_content: ToolResultContent) -> Self {
1252 Message::User {
1253 content: OneOrMany::one(UserContent::ToolResult(ToolResult {
1254 id: String::new(),
1255 call_id: None,
1256 content: OneOrMany::one(tool_result_content),
1257 })),
1258 }
1259 }
1260}
1261
1262#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
1263#[serde(rename_all = "snake_case")]
1264pub enum ToolChoice {
1265 #[default]
1266 Auto,
1267 None,
1268 Required,
1269 Specific {
1270 function_names: Vec<String>,
1271 },
1272}
1273
1274#[derive(Debug, Error)]
1280pub enum MessageError {
1281 #[error("Message conversion error: {0}")]
1282 ConversionError(String),
1283}
1284
1285impl From<MessageError> for CompletionError {
1286 fn from(error: MessageError) -> Self {
1287 CompletionError::RequestError(error.into())
1288 }
1289}
1290
1291#[cfg(test)]
1292mod tests {
1293 use super::{Reasoning, ReasoningContent};
1294
1295 #[test]
1296 fn reasoning_constructors_and_accessors_work() {
1297 let single = Reasoning::new("think");
1298 assert_eq!(single.first_text(), Some("think"));
1299 assert_eq!(single.first_signature(), None);
1300
1301 let signed = Reasoning::new_with_signature("signed", Some("sig-1".to_string()));
1302 assert_eq!(signed.first_text(), Some("signed"));
1303 assert_eq!(signed.first_signature(), Some("sig-1"));
1304
1305 let multi = Reasoning::multi(vec!["a".to_string(), "b".to_string()]);
1306 assert_eq!(multi.display_text(), "a\nb");
1307 assert_eq!(multi.first_text(), Some("a"));
1308
1309 let redacted = Reasoning::redacted("redacted-value");
1310 assert_eq!(redacted.display_text(), "redacted-value");
1311 assert_eq!(redacted.first_text(), None);
1312
1313 let encrypted = Reasoning::encrypted("enc");
1314 assert_eq!(encrypted.encrypted_content(), Some("enc"));
1315 assert_eq!(encrypted.display_text(), "");
1316
1317 let summaries = Reasoning::summaries(vec!["s1".to_string(), "s2".to_string()]);
1318 assert_eq!(summaries.display_text(), "s1\ns2");
1319 assert_eq!(summaries.encrypted_content(), None);
1320 }
1321
1322 #[test]
1323 fn reasoning_content_serde_roundtrip() {
1324 let variants = vec![
1325 ReasoningContent::Text {
1326 text: "plain".to_string(),
1327 signature: Some("sig".to_string()),
1328 },
1329 ReasoningContent::Encrypted("opaque".to_string()),
1330 ReasoningContent::Redacted {
1331 data: "redacted".to_string(),
1332 },
1333 ReasoningContent::Summary("summary".to_string()),
1334 ];
1335
1336 for variant in variants {
1337 let json = serde_json::to_string(&variant).expect("serialize");
1338 let roundtrip: ReasoningContent = serde_json::from_str(&json).expect("deserialize");
1339 assert_eq!(roundtrip, variant);
1340 }
1341 }
1342}