1use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
15
16use crate::{IntoOption, Meta, SkipListener};
17
18#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
33#[serde(tag = "type", rename_all = "snake_case")]
34#[schemars(extend("discriminator" = {"propertyName": "type"}))]
35#[non_exhaustive]
36pub enum ContentBlock {
37 Text(TextContent),
42 Image(ImageContent),
46 Audio(AudioContent),
50 ResourceLink(ResourceLink),
54 Resource(EmbeddedResource),
60}
61
62#[serde_as]
64#[skip_serializing_none]
65#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
66#[non_exhaustive]
67pub struct TextContent {
68 #[serde_as(deserialize_as = "DefaultOnError")]
69 #[serde(default)]
70 pub annotations: Option<Annotations>,
71 pub text: String,
72 #[serde(rename = "_meta")]
78 pub meta: Option<Meta>,
79}
80
81impl TextContent {
82 #[must_use]
83 pub fn new(text: impl Into<String>) -> Self {
84 Self {
85 annotations: None,
86 text: text.into(),
87 meta: None,
88 }
89 }
90
91 #[must_use]
92 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
93 self.annotations = annotations.into_option();
94 self
95 }
96
97 #[must_use]
103 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
104 self.meta = meta.into_option();
105 self
106 }
107}
108
109impl<T: Into<String>> From<T> for ContentBlock {
110 fn from(value: T) -> Self {
111 Self::Text(TextContent::new(value))
112 }
113}
114
115#[serde_as]
117#[skip_serializing_none]
118#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
119#[serde(rename_all = "camelCase")]
120#[non_exhaustive]
121pub struct ImageContent {
122 #[serde_as(deserialize_as = "DefaultOnError")]
123 #[serde(default)]
124 pub annotations: Option<Annotations>,
125 pub data: String,
126 pub mime_type: String,
127 pub uri: Option<String>,
128 #[serde(rename = "_meta")]
134 pub meta: Option<Meta>,
135}
136
137impl ImageContent {
138 #[must_use]
139 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
140 Self {
141 annotations: None,
142 data: data.into(),
143 mime_type: mime_type.into(),
144 uri: None,
145 meta: None,
146 }
147 }
148
149 #[must_use]
150 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
151 self.annotations = annotations.into_option();
152 self
153 }
154
155 #[must_use]
156 pub fn uri(mut self, uri: impl IntoOption<String>) -> Self {
157 self.uri = uri.into_option();
158 self
159 }
160
161 #[must_use]
167 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
168 self.meta = meta.into_option();
169 self
170 }
171}
172
173#[serde_as]
175#[skip_serializing_none]
176#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
177#[serde(rename_all = "camelCase")]
178#[non_exhaustive]
179pub struct AudioContent {
180 #[serde_as(deserialize_as = "DefaultOnError")]
181 #[serde(default)]
182 pub annotations: Option<Annotations>,
183 pub data: String,
184 pub mime_type: String,
185 #[serde(rename = "_meta")]
191 pub meta: Option<Meta>,
192}
193
194impl AudioContent {
195 #[must_use]
196 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
197 Self {
198 annotations: None,
199 data: data.into(),
200 mime_type: mime_type.into(),
201 meta: None,
202 }
203 }
204
205 #[must_use]
206 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
207 self.annotations = annotations.into_option();
208 self
209 }
210
211 #[must_use]
217 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
218 self.meta = meta.into_option();
219 self
220 }
221}
222
223#[serde_as]
225#[skip_serializing_none]
226#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
227#[non_exhaustive]
228pub struct EmbeddedResource {
229 #[serde_as(deserialize_as = "DefaultOnError")]
230 #[serde(default)]
231 pub annotations: Option<Annotations>,
232 pub resource: EmbeddedResourceResource,
233 #[serde(rename = "_meta")]
239 pub meta: Option<Meta>,
240}
241
242impl EmbeddedResource {
243 #[must_use]
244 pub fn new(resource: EmbeddedResourceResource) -> Self {
245 Self {
246 annotations: None,
247 resource,
248 meta: None,
249 }
250 }
251
252 #[must_use]
253 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
254 self.annotations = annotations.into_option();
255 self
256 }
257
258 #[must_use]
264 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
265 self.meta = meta.into_option();
266 self
267 }
268}
269
270#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
272#[serde(untagged)]
273#[non_exhaustive]
274pub enum EmbeddedResourceResource {
275 TextResourceContents(TextResourceContents),
276 BlobResourceContents(BlobResourceContents),
277}
278
279#[skip_serializing_none]
281#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
282#[serde(rename_all = "camelCase")]
283#[non_exhaustive]
284pub struct TextResourceContents {
285 pub mime_type: Option<String>,
286 pub text: String,
287 pub uri: String,
288 #[serde(rename = "_meta")]
294 pub meta: Option<Meta>,
295}
296
297impl TextResourceContents {
298 #[must_use]
299 pub fn new(text: impl Into<String>, uri: impl Into<String>) -> Self {
300 Self {
301 mime_type: None,
302 text: text.into(),
303 uri: uri.into(),
304 meta: None,
305 }
306 }
307
308 #[must_use]
309 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
310 self.mime_type = mime_type.into_option();
311 self
312 }
313
314 #[must_use]
320 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
321 self.meta = meta.into_option();
322 self
323 }
324}
325
326#[skip_serializing_none]
328#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
329#[serde(rename_all = "camelCase")]
330#[non_exhaustive]
331pub struct BlobResourceContents {
332 pub blob: String,
333 pub mime_type: Option<String>,
334 pub uri: String,
335 #[serde(rename = "_meta")]
341 pub meta: Option<Meta>,
342}
343
344impl BlobResourceContents {
345 #[must_use]
346 pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
347 Self {
348 blob: blob.into(),
349 mime_type: None,
350 uri: uri.into(),
351 meta: None,
352 }
353 }
354
355 #[must_use]
356 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
357 self.mime_type = mime_type.into_option();
358 self
359 }
360
361 #[must_use]
367 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
368 self.meta = meta.into_option();
369 self
370 }
371}
372
373#[serde_as]
375#[skip_serializing_none]
376#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
377#[serde(rename_all = "camelCase")]
378#[non_exhaustive]
379pub struct ResourceLink {
380 #[serde_as(deserialize_as = "DefaultOnError")]
381 #[serde(default)]
382 pub annotations: Option<Annotations>,
383 pub description: Option<String>,
384 pub mime_type: Option<String>,
385 pub name: String,
386 pub size: Option<i64>,
387 pub title: Option<String>,
388 pub uri: String,
389 #[serde(rename = "_meta")]
395 pub meta: Option<Meta>,
396}
397
398impl ResourceLink {
399 #[must_use]
400 pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
401 Self {
402 annotations: None,
403 description: None,
404 mime_type: None,
405 name: name.into(),
406 size: None,
407 title: None,
408 uri: uri.into(),
409 meta: None,
410 }
411 }
412
413 #[must_use]
414 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
415 self.annotations = annotations.into_option();
416 self
417 }
418
419 #[must_use]
420 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
421 self.description = description.into_option();
422 self
423 }
424
425 #[must_use]
426 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
427 self.mime_type = mime_type.into_option();
428 self
429 }
430
431 #[must_use]
432 pub fn size(mut self, size: impl IntoOption<i64>) -> Self {
433 self.size = size.into_option();
434 self
435 }
436
437 #[must_use]
438 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
439 self.title = title.into_option();
440 self
441 }
442
443 #[must_use]
449 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
450 self.meta = meta.into_option();
451 self
452 }
453}
454
455#[serde_as]
457#[skip_serializing_none]
458#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, Default)]
459#[serde(rename_all = "camelCase")]
460#[non_exhaustive]
461pub struct Annotations {
462 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
463 #[serde(default)]
464 pub audience: Option<Vec<Role>>,
465 pub last_modified: Option<String>,
466 pub priority: Option<f64>,
467 #[serde(rename = "_meta")]
473 pub meta: Option<Meta>,
474}
475
476impl Annotations {
477 #[must_use]
478 pub fn new() -> Self {
479 Self::default()
480 }
481
482 #[must_use]
483 pub fn audience(mut self, audience: impl IntoOption<Vec<Role>>) -> Self {
484 self.audience = audience.into_option();
485 self
486 }
487
488 #[must_use]
489 pub fn last_modified(mut self, last_modified: impl IntoOption<String>) -> Self {
490 self.last_modified = last_modified.into_option();
491 self
492 }
493
494 #[must_use]
495 pub fn priority(mut self, priority: impl IntoOption<f64>) -> Self {
496 self.priority = priority.into_option();
497 self
498 }
499
500 #[must_use]
506 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
507 self.meta = meta.into_option();
508 self
509 }
510}
511
512#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
514#[serde(rename_all = "camelCase")]
515#[non_exhaustive]
516pub enum Role {
517 Assistant,
518 User,
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn test_text_content_roundtrip() {
527 let content = TextContent::new("hello world");
528 let json = serde_json::to_value(&content).unwrap();
529 let parsed: TextContent = serde_json::from_value(json).unwrap();
530 assert_eq!(content, parsed);
531 }
532
533 #[test]
534 fn test_text_content_omits_optional_fields() {
535 let content = TextContent::new("hello");
536 let json = serde_json::to_value(&content).unwrap();
537 assert!(!json.as_object().unwrap().contains_key("annotations"));
538 assert!(!json.as_object().unwrap().contains_key("meta"));
539 }
540
541 #[test]
542 fn test_text_content_from_string() {
543 let block: ContentBlock = "hello".into();
544 match block {
545 ContentBlock::Text(c) => assert_eq!(c.text, "hello"),
546 _ => panic!("Expected Text variant"),
547 }
548 }
549
550 #[test]
551 fn test_image_content_roundtrip() {
552 let content = ImageContent::new("base64data", "image/png");
553 let json = serde_json::to_value(&content).unwrap();
554 let parsed: ImageContent = serde_json::from_value(json).unwrap();
555 assert_eq!(content, parsed);
556 }
557
558 #[test]
559 fn test_image_content_omits_optional_fields() {
560 let content = ImageContent::new("data", "image/png");
561 let json = serde_json::to_value(&content).unwrap();
562 assert!(!json.as_object().unwrap().contains_key("uri"));
563 assert!(!json.as_object().unwrap().contains_key("annotations"));
564 assert!(!json.as_object().unwrap().contains_key("meta"));
565 }
566
567 #[test]
568 fn test_image_content_with_uri() {
569 let content = ImageContent::new("data", "image/png").uri("https://example.com/image.png");
570 let json = serde_json::to_value(&content).unwrap();
571 assert_eq!(json["uri"], "https://example.com/image.png");
572 }
573
574 #[test]
575 fn test_audio_content_roundtrip() {
576 let content = AudioContent::new("base64audio", "audio/mp3");
577 let json = serde_json::to_value(&content).unwrap();
578 let parsed: AudioContent = serde_json::from_value(json).unwrap();
579 assert_eq!(content, parsed);
580 }
581
582 #[test]
583 fn test_audio_content_omits_optional_fields() {
584 let content = AudioContent::new("data", "audio/mp3");
585 let json = serde_json::to_value(&content).unwrap();
586 assert!(!json.as_object().unwrap().contains_key("annotations"));
587 assert!(!json.as_object().unwrap().contains_key("meta"));
588 }
589}