1use serde::{Deserialize, Serialize};
13use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
14
15use crate::{IntoOption, SkipListener};
16
17use super::Meta;
18
19#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
34#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
35#[serde(tag = "type", rename_all = "snake_case")]
36#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "type"})))]
37#[non_exhaustive]
38pub enum ContentBlock {
39 Text(TextContent),
44 Image(ImageContent),
48 Audio(AudioContent),
52 ResourceLink(ResourceLink),
56 Resource(EmbeddedResource),
62}
63
64#[serde_as]
66#[skip_serializing_none]
67#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
68#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
69#[non_exhaustive]
70pub struct TextContent {
71 #[serde_as(deserialize_as = "DefaultOnError")]
73 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
74 #[serde(default)]
75 pub annotations: Option<Annotations>,
76 pub text: String,
78 #[serde_as(deserialize_as = "DefaultOnError")]
84 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
85 #[serde(default)]
86 #[serde(rename = "_meta")]
87 pub meta: Option<Meta>,
88}
89
90impl TextContent {
91 #[must_use]
93 pub fn new(text: impl Into<String>) -> Self {
94 Self {
95 annotations: None,
96 text: text.into(),
97 meta: None,
98 }
99 }
100
101 #[must_use]
103 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
104 self.annotations = annotations.into_option();
105 self
106 }
107
108 #[must_use]
114 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
115 self.meta = meta.into_option();
116 self
117 }
118}
119
120impl<T: Into<String>> From<T> for ContentBlock {
121 fn from(value: T) -> Self {
122 Self::Text(TextContent::new(value))
123 }
124}
125
126#[serde_as]
128#[skip_serializing_none]
129#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
130#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
131#[serde(rename_all = "camelCase")]
132#[non_exhaustive]
133pub struct ImageContent {
134 #[serde_as(deserialize_as = "DefaultOnError")]
136 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
137 #[serde(default)]
138 pub annotations: Option<Annotations>,
139 pub data: String,
141 pub mime_type: String,
143 #[serde_as(deserialize_as = "DefaultOnError")]
145 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
146 #[serde(default)]
147 pub uri: Option<String>,
148 #[serde_as(deserialize_as = "DefaultOnError")]
154 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
155 #[serde(default)]
156 #[serde(rename = "_meta")]
157 pub meta: Option<Meta>,
158}
159
160impl ImageContent {
161 #[must_use]
163 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
164 Self {
165 annotations: None,
166 data: data.into(),
167 mime_type: mime_type.into(),
168 uri: None,
169 meta: None,
170 }
171 }
172
173 #[must_use]
175 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
176 self.annotations = annotations.into_option();
177 self
178 }
179
180 #[must_use]
182 pub fn uri(mut self, uri: impl IntoOption<String>) -> Self {
183 self.uri = uri.into_option();
184 self
185 }
186
187 #[must_use]
193 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
194 self.meta = meta.into_option();
195 self
196 }
197}
198
199#[serde_as]
201#[skip_serializing_none]
202#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
203#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
204#[serde(rename_all = "camelCase")]
205#[non_exhaustive]
206pub struct AudioContent {
207 #[serde_as(deserialize_as = "DefaultOnError")]
209 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
210 #[serde(default)]
211 pub annotations: Option<Annotations>,
212 pub data: String,
214 pub mime_type: String,
216 #[serde_as(deserialize_as = "DefaultOnError")]
222 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
223 #[serde(default)]
224 #[serde(rename = "_meta")]
225 pub meta: Option<Meta>,
226}
227
228impl AudioContent {
229 #[must_use]
231 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
232 Self {
233 annotations: None,
234 data: data.into(),
235 mime_type: mime_type.into(),
236 meta: None,
237 }
238 }
239
240 #[must_use]
242 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
243 self.annotations = annotations.into_option();
244 self
245 }
246
247 #[must_use]
253 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
254 self.meta = meta.into_option();
255 self
256 }
257}
258
259#[serde_as]
261#[skip_serializing_none]
262#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
263#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
264#[non_exhaustive]
265pub struct EmbeddedResource {
266 #[serde_as(deserialize_as = "DefaultOnError")]
268 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
269 #[serde(default)]
270 pub annotations: Option<Annotations>,
271 pub resource: EmbeddedResourceResource,
273 #[serde_as(deserialize_as = "DefaultOnError")]
279 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
280 #[serde(default)]
281 #[serde(rename = "_meta")]
282 pub meta: Option<Meta>,
283}
284
285impl EmbeddedResource {
286 #[must_use]
288 pub fn new(resource: EmbeddedResourceResource) -> Self {
289 Self {
290 annotations: None,
291 resource,
292 meta: None,
293 }
294 }
295
296 #[must_use]
298 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
299 self.annotations = annotations.into_option();
300 self
301 }
302
303 #[must_use]
309 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
310 self.meta = meta.into_option();
311 self
312 }
313}
314
315#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
317#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
318#[serde(untagged)]
319#[non_exhaustive]
320pub enum EmbeddedResourceResource {
321 TextResourceContents(TextResourceContents),
323 BlobResourceContents(BlobResourceContents),
325}
326
327#[serde_as]
329#[skip_serializing_none]
330#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
331#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
332#[serde(rename_all = "camelCase")]
333#[non_exhaustive]
334pub struct TextResourceContents {
335 #[serde_as(deserialize_as = "DefaultOnError")]
337 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
338 #[serde(default)]
339 pub mime_type: Option<String>,
340 pub text: String,
342 pub uri: String,
344 #[serde_as(deserialize_as = "DefaultOnError")]
350 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
351 #[serde(default)]
352 #[serde(rename = "_meta")]
353 pub meta: Option<Meta>,
354}
355
356impl TextResourceContents {
357 #[must_use]
359 pub fn new(text: impl Into<String>, uri: impl Into<String>) -> Self {
360 Self {
361 mime_type: None,
362 text: text.into(),
363 uri: uri.into(),
364 meta: None,
365 }
366 }
367
368 #[must_use]
370 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
371 self.mime_type = mime_type.into_option();
372 self
373 }
374
375 #[must_use]
381 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
382 self.meta = meta.into_option();
383 self
384 }
385}
386
387#[serde_as]
389#[skip_serializing_none]
390#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
391#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
392#[serde(rename_all = "camelCase")]
393#[non_exhaustive]
394pub struct BlobResourceContents {
395 pub blob: String,
397 #[serde_as(deserialize_as = "DefaultOnError")]
399 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
400 #[serde(default)]
401 pub mime_type: Option<String>,
402 pub uri: String,
404 #[serde_as(deserialize_as = "DefaultOnError")]
410 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
411 #[serde(default)]
412 #[serde(rename = "_meta")]
413 pub meta: Option<Meta>,
414}
415
416impl BlobResourceContents {
417 #[must_use]
419 pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
420 Self {
421 blob: blob.into(),
422 mime_type: None,
423 uri: uri.into(),
424 meta: None,
425 }
426 }
427
428 #[must_use]
430 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
431 self.mime_type = mime_type.into_option();
432 self
433 }
434
435 #[must_use]
441 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
442 self.meta = meta.into_option();
443 self
444 }
445}
446
447#[serde_as]
449#[skip_serializing_none]
450#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
451#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
452#[serde(rename_all = "camelCase")]
453#[non_exhaustive]
454pub struct ResourceLink {
455 #[serde_as(deserialize_as = "DefaultOnError")]
457 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
458 #[serde(default)]
459 pub annotations: Option<Annotations>,
460 #[serde_as(deserialize_as = "DefaultOnError")]
462 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
463 #[serde(default)]
464 pub description: Option<String>,
465 #[serde_as(deserialize_as = "DefaultOnError")]
467 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
468 #[serde(default)]
469 pub mime_type: Option<String>,
470 pub name: String,
472 #[serde_as(deserialize_as = "DefaultOnError")]
474 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
475 #[serde(default)]
476 pub size: Option<i64>,
477 #[serde_as(deserialize_as = "DefaultOnError")]
479 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
480 #[serde(default)]
481 pub title: Option<String>,
482 pub uri: String,
484 #[serde_as(deserialize_as = "DefaultOnError")]
490 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
491 #[serde(default)]
492 #[serde(rename = "_meta")]
493 pub meta: Option<Meta>,
494}
495
496impl ResourceLink {
497 #[must_use]
499 pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
500 Self {
501 annotations: None,
502 description: None,
503 mime_type: None,
504 name: name.into(),
505 size: None,
506 title: None,
507 uri: uri.into(),
508 meta: None,
509 }
510 }
511
512 #[must_use]
514 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
515 self.annotations = annotations.into_option();
516 self
517 }
518
519 #[must_use]
521 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
522 self.description = description.into_option();
523 self
524 }
525
526 #[must_use]
528 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
529 self.mime_type = mime_type.into_option();
530 self
531 }
532
533 #[must_use]
535 pub fn size(mut self, size: impl IntoOption<i64>) -> Self {
536 self.size = size.into_option();
537 self
538 }
539
540 #[must_use]
542 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
543 self.title = title.into_option();
544 self
545 }
546
547 #[must_use]
553 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
554 self.meta = meta.into_option();
555 self
556 }
557}
558
559#[serde_as]
561#[skip_serializing_none]
562#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
563#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
564#[serde(rename_all = "camelCase")]
565#[non_exhaustive]
566pub struct Annotations {
567 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
569 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
570 #[serde(default)]
571 pub audience: Option<Vec<Role>>,
572 #[serde_as(deserialize_as = "DefaultOnError")]
574 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
575 #[serde(default)]
576 pub last_modified: Option<String>,
577 #[serde_as(deserialize_as = "DefaultOnError")]
579 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
580 #[serde(default)]
581 pub priority: Option<f64>,
582 #[serde_as(deserialize_as = "DefaultOnError")]
588 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
589 #[serde(default)]
590 #[serde(rename = "_meta")]
591 pub meta: Option<Meta>,
592}
593
594impl Annotations {
595 #[must_use]
597 pub fn new() -> Self {
598 Self::default()
599 }
600
601 #[must_use]
603 pub fn audience(mut self, audience: impl IntoOption<Vec<Role>>) -> Self {
604 self.audience = audience.into_option();
605 self
606 }
607
608 #[must_use]
610 pub fn last_modified(mut self, last_modified: impl IntoOption<String>) -> Self {
611 self.last_modified = last_modified.into_option();
612 self
613 }
614
615 #[must_use]
617 pub fn priority(mut self, priority: impl IntoOption<f64>) -> Self {
618 self.priority = priority.into_option();
619 self
620 }
621
622 #[must_use]
628 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
629 self.meta = meta.into_option();
630 self
631 }
632}
633
634#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
636#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
637#[serde(rename_all = "camelCase")]
638#[non_exhaustive]
639pub enum Role {
640 Assistant,
642 User,
644}
645
646#[cfg(test)]
647mod tests {
648 use super::*;
649
650 #[test]
651 fn test_text_content_roundtrip() {
652 let content = TextContent::new("hello world");
653 let json = serde_json::to_value(&content).unwrap();
654 let parsed: TextContent = serde_json::from_value(json).unwrap();
655 assert_eq!(content, parsed);
656 }
657
658 #[test]
659 fn test_text_content_omits_optional_fields() {
660 let content = TextContent::new("hello");
661 let json = serde_json::to_value(&content).unwrap();
662 assert!(!json.as_object().unwrap().contains_key("annotations"));
663 assert!(!json.as_object().unwrap().contains_key("meta"));
664 }
665
666 #[test]
667 fn test_text_content_meta_defaults_on_missing_or_malformed_value() {
668 let missing: TextContent = serde_json::from_value(serde_json::json!({
669 "text": "hello"
670 }))
671 .unwrap();
672 assert_eq!(missing.meta, None);
673
674 let malformed: TextContent = serde_json::from_value(serde_json::json!({
675 "text": "hello",
676 "_meta": false
677 }))
678 .unwrap();
679 assert_eq!(malformed.meta, None);
680 }
681
682 #[test]
683 fn test_text_content_from_string() {
684 let block: ContentBlock = "hello".into();
685 match block {
686 ContentBlock::Text(c) => assert_eq!(c.text, "hello"),
687 _ => panic!("Expected Text variant"),
688 }
689 }
690
691 #[test]
692 fn test_image_content_roundtrip() {
693 let content = ImageContent::new("base64data", "image/png");
694 let json = serde_json::to_value(&content).unwrap();
695 let parsed: ImageContent = serde_json::from_value(json).unwrap();
696 assert_eq!(content, parsed);
697 }
698
699 #[test]
700 fn test_image_content_omits_optional_fields() {
701 let content = ImageContent::new("data", "image/png");
702 let json = serde_json::to_value(&content).unwrap();
703 assert!(!json.as_object().unwrap().contains_key("uri"));
704 assert!(!json.as_object().unwrap().contains_key("annotations"));
705 assert!(!json.as_object().unwrap().contains_key("meta"));
706 }
707
708 #[test]
709 fn test_image_content_with_uri() {
710 let content = ImageContent::new("data", "image/png").uri("https://example.com/image.png");
711 let json = serde_json::to_value(&content).unwrap();
712 assert_eq!(json["uri"], "https://example.com/image.png");
713 }
714
715 #[test]
716 fn test_audio_content_roundtrip() {
717 let content = AudioContent::new("base64audio", "audio/mp3");
718 let json = serde_json::to_value(&content).unwrap();
719 let parsed: AudioContent = serde_json::from_value(json).unwrap();
720 assert_eq!(content, parsed);
721 }
722
723 #[test]
724 fn test_audio_content_omits_optional_fields() {
725 let content = AudioContent::new("data", "audio/mp3");
726 let json = serde_json::to_value(&content).unwrap();
727 assert!(!json.as_object().unwrap().contains_key("annotations"));
728 assert!(!json.as_object().unwrap().contains_key("meta"));
729 }
730}