1use crate::error::ProviderError;
2use serde::{Deserialize, Serialize};
3use std::{convert::Infallible, str::FromStr};
4use thiserror::Error;
5
6#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
14#[serde(tag = "role", rename_all = "lowercase")]
15pub enum Message {
16 System { content: String },
18
19 User { content: Vec<UserContent> },
21
22 Assistant(AssistantMessage),
25}
26
27pub mod citation;
28mod identity;
29mod native;
30
31pub use citation::{Citation, DocumentRange, Source, SourceLocation, Span};
32
33pub use identity::{CallId, EmptyCallId, EmptyToolName, LocalCallId, ProviderCallId, ToolName};
34pub use native::{Api, Fingerprint, Native, Opaque, Origin, StopReason};
35
36#[non_exhaustive]
43#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
44pub struct AssistantMessage {
45 pub content: Vec<AssistantContent>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub origin: Option<Origin>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub stop: Option<StopReason>,
53}
54
55impl AssistantMessage {
56 pub fn new(content: Vec<AssistantContent>) -> Self {
58 Self {
59 content,
60 ..Self::default()
61 }
62 }
63
64 pub fn rolled_back(origin: Option<Origin>, content: Vec<AssistantContent>) -> Self {
68 Self {
69 content,
70 origin,
71 stop: Some(StopReason::ToolUse),
72 }
73 }
74
75 pub fn aborted(
78 origin: Option<Origin>,
79 content: Vec<AssistantContent>,
80 reason: impl Into<String>,
81 ) -> Self {
82 Self {
83 content,
84 origin,
85 stop: Some(StopReason::Aborted(reason.into())),
86 }
87 }
88
89 pub fn with_content(mut self, content: Vec<AssistantContent>) -> Self {
91 self.content = content;
92 self
93 }
94
95 pub fn with_origin(mut self, origin: impl Into<Option<Origin>>) -> Self {
97 self.origin = origin.into();
98 self
99 }
100
101 pub fn with_stop(mut self, stop: impl Into<Option<StopReason>>) -> Self {
103 self.stop = stop.into();
104 self
105 }
106
107 pub fn tool_calls(&self) -> impl Iterator<Item = &ToolCall> {
109 self.content.iter().filter_map(|part| match part {
110 AssistantContent::ToolCall(call) => Some(call),
111 _ => None,
112 })
113 }
114}
115
116pub fn turn_delivered_no_answer(choice: &[AssistantContent]) -> bool {
120 !choice.iter().any(AssistantContent::is_answer)
121}
122
123pub fn turn_failure(
133 choice: &[AssistantContent],
134 stop: Option<&StopReason>,
135 finish: Option<&crate::completion::FinishReason>,
136) -> Option<String> {
137 let failed = stop.filter(|stop| stop.is_failure());
138 let calls = choice
139 .iter()
140 .any(|block| matches!(block, AssistantContent::ToolCall(_)));
141 if let Some(StopReason::Error(reason) | StopReason::Aborted(reason)) = failed
142 && calls
143 {
144 return Some(format!(
145 "the turn failed, so none of its tool calls ran: {reason}"
146 ));
147 }
148 let answered = !turn_delivered_no_answer(choice);
149 if !answered && let Some(finish) = finish.filter(|finish| finish.truncated_output()) {
150 return Some(finish.no_answer_message());
151 }
152 match failed {
153 Some(StopReason::Error(reason)) if answered => {
154 Some(format!("the provider failed the turn: {reason}"))
155 }
156 Some(StopReason::Error(reason)) => Some(format!(
157 "the provider failed the turn without an answer: {reason}"
158 )),
159 _ => None,
160 }
161}
162
163#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
166#[serde(tag = "type", rename_all = "lowercase")]
167pub enum UserContent {
168 Text(Text),
170 ToolResult(ToolResult),
172 Image(Image),
174 Audio(Audio),
176 Video(Video),
178 Document(Document),
180}
181
182#[non_exhaustive]
185#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
186#[serde(tag = "type", rename_all = "lowercase")]
187pub enum AssistantContent {
188 Text(Text),
190 ToolCall(ToolCall),
192 Reasoning(Reasoning),
194 Image(Image),
196 Opaque(Opaque),
198}
199
200impl AssistantContent {
201 pub fn canonical(&self) -> Self {
204 let mut block = self.clone();
205 if let Some(native) = block.native_slot() {
206 *native = None;
207 }
208 block
209 }
210
211 pub fn is_answer(&self) -> bool {
214 match self {
215 Self::Text(text) => !text.text.trim().is_empty(),
216 Self::ToolCall(_) | Self::Image(_) => true,
217 Self::Reasoning(_) | Self::Opaque(_) => false,
218 }
219 }
220
221 pub fn is_blank(&self) -> bool {
227 match self {
228 Self::Text(Text { text, .. }) => text.trim().is_empty() && self.native_item().is_none(),
229 Self::Reasoning(Reasoning { text, redacted, .. }) => {
230 (*redacted || text.trim().is_empty()) && self.native_item().is_none()
231 }
232 Self::Opaque(opaque) => !opaque.replay,
233 Self::ToolCall(_) | Self::Image(_) => false,
234 }
235 }
236
237 pub fn fingerprint(&self) -> Fingerprint {
243 Fingerprint::of(&self.projection())
244 }
245
246 fn projection(&self) -> serde_json::Value {
248 use serde_json::json;
249 match self {
250 Self::Text(text) => json!(["v1", "text", text.text]),
251 Self::Reasoning(reasoning) => {
252 json!(["v1", "reasoning", reasoning.text, reasoning.redacted])
253 }
254 Self::ToolCall(call) => {
255 let id = match &call.id {
256 CallId::Provider(id) => id.as_str().to_owned(),
257 CallId::Local(_) => "~local".to_owned(),
258 };
259 json!([
260 "v1",
261 "call",
262 id,
263 call.function.name.as_str(),
264 call.function.arguments,
265 call.function.invalid_arguments,
266 ])
267 }
268 Self::Image(image) => {
269 json!(["v1", "image", image.media_type, image.detail, image.data,])
270 }
271 Self::Opaque(_) => json!(["v1", "opaque"]),
272 }
273 }
274
275 fn native_slot(&mut self) -> Option<&mut Option<Native>> {
276 match self {
277 Self::Text(text) => Some(&mut text.native),
278 Self::ToolCall(call) => Some(&mut call.native),
279 Self::Reasoning(reasoning) => Some(&mut reasoning.native),
280 Self::Image(image) => Some(&mut image.native),
281 Self::Opaque(_) => None,
282 }
283 }
284
285 pub fn with_native(mut self, item: serde_json::Value) -> Self {
288 let fingerprint = self.fingerprint();
289 if let Some(native) = self.native_slot() {
290 *native = Some(Native { item, fingerprint });
291 }
292 self
293 }
294
295 pub(crate) fn stale_item(&self) -> Option<&serde_json::Value> {
298 let native = match self {
299 Self::Text(text) => text.native.as_ref(),
300 Self::ToolCall(call) => call.native.as_ref(),
301 Self::Reasoning(reasoning) => reasoning.native.as_ref(),
302 Self::Image(image) => image.native.as_ref(),
303 Self::Opaque(_) => None,
304 }?;
305 (native.fingerprint != self.fingerprint()).then_some(&native.item)
306 }
307
308 pub fn native_item(&self) -> Option<&serde_json::Value> {
311 let native = match self {
312 Self::Text(text) => text.native.as_ref(),
313 Self::ToolCall(call) => call.native.as_ref(),
314 Self::Reasoning(reasoning) => reasoning.native.as_ref(),
315 Self::Image(image) => image.native.as_ref(),
316 Self::Opaque(_) => None,
317 }?;
318 (native.fingerprint == self.fingerprint()).then_some(&native.item)
319 }
320}
321
322#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
326pub struct Reasoning {
327 pub text: String,
329 #[serde(default, skip_serializing_if = "crate::json_utils::is_false")]
331 pub redacted: bool,
332 #[serde(
334 default,
335 skip_serializing_if = "Option::is_none",
336 deserialize_with = "native::lenient"
337 )]
338 pub native: Option<Native>,
339}
340
341impl Reasoning {
342 pub fn new(text: impl Into<String>) -> Self {
344 Self {
345 text: text.into(),
346 ..Self::default()
347 }
348 }
349}
350
351#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
356pub struct ToolResult {
357 pub call: CallId,
359 pub name: ToolName,
362 pub content: Vec<ToolResultContent>,
364 #[serde(default, skip_serializing_if = "crate::json_utils::is_false")]
367 pub is_error: bool,
368}
369
370#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
372#[serde(tag = "type", rename_all = "lowercase")]
373pub enum ToolResultContent {
374 Text(Text),
376 Image(Image),
378 Json {
380 value: serde_json::Value,
382 },
383}
384
385impl ToolResultContent {
386 pub fn as_text(&self) -> Option<&str> {
388 match self {
389 Self::Text(text) => Some(&text.text),
390 Self::Image(_) | Self::Json { .. } => None,
391 }
392 }
393
394 pub fn as_json(&self) -> Option<&serde_json::Value> {
396 match self {
397 Self::Json { value } => Some(value),
398 Self::Text(_) | Self::Image(_) => None,
399 }
400 }
401
402 pub fn deserialize_json<T>(&self) -> Result<T, serde_json::Error>
409 where
410 T: serde::de::DeserializeOwned,
411 {
412 match self {
413 Self::Json { value } => T::deserialize(value),
414 Self::Text(text) => serde_json::from_str(&text.text),
415 Self::Image(_) => Err(<serde_json::Error as serde::de::Error>::custom(
416 "cannot decode image tool-result content as JSON",
417 )),
418 }
419 }
420}
421
422#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
424pub struct ToolCall {
425 pub id: CallId,
428 pub function: ToolFunction,
430 #[serde(
432 default,
433 skip_serializing_if = "Option::is_none",
434 deserialize_with = "native::lenient"
435 )]
436 pub native: Option<Native>,
437}
438
439impl ToolCall {
440 pub fn new(id: CallId, function: ToolFunction) -> Self {
442 Self {
443 id,
444 function,
445 native: None,
446 }
447 }
448
449 pub fn from_wire(wire_id: impl Into<String>, function: ToolFunction) -> Self {
452 Self::new(CallId::from_wire(wire_id), function)
453 }
454
455 pub fn result(&self, content: Vec<ToolResultContent>) -> ToolResult {
457 ToolResult {
458 call: self.id.clone(),
459 name: self.function.name.clone(),
460 content,
461 is_error: false,
462 }
463 }
464
465 pub fn error_result(&self, content: Vec<ToolResultContent>) -> ToolResult {
468 ToolResult {
469 is_error: true,
470 ..self.result(content)
471 }
472 }
473}
474
475#[derive(Clone, Debug, Serialize, PartialEq)]
497pub struct ToolFunction {
498 pub name: ToolName,
500 pub arguments: serde_json::Map<String, serde_json::Value>,
502 #[serde(default, skip_serializing_if = "Option::is_none")]
505 pub invalid_arguments: Option<String>,
506}
507
508impl ToolFunction {
509 pub fn new(name: ToolName, arguments: serde_json::Value) -> Self {
513 use serde_json::Value;
514 let (arguments, invalid_arguments) = match arguments {
515 Value::Object(arguments) => (arguments, None),
516 Value::Null => (serde_json::Map::new(), None),
517 Value::String(text) => {
518 return Self::parse(name, &text);
519 }
520 other => (serde_json::Map::new(), Some(other.to_string())),
521 };
522 Self {
523 name,
524 arguments,
525 invalid_arguments,
526 }
527 }
528
529 pub fn parse(name: ToolName, text: &str) -> Self {
534 use serde_json::Value;
535 let parsed = crate::json_utils::parse_tool_arguments(text);
536 let (arguments, invalid) = match parsed {
537 Ok(Value::Object(arguments)) => (arguments, false),
538 Ok(Value::Null) => (serde_json::Map::new(), false),
539 Ok(Value::String(inner)) => match serde_json::from_str(&inner) {
540 Ok(Value::Object(arguments)) => (arguments, false),
541 _ => (serde_json::Map::new(), true),
542 },
543 Ok(_) => (serde_json::Map::new(), true),
544 Err(_) => (
545 crate::json_utils::parse_partial_object(text).unwrap_or_default(),
546 true,
547 ),
548 };
549 Self {
550 name,
551 arguments,
552 invalid_arguments: invalid.then(|| text.to_owned()),
553 }
554 }
555
556 pub fn arguments_value(&self) -> serde_json::Value {
558 serde_json::Value::Object(self.arguments.clone())
559 }
560}
561
562impl<'de> Deserialize<'de> for ToolFunction {
563 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
567 #[derive(Deserialize)]
568 struct Repr {
569 name: ToolName,
570 #[serde(default)]
571 arguments: serde_json::Value,
572 #[serde(default)]
573 invalid_arguments: Option<String>,
574 }
575 let Repr {
576 name,
577 arguments,
578 invalid_arguments,
579 } = Repr::deserialize(deserializer)?;
580 let mut function = Self::new(name, arguments);
581 if invalid_arguments.is_some() {
582 function.invalid_arguments = invalid_arguments;
583 }
584 Ok(function)
585 }
586}
587
588#[non_exhaustive]
592#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
593#[serde(from = "TextRepr")]
594pub struct Text {
595 pub text: String,
597 #[serde(default, skip_serializing_if = "Option::is_none")]
600 citations: Option<citation::Citations>,
601 #[serde(
603 default,
604 skip_serializing_if = "Option::is_none",
605 deserialize_with = "native::lenient"
606 )]
607 pub native: Option<Native>,
608}
609
610#[derive(Deserialize)]
613struct TextRepr {
614 text: String,
615 #[serde(default, deserialize_with = "citation::lenient")]
616 citations: Option<citation::Citations>,
617 #[serde(default, deserialize_with = "native::lenient")]
618 native: Option<Native>,
619}
620
621impl From<TextRepr> for Text {
622 fn from(repr: TextRepr) -> Self {
623 let TextRepr {
624 text,
625 citations,
626 native,
627 } = repr;
628 Self {
629 text,
630 citations,
631 native,
632 }
633 .checked()
634 }
635}
636
637impl Text {
638 pub fn new(text: impl Into<String>) -> Self {
640 Self {
641 text: text.into(),
642 citations: None,
643 native: None,
644 }
645 }
646
647 pub fn text(&self) -> &str {
649 &self.text
650 }
651}
652
653impl std::fmt::Display for Text {
654 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
655 let Self { text, .. } = self;
656 write!(f, "{text}")
657 }
658}
659
660#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
663pub struct Image {
664 pub data: DocumentSourceKind,
666 #[serde(skip_serializing_if = "Option::is_none")]
668 pub media_type: Option<ImageMediaType>,
669 #[serde(skip_serializing_if = "Option::is_none")]
671 pub detail: Option<ImageDetail>,
672 #[serde(
674 default,
675 skip_serializing_if = "Option::is_none",
676 deserialize_with = "native::lenient"
677 )]
678 pub native: Option<Native>,
679}
680
681#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
683#[serde(tag = "type", content = "value", rename_all = "camelCase")]
684pub enum DocumentSourceKind {
685 Url(String),
687 Base64(String),
689 FileId(String),
691 Raw(Vec<u8>),
693 String(String),
695 #[default]
696 Unknown,
698}
699
700impl DocumentSourceKind {
701 pub fn url(url: impl Into<String>) -> Self {
703 Self::Url(url.into())
704 }
705
706 pub fn base64(base64_string: impl Into<String>) -> Self {
708 Self::Base64(base64_string.into())
709 }
710
711 pub fn file_id(file_id: impl Into<String>) -> Self {
713 Self::FileId(file_id.into())
714 }
715
716 pub fn string(input: impl Into<String>) -> Self {
718 Self::String(input.into())
719 }
720
721 pub fn try_into_inner(self) -> Option<String> {
723 match self {
724 Self::Url(s) | Self::Base64(s) | Self::FileId(s) => Some(s),
725 _ => None,
726 }
727 }
728}
729
730impl std::fmt::Display for DocumentSourceKind {
731 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
732 match self {
733 Self::Url(string) => write!(f, "{string}"),
734 Self::Base64(string) => write!(f, "{string}"),
735 Self::FileId(string) => write!(f, "{string}"),
736 Self::String(string) => write!(f, "{string}"),
737 Self::Raw(_) => write!(f, "<binary data>"),
738 Self::Unknown => write!(f, "<unknown>"),
739 }
740 }
741}
742
743#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
745pub struct Audio {
746 pub data: DocumentSourceKind,
748 #[serde(skip_serializing_if = "Option::is_none")]
750 pub media_type: Option<AudioMediaType>,
751}
752
753#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
755pub struct Video {
756 pub data: DocumentSourceKind,
758 #[serde(skip_serializing_if = "Option::is_none")]
760 pub media_type: Option<VideoMediaType>,
761 #[serde(default, skip_serializing_if = "Option::is_none")]
764 pub additional_params: Option<serde_json::Value>,
765}
766
767#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
769pub struct Document {
770 pub data: DocumentSourceKind,
772 #[serde(skip_serializing_if = "Option::is_none")]
774 pub media_type: Option<DocumentMediaType>,
775 #[serde(default, skip_serializing_if = "Option::is_none")]
778 pub additional_params: Option<serde_json::Value>,
779}
780
781#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
783#[serde(rename_all = "lowercase")]
784pub enum ContentFormat {
785 #[default]
786 Base64,
787 String,
788 Url,
789}
790
791#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
793pub enum MediaType {
794 Image(ImageMediaType),
795 Audio(AudioMediaType),
796 Document(DocumentMediaType),
797 Video(VideoMediaType),
798}
799
800#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
803#[serde(rename_all = "lowercase")]
804pub enum ImageMediaType {
805 JPEG,
806 PNG,
807 GIF,
808 WEBP,
809 HEIC,
810 HEIF,
811 SVG,
812}
813
814#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
818#[serde(rename_all = "lowercase")]
819pub enum DocumentMediaType {
820 PDF,
821 TXT,
822 RTF,
823 HTML,
824 CSS,
825 MARKDOWN,
826 CSV,
827 XML,
828 Javascript,
829 Python,
830}
831
832impl DocumentMediaType {
833 pub fn is_code(&self) -> bool {
834 matches!(self, Self::Javascript | Self::Python)
835 }
836}
837
838#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
841#[serde(rename_all = "lowercase")]
842pub enum AudioMediaType {
843 WAV,
844 MP3,
845 AIFF,
846 AAC,
847 OGG,
848 FLAC,
849 M4A,
850 PCM16,
851 PCM24,
852}
853
854#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
857#[serde(rename_all = "lowercase")]
858pub enum VideoMediaType {
859 AVI,
860 MP4,
861 MPEG,
862 MOV,
863 WEBM,
864}
865
866#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
868#[serde(rename_all = "lowercase")]
869pub enum ImageDetail {
870 Low,
871 High,
872 #[default]
873 Auto,
874}
875
876impl Message {
877 pub fn rag_text(&self) -> Option<String> {
879 match self {
880 Message::User { content } => {
881 for item in content.iter() {
882 if let UserContent::Text(Text { text, .. }) = item {
883 return Some(text.clone());
884 }
885 }
886 None
887 }
888 Message::System { .. } => None,
889 _ => None,
890 }
891 }
892
893 pub fn system(text: impl Into<String>) -> Self {
895 Message::System {
896 content: text.into(),
897 }
898 }
899
900 pub fn user(text: impl Into<String>) -> Self {
902 Message::User {
903 content: vec![UserContent::text(text)],
904 }
905 }
906
907 pub fn assistant(text: impl Into<String>) -> Self {
909 Message::Assistant(AssistantMessage::new(vec![AssistantContent::text(text)]))
910 }
911
912 pub fn tool_result(call: CallId, name: ToolName, content: impl Into<String>) -> Self {
916 Message::User {
917 content: vec![UserContent::tool_result(
918 call,
919 name,
920 vec![ToolResultContent::text(content)],
921 )],
922 }
923 }
924
925 pub fn tool_results(results: Vec<ToolResult>) -> Self {
927 Message::User {
928 content: results.into_iter().map(UserContent::ToolResult).collect(),
929 }
930 }
931}
932
933macro_rules! media_ctors {
935 () => {};
936 (
937 $(#[$meta:meta])* $name:ident => Image($kind:ident: $data:ty);
938 $($rest:tt)*
939 ) => {
940 $(#[$meta])*
941 pub fn $name(
942 data: impl Into<$data>,
943 media_type: Option<ImageMediaType>,
944 detail: Option<ImageDetail>,
945 ) -> Self {
946 Self::Image(Image {
947 data: DocumentSourceKind::$kind(data.into()),
948 media_type,
949 detail,
950 native: None,
951 })
952 }
953 media_ctors! { $($rest)* }
954 };
955 (
956 $(#[$meta:meta])* $name:ident => $variant:ident(params $mt:ty, $kind:ident: $data:ty);
957 $($rest:tt)*
958 ) => {
959 $(#[$meta])*
960 pub fn $name(data: impl Into<$data>, media_type: Option<$mt>) -> Self {
961 Self::$variant($variant {
962 data: DocumentSourceKind::$kind(data.into()),
963 media_type,
964 additional_params: None,
965 })
966 }
967 media_ctors! { $($rest)* }
968 };
969 (
970 $(#[$meta:meta])* $name:ident => $variant:ident($mt:ty, $kind:ident: $data:ty);
971 $($rest:tt)*
972 ) => {
973 $(#[$meta])*
974 pub fn $name(data: impl Into<$data>, media_type: Option<$mt>) -> Self {
975 Self::$variant($variant {
976 data: DocumentSourceKind::$kind(data.into()),
977 media_type,
978 })
979 }
980 media_ctors! { $($rest)* }
981 };
982}
983
984impl UserContent {
985 pub fn text(text: impl Into<String>) -> Self {
987 UserContent::Text(text.into().into())
988 }
989
990 media_ctors! {
991 image_base64 => Image(Base64: String);
993 image_raw => Image(Raw: Vec<u8>);
995 image_url => Image(Url: String);
997 audio_base64 => Audio(AudioMediaType, Base64: String);
999 audio_raw => Audio(AudioMediaType, Raw: Vec<u8>);
1001 audio_url => Audio(AudioMediaType, Url: String);
1003 video_base64 => Video(params VideoMediaType, Base64: String);
1005 video_raw => Video(params VideoMediaType, Raw: Vec<u8>);
1007 video_url => Video(params VideoMediaType, Url: String);
1009 document_base64 => Document(params DocumentMediaType, Base64: String);
1011 document_raw => Document(params DocumentMediaType, Raw: Vec<u8>);
1013 document_url => Document(params DocumentMediaType, Url: String);
1015 document_text => Document(params DocumentMediaType, String: String);
1019 }
1020
1021 pub fn tool_result(call: CallId, name: ToolName, content: Vec<ToolResultContent>) -> Self {
1023 UserContent::ToolResult(ToolResult {
1024 call,
1025 name,
1026 content,
1027 is_error: false,
1028 })
1029 }
1030}
1031
1032impl AssistantContent {
1033 pub fn text(text: impl Into<String>) -> Self {
1035 AssistantContent::Text(text.into().into())
1036 }
1037
1038 media_ctors! {
1039 image_base64 => Image(Base64: String);
1041 }
1042
1043 pub fn tool_call(id: impl Into<String>, name: ToolName, arguments: serde_json::Value) -> Self {
1046 AssistantContent::ToolCall(ToolCall::from_wire(id, ToolFunction::new(name, arguments)))
1047 }
1048
1049 pub fn reasoning(reasoning: impl Into<String>) -> Self {
1051 AssistantContent::Reasoning(Reasoning::new(reasoning))
1052 }
1053}
1054
1055impl ToolResultContent {
1056 pub fn text(text: impl Into<String>) -> Self {
1058 ToolResultContent::Text(text.into().into())
1059 }
1060
1061 pub fn json(value: serde_json::Value) -> Self {
1063 ToolResultContent::Json { value }
1064 }
1065
1066 media_ctors! {
1067 image_base64 => Image(Base64: String);
1069 image_raw => Image(Raw: Vec<u8>);
1071 image_url => Image(Url: String);
1073 }
1074}
1075
1076pub trait MimeType {
1078 fn from_mime_type(mime_type: &str) -> Option<Self>
1079 where
1080 Self: Sized;
1081 fn to_mime_type(&self) -> &'static str;
1082}
1083
1084impl MimeType for MediaType {
1085 fn from_mime_type(mime_type: &str) -> Option<Self> {
1086 ImageMediaType::from_mime_type(mime_type)
1087 .map(MediaType::Image)
1088 .or_else(|| DocumentMediaType::from_mime_type(mime_type).map(MediaType::Document))
1089 .or_else(|| AudioMediaType::from_mime_type(mime_type).map(MediaType::Audio))
1090 .or_else(|| VideoMediaType::from_mime_type(mime_type).map(MediaType::Video))
1091 }
1092
1093 fn to_mime_type(&self) -> &'static str {
1094 match self {
1095 MediaType::Image(media_type) => media_type.to_mime_type(),
1096 MediaType::Audio(media_type) => media_type.to_mime_type(),
1097 MediaType::Document(media_type) => media_type.to_mime_type(),
1098 MediaType::Video(media_type) => media_type.to_mime_type(),
1099 }
1100 }
1101}
1102
1103macro_rules! impl_mime_type {
1108 ($ty:ident { $($variant:ident => $canonical:literal $(| $alias:literal)*),+ $(,)? }) => {
1109 impl MimeType for $ty {
1110 fn from_mime_type(mime_type: &str) -> Option<Self> {
1111 match mime_type {
1112 $($canonical $(| $alias)* => Some($ty::$variant),)+
1113 _ => None,
1114 }
1115 }
1116
1117 fn to_mime_type(&self) -> &'static str {
1118 match self {
1119 $($ty::$variant => $canonical,)+
1120 }
1121 }
1122 }
1123 };
1124}
1125
1126impl_mime_type!(ImageMediaType {
1127 JPEG => "image/jpeg",
1128 PNG => "image/png",
1129 GIF => "image/gif",
1130 WEBP => "image/webp",
1131 HEIC => "image/heic",
1132 HEIF => "image/heif",
1133 SVG => "image/svg+xml",
1134});
1135
1136impl_mime_type!(DocumentMediaType {
1137 PDF => "application/pdf",
1138 TXT => "text/plain",
1139 RTF => "text/rtf",
1140 HTML => "text/html",
1141 CSS => "text/css",
1142 MARKDOWN => "text/markdown" | "text/md",
1143 CSV => "text/csv",
1144 XML => "text/xml",
1145 Javascript => "application/x-javascript" | "text/x-javascript",
1146 Python => "application/x-python" | "text/x-python",
1147});
1148
1149impl_mime_type!(AudioMediaType {
1150 WAV => "audio/wav",
1151 MP3 => "audio/mp3",
1152 AIFF => "audio/aiff",
1153 AAC => "audio/aac",
1154 OGG => "audio/ogg",
1155 FLAC => "audio/flac",
1156 M4A => "audio/m4a",
1157 PCM16 => "audio/pcm16",
1158 PCM24 => "audio/pcm24",
1159});
1160
1161impl_mime_type!(VideoMediaType {
1162 AVI => "video/avi",
1163 MP4 => "video/mp4",
1164 MPEG => "video/mpeg",
1165 MOV => "video/mov",
1166 WEBM => "video/webm",
1167});
1168
1169impl std::str::FromStr for ImageDetail {
1170 type Err = ();
1171
1172 fn from_str(s: &str) -> Result<Self, Self::Err> {
1173 match s.to_lowercase().as_str() {
1174 "low" => Ok(ImageDetail::Low),
1175 "high" => Ok(ImageDetail::High),
1176 "auto" => Ok(ImageDetail::Auto),
1177 _ => Err(()),
1178 }
1179 }
1180}
1181
1182macro_rules! text_from {
1184 ($($src:ty),+ $(,)?) => {$(
1185 impl From<$src> for Text {
1186 fn from(text: $src) -> Self {
1187 Text::new(text)
1188 }
1189 }
1190 )+};
1191}
1192
1193text_from!(String, &String, &str);
1194
1195macro_rules! text_content_from_string {
1197 ($($ty:ident),+ $(,)?) => {$(
1198 impl From<String> for $ty {
1199 fn from(text: String) -> Self {
1200 $ty::text(text)
1201 }
1202 }
1203 )+};
1204}
1205
1206text_content_from_string!(ToolResultContent, AssistantContent, UserContent);
1207
1208macro_rules! single_content_message_from {
1211 (User { $($src:ty => $variant:ident),+ $(,)? }) => {$(
1212 impl From<$src> for Message {
1213 fn from(value: $src) -> Self {
1214 Message::User {
1215 content: vec![UserContent::$variant(value.into())],
1216 }
1217 }
1218 }
1219 )+};
1220 (Assistant { $($src:ty => $variant:ident),+ $(,)? }) => {$(
1221 impl From<$src> for Message {
1222 fn from(value: $src) -> Self {
1223 Message::Assistant(AssistantMessage::new(vec![AssistantContent::$variant(
1224 value.into(),
1225 )]))
1226 }
1227 }
1228 )+};
1229}
1230
1231single_content_message_from!(User {
1232 String => Text,
1233 &str => Text,
1234 &String => Text,
1235 Text => Text,
1236 Image => Image,
1237 Audio => Audio,
1238 Document => Document,
1239 ToolResult => ToolResult,
1240});
1241
1242single_content_message_from!(Assistant {
1243 ToolCall => ToolCall,
1244});
1245
1246impl FromStr for Text {
1247 type Err = Infallible;
1248
1249 fn from_str(s: &str) -> Result<Self, Self::Err> {
1250 Ok(s.into())
1251 }
1252}
1253
1254impl From<&Message> for Message {
1255 fn from(msg: &Message) -> Self {
1256 msg.clone()
1257 }
1258}
1259
1260impl From<AssistantContent> for Message {
1261 fn from(content: AssistantContent) -> Self {
1262 Message::Assistant(AssistantMessage::new(vec![content]))
1263 }
1264}
1265
1266impl From<AssistantMessage> for Message {
1267 fn from(message: AssistantMessage) -> Self {
1268 Message::Assistant(message)
1269 }
1270}
1271
1272impl From<UserContent> for Message {
1273 fn from(content: UserContent) -> Self {
1274 Message::User {
1275 content: vec![content],
1276 }
1277 }
1278}
1279
1280impl From<Vec<AssistantContent>> for Message {
1281 fn from(content: Vec<AssistantContent>) -> Self {
1282 Message::Assistant(AssistantMessage::new(content))
1283 }
1284}
1285
1286impl From<Vec<UserContent>> for Message {
1287 fn from(content: Vec<UserContent>) -> Self {
1288 Message::User { content }
1289 }
1290}
1291
1292#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
1293#[serde(rename_all = "snake_case")]
1294pub enum ToolChoice {
1295 #[default]
1296 Auto,
1297 None,
1298 Required,
1299 Specific {
1300 function_names: Vec<ToolName>,
1301 },
1302}
1303
1304#[derive(Debug, Error)]
1306pub enum MessageError {
1307 #[error("Message conversion error: {0}")]
1308 ConversionError(String),
1309}
1310
1311impl From<MessageError> for ProviderError {
1312 fn from(error: MessageError) -> Self {
1313 ProviderError::request(error)
1314 }
1315}
1316
1317#[cfg(test)]
1318mod tests;