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