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 #[schemars(extend("x-deserialize-default-on-error" = true))]
70 #[serde(default)]
71 pub annotations: Option<Annotations>,
72 pub text: String,
73 #[serde(rename = "_meta")]
79 pub meta: Option<Meta>,
80}
81
82impl TextContent {
83 #[must_use]
84 pub fn new(text: impl Into<String>) -> Self {
85 Self {
86 annotations: None,
87 text: text.into(),
88 meta: None,
89 }
90 }
91
92 #[must_use]
93 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
94 self.annotations = annotations.into_option();
95 self
96 }
97
98 #[must_use]
104 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
105 self.meta = meta.into_option();
106 self
107 }
108}
109
110impl<T: Into<String>> From<T> for ContentBlock {
111 fn from(value: T) -> Self {
112 Self::Text(TextContent::new(value))
113 }
114}
115
116#[serde_as]
118#[skip_serializing_none]
119#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
120#[serde(rename_all = "camelCase")]
121#[non_exhaustive]
122pub struct ImageContent {
123 #[serde_as(deserialize_as = "DefaultOnError")]
124 #[schemars(extend("x-deserialize-default-on-error" = true))]
125 #[serde(default)]
126 pub annotations: Option<Annotations>,
127 pub data: String,
128 pub mime_type: String,
129 pub uri: Option<String>,
130 #[serde(rename = "_meta")]
136 pub meta: Option<Meta>,
137}
138
139impl ImageContent {
140 #[must_use]
141 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
142 Self {
143 annotations: None,
144 data: data.into(),
145 mime_type: mime_type.into(),
146 uri: None,
147 meta: None,
148 }
149 }
150
151 #[must_use]
152 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
153 self.annotations = annotations.into_option();
154 self
155 }
156
157 #[must_use]
158 pub fn uri(mut self, uri: impl IntoOption<String>) -> Self {
159 self.uri = uri.into_option();
160 self
161 }
162
163 #[must_use]
169 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
170 self.meta = meta.into_option();
171 self
172 }
173}
174
175#[serde_as]
177#[skip_serializing_none]
178#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
179#[serde(rename_all = "camelCase")]
180#[non_exhaustive]
181pub struct AudioContent {
182 #[serde_as(deserialize_as = "DefaultOnError")]
183 #[schemars(extend("x-deserialize-default-on-error" = true))]
184 #[serde(default)]
185 pub annotations: Option<Annotations>,
186 pub data: String,
187 pub mime_type: String,
188 #[serde(rename = "_meta")]
194 pub meta: Option<Meta>,
195}
196
197impl AudioContent {
198 #[must_use]
199 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
200 Self {
201 annotations: None,
202 data: data.into(),
203 mime_type: mime_type.into(),
204 meta: None,
205 }
206 }
207
208 #[must_use]
209 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
210 self.annotations = annotations.into_option();
211 self
212 }
213
214 #[must_use]
220 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
221 self.meta = meta.into_option();
222 self
223 }
224}
225
226#[serde_as]
228#[skip_serializing_none]
229#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
230#[non_exhaustive]
231pub struct EmbeddedResource {
232 #[serde_as(deserialize_as = "DefaultOnError")]
233 #[schemars(extend("x-deserialize-default-on-error" = true))]
234 #[serde(default)]
235 pub annotations: Option<Annotations>,
236 pub resource: EmbeddedResourceResource,
237 #[serde(rename = "_meta")]
243 pub meta: Option<Meta>,
244}
245
246impl EmbeddedResource {
247 #[must_use]
248 pub fn new(resource: EmbeddedResourceResource) -> Self {
249 Self {
250 annotations: None,
251 resource,
252 meta: None,
253 }
254 }
255
256 #[must_use]
257 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
258 self.annotations = annotations.into_option();
259 self
260 }
261
262 #[must_use]
268 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
269 self.meta = meta.into_option();
270 self
271 }
272}
273
274#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
276#[serde(untagged)]
277#[non_exhaustive]
278pub enum EmbeddedResourceResource {
279 TextResourceContents(TextResourceContents),
280 BlobResourceContents(BlobResourceContents),
281}
282
283#[skip_serializing_none]
285#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
286#[serde(rename_all = "camelCase")]
287#[non_exhaustive]
288pub struct TextResourceContents {
289 pub mime_type: Option<String>,
290 pub text: String,
291 pub uri: String,
292 #[serde(rename = "_meta")]
298 pub meta: Option<Meta>,
299}
300
301impl TextResourceContents {
302 #[must_use]
303 pub fn new(text: impl Into<String>, uri: impl Into<String>) -> Self {
304 Self {
305 mime_type: None,
306 text: text.into(),
307 uri: uri.into(),
308 meta: None,
309 }
310 }
311
312 #[must_use]
313 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
314 self.mime_type = mime_type.into_option();
315 self
316 }
317
318 #[must_use]
324 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
325 self.meta = meta.into_option();
326 self
327 }
328}
329
330#[skip_serializing_none]
332#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
333#[serde(rename_all = "camelCase")]
334#[non_exhaustive]
335pub struct BlobResourceContents {
336 pub blob: String,
337 pub mime_type: Option<String>,
338 pub uri: String,
339 #[serde(rename = "_meta")]
345 pub meta: Option<Meta>,
346}
347
348impl BlobResourceContents {
349 #[must_use]
350 pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
351 Self {
352 blob: blob.into(),
353 mime_type: None,
354 uri: uri.into(),
355 meta: None,
356 }
357 }
358
359 #[must_use]
360 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
361 self.mime_type = mime_type.into_option();
362 self
363 }
364
365 #[must_use]
371 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
372 self.meta = meta.into_option();
373 self
374 }
375}
376
377#[serde_as]
379#[skip_serializing_none]
380#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
381#[serde(rename_all = "camelCase")]
382#[non_exhaustive]
383pub struct ResourceLink {
384 #[serde_as(deserialize_as = "DefaultOnError")]
385 #[schemars(extend("x-deserialize-default-on-error" = true))]
386 #[serde(default)]
387 pub annotations: Option<Annotations>,
388 pub description: Option<String>,
389 pub mime_type: Option<String>,
390 pub name: String,
391 pub size: Option<i64>,
392 pub title: Option<String>,
393 pub uri: String,
394 #[serde(rename = "_meta")]
400 pub meta: Option<Meta>,
401}
402
403impl ResourceLink {
404 #[must_use]
405 pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
406 Self {
407 annotations: None,
408 description: None,
409 mime_type: None,
410 name: name.into(),
411 size: None,
412 title: None,
413 uri: uri.into(),
414 meta: None,
415 }
416 }
417
418 #[must_use]
419 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
420 self.annotations = annotations.into_option();
421 self
422 }
423
424 #[must_use]
425 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
426 self.description = description.into_option();
427 self
428 }
429
430 #[must_use]
431 pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
432 self.mime_type = mime_type.into_option();
433 self
434 }
435
436 #[must_use]
437 pub fn size(mut self, size: impl IntoOption<i64>) -> Self {
438 self.size = size.into_option();
439 self
440 }
441
442 #[must_use]
443 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
444 self.title = title.into_option();
445 self
446 }
447
448 #[must_use]
454 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
455 self.meta = meta.into_option();
456 self
457 }
458}
459
460#[serde_as]
462#[skip_serializing_none]
463#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, Default)]
464#[serde(rename_all = "camelCase")]
465#[non_exhaustive]
466pub struct Annotations {
467 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
468 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
469 #[serde(default)]
470 pub audience: Option<Vec<Role>>,
471 pub last_modified: Option<String>,
472 pub priority: Option<f64>,
473 #[serde(rename = "_meta")]
479 pub meta: Option<Meta>,
480}
481
482impl Annotations {
483 #[must_use]
484 pub fn new() -> Self {
485 Self::default()
486 }
487
488 #[must_use]
489 pub fn audience(mut self, audience: impl IntoOption<Vec<Role>>) -> Self {
490 self.audience = audience.into_option();
491 self
492 }
493
494 #[must_use]
495 pub fn last_modified(mut self, last_modified: impl IntoOption<String>) -> Self {
496 self.last_modified = last_modified.into_option();
497 self
498 }
499
500 #[must_use]
501 pub fn priority(mut self, priority: impl IntoOption<f64>) -> Self {
502 self.priority = priority.into_option();
503 self
504 }
505
506 #[must_use]
512 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
513 self.meta = meta.into_option();
514 self
515 }
516}
517
518#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
520#[serde(rename_all = "camelCase")]
521#[non_exhaustive]
522pub enum Role {
523 Assistant,
524 User,
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_text_content_roundtrip() {
533 let content = TextContent::new("hello world");
534 let json = serde_json::to_value(&content).unwrap();
535 let parsed: TextContent = serde_json::from_value(json).unwrap();
536 assert_eq!(content, parsed);
537 }
538
539 #[test]
540 fn test_text_content_omits_optional_fields() {
541 let content = TextContent::new("hello");
542 let json = serde_json::to_value(&content).unwrap();
543 assert!(!json.as_object().unwrap().contains_key("annotations"));
544 assert!(!json.as_object().unwrap().contains_key("meta"));
545 }
546
547 #[test]
548 fn test_text_content_from_string() {
549 let block: ContentBlock = "hello".into();
550 match block {
551 ContentBlock::Text(c) => assert_eq!(c.text, "hello"),
552 _ => panic!("Expected Text variant"),
553 }
554 }
555
556 #[test]
557 fn test_image_content_roundtrip() {
558 let content = ImageContent::new("base64data", "image/png");
559 let json = serde_json::to_value(&content).unwrap();
560 let parsed: ImageContent = serde_json::from_value(json).unwrap();
561 assert_eq!(content, parsed);
562 }
563
564 #[test]
565 fn test_image_content_omits_optional_fields() {
566 let content = ImageContent::new("data", "image/png");
567 let json = serde_json::to_value(&content).unwrap();
568 assert!(!json.as_object().unwrap().contains_key("uri"));
569 assert!(!json.as_object().unwrap().contains_key("annotations"));
570 assert!(!json.as_object().unwrap().contains_key("meta"));
571 }
572
573 #[test]
574 fn test_image_content_with_uri() {
575 let content = ImageContent::new("data", "image/png").uri("https://example.com/image.png");
576 let json = serde_json::to_value(&content).unwrap();
577 assert_eq!(json["uri"], "https://example.com/image.png");
578 }
579
580 #[test]
581 fn test_audio_content_roundtrip() {
582 let content = AudioContent::new("base64audio", "audio/mp3");
583 let json = serde_json::to_value(&content).unwrap();
584 let parsed: AudioContent = serde_json::from_value(json).unwrap();
585 assert_eq!(content, parsed);
586 }
587
588 #[test]
589 fn test_audio_content_omits_optional_fields() {
590 let content = AudioContent::new("data", "audio/mp3");
591 let json = serde_json::to_value(&content).unwrap();
592 assert!(!json.as_object().unwrap().contains_key("annotations"));
593 assert!(!json.as_object().unwrap().contains_key("meta"));
594 }
595}