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 {
24 id: Option<String>,
26 content: Vec<AssistantContent>,
27 },
28}
29
30mod identity;
31
32pub use identity::{
33 CallId, EmptyCallId, EmptyToolName, Issuer, LocalCallId, ProviderCallId, Sealed, ToolName,
34};
35
36pub const EMPTY_RESPONSE_ERROR: &str = "Response contained no message or tool call (empty)";
40
41pub fn require_non_empty<T, E>(items: Vec<T>, error: impl FnOnce() -> E) -> Result<Vec<T>, E> {
46 if items.is_empty() {
47 return Err(error());
48 }
49 Ok(items)
50}
51
52pub fn require_non_empty_response<T>(items: Vec<T>) -> Result<Vec<T>, ProviderError> {
55 require_non_empty(items, || {
56 ProviderError::Response(EMPTY_RESPONSE_ERROR.to_owned())
57 })
58}
59
60pub fn non_empty<T>(items: Vec<T>) -> Option<Vec<T>> {
63 if items.is_empty() { None } else { Some(items) }
64}
65
66pub fn ordered_assistant_content(
69 reasoning_items: impl IntoIterator<Item = Sealed<Reasoning>>,
70 text_items: impl IntoIterator<Item = AssistantContent>,
71 trailing_items: impl IntoIterator<Item = AssistantContent>,
72) -> Vec<AssistantContent> {
73 let mut content_items = reasoning_items
74 .into_iter()
75 .map(AssistantContent::Reasoning)
76 .collect::<Vec<_>>();
77 content_items.extend(text_items);
78 content_items.extend(trailing_items);
79 content_items
80}
81
82pub fn turn_delivered_no_answer(choice: &[AssistantContent]) -> bool {
85 !choice.iter().any(|content| match content {
86 AssistantContent::Text(text) => !text.text.is_empty(),
88 AssistantContent::ToolCall(_) => true,
89 AssistantContent::Image(_) => true,
90 AssistantContent::Reasoning(_) => false,
92 })
93}
94
95pub fn canonical_streamed_choice(choice: Vec<AssistantContent>) -> Vec<AssistantContent> {
99 let regroup = choice.iter().any(|part| {
100 matches!(
101 part,
102 AssistantContent::Reasoning(_) | AssistantContent::ToolCall(_)
103 )
104 });
105 if !regroup {
106 return choice;
107 }
108 let mut reasoning = Vec::new();
109 let mut text = Vec::new();
110 let mut calls = Vec::new();
111 let mut images = Vec::new();
112 for part in choice {
113 match part {
114 AssistantContent::Reasoning(block) => reasoning.push(block),
115 AssistantContent::Text(_) => text.push(part),
116 AssistantContent::ToolCall(_) => calls.push(part),
117 AssistantContent::Image(_) => images.push(part),
118 }
119 }
120 ordered_assistant_content(reasoning, text, calls.into_iter().chain(images))
121}
122
123#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
126#[serde(tag = "type", rename_all = "lowercase")]
127pub enum UserContent {
128 Text(Text),
130 ToolResult(ToolResult),
132 Image(Image),
134 Audio(Audio),
136 Video(Video),
138 Document(Document),
140}
141
142#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
145#[serde(tag = "type", rename_all = "lowercase")]
146pub enum AssistantContent {
147 Text(Text),
149 ToolCall(ToolCall),
151 Reasoning(Sealed<Reasoning>),
154 Image(Image),
156}
157
158#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
159#[serde(tag = "type", content = "content", rename_all = "snake_case")]
160pub enum ReasoningContent {
162 Text {
164 text: String,
165 #[serde(skip_serializing_if = "Option::is_none")]
166 signature: Option<String>,
167 },
168 Encrypted(String),
170 Redacted { data: String },
172 Summary(String),
174}
175
176#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
177pub struct Reasoning {
182 pub id: Option<String>,
184 pub content: Vec<ReasoningContent>,
186}
187
188impl Reasoning {
189 pub fn new(input: &str) -> Self {
191 Self::new_with_signature(input, None)
192 }
193
194 pub fn new_with_signature(input: &str, signature: Option<String>) -> Self {
196 Self {
197 id: None,
198 content: vec![ReasoningContent::Text {
199 text: input.to_string(),
200 signature,
201 }],
202 }
203 }
204
205 pub fn sealed(self, issuer: impl Into<Issuer>) -> Sealed<Self> {
207 Sealed::new(issuer, self)
208 }
209
210 pub fn with_id(mut self, id: String) -> Self {
212 self.id = Some(id);
213 self
214 }
215
216 pub fn multi(input: Vec<String>) -> Self {
218 Self {
219 id: None,
220 content: input
221 .into_iter()
222 .map(|text| ReasoningContent::Text {
223 text,
224 signature: None,
225 })
226 .collect(),
227 }
228 }
229
230 pub fn redacted(data: impl Into<String>) -> Self {
232 Self {
233 id: None,
234 content: vec![ReasoningContent::Redacted { data: data.into() }],
235 }
236 }
237
238 pub fn encrypted(data: impl Into<String>) -> Self {
240 Self {
241 id: None,
242 content: vec![ReasoningContent::Encrypted(data.into())],
243 }
244 }
245
246 pub fn summaries(input: Vec<String>) -> Self {
248 Self {
249 id: None,
250 content: input.into_iter().map(ReasoningContent::Summary).collect(),
251 }
252 }
253
254 pub fn display_text(&self) -> String {
256 self.content
257 .iter()
258 .filter_map(|content| match content {
259 ReasoningContent::Text { text, .. } => Some(text.as_str()),
260 ReasoningContent::Summary(summary) => Some(summary.as_str()),
261 ReasoningContent::Redacted { data } => Some(data.as_str()),
262 ReasoningContent::Encrypted(_) => None,
263 })
264 .collect::<Vec<_>>()
265 .join("\n")
266 }
267
268 pub fn first_text(&self) -> Option<&str> {
270 self.content.iter().find_map(|content| match content {
271 ReasoningContent::Text { text, .. } => Some(text.as_str()),
272 _ => None,
273 })
274 }
275
276 pub fn first_signature(&self) -> Option<&str> {
278 self.content.iter().find_map(|content| match content {
279 ReasoningContent::Text {
280 signature: Some(signature),
281 ..
282 } => Some(signature.as_str()),
283 _ => None,
284 })
285 }
286
287 pub fn encrypted_content(&self) -> Option<&str> {
289 self.content.iter().find_map(|content| match content {
290 ReasoningContent::Encrypted(data) => Some(data.as_str()),
291 _ => None,
292 })
293 }
294}
295
296#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
301pub struct ToolResult {
302 pub call: CallId,
304 pub name: ToolName,
307 pub content: Vec<ToolResultContent>,
309}
310
311#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
313#[serde(tag = "type", rename_all = "lowercase")]
314pub enum ToolResultContent {
315 Text(Text),
317 Image(Image),
319 Json {
321 value: serde_json::Value,
323 },
324}
325
326impl ToolResultContent {
327 pub fn as_text(&self) -> Option<&str> {
329 match self {
330 Self::Text(text) => Some(&text.text),
331 Self::Image(_) | Self::Json { .. } => None,
332 }
333 }
334
335 pub fn as_json(&self) -> Option<&serde_json::Value> {
337 match self {
338 Self::Json { value } => Some(value),
339 Self::Text(_) | Self::Image(_) => None,
340 }
341 }
342
343 pub fn deserialize_json<T>(&self) -> Result<T, serde_json::Error>
350 where
351 T: serde::de::DeserializeOwned,
352 {
353 match self {
354 Self::Json { value } => T::deserialize(value),
355 Self::Text(text) => serde_json::from_str(&text.text),
356 Self::Image(_) => Err(<serde_json::Error as serde::de::Error>::custom(
357 "cannot decode image tool-result content as JSON",
358 )),
359 }
360 }
361}
362
363#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
365pub struct ToolCall {
366 pub id: CallId,
369 pub function: ToolFunction,
371 #[serde(default)]
373 pub signature: Option<String>,
374 #[serde(default)]
376 pub additional_params: Option<serde_json::Value>,
377}
378
379impl ToolCall {
380 pub fn new(id: CallId, function: ToolFunction) -> Self {
382 Self {
383 id,
384 function,
385 signature: None,
386 additional_params: None,
387 }
388 }
389
390 pub fn from_wire(wire_id: impl Into<String>, function: ToolFunction) -> Self {
393 Self::new(CallId::from_wire(wire_id), function)
394 }
395
396 pub fn from_dual_wire(
400 item_id: impl Into<String>,
401 call_id: impl Into<String>,
402 function: ToolFunction,
403 ) -> Self {
404 Self::new(CallId::from_dual_wire(item_id, call_id), function)
405 }
406
407 pub fn result(&self, content: Vec<ToolResultContent>) -> ToolResult {
409 ToolResult {
410 call: self.id.clone(),
411 name: self.function.name.clone(),
412 content,
413 }
414 }
415
416 pub fn with_signature(mut self, signature: Option<String>) -> Self {
417 self.signature = signature;
418 self
419 }
420
421 pub fn with_additional_params(mut self, additional_params: Option<serde_json::Value>) -> Self {
422 self.additional_params = additional_params;
423 self
424 }
425}
426
427#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
429pub struct ToolFunction {
430 pub name: ToolName,
432 pub arguments: serde_json::Value,
434}
435
436impl ToolFunction {
437 pub fn new(name: ToolName, arguments: serde_json::Value) -> Self {
439 Self { name, arguments }
440 }
441}
442
443#[derive(Clone, Debug, PartialEq, Serialize)]
449#[serde(transparent)]
450pub struct AdditionalParams(serde_json::Map<String, serde_json::Value>);
451
452impl AdditionalParams {
453 pub fn new(map: serde_json::Map<String, serde_json::Value>) -> Option<Self> {
455 if map.is_empty() {
456 None
457 } else {
458 Some(Self(map))
459 }
460 }
461
462 pub fn from_entries<K, I>(entries: I) -> Option<Self>
467 where
468 K: Into<String>,
469 I: IntoIterator<Item = (K, serde_json::Value)>,
470 {
471 Self::new(
472 entries
473 .into_iter()
474 .map(|(key, value)| (key.into(), value))
475 .collect(),
476 )
477 }
478
479 pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
481 self.0.get(key)
482 }
483
484 pub fn as_map(&self) -> &serde_json::Map<String, serde_json::Value> {
486 &self.0
487 }
488
489 pub fn into_value(self) -> serde_json::Value {
491 serde_json::Value::Object(self.0)
492 }
493
494 pub fn merge(&mut self, incoming: Self) {
498 fn merge_maps(
499 existing: &mut serde_json::Map<String, serde_json::Value>,
500 incoming: serde_json::Map<String, serde_json::Value>,
501 ) {
502 for (key, incoming_value) in incoming {
503 match existing.get_mut(&key) {
504 Some(existing_value) => merge_value(existing_value, incoming_value),
505 None => {
506 existing.insert(key, incoming_value);
507 }
508 }
509 }
510 }
511 fn merge_value(existing: &mut serde_json::Value, incoming: serde_json::Value) {
512 match (existing, incoming) {
513 (
514 serde_json::Value::Object(existing_map),
515 serde_json::Value::Object(incoming_map),
516 ) => merge_maps(existing_map, incoming_map),
517 (
518 serde_json::Value::Array(existing_array),
519 serde_json::Value::Array(mut incoming_array),
520 ) => existing_array.append(&mut incoming_array),
521 (existing, incoming) => *existing = incoming,
522 }
523 }
524 merge_maps(&mut self.0, incoming.0);
525 }
526
527 pub fn wire_extras(
530 &self,
531 wire_key: &str,
532 ) -> Option<&serde_json::Map<String, serde_json::Value>> {
533 self.0.get(wire_key).and_then(serde_json::Value::as_object)
534 }
535
536 pub fn into_wire_extras(
540 mut self,
541 wire_key: &str,
542 ) -> Option<serde_json::Map<String, serde_json::Value>> {
543 match self.0.remove(wire_key) {
544 Some(serde_json::Value::Object(map)) => Some(map),
545 _ => None,
546 }
547 }
548
549 pub fn try_from_value(value: serde_json::Value) -> Result<Option<Self>, serde_json::Value> {
552 match value {
553 serde_json::Value::Null => Ok(None),
554 serde_json::Value::Object(map) => Ok(Self::new(map)),
555 other => Err(other),
556 }
557 }
558}
559
560impl From<AdditionalParams> for serde_json::Value {
561 fn from(params: AdditionalParams) -> Self {
562 params.into_value()
563 }
564}
565
566impl std::ops::Index<&str> for AdditionalParams {
567 type Output = serde_json::Value;
568
569 #[allow(clippy::indexing_slicing)]
574 fn index(&self, key: &str) -> &serde_json::Value {
575 &self.0[key]
576 }
577}
578
579impl<'de> Deserialize<'de> for AdditionalParams {
580 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
581 where
582 D: serde::Deserializer<'de>,
583 {
584 match Self::try_from_value(serde_json::Value::deserialize(deserializer)?) {
585 Ok(Some(params)) => Ok(params),
586 Ok(None) => Err(serde::de::Error::custom(
589 "`additional_params` carries no data — omit the field (an `Option` \
590 field routed through `optional_additional_params` canonicalizes \
591 `{}` and `null` to absent)",
592 )),
593 Err(_) => Err(serde::de::Error::custom(
594 "`additional_params` must be a non-empty JSON object",
595 )),
596 }
597 }
598}
599
600pub fn keys_lost_in_round_trip(
621 original: &serde_json::Value,
622 round_tripped: &serde_json::Value,
623) -> Vec<String> {
624 fn walk(
625 original: &serde_json::Value,
626 round_tripped: &serde_json::Value,
627 path: &mut String,
628 lost: &mut Vec<String>,
629 ) {
630 match (original, round_tripped) {
631 (serde_json::Value::Object(original_map), serde_json::Value::Object(round_map)) => {
632 for (key, original_value) in original_map {
633 if original_value.is_null() {
634 continue;
635 }
636 let checkpoint = path.len();
637 if !path.is_empty() {
638 path.push('.');
639 }
640 path.push_str(key);
641 match round_map.get(key) {
642 Some(round_value) => walk(original_value, round_value, path, lost),
643 None => {
645 if !original_value
646 .as_object()
647 .is_some_and(serde_json::Map::is_empty)
648 {
649 lost.push(path.clone());
650 }
651 }
652 }
653 path.truncate(checkpoint);
654 }
655 }
656 (serde_json::Value::Array(original_items), serde_json::Value::Array(round_items)) => {
657 for (index, original_value) in original_items.iter().enumerate() {
658 let checkpoint = path.len();
659 if !path.is_empty() {
660 path.push('.');
661 }
662 path.push_str(&index.to_string());
663 match round_items.get(index) {
664 Some(round_value) => walk(original_value, round_value, path, lost),
665 None => lost.push(path.clone()),
666 }
667 path.truncate(checkpoint);
668 }
669 }
670 (original, round_tripped) => {
671 if original != round_tripped {
672 lost.push(path.clone());
673 }
674 }
675 }
676 }
677
678 let mut lost = Vec::new();
679 walk(original, round_tripped, &mut String::new(), &mut lost);
680 lost
681}
682
683pub fn optional_additional_params<'de, D>(
686 deserializer: D,
687) -> Result<Option<AdditionalParams>, D::Error>
688where
689 D: serde::Deserializer<'de>,
690{
691 match Option::<serde_json::Value>::deserialize(deserializer)? {
692 None => Ok(None),
693 Some(value) => AdditionalParams::try_from_value(value).map_err(|_| {
694 serde::de::Error::custom("`additional_params` must be a JSON object (or null)")
695 }),
696 }
697}
698
699#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
702pub struct Text {
703 pub text: String,
705 #[serde(
707 default,
708 deserialize_with = "optional_additional_params",
709 skip_serializing_if = "Option::is_none"
710 )]
711 pub additional_params: Option<AdditionalParams>,
712}
713
714impl Text {
715 pub fn new(text: impl Into<String>) -> Self {
717 Self {
718 text: text.into(),
719 additional_params: None,
720 }
721 }
722
723 pub fn text(&self) -> &str {
725 &self.text
726 }
727}
728
729impl std::fmt::Display for Text {
730 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
731 let Self { text, .. } = self;
732 write!(f, "{text}")
733 }
734}
735
736#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
738pub struct Image {
739 pub data: DocumentSourceKind,
741 #[serde(skip_serializing_if = "Option::is_none")]
743 pub media_type: Option<ImageMediaType>,
744 #[serde(skip_serializing_if = "Option::is_none")]
746 pub detail: Option<ImageDetail>,
747 #[serde(
749 default,
750 deserialize_with = "optional_additional_params",
751 skip_serializing_if = "Option::is_none"
752 )]
753 pub additional_params: Option<AdditionalParams>,
754}
755
756#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
758#[serde(tag = "type", content = "value", rename_all = "camelCase")]
759pub enum DocumentSourceKind {
760 Url(String),
762 Base64(String),
764 FileId(String),
766 Raw(Vec<u8>),
768 String(String),
770 #[default]
771 Unknown,
773}
774
775impl DocumentSourceKind {
776 pub fn url(url: &str) -> Self {
778 Self::Url(url.to_string())
779 }
780
781 pub fn base64(base64_string: &str) -> Self {
783 Self::Base64(base64_string.to_string())
784 }
785
786 pub fn file_id(file_id: &str) -> Self {
788 Self::FileId(file_id.to_string())
789 }
790
791 pub fn string(input: &str) -> Self {
793 Self::String(input.into())
794 }
795
796 pub fn try_into_inner(self) -> Option<String> {
798 match self {
799 Self::Url(s) | Self::Base64(s) | Self::FileId(s) => Some(s),
800 _ => None,
801 }
802 }
803}
804
805impl std::fmt::Display for DocumentSourceKind {
806 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
807 match self {
808 Self::Url(string) => write!(f, "{string}"),
809 Self::Base64(string) => write!(f, "{string}"),
810 Self::FileId(string) => write!(f, "{string}"),
811 Self::String(string) => write!(f, "{string}"),
812 Self::Raw(_) => write!(f, "<binary data>"),
813 Self::Unknown => write!(f, "<unknown>"),
814 }
815 }
816}
817
818#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
820pub struct Audio {
821 pub data: DocumentSourceKind,
823 #[serde(skip_serializing_if = "Option::is_none")]
825 pub media_type: Option<AudioMediaType>,
826 #[serde(
828 default,
829 deserialize_with = "optional_additional_params",
830 skip_serializing_if = "Option::is_none"
831 )]
832 pub additional_params: Option<AdditionalParams>,
833}
834
835#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
837pub struct Video {
838 pub data: DocumentSourceKind,
840 #[serde(skip_serializing_if = "Option::is_none")]
842 pub media_type: Option<VideoMediaType>,
843 #[serde(
845 default,
846 deserialize_with = "optional_additional_params",
847 skip_serializing_if = "Option::is_none"
848 )]
849 pub additional_params: Option<AdditionalParams>,
850}
851
852#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
854pub struct Document {
855 pub data: DocumentSourceKind,
857 #[serde(skip_serializing_if = "Option::is_none")]
859 pub media_type: Option<DocumentMediaType>,
860 #[serde(
862 default,
863 deserialize_with = "optional_additional_params",
864 skip_serializing_if = "Option::is_none"
865 )]
866 pub additional_params: Option<AdditionalParams>,
867}
868
869#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
871#[serde(rename_all = "lowercase")]
872pub enum ContentFormat {
873 #[default]
874 Base64,
875 String,
876 Url,
877}
878
879#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
881pub enum MediaType {
882 Image(ImageMediaType),
883 Audio(AudioMediaType),
884 Document(DocumentMediaType),
885 Video(VideoMediaType),
886}
887
888#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
891#[serde(rename_all = "lowercase")]
892pub enum ImageMediaType {
893 JPEG,
894 PNG,
895 GIF,
896 WEBP,
897 HEIC,
898 HEIF,
899 SVG,
900}
901
902#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
906#[serde(rename_all = "lowercase")]
907pub enum DocumentMediaType {
908 PDF,
909 TXT,
910 RTF,
911 HTML,
912 CSS,
913 MARKDOWN,
914 CSV,
915 XML,
916 Javascript,
917 Python,
918}
919
920impl DocumentMediaType {
921 pub fn is_code(&self) -> bool {
922 matches!(self, Self::Javascript | Self::Python)
923 }
924}
925
926#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
929#[serde(rename_all = "lowercase")]
930pub enum AudioMediaType {
931 WAV,
932 MP3,
933 AIFF,
934 AAC,
935 OGG,
936 FLAC,
937 M4A,
938 PCM16,
939 PCM24,
940}
941
942#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
945#[serde(rename_all = "lowercase")]
946pub enum VideoMediaType {
947 AVI,
948 MP4,
949 MPEG,
950 MOV,
951 WEBM,
952}
953
954#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
956#[serde(rename_all = "lowercase")]
957pub enum ImageDetail {
958 Low,
959 High,
960 #[default]
961 Auto,
962}
963
964impl Message {
965 pub fn rag_text(&self) -> Option<String> {
967 match self {
968 Message::User { content } => {
969 for item in content.iter() {
970 if let UserContent::Text(Text { text, .. }) = item {
971 return Some(text.clone());
972 }
973 }
974 None
975 }
976 Message::System { .. } => None,
977 _ => None,
978 }
979 }
980
981 pub fn replays_to(&self, issuers: &[Issuer]) -> bool {
985 match self {
986 Message::Assistant { content, .. } => content.iter().any(|part| match part {
987 AssistantContent::Reasoning(reasoning) => reasoning.open_for(issuers).is_some(),
988 _ => true,
989 }),
990 Message::System { .. } | Message::User { .. } => true,
991 }
992 }
993
994 pub fn system(text: impl Into<String>) -> Self {
996 Message::System {
997 content: text.into(),
998 }
999 }
1000
1001 pub fn user(text: impl Into<String>) -> Self {
1003 Message::User {
1004 content: vec![UserContent::text(text)],
1005 }
1006 }
1007
1008 pub fn assistant(text: impl Into<String>) -> Self {
1010 Message::Assistant {
1011 id: None,
1012 content: vec![AssistantContent::text(text)],
1013 }
1014 }
1015
1016 pub fn tool_result(call: CallId, name: ToolName, content: impl Into<String>) -> Self {
1020 Message::User {
1021 content: vec![UserContent::tool_result(
1022 call,
1023 name,
1024 vec![ToolResultContent::text(content)],
1025 )],
1026 }
1027 }
1028
1029 pub fn tool_results(results: Vec<ToolResult>) -> Self {
1031 Message::User {
1032 content: results.into_iter().map(UserContent::ToolResult).collect(),
1033 }
1034 }
1035}
1036
1037macro_rules! media_ctors {
1039 () => {};
1040 (
1041 $(#[$meta:meta])* $name:ident => Image($kind:ident: $data:ty);
1042 $($rest:tt)*
1043 ) => {
1044 $(#[$meta])*
1045 pub fn $name(
1046 data: impl Into<$data>,
1047 media_type: Option<ImageMediaType>,
1048 detail: Option<ImageDetail>,
1049 ) -> Self {
1050 Self::Image(Image {
1051 data: DocumentSourceKind::$kind(data.into()),
1052 media_type,
1053 detail,
1054 additional_params: None,
1055 })
1056 }
1057 media_ctors! { $($rest)* }
1058 };
1059 (
1060 $(#[$meta:meta])* $name:ident => $variant:ident($mt:ty, $kind:ident: $data:ty);
1061 $($rest:tt)*
1062 ) => {
1063 $(#[$meta])*
1064 pub fn $name(data: impl Into<$data>, media_type: Option<$mt>) -> Self {
1065 Self::$variant($variant {
1066 data: DocumentSourceKind::$kind(data.into()),
1067 media_type,
1068 additional_params: None,
1069 })
1070 }
1071 media_ctors! { $($rest)* }
1072 };
1073}
1074
1075impl UserContent {
1076 pub fn text(text: impl Into<String>) -> Self {
1078 UserContent::Text(text.into().into())
1079 }
1080
1081 media_ctors! {
1082 image_base64 => Image(Base64: String);
1084 image_raw => Image(Raw: Vec<u8>);
1086 image_url => Image(Url: String);
1088 audio => Audio(AudioMediaType, Base64: String);
1090 audio_raw => Audio(AudioMediaType, Raw: Vec<u8>);
1092 audio_url => Audio(AudioMediaType, Url: String);
1094 video => Video(VideoMediaType, Base64: String);
1096 video_raw => Video(VideoMediaType, Raw: Vec<u8>);
1098 video_url => Video(VideoMediaType, Url: String);
1100 document_raw => Document(DocumentMediaType, Raw: Vec<u8>);
1102 document_url => Document(DocumentMediaType, Url: String);
1104 }
1105
1106 pub fn document(data: impl Into<String>, media_type: Option<DocumentMediaType>) -> Self {
1108 let data: String = data.into();
1109 UserContent::Document(Document {
1110 data: DocumentSourceKind::string(&data),
1111 media_type,
1112 additional_params: None,
1113 })
1114 }
1115
1116 pub fn tool_result(call: CallId, name: ToolName, content: Vec<ToolResultContent>) -> Self {
1118 UserContent::ToolResult(ToolResult {
1119 call,
1120 name,
1121 content,
1122 })
1123 }
1124}
1125
1126impl AssistantContent {
1127 pub fn text(text: impl Into<String>) -> Self {
1129 AssistantContent::Text(text.into().into())
1130 }
1131
1132 media_ctors! {
1133 image_base64 => Image(Base64: String);
1135 }
1136
1137 pub fn tool_call(id: impl Into<String>, name: ToolName, arguments: serde_json::Value) -> Self {
1140 AssistantContent::ToolCall(ToolCall::from_wire(id, ToolFunction { name, arguments }))
1141 }
1142
1143 pub fn tool_call_with_call_id(
1146 id: impl Into<String>,
1147 call_id: String,
1148 name: ToolName,
1149 arguments: serde_json::Value,
1150 ) -> Self {
1151 AssistantContent::ToolCall(ToolCall::from_dual_wire(
1152 id,
1153 call_id,
1154 ToolFunction { name, arguments },
1155 ))
1156 }
1157
1158 pub fn reasoning(issuer: impl Into<Issuer>, reasoning: impl AsRef<str>) -> Self {
1160 AssistantContent::Reasoning(Reasoning::new(reasoning.as_ref()).sealed(issuer))
1161 }
1162}
1163
1164impl ToolResultContent {
1165 pub fn text(text: impl Into<String>) -> Self {
1167 ToolResultContent::Text(text.into().into())
1168 }
1169
1170 pub fn json(value: serde_json::Value) -> Self {
1172 ToolResultContent::Json { value }
1173 }
1174
1175 media_ctors! {
1176 image_base64 => Image(Base64: String);
1178 image_raw => Image(Raw: Vec<u8>);
1180 image_url => Image(Url: String);
1182 }
1183}
1184
1185pub trait MimeType {
1187 fn from_mime_type(mime_type: &str) -> Option<Self>
1188 where
1189 Self: Sized;
1190 fn to_mime_type(&self) -> &'static str;
1191}
1192
1193impl MimeType for MediaType {
1194 fn from_mime_type(mime_type: &str) -> Option<Self> {
1195 ImageMediaType::from_mime_type(mime_type)
1196 .map(MediaType::Image)
1197 .or_else(|| DocumentMediaType::from_mime_type(mime_type).map(MediaType::Document))
1198 .or_else(|| AudioMediaType::from_mime_type(mime_type).map(MediaType::Audio))
1199 .or_else(|| VideoMediaType::from_mime_type(mime_type).map(MediaType::Video))
1200 }
1201
1202 fn to_mime_type(&self) -> &'static str {
1203 match self {
1204 MediaType::Image(media_type) => media_type.to_mime_type(),
1205 MediaType::Audio(media_type) => media_type.to_mime_type(),
1206 MediaType::Document(media_type) => media_type.to_mime_type(),
1207 MediaType::Video(media_type) => media_type.to_mime_type(),
1208 }
1209 }
1210}
1211
1212macro_rules! impl_mime_type {
1217 ($ty:ident { $($variant:ident => $canonical:literal $(| $alias:literal)*),+ $(,)? }) => {
1218 impl MimeType for $ty {
1219 fn from_mime_type(mime_type: &str) -> Option<Self> {
1220 match mime_type {
1221 $($canonical $(| $alias)* => Some($ty::$variant),)+
1222 _ => None,
1223 }
1224 }
1225
1226 fn to_mime_type(&self) -> &'static str {
1227 match self {
1228 $($ty::$variant => $canonical,)+
1229 }
1230 }
1231 }
1232 };
1233}
1234
1235impl_mime_type!(ImageMediaType {
1236 JPEG => "image/jpeg",
1237 PNG => "image/png",
1238 GIF => "image/gif",
1239 WEBP => "image/webp",
1240 HEIC => "image/heic",
1241 HEIF => "image/heif",
1242 SVG => "image/svg+xml",
1243});
1244
1245impl_mime_type!(DocumentMediaType {
1246 PDF => "application/pdf",
1247 TXT => "text/plain",
1248 RTF => "text/rtf",
1249 HTML => "text/html",
1250 CSS => "text/css",
1251 MARKDOWN => "text/markdown" | "text/md",
1252 CSV => "text/csv",
1253 XML => "text/xml",
1254 Javascript => "application/x-javascript" | "text/x-javascript",
1255 Python => "application/x-python" | "text/x-python",
1256});
1257
1258impl_mime_type!(AudioMediaType {
1259 WAV => "audio/wav",
1260 MP3 => "audio/mp3",
1261 AIFF => "audio/aiff",
1262 AAC => "audio/aac",
1263 OGG => "audio/ogg",
1264 FLAC => "audio/flac",
1265 M4A => "audio/m4a",
1266 PCM16 => "audio/pcm16",
1267 PCM24 => "audio/pcm24",
1268});
1269
1270impl_mime_type!(VideoMediaType {
1271 AVI => "video/avi",
1272 MP4 => "video/mp4",
1273 MPEG => "video/mpeg",
1274 MOV => "video/mov",
1275 WEBM => "video/webm",
1276});
1277
1278impl std::str::FromStr for ImageDetail {
1279 type Err = ();
1280
1281 fn from_str(s: &str) -> Result<Self, Self::Err> {
1282 match s.to_lowercase().as_str() {
1283 "low" => Ok(ImageDetail::Low),
1284 "high" => Ok(ImageDetail::High),
1285 "auto" => Ok(ImageDetail::Auto),
1286 _ => Err(()),
1287 }
1288 }
1289}
1290
1291macro_rules! text_from {
1293 ($($src:ty),+ $(,)?) => {$(
1294 impl From<$src> for Text {
1295 fn from(text: $src) -> Self {
1296 Text {
1297 text: text.into(),
1298 additional_params: None,
1299 }
1300 }
1301 }
1302 )+};
1303}
1304
1305text_from!(String, &String, &str);
1306
1307macro_rules! text_content_from_string {
1309 ($($ty:ident),+ $(,)?) => {$(
1310 impl From<String> for $ty {
1311 fn from(text: String) -> Self {
1312 $ty::text(text)
1313 }
1314 }
1315 )+};
1316}
1317
1318text_content_from_string!(ToolResultContent, AssistantContent, UserContent);
1319
1320macro_rules! single_content_message_from {
1323 (User { $($src:ty => $variant:ident),+ $(,)? }) => {$(
1324 impl From<$src> for Message {
1325 fn from(value: $src) -> Self {
1326 Message::User {
1327 content: vec![UserContent::$variant(value.into())],
1328 }
1329 }
1330 }
1331 )+};
1332 (Assistant { $($src:ty => $variant:ident),+ $(,)? }) => {$(
1333 impl From<$src> for Message {
1334 fn from(value: $src) -> Self {
1335 Message::Assistant {
1336 id: None,
1337 content: vec![AssistantContent::$variant(value.into())],
1338 }
1339 }
1340 }
1341 )+};
1342}
1343
1344single_content_message_from!(User {
1345 String => Text,
1346 &str => Text,
1347 &String => Text,
1348 Text => Text,
1349 Image => Image,
1350 Audio => Audio,
1351 Document => Document,
1352 ToolResult => ToolResult,
1353});
1354
1355single_content_message_from!(Assistant {
1356 ToolCall => ToolCall,
1357});
1358
1359impl FromStr for Text {
1360 type Err = Infallible;
1361
1362 fn from_str(s: &str) -> Result<Self, Self::Err> {
1363 Ok(s.into())
1364 }
1365}
1366
1367impl From<&Message> for Message {
1368 fn from(msg: &Message) -> Self {
1369 msg.clone()
1370 }
1371}
1372
1373impl From<AssistantContent> for Message {
1374 fn from(content: AssistantContent) -> Self {
1375 Message::Assistant {
1376 id: None,
1377 content: vec![content],
1378 }
1379 }
1380}
1381
1382impl From<UserContent> for Message {
1383 fn from(content: UserContent) -> Self {
1384 Message::User {
1385 content: vec![content],
1386 }
1387 }
1388}
1389
1390impl From<Vec<AssistantContent>> for Message {
1391 fn from(content: Vec<AssistantContent>) -> Self {
1392 Message::Assistant { id: None, content }
1393 }
1394}
1395
1396impl From<Vec<UserContent>> for Message {
1397 fn from(content: Vec<UserContent>) -> Self {
1398 Message::User { content }
1399 }
1400}
1401
1402#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)]
1403#[serde(rename_all = "snake_case")]
1404pub enum ToolChoice {
1405 #[default]
1406 Auto,
1407 None,
1408 Required,
1409 Specific {
1410 function_names: Vec<String>,
1411 },
1412}
1413
1414#[derive(Debug, Error)]
1416pub enum MessageError {
1417 #[error("Message conversion error: {0}")]
1418 ConversionError(String),
1419}
1420
1421impl From<MessageError> for ProviderError {
1422 fn from(error: MessageError) -> Self {
1423 ProviderError::request(error)
1424 }
1425}
1426
1427#[cfg(test)]
1428mod tests;