1use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14#[cfg(not(feature = "std"))]
15use alloc::collections::BTreeMap as HashMap;
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
490impl ResourceContents {
491 #[must_use]
493 pub fn uri(&self) -> &str {
494 match self {
495 Self::Text(t) => &t.uri,
496 Self::Blob(b) => &b.uri,
497 }
498 }
499
500 #[must_use]
502 pub fn text(&self) -> Option<&str> {
503 match self {
504 Self::Text(t) => Some(&t.text),
505 Self::Blob(_) => None,
506 }
507 }
508
509 #[must_use]
511 pub fn blob(&self) -> Option<&str> {
512 match self {
513 Self::Text(_) => None,
514 Self::Blob(b) => Some(&b.blob),
515 }
516 }
517
518 #[must_use]
520 pub fn mime_type(&self) -> Option<&str> {
521 match self {
522 Self::Text(t) => t.mime_type.as_deref(),
523 Self::Blob(b) => b.mime_type.as_deref(),
524 }
525 }
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
530pub struct TextResourceContents {
531 pub uri: String,
533 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
535 pub mime_type: Option<String>,
536 pub text: String,
538 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
540 pub meta: Option<HashMap<String, Value>>,
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
545pub struct BlobResourceContents {
546 pub uri: String,
548 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
550 pub mime_type: Option<String>,
551 pub blob: String,
553 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
555 pub meta: Option<HashMap<String, Value>>,
556}
557
558#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
569pub struct Annotations {
570 #[serde(skip_serializing_if = "Option::is_none")]
572 pub audience: Option<Vec<Role>>,
573 #[serde(skip_serializing_if = "Option::is_none")]
575 pub priority: Option<f64>,
576 #[serde(rename = "lastModified", skip_serializing_if = "Option::is_none")]
578 pub last_modified: Option<String>,
579}
580
581impl Annotations {
582 #[must_use]
584 pub fn for_user() -> Self {
585 Self {
586 audience: Some(vec![Role::User]),
587 ..Default::default()
588 }
589 }
590
591 #[must_use]
593 pub fn for_assistant() -> Self {
594 Self {
595 audience: Some(vec![Role::Assistant]),
596 ..Default::default()
597 }
598 }
599
600 #[must_use]
602 pub fn with_priority(mut self, priority: f64) -> Self {
603 self.priority = Some(priority);
604 self
605 }
606
607 #[must_use]
609 pub fn with_last_modified(mut self, timestamp: impl Into<String>) -> Self {
610 self.last_modified = Some(timestamp.into());
611 self
612 }
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
623pub struct Message {
624 pub role: Role,
626 pub content: Content,
628}
629
630impl Message {
631 #[must_use]
633 pub fn new(role: Role, content: Content) -> Self {
634 Self { role, content }
635 }
636
637 #[must_use]
639 pub fn user(text: impl Into<String>) -> Self {
640 Self {
641 role: Role::User,
642 content: Content::text(text),
643 }
644 }
645
646 #[must_use]
648 pub fn assistant(text: impl Into<String>) -> Self {
649 Self {
650 role: Role::Assistant,
651 content: Content::text(text),
652 }
653 }
654
655 #[must_use]
657 pub fn is_user(&self) -> bool {
658 self.role == Role::User
659 }
660
661 #[must_use]
663 pub fn is_assistant(&self) -> bool {
664 self.role == Role::Assistant
665 }
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671
672 #[test]
673 fn test_content_text() {
674 let content = Content::text("Hello");
675 assert!(content.is_text());
676 assert_eq!(content.as_text(), Some("Hello"));
677 }
678
679 #[test]
680 fn test_content_image() {
681 let content = Content::image("base64data", "image/png");
682 assert!(content.is_image());
683 assert!(!content.is_text());
684 }
685
686 #[test]
687 fn test_content_serde() {
688 let content = Content::text("Hello");
689 let json = serde_json::to_string(&content).unwrap();
690 assert!(json.contains("\"type\":\"text\""));
691 assert!(json.contains("\"text\":\"Hello\""));
692 }
693
694 #[test]
695 fn test_resource_link_serde() {
696 let link = Content::ResourceLink(ResourceLink {
697 uri: "file:///test.txt".into(),
698 name: "test".into(),
699 description: None,
700 title: None,
701 icons: None,
702 mime_type: Some("text/plain".into()),
703 annotations: None,
704 size: None,
705 meta: None,
706 });
707 let json = serde_json::to_string(&link).unwrap();
708 assert!(json.contains("\"type\":\"resource_link\""));
709 assert!(json.contains("\"uri\":\"file:///test.txt\""));
710
711 let parsed: Content = serde_json::from_str(&json).unwrap();
713 assert!(parsed.is_resource_link());
714 }
715
716 #[test]
717 fn test_sampling_content_tool_use_serde() {
718 let content = SamplingContent::ToolUse(ToolUseContent {
719 id: "tu_1".into(),
720 name: "search".into(),
721 input: [("query".to_string(), Value::String("test".into()))].into(),
722 meta: None,
723 });
724 let json = serde_json::to_string(&content).unwrap();
725 assert!(json.contains("\"type\":\"tool_use\""));
726 assert!(json.contains("\"id\":\"tu_1\""));
727
728 let parsed: SamplingContent = serde_json::from_str(&json).unwrap();
729 assert!(matches!(parsed, SamplingContent::ToolUse(_)));
730 }
731
732 #[test]
733 fn test_sampling_content_block_single() {
734 let block = SamplingContentBlock::Single(SamplingContent::text("hello"));
735 let json = serde_json::to_string(&block).unwrap();
736 assert!(json.starts_with('{'));
738 let parsed: SamplingContentBlock = serde_json::from_str(&json).unwrap();
739 assert!(matches!(parsed, SamplingContentBlock::Single(_)));
740 }
741
742 #[test]
743 fn test_sampling_content_block_multiple() {
744 let block = SamplingContentBlock::Multiple(vec![
745 SamplingContent::text("hello"),
746 SamplingContent::text("world"),
747 ]);
748 let json = serde_json::to_string(&block).unwrap();
749 assert!(json.starts_with('['));
751 let parsed: SamplingContentBlock = serde_json::from_str(&json).unwrap();
752 assert!(matches!(parsed, SamplingContentBlock::Multiple(v) if v.len() == 2));
753 }
754
755 #[test]
756 fn test_message_user() {
757 let msg = Message::user("Hello");
758 assert!(msg.is_user());
759 assert!(!msg.is_assistant());
760 }
761
762 #[test]
763 fn test_message_assistant() {
764 let msg = Message::assistant("Hi there");
765 assert!(msg.is_assistant());
766 assert!(!msg.is_user());
767 }
768
769 #[test]
770 fn test_annotations_for_user() {
771 let ann = Annotations::for_user().with_priority(1.0);
772 assert_eq!(ann.audience, Some(vec![Role::User]));
773 assert_eq!(ann.priority, Some(1.0));
774 }
775
776 #[test]
777 fn test_content_with_annotations() {
778 let content = Content::text("Hello").with_annotations(Annotations::for_user());
779 if let Content::Text(t) = content {
780 assert!(t.annotations.is_some());
781 } else {
782 panic!("Expected text content");
783 }
784 }
785
786 #[test]
788 fn test_resource_contents_text_deser() {
789 let json = r#"{"uri":"file:///test.txt","mimeType":"text/plain","text":"hello"}"#;
790 let rc: ResourceContents = serde_json::from_str(json).unwrap();
791 assert!(matches!(rc, ResourceContents::Text(_)));
792 assert_eq!(rc.uri(), "file:///test.txt");
793 assert_eq!(rc.text(), Some("hello"));
794 assert!(rc.blob().is_none());
795 }
796
797 #[test]
798 fn test_resource_contents_blob_deser() {
799 let json = r#"{"uri":"file:///img.png","mimeType":"image/png","blob":"aGVsbG8="}"#;
800 let rc: ResourceContents = serde_json::from_str(json).unwrap();
801 assert!(matches!(rc, ResourceContents::Blob(_)));
802 assert_eq!(rc.uri(), "file:///img.png");
803 assert_eq!(rc.blob(), Some("aGVsbG8="));
804 assert!(rc.text().is_none());
805 }
806
807 #[test]
808 fn test_resource_contents_round_trip() {
809 let text = ResourceContents::Text(TextResourceContents {
810 uri: "file:///a.txt".into(),
811 mime_type: Some("text/plain".into()),
812 text: "content".into(),
813 meta: None,
814 });
815 let json = serde_json::to_string(&text).unwrap();
816 let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
817 assert_eq!(text, parsed);
818
819 let blob = ResourceContents::Blob(BlobResourceContents {
820 uri: "file:///b.bin".into(),
821 mime_type: Some("application/octet-stream".into()),
822 blob: "AQID".into(),
823 meta: None,
824 });
825 let json = serde_json::to_string(&blob).unwrap();
826 let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
827 assert_eq!(blob, parsed);
828 }
829
830 #[test]
832 fn test_sampling_content_tool_result_serde() {
833 let content = SamplingContent::ToolResult(ToolResultContent {
834 tool_use_id: "tu_1".into(),
835 content: vec![Content::text("result data")],
836 structured_content: Some(serde_json::json!({"key": "value"})),
837 is_error: Some(false),
838 meta: None,
839 });
840 let json = serde_json::to_string(&content).unwrap();
841 assert!(json.contains("\"type\":\"tool_result\""));
842 assert!(json.contains("\"toolUseId\":\"tu_1\""));
843 assert!(json.contains("\"structuredContent\""));
844
845 let parsed: SamplingContent = serde_json::from_str(&json).unwrap();
846 assert!(matches!(parsed, SamplingContent::ToolResult(_)));
847 }
848
849 #[test]
851 fn test_sampling_content_block_empty_array() {
852 let parsed: SamplingContentBlock = serde_json::from_str("[]").unwrap();
853 assert!(matches!(parsed, SamplingContentBlock::Multiple(v) if v.is_empty()));
854 }
855
856 #[test]
858 fn test_sampling_content_block_single_element_array() {
859 let single_obj = r#"{"type":"text","text":"x"}"#;
860 let single_arr = r#"[{"type":"text","text":"x"}]"#;
861
862 let parsed_obj: SamplingContentBlock = serde_json::from_str(single_obj).unwrap();
863 assert!(matches!(parsed_obj, SamplingContentBlock::Single(_)));
864
865 let parsed_arr: SamplingContentBlock = serde_json::from_str(single_arr).unwrap();
866 assert!(matches!(parsed_arr, SamplingContentBlock::Multiple(v) if v.len() == 1));
867 }
868
869 #[test]
871 fn test_content_all_type_discriminants() {
872 let variants: Vec<(&str, Content)> = vec![
873 ("text", Content::text("hi")),
874 ("image", Content::image("data", "image/png")),
875 ("audio", Content::audio("data", "audio/wav")),
876 (
877 "resource_link",
878 Content::ResourceLink(ResourceLink {
879 uri: "file:///x".into(),
880 name: "x".into(),
881 description: None,
882 title: None,
883 icons: None,
884 mime_type: None,
885 annotations: None,
886 size: None,
887 meta: None,
888 }),
889 ),
890 ("resource", Content::resource("file:///x", "text")),
891 ];
892
893 for (expected_type, content) in variants {
894 let json = serde_json::to_string(&content).unwrap();
895 assert!(
896 json.contains(&format!("\"type\":\"{}\"", expected_type)),
897 "Missing type discriminant for {expected_type}: {json}"
898 );
899 let parsed: Content = serde_json::from_str(&json).unwrap();
900 assert_eq!(content, parsed, "Round-trip failed for {expected_type}");
901 }
902 }
903
904 #[test]
906 fn test_meta_field_skip_serializing_if_none() {
907 let content = TextContent::new("hello");
908 let json = serde_json::to_string(&content).unwrap();
909 assert!(!json.contains("_meta"), "None meta should be omitted");
910
911 let mut meta = HashMap::new();
912 meta.insert("key".into(), Value::String("val".into()));
913 let content = TextContent {
914 text: "hello".into(),
915 annotations: None,
916 meta: Some(meta),
917 };
918 let json = serde_json::to_string(&content).unwrap();
919 assert!(json.contains("\"_meta\""), "Some meta should be present");
920 }
921
922 #[test]
924 fn test_resource_contents_meta_field() {
925 let mut meta = HashMap::new();
926 meta.insert("k".into(), Value::Bool(true));
927 let rc = ResourceContents::Text(TextResourceContents {
928 uri: "x".into(),
929 mime_type: None,
930 text: "y".into(),
931 meta: Some(meta),
932 });
933 let json = serde_json::to_string(&rc).unwrap();
934 assert!(json.contains("\"_meta\""));
935 let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
936 assert_eq!(rc, parsed);
937 }
938
939 #[test]
941 fn test_sampling_content_block_to_vec() {
942 let single = SamplingContentBlock::Single(SamplingContent::text("a"));
943 assert_eq!(single.to_vec().len(), 1);
944
945 let multi = SamplingContentBlock::Multiple(vec![
946 SamplingContent::text("a"),
947 SamplingContent::text("b"),
948 ]);
949 assert_eq!(multi.to_vec().len(), 2);
950
951 assert_eq!(multi.as_text(), Some("a"));
953 }
954}