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