1use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14#[cfg(not(feature = "std"))]
15use alloc::{collections::BTreeMap as HashMap, string::String, vec, vec::Vec};
16#[cfg(feature = "std")]
17use std::collections::HashMap;
18
19#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
21#[serde(rename_all = "lowercase")]
22pub enum Role {
23 #[default]
25 User,
26 Assistant,
28}
29
30impl core::fmt::Display for Role {
31 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32 match self {
33 Self::User => f.write_str("user"),
34 Self::Assistant => f.write_str("assistant"),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50#[serde(tag = "type")]
51pub enum Content {
52 #[serde(rename = "text")]
54 Text(TextContent),
55 #[serde(rename = "image")]
57 Image(ImageContent),
58 #[serde(rename = "audio")]
60 Audio(AudioContent),
61 #[serde(rename = "resource_link")]
63 ResourceLink(ResourceLink),
64 #[serde(rename = "resource")]
66 Resource(EmbeddedResource),
67}
68
69impl Default for Content {
70 fn default() -> Self {
71 Self::text("")
72 }
73}
74
75impl Content {
76 #[must_use]
78 pub fn text(text: impl Into<String>) -> Self {
79 Self::Text(TextContent {
80 text: text.into(),
81 annotations: None,
82 meta: None,
83 })
84 }
85
86 #[must_use]
88 pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
89 Self::Image(ImageContent {
90 data: data.into(),
91 mime_type: mime_type.into(),
92 annotations: None,
93 meta: None,
94 })
95 }
96
97 #[must_use]
99 pub fn audio(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
100 Self::Audio(AudioContent {
101 data: data.into(),
102 mime_type: mime_type.into(),
103 annotations: None,
104 meta: None,
105 })
106 }
107
108 #[must_use]
110 pub fn resource_link(resource: crate::definitions::Resource) -> Self {
111 Self::ResourceLink(ResourceLink {
112 uri: resource.uri,
113 name: resource.name,
114 description: resource.description,
115 title: resource.title,
116 icons: resource.icons,
117 mime_type: resource.mime_type,
118 annotations: resource.annotations,
119 size: resource.size,
120 meta: resource.meta,
121 })
122 }
123
124 #[must_use]
126 pub fn resource(uri: impl Into<String>, text: impl Into<String>) -> Self {
127 Self::Resource(EmbeddedResource {
128 resource: ResourceContents::Text(TextResourceContents {
129 uri: uri.into(),
130 mime_type: Some("text/plain".into()),
131 text: text.into(),
132 meta: None,
133 }),
134 annotations: None,
135 meta: None,
136 })
137 }
138
139 #[must_use]
141 pub fn is_text(&self) -> bool {
142 matches!(self, Self::Text(_))
143 }
144
145 #[must_use]
147 pub fn as_text(&self) -> Option<&str> {
148 match self {
149 Self::Text(t) => Some(&t.text),
150 _ => None,
151 }
152 }
153
154 #[must_use]
156 pub fn is_image(&self) -> bool {
157 matches!(self, Self::Image(_))
158 }
159
160 #[must_use]
162 pub fn is_audio(&self) -> bool {
163 matches!(self, Self::Audio(_))
164 }
165
166 #[must_use]
168 pub fn is_resource_link(&self) -> bool {
169 matches!(self, Self::ResourceLink(_))
170 }
171
172 #[must_use]
174 pub fn is_resource(&self) -> bool {
175 matches!(self, Self::Resource(_))
176 }
177
178 #[must_use]
180 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
181 match &mut self {
182 Self::Text(t) => t.annotations = Some(annotations),
183 Self::Image(i) => i.annotations = Some(annotations),
184 Self::Audio(a) => a.annotations = Some(annotations),
185 Self::ResourceLink(r) => {
186 r.annotations = Some(crate::definitions::ResourceAnnotations {
187 audience: annotations.audience,
188 priority: annotations.priority,
189 last_modified: annotations.last_modified,
190 })
191 }
192 Self::Resource(r) => r.annotations = Some(annotations),
193 }
194 self
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
207#[serde(tag = "type")]
208pub enum SamplingContent {
209 #[serde(rename = "text")]
211 Text(TextContent),
212 #[serde(rename = "image")]
214 Image(ImageContent),
215 #[serde(rename = "audio")]
217 Audio(AudioContent),
218 #[serde(rename = "tool_use")]
220 ToolUse(ToolUseContent),
221 #[serde(rename = "tool_result")]
223 ToolResult(ToolResultContent),
224}
225
226impl Default for SamplingContent {
227 fn default() -> Self {
228 Self::text("")
229 }
230}
231
232impl SamplingContent {
233 #[must_use]
235 pub fn text(text: impl Into<String>) -> Self {
236 Self::Text(TextContent {
237 text: text.into(),
238 annotations: None,
239 meta: None,
240 })
241 }
242
243 #[must_use]
245 pub fn as_text(&self) -> Option<&str> {
246 match self {
247 Self::Text(t) => Some(&t.text),
248 _ => None,
249 }
250 }
251}
252
253#[derive(Debug, Clone, PartialEq)]
261pub enum SamplingContentBlock {
262 Single(SamplingContent),
264 Multiple(Vec<SamplingContent>),
266}
267
268impl Default for SamplingContentBlock {
269 fn default() -> Self {
270 Self::Single(SamplingContent::default())
271 }
272}
273
274impl SamplingContentBlock {
275 #[must_use]
277 pub fn as_text(&self) -> Option<&str> {
278 match self {
279 Self::Single(c) => c.as_text(),
280 Self::Multiple(v) => v.iter().find_map(|c| c.as_text()),
281 }
282 }
283
284 #[must_use]
288 pub fn to_vec(&self) -> Vec<&SamplingContent> {
289 match self {
290 Self::Single(c) => vec![c],
291 Self::Multiple(v) => v.iter().collect(),
292 }
293 }
294}
295
296impl Serialize for SamplingContentBlock {
297 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
298 match self {
299 Self::Single(c) => c.serialize(serializer),
300 Self::Multiple(v) => v.serialize(serializer),
301 }
302 }
303}
304
305impl<'de> Deserialize<'de> for SamplingContentBlock {
306 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
307 let value = Value::deserialize(deserializer)?;
308 if value.is_array() {
309 let v: Vec<SamplingContent> =
310 serde_json::from_value(value).map_err(serde::de::Error::custom)?;
311 Ok(Self::Multiple(v))
312 } else {
313 let c: SamplingContent =
314 serde_json::from_value(value).map_err(serde::de::Error::custom)?;
315 Ok(Self::Single(c))
316 }
317 }
318}
319
320impl From<SamplingContent> for SamplingContentBlock {
321 fn from(c: SamplingContent) -> Self {
322 Self::Single(c)
323 }
324}
325
326impl From<Vec<SamplingContent>> for SamplingContentBlock {
327 fn from(v: Vec<SamplingContent>) -> Self {
328 Self::Multiple(v)
329 }
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
338pub struct TextContent {
339 pub text: String,
341 #[serde(skip_serializing_if = "Option::is_none")]
343 pub annotations: Option<Annotations>,
344 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
346 pub meta: Option<HashMap<String, Value>>,
347}
348
349impl TextContent {
350 #[must_use]
352 pub fn new(text: impl Into<String>) -> Self {
353 Self {
354 text: text.into(),
355 annotations: None,
356 meta: None,
357 }
358 }
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
363pub struct ImageContent {
364 pub data: String,
366 #[serde(rename = "mimeType")]
368 pub mime_type: String,
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub annotations: Option<Annotations>,
372 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
374 pub meta: Option<HashMap<String, Value>>,
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
379pub struct AudioContent {
380 pub data: String,
382 #[serde(rename = "mimeType")]
384 pub mime_type: String,
385 #[serde(skip_serializing_if = "Option::is_none")]
387 pub annotations: Option<Annotations>,
388 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
390 pub meta: Option<HashMap<String, Value>>,
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
397pub struct ToolUseContent {
398 pub id: String,
400 pub name: String,
402 pub input: HashMap<String, Value>,
404 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
406 pub meta: Option<HashMap<String, Value>>,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
413pub struct ToolResultContent {
414 #[serde(rename = "toolUseId")]
416 pub tool_use_id: String,
417 pub content: Vec<Content>,
419 #[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
421 pub structured_content: Option<Value>,
422 #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
424 pub is_error: Option<bool>,
425 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
427 pub meta: Option<HashMap<String, Value>>,
428}
429
430#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
435pub struct ResourceLink {
436 pub uri: String,
438 pub name: String,
440 #[serde(skip_serializing_if = "Option::is_none")]
442 pub description: Option<String>,
443 #[serde(skip_serializing_if = "Option::is_none")]
445 pub title: Option<String>,
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub icons: Option<Vec<crate::definitions::Icon>>,
449 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
451 pub mime_type: Option<String>,
452 #[serde(skip_serializing_if = "Option::is_none")]
454 pub annotations: Option<crate::definitions::ResourceAnnotations>,
455 #[serde(skip_serializing_if = "Option::is_none")]
457 pub size: Option<u64>,
458 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
460 pub meta: Option<HashMap<String, Value>>,
461}
462
463#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
465pub struct EmbeddedResource {
466 pub resource: ResourceContents,
468 #[serde(skip_serializing_if = "Option::is_none")]
470 pub annotations: Option<Annotations>,
471 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
473 pub meta: Option<HashMap<String, Value>>,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
482#[serde(untagged)]
483pub enum ResourceContents {
484 Text(TextResourceContents),
486 Blob(BlobResourceContents),
488}
489
490pub type ResourceContent = ResourceContents;
492
493impl ResourceContents {
494 #[must_use]
496 pub fn uri(&self) -> &str {
497 match self {
498 Self::Text(t) => &t.uri,
499 Self::Blob(b) => &b.uri,
500 }
501 }
502
503 #[must_use]
505 pub fn text(&self) -> Option<&str> {
506 match self {
507 Self::Text(t) => Some(&t.text),
508 Self::Blob(_) => None,
509 }
510 }
511
512 #[must_use]
514 pub fn blob(&self) -> Option<&str> {
515 match self {
516 Self::Text(_) => None,
517 Self::Blob(b) => Some(&b.blob),
518 }
519 }
520
521 #[must_use]
523 pub fn mime_type(&self) -> Option<&str> {
524 match self {
525 Self::Text(t) => t.mime_type.as_deref(),
526 Self::Blob(b) => b.mime_type.as_deref(),
527 }
528 }
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
533pub struct TextResourceContents {
534 pub uri: String,
536 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
538 pub mime_type: Option<String>,
539 pub text: String,
541 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
543 pub meta: Option<HashMap<String, Value>>,
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
548pub struct BlobResourceContents {
549 pub uri: String,
551 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
553 pub mime_type: Option<String>,
554 pub blob: String,
556 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
558 pub meta: Option<HashMap<String, Value>>,
559}
560
561#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
572pub struct Annotations {
573 #[serde(skip_serializing_if = "Option::is_none")]
575 pub audience: Option<Vec<Role>>,
576 #[serde(skip_serializing_if = "Option::is_none")]
578 pub priority: Option<f64>,
579 #[serde(rename = "lastModified", skip_serializing_if = "Option::is_none")]
581 pub last_modified: Option<String>,
582}
583
584impl Annotations {
585 #[must_use]
587 pub fn for_user() -> Self {
588 Self {
589 audience: Some(vec![Role::User]),
590 ..Default::default()
591 }
592 }
593
594 #[must_use]
596 pub fn for_assistant() -> Self {
597 Self {
598 audience: Some(vec![Role::Assistant]),
599 ..Default::default()
600 }
601 }
602
603 #[must_use]
605 pub fn with_priority(mut self, priority: f64) -> Self {
606 self.priority = Some(priority);
607 self
608 }
609
610 #[must_use]
612 pub fn with_last_modified(mut self, timestamp: impl Into<String>) -> Self {
613 self.last_modified = Some(timestamp.into());
614 self
615 }
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
626pub struct Message {
627 pub role: Role,
629 pub content: Content,
631}
632
633impl Message {
634 #[must_use]
636 pub fn new(role: Role, content: Content) -> Self {
637 Self { role, content }
638 }
639
640 #[must_use]
642 pub fn user(text: impl Into<String>) -> Self {
643 Self {
644 role: Role::User,
645 content: Content::text(text),
646 }
647 }
648
649 #[must_use]
651 pub fn assistant(text: impl Into<String>) -> Self {
652 Self {
653 role: Role::Assistant,
654 content: Content::text(text),
655 }
656 }
657
658 #[must_use]
660 pub fn is_user(&self) -> bool {
661 self.role == Role::User
662 }
663
664 #[must_use]
666 pub fn is_assistant(&self) -> bool {
667 self.role == Role::Assistant
668 }
669}
670
671pub type PromptMessage = Message;
676
677#[cfg(test)]
678mod tests {
679 use super::*;
680
681 #[test]
682 fn test_content_text() {
683 let content = Content::text("Hello");
684 assert!(content.is_text());
685 assert_eq!(content.as_text(), Some("Hello"));
686 }
687
688 #[test]
689 fn test_content_image() {
690 let content = Content::image("base64data", "image/png");
691 assert!(content.is_image());
692 assert!(!content.is_text());
693 }
694
695 #[test]
696 fn test_content_serde() {
697 let content = Content::text("Hello");
698 let json = serde_json::to_string(&content).unwrap();
699 assert!(json.contains("\"type\":\"text\""));
700 assert!(json.contains("\"text\":\"Hello\""));
701 }
702
703 #[test]
704 fn test_resource_link_serde() {
705 let link = Content::ResourceLink(ResourceLink {
706 uri: "file:///test.txt".into(),
707 name: "test".into(),
708 description: None,
709 title: None,
710 icons: None,
711 mime_type: Some("text/plain".into()),
712 annotations: None,
713 size: None,
714 meta: None,
715 });
716 let json = serde_json::to_string(&link).unwrap();
717 assert!(json.contains("\"type\":\"resource_link\""));
718 assert!(json.contains("\"uri\":\"file:///test.txt\""));
719
720 let parsed: Content = serde_json::from_str(&json).unwrap();
722 assert!(parsed.is_resource_link());
723 }
724
725 #[test]
726 fn test_sampling_content_tool_use_serde() {
727 let content = SamplingContent::ToolUse(ToolUseContent {
728 id: "tu_1".into(),
729 name: "search".into(),
730 input: [("query".to_string(), Value::String("test".into()))].into(),
731 meta: None,
732 });
733 let json = serde_json::to_string(&content).unwrap();
734 assert!(json.contains("\"type\":\"tool_use\""));
735 assert!(json.contains("\"id\":\"tu_1\""));
736
737 let parsed: SamplingContent = serde_json::from_str(&json).unwrap();
738 assert!(matches!(parsed, SamplingContent::ToolUse(_)));
739 }
740
741 #[test]
742 fn test_sampling_content_block_single() {
743 let block = SamplingContentBlock::Single(SamplingContent::text("hello"));
744 let json = serde_json::to_string(&block).unwrap();
745 assert!(json.starts_with('{'));
747 let parsed: SamplingContentBlock = serde_json::from_str(&json).unwrap();
748 assert!(matches!(parsed, SamplingContentBlock::Single(_)));
749 }
750
751 #[test]
752 fn test_sampling_content_block_multiple() {
753 let block = SamplingContentBlock::Multiple(vec![
754 SamplingContent::text("hello"),
755 SamplingContent::text("world"),
756 ]);
757 let json = serde_json::to_string(&block).unwrap();
758 assert!(json.starts_with('['));
760 let parsed: SamplingContentBlock = serde_json::from_str(&json).unwrap();
761 assert!(matches!(parsed, SamplingContentBlock::Multiple(v) if v.len() == 2));
762 }
763
764 #[test]
765 fn test_message_user() {
766 let msg = Message::user("Hello");
767 assert!(msg.is_user());
768 assert!(!msg.is_assistant());
769 }
770
771 #[test]
772 fn test_message_assistant() {
773 let msg = Message::assistant("Hi there");
774 assert!(msg.is_assistant());
775 assert!(!msg.is_user());
776 }
777
778 #[test]
779 fn test_annotations_for_user() {
780 let ann = Annotations::for_user().with_priority(1.0);
781 assert_eq!(ann.audience, Some(vec![Role::User]));
782 assert_eq!(ann.priority, Some(1.0));
783 }
784
785 #[test]
786 fn test_content_with_annotations() {
787 let content = Content::text("Hello").with_annotations(Annotations::for_user());
788 if let Content::Text(t) = content {
789 assert!(t.annotations.is_some());
790 } else {
791 panic!("Expected text content");
792 }
793 }
794
795 #[test]
797 fn test_resource_contents_text_deser() {
798 let json = r#"{"uri":"file:///test.txt","mimeType":"text/plain","text":"hello"}"#;
799 let rc: ResourceContents = serde_json::from_str(json).unwrap();
800 assert!(matches!(rc, ResourceContents::Text(_)));
801 assert_eq!(rc.uri(), "file:///test.txt");
802 assert_eq!(rc.text(), Some("hello"));
803 assert!(rc.blob().is_none());
804 }
805
806 #[test]
807 fn test_resource_contents_blob_deser() {
808 let json = r#"{"uri":"file:///img.png","mimeType":"image/png","blob":"aGVsbG8="}"#;
809 let rc: ResourceContents = serde_json::from_str(json).unwrap();
810 assert!(matches!(rc, ResourceContents::Blob(_)));
811 assert_eq!(rc.uri(), "file:///img.png");
812 assert_eq!(rc.blob(), Some("aGVsbG8="));
813 assert!(rc.text().is_none());
814 }
815
816 #[test]
817 fn test_resource_contents_round_trip() {
818 let text = ResourceContents::Text(TextResourceContents {
819 uri: "file:///a.txt".into(),
820 mime_type: Some("text/plain".into()),
821 text: "content".into(),
822 meta: None,
823 });
824 let json = serde_json::to_string(&text).unwrap();
825 let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
826 assert_eq!(text, parsed);
827
828 let blob = ResourceContents::Blob(BlobResourceContents {
829 uri: "file:///b.bin".into(),
830 mime_type: Some("application/octet-stream".into()),
831 blob: "AQID".into(),
832 meta: None,
833 });
834 let json = serde_json::to_string(&blob).unwrap();
835 let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
836 assert_eq!(blob, parsed);
837 }
838
839 #[test]
841 fn test_sampling_content_tool_result_serde() {
842 let content = SamplingContent::ToolResult(ToolResultContent {
843 tool_use_id: "tu_1".into(),
844 content: vec![Content::text("result data")],
845 structured_content: Some(serde_json::json!({"key": "value"})),
846 is_error: Some(false),
847 meta: None,
848 });
849 let json = serde_json::to_string(&content).unwrap();
850 assert!(json.contains("\"type\":\"tool_result\""));
851 assert!(json.contains("\"toolUseId\":\"tu_1\""));
852 assert!(json.contains("\"structuredContent\""));
853
854 let parsed: SamplingContent = serde_json::from_str(&json).unwrap();
855 assert!(matches!(parsed, SamplingContent::ToolResult(_)));
856 }
857
858 #[test]
860 fn test_sampling_content_block_empty_array() {
861 let parsed: SamplingContentBlock = serde_json::from_str("[]").unwrap();
862 assert!(matches!(parsed, SamplingContentBlock::Multiple(v) if v.is_empty()));
863 }
864
865 #[test]
867 fn test_sampling_content_block_single_element_array() {
868 let single_obj = r#"{"type":"text","text":"x"}"#;
869 let single_arr = r#"[{"type":"text","text":"x"}]"#;
870
871 let parsed_obj: SamplingContentBlock = serde_json::from_str(single_obj).unwrap();
872 assert!(matches!(parsed_obj, SamplingContentBlock::Single(_)));
873
874 let parsed_arr: SamplingContentBlock = serde_json::from_str(single_arr).unwrap();
875 assert!(matches!(parsed_arr, SamplingContentBlock::Multiple(v) if v.len() == 1));
876 }
877
878 #[test]
880 fn test_content_all_type_discriminants() {
881 let variants: Vec<(&str, Content)> = vec![
882 ("text", Content::text("hi")),
883 ("image", Content::image("data", "image/png")),
884 ("audio", Content::audio("data", "audio/wav")),
885 (
886 "resource_link",
887 Content::ResourceLink(ResourceLink {
888 uri: "file:///x".into(),
889 name: "x".into(),
890 description: None,
891 title: None,
892 icons: None,
893 mime_type: None,
894 annotations: None,
895 size: None,
896 meta: None,
897 }),
898 ),
899 ("resource", Content::resource("file:///x", "text")),
900 ];
901
902 for (expected_type, content) in variants {
903 let json = serde_json::to_string(&content).unwrap();
904 assert!(
905 json.contains(&format!("\"type\":\"{}\"", expected_type)),
906 "Missing type discriminant for {expected_type}: {json}"
907 );
908 let parsed: Content = serde_json::from_str(&json).unwrap();
909 assert_eq!(content, parsed, "Round-trip failed for {expected_type}");
910 }
911 }
912
913 #[test]
915 fn test_meta_field_skip_serializing_if_none() {
916 let content = TextContent::new("hello");
917 let json = serde_json::to_string(&content).unwrap();
918 assert!(!json.contains("_meta"), "None meta should be omitted");
919
920 let mut meta = HashMap::new();
921 meta.insert("key".into(), Value::String("val".into()));
922 let content = TextContent {
923 text: "hello".into(),
924 annotations: None,
925 meta: Some(meta),
926 };
927 let json = serde_json::to_string(&content).unwrap();
928 assert!(json.contains("\"_meta\""), "Some meta should be present");
929 }
930
931 #[test]
933 fn test_resource_contents_meta_field() {
934 let mut meta = HashMap::new();
935 meta.insert("k".into(), Value::Bool(true));
936 let rc = ResourceContents::Text(TextResourceContents {
937 uri: "x".into(),
938 mime_type: None,
939 text: "y".into(),
940 meta: Some(meta),
941 });
942 let json = serde_json::to_string(&rc).unwrap();
943 assert!(json.contains("\"_meta\""));
944 let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
945 assert_eq!(rc, parsed);
946 }
947
948 #[test]
950 fn test_sampling_content_block_to_vec() {
951 let single = SamplingContentBlock::Single(SamplingContent::text("a"));
952 assert_eq!(single.to_vec().len(), 1);
953
954 let multi = SamplingContentBlock::Multiple(vec![
955 SamplingContent::text("a"),
956 SamplingContent::text("b"),
957 ]);
958 assert_eq!(multi.to_vec().len(), 2);
959
960 assert_eq!(multi.as_text(), Some("a"));
962 }
963}