1use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
17#[serde(rename_all = "lowercase")]
18pub enum Role {
19 #[default]
21 User,
22 Assistant,
24}
25
26impl std::fmt::Display for Role {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 Self::User => f.write_str("user"),
30 Self::Assistant => f.write_str("assistant"),
31 }
32 }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46#[serde(tag = "type")]
47pub enum Content {
48 #[serde(rename = "text")]
50 Text(TextContent),
51 #[serde(rename = "image")]
53 Image(ImageContent),
54 #[serde(rename = "audio")]
56 Audio(AudioContent),
57 #[serde(rename = "resource_link")]
59 ResourceLink(ResourceLink),
60 #[serde(rename = "resource")]
62 Resource(EmbeddedResource),
63}
64
65impl Default for Content {
66 fn default() -> Self {
67 Self::text("")
68 }
69}
70
71impl Content {
72 #[must_use]
74 pub fn text(text: impl Into<String>) -> Self {
75 Self::Text(TextContent {
76 text: text.into(),
77 annotations: None,
78 meta: None,
79 })
80 }
81
82 #[must_use]
84 pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
85 Self::Image(ImageContent {
86 data: data.into(),
87 mime_type: mime_type.into(),
88 annotations: None,
89 meta: None,
90 })
91 }
92
93 #[must_use]
95 pub fn audio(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
96 Self::Audio(AudioContent {
97 data: data.into(),
98 mime_type: mime_type.into(),
99 annotations: None,
100 meta: None,
101 })
102 }
103
104 #[must_use]
106 pub fn resource_link(resource: crate::definitions::Resource) -> Self {
107 Self::ResourceLink(ResourceLink {
108 uri: resource.uri,
109 name: resource.name,
110 description: resource.description,
111 title: resource.title,
112 icons: resource.icons,
113 mime_type: resource.mime_type,
114 annotations: resource.annotations,
115 size: resource.size,
116 meta: resource.meta,
117 })
118 }
119
120 #[must_use]
122 pub fn resource(uri: impl Into<String>, text: impl Into<String>) -> Self {
123 Self::Resource(EmbeddedResource {
124 resource: ResourceContents::Text(TextResourceContents {
125 uri: uri.into(),
126 mime_type: Some("text/plain".into()),
127 text: text.into(),
128 meta: None,
129 }),
130 annotations: None,
131 meta: None,
132 })
133 }
134
135 #[must_use]
137 pub fn is_text(&self) -> bool {
138 matches!(self, Self::Text(_))
139 }
140
141 #[must_use]
143 pub fn as_text(&self) -> Option<&str> {
144 match self {
145 Self::Text(t) => Some(&t.text),
146 _ => None,
147 }
148 }
149
150 #[must_use]
152 pub fn is_image(&self) -> bool {
153 matches!(self, Self::Image(_))
154 }
155
156 #[must_use]
158 pub fn is_audio(&self) -> bool {
159 matches!(self, Self::Audio(_))
160 }
161
162 #[must_use]
164 pub fn is_resource_link(&self) -> bool {
165 matches!(self, Self::ResourceLink(_))
166 }
167
168 #[must_use]
170 pub fn is_resource(&self) -> bool {
171 matches!(self, Self::Resource(_))
172 }
173
174 #[must_use]
176 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
177 match &mut self {
178 Self::Text(t) => t.annotations = Some(annotations),
179 Self::Image(i) => i.annotations = Some(annotations),
180 Self::Audio(a) => a.annotations = Some(annotations),
181 Self::ResourceLink(r) => {
182 r.annotations = Some(crate::definitions::ResourceAnnotations {
183 audience: annotations.audience,
184 priority: annotations.priority,
185 last_modified: annotations.last_modified,
186 })
187 }
188 Self::Resource(r) => r.annotations = Some(annotations),
189 }
190 self
191 }
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
203#[serde(tag = "type")]
204pub enum SamplingContent {
205 #[serde(rename = "text")]
207 Text(TextContent),
208 #[serde(rename = "image")]
210 Image(ImageContent),
211 #[serde(rename = "audio")]
213 Audio(AudioContent),
214 #[serde(rename = "tool_use")]
216 ToolUse(ToolUseContent),
217 #[serde(rename = "tool_result")]
219 ToolResult(ToolResultContent),
220}
221
222impl Default for SamplingContent {
223 fn default() -> Self {
224 Self::text("")
225 }
226}
227
228impl SamplingContent {
229 #[must_use]
231 pub fn text(text: impl Into<String>) -> Self {
232 Self::Text(TextContent {
233 text: text.into(),
234 annotations: None,
235 meta: None,
236 })
237 }
238
239 #[must_use]
241 pub fn as_text(&self) -> Option<&str> {
242 match self {
243 Self::Text(t) => Some(&t.text),
244 _ => None,
245 }
246 }
247}
248
249#[derive(Debug, Clone, PartialEq)]
257pub enum SamplingContentBlock {
258 Single(SamplingContent),
260 Multiple(Vec<SamplingContent>),
262}
263
264impl Default for SamplingContentBlock {
265 fn default() -> Self {
266 Self::Single(SamplingContent::default())
267 }
268}
269
270impl SamplingContentBlock {
271 #[must_use]
273 pub fn as_text(&self) -> Option<&str> {
274 match self {
275 Self::Single(c) => c.as_text(),
276 Self::Multiple(v) => v.iter().find_map(|c| c.as_text()),
277 }
278 }
279
280 #[must_use]
284 pub fn to_vec(&self) -> Vec<&SamplingContent> {
285 match self {
286 Self::Single(c) => vec![c],
287 Self::Multiple(v) => v.iter().collect(),
288 }
289 }
290}
291
292impl Serialize for SamplingContentBlock {
293 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
294 match self {
295 Self::Single(c) => c.serialize(serializer),
296 Self::Multiple(v) => v.serialize(serializer),
297 }
298 }
299}
300
301impl<'de> Deserialize<'de> for SamplingContentBlock {
302 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
303 let value = Value::deserialize(deserializer)?;
304 if value.is_array() {
305 let v: Vec<SamplingContent> =
306 serde_json::from_value(value).map_err(serde::de::Error::custom)?;
307 Ok(Self::Multiple(v))
308 } else {
309 let c: SamplingContent =
310 serde_json::from_value(value).map_err(serde::de::Error::custom)?;
311 Ok(Self::Single(c))
312 }
313 }
314}
315
316impl From<SamplingContent> for SamplingContentBlock {
317 fn from(c: SamplingContent) -> Self {
318 Self::Single(c)
319 }
320}
321
322impl From<Vec<SamplingContent>> for SamplingContentBlock {
323 fn from(v: Vec<SamplingContent>) -> Self {
324 Self::Multiple(v)
325 }
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
334pub struct TextContent {
335 pub text: String,
337 #[serde(skip_serializing_if = "Option::is_none")]
339 pub annotations: Option<Annotations>,
340 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
342 pub meta: Option<HashMap<String, Value>>,
343}
344
345impl TextContent {
346 #[must_use]
348 pub fn new(text: impl Into<String>) -> Self {
349 Self {
350 text: text.into(),
351 annotations: None,
352 meta: None,
353 }
354 }
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
359pub struct ImageContent {
360 pub data: String,
362 #[serde(rename = "mimeType")]
364 pub mime_type: String,
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub annotations: Option<Annotations>,
368 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
370 pub meta: Option<HashMap<String, Value>>,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
375pub struct AudioContent {
376 pub data: String,
378 #[serde(rename = "mimeType")]
380 pub mime_type: String,
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub annotations: Option<Annotations>,
384 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
386 pub meta: Option<HashMap<String, Value>>,
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
393pub struct ToolUseContent {
394 pub id: String,
396 pub name: String,
398 pub input: HashMap<String, Value>,
400 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
402 pub meta: Option<HashMap<String, Value>>,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
409pub struct ToolResultContent {
410 #[serde(rename = "toolUseId")]
412 pub tool_use_id: String,
413 pub content: Vec<Content>,
415 #[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
417 pub structured_content: Option<Value>,
418 #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
420 pub is_error: Option<bool>,
421 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
423 pub meta: Option<HashMap<String, Value>>,
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
431pub struct ResourceLink {
432 pub uri: String,
434 pub name: String,
436 #[serde(skip_serializing_if = "Option::is_none")]
438 pub description: Option<String>,
439 #[serde(skip_serializing_if = "Option::is_none")]
441 pub title: Option<String>,
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub icons: Option<Vec<crate::definitions::Icon>>,
445 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
447 pub mime_type: Option<String>,
448 #[serde(skip_serializing_if = "Option::is_none")]
450 pub annotations: Option<crate::definitions::ResourceAnnotations>,
451 #[serde(skip_serializing_if = "Option::is_none")]
453 pub size: Option<u64>,
454 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
456 pub meta: Option<std::collections::HashMap<String, Value>>,
457}
458
459#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
461pub struct EmbeddedResource {
462 pub resource: ResourceContents,
464 #[serde(skip_serializing_if = "Option::is_none")]
466 pub annotations: Option<Annotations>,
467 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
469 pub meta: Option<HashMap<String, Value>>,
470}
471
472#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
478#[serde(untagged)]
479pub enum ResourceContents {
480 Text(TextResourceContents),
482 Blob(BlobResourceContents),
484}
485
486impl ResourceContents {
487 #[must_use]
489 pub fn uri(&self) -> &str {
490 match self {
491 Self::Text(t) => &t.uri,
492 Self::Blob(b) => &b.uri,
493 }
494 }
495
496 #[must_use]
498 pub fn text(&self) -> Option<&str> {
499 match self {
500 Self::Text(t) => Some(&t.text),
501 Self::Blob(_) => None,
502 }
503 }
504
505 #[must_use]
507 pub fn blob(&self) -> Option<&str> {
508 match self {
509 Self::Text(_) => None,
510 Self::Blob(b) => Some(&b.blob),
511 }
512 }
513
514 #[must_use]
516 pub fn mime_type(&self) -> Option<&str> {
517 match self {
518 Self::Text(t) => t.mime_type.as_deref(),
519 Self::Blob(b) => b.mime_type.as_deref(),
520 }
521 }
522}
523
524#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
526pub struct TextResourceContents {
527 pub uri: String,
529 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
531 pub mime_type: Option<String>,
532 pub text: String,
534 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
536 pub meta: Option<HashMap<String, Value>>,
537}
538
539#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
541pub struct BlobResourceContents {
542 pub uri: String,
544 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
546 pub mime_type: Option<String>,
547 pub blob: String,
549 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
551 pub meta: Option<HashMap<String, Value>>,
552}
553
554#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
565pub struct Annotations {
566 #[serde(skip_serializing_if = "Option::is_none")]
568 pub audience: Option<Vec<Role>>,
569 #[serde(skip_serializing_if = "Option::is_none")]
571 pub priority: Option<f64>,
572 #[serde(rename = "lastModified", skip_serializing_if = "Option::is_none")]
574 pub last_modified: Option<String>,
575}
576
577impl Annotations {
578 #[must_use]
580 pub fn for_user() -> Self {
581 Self {
582 audience: Some(vec![Role::User]),
583 ..Default::default()
584 }
585 }
586
587 #[must_use]
589 pub fn for_assistant() -> Self {
590 Self {
591 audience: Some(vec![Role::Assistant]),
592 ..Default::default()
593 }
594 }
595
596 #[must_use]
598 pub fn with_priority(mut self, priority: f64) -> Self {
599 self.priority = Some(priority);
600 self
601 }
602
603 #[must_use]
605 pub fn with_last_modified(mut self, timestamp: impl Into<String>) -> Self {
606 self.last_modified = Some(timestamp.into());
607 self
608 }
609}
610
611#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
619pub struct Message {
620 pub role: Role,
622 pub content: Content,
624}
625
626impl Message {
627 #[must_use]
629 pub fn new(role: Role, content: Content) -> Self {
630 Self { role, content }
631 }
632
633 #[must_use]
635 pub fn user(text: impl Into<String>) -> Self {
636 Self {
637 role: Role::User,
638 content: Content::text(text),
639 }
640 }
641
642 #[must_use]
644 pub fn assistant(text: impl Into<String>) -> Self {
645 Self {
646 role: Role::Assistant,
647 content: Content::text(text),
648 }
649 }
650
651 #[must_use]
653 pub fn is_user(&self) -> bool {
654 self.role == Role::User
655 }
656
657 #[must_use]
659 pub fn is_assistant(&self) -> bool {
660 self.role == Role::Assistant
661 }
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667
668 #[test]
669 fn test_content_text() {
670 let content = Content::text("Hello");
671 assert!(content.is_text());
672 assert_eq!(content.as_text(), Some("Hello"));
673 }
674
675 #[test]
676 fn test_content_image() {
677 let content = Content::image("base64data", "image/png");
678 assert!(content.is_image());
679 assert!(!content.is_text());
680 }
681
682 #[test]
683 fn test_content_serde() {
684 let content = Content::text("Hello");
685 let json = serde_json::to_string(&content).unwrap();
686 assert!(json.contains("\"type\":\"text\""));
687 assert!(json.contains("\"text\":\"Hello\""));
688 }
689
690 #[test]
691 fn test_resource_link_serde() {
692 let link = Content::ResourceLink(ResourceLink {
693 uri: "file:///test.txt".into(),
694 name: "test".into(),
695 description: None,
696 title: None,
697 icons: None,
698 mime_type: Some("text/plain".into()),
699 annotations: None,
700 size: None,
701 meta: None,
702 });
703 let json = serde_json::to_string(&link).unwrap();
704 assert!(json.contains("\"type\":\"resource_link\""));
705 assert!(json.contains("\"uri\":\"file:///test.txt\""));
706
707 let parsed: Content = serde_json::from_str(&json).unwrap();
709 assert!(parsed.is_resource_link());
710 }
711
712 #[test]
713 fn test_sampling_content_tool_use_serde() {
714 let content = SamplingContent::ToolUse(ToolUseContent {
715 id: "tu_1".into(),
716 name: "search".into(),
717 input: [("query".to_string(), Value::String("test".into()))].into(),
718 meta: None,
719 });
720 let json = serde_json::to_string(&content).unwrap();
721 assert!(json.contains("\"type\":\"tool_use\""));
722 assert!(json.contains("\"id\":\"tu_1\""));
723
724 let parsed: SamplingContent = serde_json::from_str(&json).unwrap();
725 assert!(matches!(parsed, SamplingContent::ToolUse(_)));
726 }
727
728 #[test]
729 fn test_sampling_content_block_single() {
730 let block = SamplingContentBlock::Single(SamplingContent::text("hello"));
731 let json = serde_json::to_string(&block).unwrap();
732 assert!(json.starts_with('{'));
734 let parsed: SamplingContentBlock = serde_json::from_str(&json).unwrap();
735 assert!(matches!(parsed, SamplingContentBlock::Single(_)));
736 }
737
738 #[test]
739 fn test_sampling_content_block_multiple() {
740 let block = SamplingContentBlock::Multiple(vec![
741 SamplingContent::text("hello"),
742 SamplingContent::text("world"),
743 ]);
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::Multiple(v) if v.len() == 2));
749 }
750
751 #[test]
752 fn test_message_user() {
753 let msg = Message::user("Hello");
754 assert!(msg.is_user());
755 assert!(!msg.is_assistant());
756 }
757
758 #[test]
759 fn test_message_assistant() {
760 let msg = Message::assistant("Hi there");
761 assert!(msg.is_assistant());
762 assert!(!msg.is_user());
763 }
764
765 #[test]
766 fn test_annotations_for_user() {
767 let ann = Annotations::for_user().with_priority(1.0);
768 assert_eq!(ann.audience, Some(vec![Role::User]));
769 assert_eq!(ann.priority, Some(1.0));
770 }
771
772 #[test]
773 fn test_content_with_annotations() {
774 let content = Content::text("Hello").with_annotations(Annotations::for_user());
775 if let Content::Text(t) = content {
776 assert!(t.annotations.is_some());
777 } else {
778 panic!("Expected text content");
779 }
780 }
781
782 #[test]
784 fn test_resource_contents_text_deser() {
785 let json = r#"{"uri":"file:///test.txt","mimeType":"text/plain","text":"hello"}"#;
786 let rc: ResourceContents = serde_json::from_str(json).unwrap();
787 assert!(matches!(rc, ResourceContents::Text(_)));
788 assert_eq!(rc.uri(), "file:///test.txt");
789 assert_eq!(rc.text(), Some("hello"));
790 assert!(rc.blob().is_none());
791 }
792
793 #[test]
794 fn test_resource_contents_blob_deser() {
795 let json = r#"{"uri":"file:///img.png","mimeType":"image/png","blob":"aGVsbG8="}"#;
796 let rc: ResourceContents = serde_json::from_str(json).unwrap();
797 assert!(matches!(rc, ResourceContents::Blob(_)));
798 assert_eq!(rc.uri(), "file:///img.png");
799 assert_eq!(rc.blob(), Some("aGVsbG8="));
800 assert!(rc.text().is_none());
801 }
802
803 #[test]
804 fn test_resource_contents_round_trip() {
805 let text = ResourceContents::Text(TextResourceContents {
806 uri: "file:///a.txt".into(),
807 mime_type: Some("text/plain".into()),
808 text: "content".into(),
809 meta: None,
810 });
811 let json = serde_json::to_string(&text).unwrap();
812 let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
813 assert_eq!(text, parsed);
814
815 let blob = ResourceContents::Blob(BlobResourceContents {
816 uri: "file:///b.bin".into(),
817 mime_type: Some("application/octet-stream".into()),
818 blob: "AQID".into(),
819 meta: None,
820 });
821 let json = serde_json::to_string(&blob).unwrap();
822 let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
823 assert_eq!(blob, parsed);
824 }
825
826 #[test]
828 fn test_sampling_content_tool_result_serde() {
829 let content = SamplingContent::ToolResult(ToolResultContent {
830 tool_use_id: "tu_1".into(),
831 content: vec![Content::text("result data")],
832 structured_content: Some(serde_json::json!({"key": "value"})),
833 is_error: Some(false),
834 meta: None,
835 });
836 let json = serde_json::to_string(&content).unwrap();
837 assert!(json.contains("\"type\":\"tool_result\""));
838 assert!(json.contains("\"toolUseId\":\"tu_1\""));
839 assert!(json.contains("\"structuredContent\""));
840
841 let parsed: SamplingContent = serde_json::from_str(&json).unwrap();
842 assert!(matches!(parsed, SamplingContent::ToolResult(_)));
843 }
844
845 #[test]
847 fn test_sampling_content_block_empty_array() {
848 let parsed: SamplingContentBlock = serde_json::from_str("[]").unwrap();
849 assert!(matches!(parsed, SamplingContentBlock::Multiple(v) if v.is_empty()));
850 }
851
852 #[test]
854 fn test_sampling_content_block_single_element_array() {
855 let single_obj = r#"{"type":"text","text":"x"}"#;
856 let single_arr = r#"[{"type":"text","text":"x"}]"#;
857
858 let parsed_obj: SamplingContentBlock = serde_json::from_str(single_obj).unwrap();
859 assert!(matches!(parsed_obj, SamplingContentBlock::Single(_)));
860
861 let parsed_arr: SamplingContentBlock = serde_json::from_str(single_arr).unwrap();
862 assert!(matches!(parsed_arr, SamplingContentBlock::Multiple(v) if v.len() == 1));
863 }
864
865 #[test]
867 fn test_content_all_type_discriminants() {
868 let variants: Vec<(&str, Content)> = vec![
869 ("text", Content::text("hi")),
870 ("image", Content::image("data", "image/png")),
871 ("audio", Content::audio("data", "audio/wav")),
872 (
873 "resource_link",
874 Content::ResourceLink(ResourceLink {
875 uri: "file:///x".into(),
876 name: "x".into(),
877 description: None,
878 title: None,
879 icons: None,
880 mime_type: None,
881 annotations: None,
882 size: None,
883 meta: None,
884 }),
885 ),
886 ("resource", Content::resource("file:///x", "text")),
887 ];
888
889 for (expected_type, content) in variants {
890 let json = serde_json::to_string(&content).unwrap();
891 assert!(
892 json.contains(&format!("\"type\":\"{}\"", expected_type)),
893 "Missing type discriminant for {expected_type}: {json}"
894 );
895 let parsed: Content = serde_json::from_str(&json).unwrap();
896 assert_eq!(content, parsed, "Round-trip failed for {expected_type}");
897 }
898 }
899
900 #[test]
902 fn test_meta_field_skip_serializing_if_none() {
903 let content = TextContent::new("hello");
904 let json = serde_json::to_string(&content).unwrap();
905 assert!(!json.contains("_meta"), "None meta should be omitted");
906
907 let mut meta = HashMap::new();
908 meta.insert("key".into(), Value::String("val".into()));
909 let content = TextContent {
910 text: "hello".into(),
911 annotations: None,
912 meta: Some(meta),
913 };
914 let json = serde_json::to_string(&content).unwrap();
915 assert!(json.contains("\"_meta\""), "Some meta should be present");
916 }
917
918 #[test]
920 fn test_resource_contents_meta_field() {
921 let mut meta = HashMap::new();
922 meta.insert("k".into(), Value::Bool(true));
923 let rc = ResourceContents::Text(TextResourceContents {
924 uri: "x".into(),
925 mime_type: None,
926 text: "y".into(),
927 meta: Some(meta),
928 });
929 let json = serde_json::to_string(&rc).unwrap();
930 assert!(json.contains("\"_meta\""));
931 let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
932 assert_eq!(rc, parsed);
933 }
934
935 #[test]
937 fn test_sampling_content_block_to_vec() {
938 let single = SamplingContentBlock::Single(SamplingContent::text("a"));
939 assert_eq!(single.to_vec().len(), 1);
940
941 let multi = SamplingContentBlock::Multiple(vec![
942 SamplingContent::text("a"),
943 SamplingContent::text("b"),
944 ]);
945 assert_eq!(multi.to_vec().len(), 2);
946
947 assert_eq!(multi.as_text(), Some("a"));
949 }
950}