1use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
15
16use crate::{IntoOption, SkipListener};
17
18use super::Meta;
19
20#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
35#[serde(tag = "type", rename_all = "snake_case")]
36#[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#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
68#[non_exhaustive]
69pub struct TextContent {
70 #[serde_as(deserialize_as = "DefaultOnError")]
72 #[schemars(extend("x-deserialize-default-on-error" = true))]
73 #[serde(default)]
74 pub annotations: Option<Annotations>,
75 pub text: String,
77 #[serde_as(deserialize_as = "DefaultOnError")]
83 #[schemars(extend("x-deserialize-default-on-error" = true))]
84 #[serde(default)]
85 #[serde(rename = "_meta")]
86 pub meta: Option<Meta>,
87}
88
89impl TextContent {
90 #[must_use]
92 pub fn new(text: impl Into<String>) -> Self {
93 Self {
94 annotations: None,
95 text: text.into(),
96 meta: None,
97 }
98 }
99
100 #[must_use]
102 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
103 self.annotations = annotations.into_option();
104 self
105 }
106
107 #[must_use]
113 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
114 self.meta = meta.into_option();
115 self
116 }
117}
118
119impl<T: Into<String>> From<T> for ContentBlock {
120 fn from(value: T) -> Self {
121 Self::Text(TextContent::new(value))
122 }
123}
124
125#[serde_as]
127#[skip_serializing_none]
128#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
129#[serde(rename_all = "camelCase")]
130#[non_exhaustive]
131pub struct ImageContent {
132 #[serde_as(deserialize_as = "DefaultOnError")]
134 #[schemars(extend("x-deserialize-default-on-error" = true))]
135 #[serde(default)]
136 pub annotations: Option<Annotations>,
137 pub data: String,
139 pub mime_type: String,
141 #[serde_as(deserialize_as = "DefaultOnError")]
143 #[schemars(extend("x-deserialize-default-on-error" = true))]
144 #[serde(default)]
145 pub uri: Option<String>,
146 #[serde_as(deserialize_as = "DefaultOnError")]
152 #[schemars(extend("x-deserialize-default-on-error" = true))]
153 #[serde(default)]
154 #[serde(rename = "_meta")]
155 pub meta: Option<Meta>,
156}
157
158impl ImageContent {
159 #[must_use]
161 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
162 Self {
163 annotations: None,
164 data: data.into(),
165 mime_type: mime_type.into(),
166 uri: None,
167 meta: None,
168 }
169 }
170
171 #[must_use]
173 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
174 self.annotations = annotations.into_option();
175 self
176 }
177
178 #[must_use]
180 pub fn uri(mut self, uri: impl IntoOption<String>) -> Self {
181 self.uri = uri.into_option();
182 self
183 }
184
185 #[must_use]
191 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
192 self.meta = meta.into_option();
193 self
194 }
195}
196
197#[serde_as]
199#[skip_serializing_none]
200#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
201#[serde(rename_all = "camelCase")]
202#[non_exhaustive]
203pub struct AudioContent {
204 #[serde_as(deserialize_as = "DefaultOnError")]
206 #[schemars(extend("x-deserialize-default-on-error" = true))]
207 #[serde(default)]
208 pub annotations: Option<Annotations>,
209 pub data: String,
211 pub mime_type: String,
213 #[serde_as(deserialize_as = "DefaultOnError")]
219 #[schemars(extend("x-deserialize-default-on-error" = true))]
220 #[serde(default)]
221 #[serde(rename = "_meta")]
222 pub meta: Option<Meta>,
223}
224
225impl AudioContent {
226 #[must_use]
228 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
229 Self {
230 annotations: None,
231 data: data.into(),
232 mime_type: mime_type.into(),
233 meta: None,
234 }
235 }
236
237 #[must_use]
239 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
240 self.annotations = annotations.into_option();
241 self
242 }
243
244 #[must_use]
250 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
251 self.meta = meta.into_option();
252 self
253 }
254}
255
256#[serde_as]
258#[skip_serializing_none]
259#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
260#[non_exhaustive]
261pub struct EmbeddedResource {
262 #[serde_as(deserialize_as = "DefaultOnError")]
264 #[schemars(extend("x-deserialize-default-on-error" = true))]
265 #[serde(default)]
266 pub annotations: Option<Annotations>,
267 pub resource: EmbeddedResourceResource,
269 #[serde_as(deserialize_as = "DefaultOnError")]
275 #[schemars(extend("x-deserialize-default-on-error" = true))]
276 #[serde(default)]
277 #[serde(rename = "_meta")]
278 pub meta: Option<Meta>,
279}
280
281impl EmbeddedResource {
282 #[must_use]
284 pub fn new(resource: EmbeddedResourceResource) -> Self {
285 Self {
286 annotations: None,
287 resource,
288 meta: None,
289 }
290 }
291
292 #[must_use]
294 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
295 self.annotations = annotations.into_option();
296 self
297 }
298
299 #[must_use]
305 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
306 self.meta = meta.into_option();
307 self
308 }
309}
310
311#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
313#[serde(untagged)]
314#[non_exhaustive]
315pub enum EmbeddedResourceResource {
316 TextResourceContents(TextResourceContents),
318 BlobResourceContents(BlobResourceContents),
320}
321
322#[serde_as]
324#[skip_serializing_none]
325#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
326#[serde(rename_all = "camelCase")]
327#[non_exhaustive]
328pub struct TextResourceContents {
329 #[serde_as(deserialize_as = "DefaultOnError")]
331 #[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 #[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#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
385#[serde(rename_all = "camelCase")]
386#[non_exhaustive]
387pub struct BlobResourceContents {
388 pub blob: String,
390 #[serde_as(deserialize_as = "DefaultOnError")]
392 #[schemars(extend("x-deserialize-default-on-error" = true))]
393 #[serde(default)]
394 pub mime_type: Option<String>,
395 pub uri: String,
397 #[serde_as(deserialize_as = "DefaultOnError")]
403 #[schemars(extend("x-deserialize-default-on-error" = true))]
404 #[serde(default)]
405 #[serde(rename = "_meta")]
406 pub meta: Option<Meta>,
407}
408
409impl BlobResourceContents {
410 #[must_use]
412 pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
413 Self {
414 blob: blob.into(),
415 mime_type: None,
416 uri: uri.into(),
417 meta: None,
418 }
419 }
420
421 #[must_use]
423 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
424 self.mime_type = mime_type.into_option();
425 self
426 }
427
428 #[must_use]
434 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
435 self.meta = meta.into_option();
436 self
437 }
438}
439
440#[serde_as]
442#[skip_serializing_none]
443#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
444#[serde(rename_all = "camelCase")]
445#[non_exhaustive]
446pub struct ResourceLink {
447 #[serde_as(deserialize_as = "DefaultOnError")]
449 #[schemars(extend("x-deserialize-default-on-error" = true))]
450 #[serde(default)]
451 pub annotations: Option<Annotations>,
452 #[serde_as(deserialize_as = "DefaultOnError")]
454 #[schemars(extend("x-deserialize-default-on-error" = true))]
455 #[serde(default)]
456 pub description: Option<String>,
457 #[serde_as(deserialize_as = "DefaultOnError")]
459 #[schemars(extend("x-deserialize-default-on-error" = true))]
460 #[serde(default)]
461 pub mime_type: Option<String>,
462 pub name: String,
464 #[serde_as(deserialize_as = "DefaultOnError")]
466 #[schemars(extend("x-deserialize-default-on-error" = true))]
467 #[serde(default)]
468 pub size: Option<i64>,
469 #[serde_as(deserialize_as = "DefaultOnError")]
471 #[schemars(extend("x-deserialize-default-on-error" = true))]
472 #[serde(default)]
473 pub title: Option<String>,
474 pub uri: String,
476 #[serde_as(deserialize_as = "DefaultOnError")]
482 #[schemars(extend("x-deserialize-default-on-error" = true))]
483 #[serde(default)]
484 #[serde(rename = "_meta")]
485 pub meta: Option<Meta>,
486}
487
488impl ResourceLink {
489 #[must_use]
491 pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
492 Self {
493 annotations: None,
494 description: None,
495 mime_type: None,
496 name: name.into(),
497 size: None,
498 title: None,
499 uri: uri.into(),
500 meta: None,
501 }
502 }
503
504 #[must_use]
506 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
507 self.annotations = annotations.into_option();
508 self
509 }
510
511 #[must_use]
513 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
514 self.description = description.into_option();
515 self
516 }
517
518 #[must_use]
520 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
521 self.mime_type = mime_type.into_option();
522 self
523 }
524
525 #[must_use]
527 pub fn size(mut self, size: impl IntoOption<i64>) -> Self {
528 self.size = size.into_option();
529 self
530 }
531
532 #[must_use]
534 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
535 self.title = title.into_option();
536 self
537 }
538
539 #[must_use]
545 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
546 self.meta = meta.into_option();
547 self
548 }
549}
550
551#[serde_as]
553#[skip_serializing_none]
554#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, Default)]
555#[serde(rename_all = "camelCase")]
556#[non_exhaustive]
557pub struct Annotations {
558 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
560 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
561 #[serde(default)]
562 pub audience: Option<Vec<Role>>,
563 #[serde_as(deserialize_as = "DefaultOnError")]
565 #[schemars(extend("x-deserialize-default-on-error" = true))]
566 #[serde(default)]
567 pub last_modified: Option<String>,
568 #[serde_as(deserialize_as = "DefaultOnError")]
570 #[schemars(extend("x-deserialize-default-on-error" = true))]
571 #[serde(default)]
572 pub priority: Option<f64>,
573 #[serde_as(deserialize_as = "DefaultOnError")]
579 #[schemars(extend("x-deserialize-default-on-error" = true))]
580 #[serde(default)]
581 #[serde(rename = "_meta")]
582 pub meta: Option<Meta>,
583}
584
585impl Annotations {
586 #[must_use]
588 pub fn new() -> Self {
589 Self::default()
590 }
591
592 #[must_use]
594 pub fn audience(mut self, audience: impl IntoOption<Vec<Role>>) -> Self {
595 self.audience = audience.into_option();
596 self
597 }
598
599 #[must_use]
601 pub fn last_modified(mut self, last_modified: impl IntoOption<String>) -> Self {
602 self.last_modified = last_modified.into_option();
603 self
604 }
605
606 #[must_use]
608 pub fn priority(mut self, priority: impl IntoOption<f64>) -> Self {
609 self.priority = priority.into_option();
610 self
611 }
612
613 #[must_use]
619 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
620 self.meta = meta.into_option();
621 self
622 }
623}
624
625#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
627#[serde(rename_all = "camelCase")]
628#[non_exhaustive]
629pub enum Role {
630 Assistant,
632 User,
634}
635
636#[cfg(test)]
637mod tests {
638 use super::*;
639
640 #[test]
641 fn test_text_content_roundtrip() {
642 let content = TextContent::new("hello world");
643 let json = serde_json::to_value(&content).unwrap();
644 let parsed: TextContent = serde_json::from_value(json).unwrap();
645 assert_eq!(content, parsed);
646 }
647
648 #[test]
649 fn test_text_content_omits_optional_fields() {
650 let content = TextContent::new("hello");
651 let json = serde_json::to_value(&content).unwrap();
652 assert!(!json.as_object().unwrap().contains_key("annotations"));
653 assert!(!json.as_object().unwrap().contains_key("meta"));
654 }
655
656 #[test]
657 fn test_text_content_meta_defaults_on_missing_or_malformed_value() {
658 let missing: TextContent = serde_json::from_value(serde_json::json!({
659 "text": "hello"
660 }))
661 .unwrap();
662 assert_eq!(missing.meta, None);
663
664 let malformed: TextContent = serde_json::from_value(serde_json::json!({
665 "text": "hello",
666 "_meta": false
667 }))
668 .unwrap();
669 assert_eq!(malformed.meta, None);
670 }
671
672 #[test]
673 fn test_text_content_from_string() {
674 let block: ContentBlock = "hello".into();
675 match block {
676 ContentBlock::Text(c) => assert_eq!(c.text, "hello"),
677 _ => panic!("Expected Text variant"),
678 }
679 }
680
681 #[test]
682 fn test_image_content_roundtrip() {
683 let content = ImageContent::new("base64data", "image/png");
684 let json = serde_json::to_value(&content).unwrap();
685 let parsed: ImageContent = serde_json::from_value(json).unwrap();
686 assert_eq!(content, parsed);
687 }
688
689 #[test]
690 fn test_image_content_omits_optional_fields() {
691 let content = ImageContent::new("data", "image/png");
692 let json = serde_json::to_value(&content).unwrap();
693 assert!(!json.as_object().unwrap().contains_key("uri"));
694 assert!(!json.as_object().unwrap().contains_key("annotations"));
695 assert!(!json.as_object().unwrap().contains_key("meta"));
696 }
697
698 #[test]
699 fn test_image_content_with_uri() {
700 let content = ImageContent::new("data", "image/png").uri("https://example.com/image.png");
701 let json = serde_json::to_value(&content).unwrap();
702 assert_eq!(json["uri"], "https://example.com/image.png");
703 }
704
705 #[test]
706 fn test_audio_content_roundtrip() {
707 let content = AudioContent::new("base64audio", "audio/mp3");
708 let json = serde_json::to_value(&content).unwrap();
709 let parsed: AudioContent = serde_json::from_value(json).unwrap();
710 assert_eq!(content, parsed);
711 }
712
713 #[test]
714 fn test_audio_content_omits_optional_fields() {
715 let content = AudioContent::new("data", "audio/mp3");
716 let json = serde_json::to_value(&content).unwrap();
717 assert!(!json.as_object().unwrap().contains_key("annotations"));
718 assert!(!json.as_object().unwrap().contains_key("meta"));
719 }
720}