1use serde::{Deserialize, Serialize};
10use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
11
12use crate::IntoOption;
13
14use super::Meta;
15
16#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
28#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
29#[serde(tag = "type", rename_all = "snake_case")]
30#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "type"})))]
31#[non_exhaustive]
32pub enum ContentBlock {
33 Text(TextContent),
38 Image(ImageContent),
42 Audio(AudioContent),
46 ResourceLink(ResourceLink),
50 Resource(EmbeddedResource),
56}
57
58#[serde_as]
60#[skip_serializing_none]
61#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
62#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
63#[non_exhaustive]
64pub struct TextContent {
65 #[serde_as(deserialize_as = "DefaultOnError")]
67 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
68 #[serde(default)]
69 pub annotations: Option<Annotations>,
70 pub text: String,
72 #[serde_as(deserialize_as = "DefaultOnError")]
78 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
79 #[serde(default)]
80 #[serde(rename = "_meta")]
81 pub meta: Option<Meta>,
82}
83
84impl TextContent {
85 #[must_use]
87 pub fn new(text: impl Into<String>) -> Self {
88 Self {
89 annotations: None,
90 text: text.into(),
91 meta: None,
92 }
93 }
94
95 #[must_use]
97 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
98 self.annotations = annotations.into_option();
99 self
100 }
101
102 #[must_use]
108 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
109 self.meta = meta.into_option();
110 self
111 }
112}
113
114impl<T: Into<String>> From<T> for ContentBlock {
115 fn from(value: T) -> Self {
116 Self::Text(TextContent::new(value))
117 }
118}
119
120#[serde_as]
122#[skip_serializing_none]
123#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
124#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
125#[serde(rename_all = "camelCase")]
126#[non_exhaustive]
127pub struct ImageContent {
128 #[serde_as(deserialize_as = "DefaultOnError")]
130 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
131 #[serde(default)]
132 pub annotations: Option<Annotations>,
133 pub data: String,
135 pub mime_type: String,
137 #[serde_as(deserialize_as = "DefaultOnError")]
139 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
140 #[serde(default)]
141 pub uri: Option<String>,
142 #[serde_as(deserialize_as = "DefaultOnError")]
148 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
149 #[serde(default)]
150 #[serde(rename = "_meta")]
151 pub meta: Option<Meta>,
152}
153
154impl ImageContent {
155 #[must_use]
157 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
158 Self {
159 annotations: None,
160 data: data.into(),
161 mime_type: mime_type.into(),
162 uri: None,
163 meta: None,
164 }
165 }
166
167 #[must_use]
169 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
170 self.annotations = annotations.into_option();
171 self
172 }
173
174 #[must_use]
176 pub fn uri(mut self, uri: impl IntoOption<String>) -> Self {
177 self.uri = uri.into_option();
178 self
179 }
180
181 #[must_use]
187 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
188 self.meta = meta.into_option();
189 self
190 }
191}
192
193#[serde_as]
195#[skip_serializing_none]
196#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
197#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
198#[serde(rename_all = "camelCase")]
199#[non_exhaustive]
200pub struct AudioContent {
201 #[serde_as(deserialize_as = "DefaultOnError")]
203 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
204 #[serde(default)]
205 pub annotations: Option<Annotations>,
206 pub data: String,
208 pub mime_type: String,
210 #[serde_as(deserialize_as = "DefaultOnError")]
216 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
217 #[serde(default)]
218 #[serde(rename = "_meta")]
219 pub meta: Option<Meta>,
220}
221
222impl AudioContent {
223 #[must_use]
225 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
226 Self {
227 annotations: None,
228 data: data.into(),
229 mime_type: mime_type.into(),
230 meta: None,
231 }
232 }
233
234 #[must_use]
236 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
237 self.annotations = annotations.into_option();
238 self
239 }
240
241 #[must_use]
247 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
248 self.meta = meta.into_option();
249 self
250 }
251}
252
253#[serde_as]
255#[skip_serializing_none]
256#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
257#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
258#[non_exhaustive]
259pub struct EmbeddedResource {
260 #[serde_as(deserialize_as = "DefaultOnError")]
262 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
263 #[serde(default)]
264 pub annotations: Option<Annotations>,
265 pub resource: EmbeddedResourceResource,
267 #[serde_as(deserialize_as = "DefaultOnError")]
273 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
274 #[serde(default)]
275 #[serde(rename = "_meta")]
276 pub meta: Option<Meta>,
277}
278
279impl EmbeddedResource {
280 #[must_use]
282 pub fn new(resource: EmbeddedResourceResource) -> Self {
283 Self {
284 annotations: None,
285 resource,
286 meta: None,
287 }
288 }
289
290 #[must_use]
292 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
293 self.annotations = annotations.into_option();
294 self
295 }
296
297 #[must_use]
303 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
304 self.meta = meta.into_option();
305 self
306 }
307}
308
309#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
311#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
312#[serde(untagged)]
313#[non_exhaustive]
314pub enum EmbeddedResourceResource {
315 TextResourceContents(TextResourceContents),
317 BlobResourceContents(BlobResourceContents),
319}
320
321#[serde_as]
323#[skip_serializing_none]
324#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
325#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
326#[serde(rename_all = "camelCase")]
327#[non_exhaustive]
328pub struct TextResourceContents {
329 #[serde_as(deserialize_as = "DefaultOnError")]
331 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
332 #[serde(default)]
333 pub mime_type: Option<String>,
334 pub text: String,
336 pub uri: String,
338 #[serde_as(deserialize_as = "DefaultOnError")]
344 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
345 #[serde(default)]
346 #[serde(rename = "_meta")]
347 pub meta: Option<Meta>,
348}
349
350impl TextResourceContents {
351 #[must_use]
353 pub fn new(text: impl Into<String>, uri: impl Into<String>) -> Self {
354 Self {
355 mime_type: None,
356 text: text.into(),
357 uri: uri.into(),
358 meta: None,
359 }
360 }
361
362 #[must_use]
364 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
365 self.mime_type = mime_type.into_option();
366 self
367 }
368
369 #[must_use]
375 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
376 self.meta = meta.into_option();
377 self
378 }
379}
380
381#[serde_as]
383#[skip_serializing_none]
384#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
385#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
386#[serde(rename_all = "camelCase")]
387#[non_exhaustive]
388pub struct BlobResourceContents {
389 pub blob: String,
391 #[serde_as(deserialize_as = "DefaultOnError")]
393 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
394 #[serde(default)]
395 pub mime_type: Option<String>,
396 pub uri: String,
398 #[serde_as(deserialize_as = "DefaultOnError")]
404 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
405 #[serde(default)]
406 #[serde(rename = "_meta")]
407 pub meta: Option<Meta>,
408}
409
410impl BlobResourceContents {
411 #[must_use]
413 pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
414 Self {
415 blob: blob.into(),
416 mime_type: None,
417 uri: uri.into(),
418 meta: None,
419 }
420 }
421
422 #[must_use]
424 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
425 self.mime_type = mime_type.into_option();
426 self
427 }
428
429 #[must_use]
435 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
436 self.meta = meta.into_option();
437 self
438 }
439}
440
441#[serde_as]
443#[skip_serializing_none]
444#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
445#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
446#[serde(rename_all = "camelCase")]
447#[non_exhaustive]
448pub struct ResourceLink {
449 #[serde_as(deserialize_as = "DefaultOnError")]
451 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
452 #[serde(default)]
453 pub annotations: Option<Annotations>,
454 #[serde_as(deserialize_as = "DefaultOnError")]
456 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
457 #[serde(default)]
458 pub description: Option<String>,
459 #[serde_as(deserialize_as = "DefaultOnError")]
461 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
462 #[serde(default)]
463 pub mime_type: Option<String>,
464 pub name: String,
466 #[serde_as(deserialize_as = "DefaultOnError")]
468 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
469 #[serde(default)]
470 pub size: Option<i64>,
471 #[serde_as(deserialize_as = "DefaultOnError")]
473 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
474 #[serde(default)]
475 pub title: Option<String>,
476 pub uri: String,
478 #[serde_as(deserialize_as = "DefaultOnError")]
484 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
485 #[serde(default)]
486 #[serde(rename = "_meta")]
487 pub meta: Option<Meta>,
488}
489
490impl ResourceLink {
491 #[must_use]
493 pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
494 Self {
495 annotations: None,
496 description: None,
497 mime_type: None,
498 name: name.into(),
499 size: None,
500 title: None,
501 uri: uri.into(),
502 meta: None,
503 }
504 }
505
506 #[must_use]
508 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
509 self.annotations = annotations.into_option();
510 self
511 }
512
513 #[must_use]
515 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
516 self.description = description.into_option();
517 self
518 }
519
520 #[must_use]
522 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
523 self.mime_type = mime_type.into_option();
524 self
525 }
526
527 #[must_use]
529 pub fn size(mut self, size: impl IntoOption<i64>) -> Self {
530 self.size = size.into_option();
531 self
532 }
533
534 #[must_use]
536 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
537 self.title = title.into_option();
538 self
539 }
540
541 #[must_use]
547 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
548 self.meta = meta.into_option();
549 self
550 }
551}
552
553#[serde_as]
555#[skip_serializing_none]
556#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
557#[derive(Debug, Clone, Deserialize, Serialize, Default)]
558#[serde(rename_all = "camelCase")]
559#[non_exhaustive]
560pub struct Annotations {
561 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
563 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
564 #[serde(default)]
565 pub audience: Option<Vec<Role>>,
566 #[serde_as(deserialize_as = "DefaultOnError")]
568 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
569 #[serde(default)]
570 pub last_modified: Option<String>,
571 #[serde_as(deserialize_as = "DefaultOnError")]
573 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
574 #[serde(default)]
575 pub priority: Option<f64>,
576 #[serde_as(deserialize_as = "DefaultOnError")]
582 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
583 #[serde(default)]
584 #[serde(rename = "_meta")]
585 pub meta: Option<Meta>,
586}
587
588impl PartialEq for Annotations {
593 fn eq(&self, other: &Self) -> bool {
594 let Self {
595 audience,
596 last_modified,
597 priority,
598 meta,
599 } = self;
600 *audience == other.audience
601 && *last_modified == other.last_modified
602 && priority_eq(*priority, other.priority)
603 && *meta == other.meta
604 }
605}
606
607impl Eq for Annotations {}
608
609fn priority_eq(a: Option<f64>, b: Option<f64>) -> bool {
610 match (a, b) {
611 (Some(a), Some(b)) => a.total_cmp(&b).is_eq(),
612 (None, None) => true,
613 _ => false,
614 }
615}
616
617impl Annotations {
618 #[must_use]
620 pub fn new() -> Self {
621 Self::default()
622 }
623
624 #[must_use]
626 pub fn audience(mut self, audience: impl IntoOption<Vec<Role>>) -> Self {
627 self.audience = audience.into_option();
628 self
629 }
630
631 #[must_use]
633 pub fn last_modified(mut self, last_modified: impl IntoOption<String>) -> Self {
634 self.last_modified = last_modified.into_option();
635 self
636 }
637
638 #[must_use]
640 pub fn priority(mut self, priority: impl IntoOption<f64>) -> Self {
641 self.priority = priority.into_option();
642 self
643 }
644
645 #[must_use]
651 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
652 self.meta = meta.into_option();
653 self
654 }
655}
656
657#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
659#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
660#[serde(rename_all = "camelCase")]
661#[non_exhaustive]
662pub enum Role {
663 Assistant,
665 User,
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672
673 #[test]
674 fn test_annotations_equality_is_reflexive() {
675 let nan = Annotations::new().priority(f64::NAN);
676 assert_eq!(nan, nan.clone());
677 assert_eq!(
678 Annotations::new().priority(0.5),
679 Annotations::new().priority(0.5)
680 );
681 assert_ne!(
682 Annotations::new().priority(0.0),
683 Annotations::new().priority(-0.0)
684 );
685 assert_ne!(
686 serde_json::to_string(&Annotations::new().priority(0.0)).unwrap(),
687 serde_json::to_string(&Annotations::new().priority(-0.0)).unwrap()
688 );
689 assert_ne!(
690 Annotations::new().priority(0.5),
691 Annotations::new().priority(1.0)
692 );
693 assert_ne!(nan, Annotations::new().priority(0.5));
694 assert_ne!(Annotations::new().priority(0.5), Annotations::new());
695 assert_ne!(
696 Annotations::new().priority(0.5).audience(vec![Role::User]),
697 Annotations::new().priority(0.5)
698 );
699 }
700
701 #[test]
702 fn test_text_content_roundtrip() {
703 let content = TextContent::new("hello world");
704 let json = serde_json::to_value(&content).unwrap();
705 let parsed: TextContent = serde_json::from_value(json).unwrap();
706 assert_eq!(content, parsed);
707 }
708
709 #[test]
710 fn test_text_content_omits_optional_fields() {
711 let content = TextContent::new("hello");
712 let json = serde_json::to_value(&content).unwrap();
713 assert!(!json.as_object().unwrap().contains_key("annotations"));
714 assert!(!json.as_object().unwrap().contains_key("meta"));
715 }
716
717 #[test]
718 fn test_text_content_meta_defaults_on_missing_or_malformed_value() {
719 let missing: TextContent = serde_json::from_value(serde_json::json!({
720 "text": "hello"
721 }))
722 .unwrap();
723 assert_eq!(missing.meta, None);
724
725 let malformed: TextContent = serde_json::from_value(serde_json::json!({
726 "text": "hello",
727 "_meta": false
728 }))
729 .unwrap();
730 assert_eq!(malformed.meta, None);
731 }
732
733 #[test]
734 fn test_text_content_from_string() {
735 let block: ContentBlock = "hello".into();
736 match block {
737 ContentBlock::Text(c) => assert_eq!(c.text, "hello"),
738 _ => panic!("Expected Text variant"),
739 }
740 }
741
742 #[test]
743 fn test_image_content_roundtrip() {
744 let content = ImageContent::new("base64data", "image/png");
745 let json = serde_json::to_value(&content).unwrap();
746 let parsed: ImageContent = serde_json::from_value(json).unwrap();
747 assert_eq!(content, parsed);
748 }
749
750 #[test]
751 fn test_image_content_omits_optional_fields() {
752 let content = ImageContent::new("data", "image/png");
753 let json = serde_json::to_value(&content).unwrap();
754 assert!(!json.as_object().unwrap().contains_key("uri"));
755 assert!(!json.as_object().unwrap().contains_key("annotations"));
756 assert!(!json.as_object().unwrap().contains_key("meta"));
757 }
758
759 #[test]
760 fn test_image_content_with_uri() {
761 let content = ImageContent::new("data", "image/png").uri("https://example.com/image.png");
762 let json = serde_json::to_value(&content).unwrap();
763 assert_eq!(json["uri"], "https://example.com/image.png");
764 }
765
766 #[test]
767 fn test_audio_content_roundtrip() {
768 let content = AudioContent::new("base64audio", "audio/mp3");
769 let json = serde_json::to_value(&content).unwrap();
770 let parsed: AudioContent = serde_json::from_value(json).unwrap();
771 assert_eq!(content, parsed);
772 }
773
774 #[test]
775 fn test_audio_content_omits_optional_fields() {
776 let content = AudioContent::new("data", "audio/mp3");
777 let json = serde_json::to_value(&content).unwrap();
778 assert!(!json.as_object().unwrap().contains_key("annotations"));
779 assert!(!json.as_object().unwrap().contains_key("meta"));
780 }
781}