1use serde::{Deserialize, Serialize};
7
8use super::media::{AudioMediaType, DocumentMediaType, ImageMediaType, VideoMediaType};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12#[serde(untagged)]
13pub enum UserContent {
14 Text(String),
16 Parts(Vec<UserContentPart>),
18}
19
20impl UserContent {
21 #[must_use]
23 pub fn text(s: impl Into<String>) -> Self {
24 Self::Text(s.into())
25 }
26
27 #[must_use]
29 pub fn parts(parts: Vec<UserContentPart>) -> Self {
30 Self::Parts(parts)
31 }
32
33 #[must_use]
35 pub fn is_text(&self) -> bool {
36 matches!(self, Self::Text(_))
37 }
38
39 #[must_use]
41 pub fn as_text(&self) -> Option<&str> {
42 match self {
43 Self::Text(s) => Some(s),
44 _ => None,
45 }
46 }
47
48 #[must_use]
50 pub fn to_parts(&self) -> Vec<UserContentPart> {
51 match self {
52 Self::Text(s) => vec![UserContentPart::Text { text: s.clone() }],
53 Self::Parts(parts) => parts.clone(),
54 }
55 }
56}
57
58impl Default for UserContent {
59 fn default() -> Self {
60 Self::Text(String::new())
61 }
62}
63
64impl From<String> for UserContent {
65 fn from(s: String) -> Self {
66 Self::Text(s)
67 }
68}
69
70impl From<&str> for UserContent {
71 fn from(s: &str) -> Self {
72 Self::Text(s.to_string())
73 }
74}
75
76impl From<Vec<UserContentPart>> for UserContent {
77 fn from(parts: Vec<UserContentPart>) -> Self {
78 Self::Parts(parts)
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84#[serde(tag = "type", rename_all = "snake_case")]
85pub enum UserContentPart {
86 Text {
88 text: String,
90 },
91 Image {
93 #[serde(flatten)]
95 image: ImageContent,
96 },
97 Audio {
99 #[serde(flatten)]
101 audio: AudioContent,
102 },
103 Video {
105 #[serde(flatten)]
107 video: VideoContent,
108 },
109 Document {
111 #[serde(flatten)]
113 document: DocumentContent,
114 },
115 File {
117 #[serde(flatten)]
119 file: FileContent,
120 },
121}
122
123impl UserContentPart {
124 #[must_use]
126 pub fn text(s: impl Into<String>) -> Self {
127 Self::Text { text: s.into() }
128 }
129
130 #[must_use]
132 pub fn image_url(url: impl Into<String>) -> Self {
133 Self::Image {
134 image: ImageContent::url(url),
135 }
136 }
137
138 #[must_use]
140 pub fn image_binary(data: Vec<u8>, media_type: ImageMediaType) -> Self {
141 Self::Image {
142 image: ImageContent::binary(data, media_type),
143 }
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
149#[serde(untagged)]
150pub enum ImageContent {
151 Url(ImageUrl),
153 Binary(BinaryImage),
155}
156
157impl ImageContent {
158 #[must_use]
160 pub fn url(url: impl Into<String>) -> Self {
161 Self::Url(ImageUrl::new(url))
162 }
163
164 #[must_use]
166 pub fn binary(data: Vec<u8>, media_type: ImageMediaType) -> Self {
167 Self::Binary(BinaryImage::new(data, media_type))
168 }
169
170 #[must_use]
172 pub fn media_type(&self) -> Option<ImageMediaType> {
173 match self {
174 Self::Url(u) => u.media_type,
175 Self::Binary(b) => Some(b.media_type),
176 }
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct ImageUrl {
183 pub url: String,
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub media_type: Option<ImageMediaType>,
188 #[serde(default)]
190 pub force_download: bool,
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub vendor_metadata: Option<serde_json::Value>,
194}
195
196impl ImageUrl {
197 #[must_use]
199 pub fn new(url: impl Into<String>) -> Self {
200 Self {
201 url: url.into(),
202 media_type: None,
203 force_download: false,
204 vendor_metadata: None,
205 }
206 }
207
208 #[must_use]
210 pub fn with_media_type(mut self, media_type: ImageMediaType) -> Self {
211 self.media_type = Some(media_type);
212 self
213 }
214
215 #[must_use]
217 pub fn with_force_download(mut self, force: bool) -> Self {
218 self.force_download = force;
219 self
220 }
221
222 #[must_use]
224 pub fn with_vendor_metadata(mut self, metadata: serde_json::Value) -> Self {
225 self.vendor_metadata = Some(metadata);
226 self
227 }
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
232pub struct BinaryImage {
233 #[serde(with = "base64_serde")]
235 pub data: Vec<u8>,
236 pub media_type: ImageMediaType,
238}
239
240impl BinaryImage {
241 #[must_use]
243 pub fn new(data: Vec<u8>, media_type: ImageMediaType) -> Self {
244 Self { data, media_type }
245 }
246
247 #[must_use]
249 pub fn to_base64(&self) -> String {
250 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &self.data)
251 }
252
253 #[must_use]
255 pub fn to_data_url(&self) -> String {
256 format!(
257 "data:{};base64,{}",
258 self.media_type.mime_type(),
259 self.to_base64()
260 )
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
266#[serde(untagged)]
267pub enum AudioContent {
268 Url(AudioUrl),
270 Binary(BinaryAudio),
272}
273
274impl AudioContent {
275 #[must_use]
277 pub fn url(url: impl Into<String>) -> Self {
278 Self::Url(AudioUrl::new(url))
279 }
280
281 #[must_use]
283 pub fn binary(data: Vec<u8>, media_type: AudioMediaType) -> Self {
284 Self::Binary(BinaryAudio::new(data, media_type))
285 }
286}
287
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
290pub struct AudioUrl {
291 pub url: String,
293 #[serde(skip_serializing_if = "Option::is_none")]
295 pub media_type: Option<AudioMediaType>,
296 #[serde(default)]
298 pub force_download: bool,
299 #[serde(skip_serializing_if = "Option::is_none")]
301 pub vendor_metadata: Option<serde_json::Value>,
302}
303
304impl AudioUrl {
305 #[must_use]
307 pub fn new(url: impl Into<String>) -> Self {
308 Self {
309 url: url.into(),
310 media_type: None,
311 force_download: false,
312 vendor_metadata: None,
313 }
314 }
315
316 #[must_use]
318 pub fn with_media_type(mut self, media_type: AudioMediaType) -> Self {
319 self.media_type = Some(media_type);
320 self
321 }
322}
323
324#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
326pub struct BinaryAudio {
327 #[serde(with = "base64_serde")]
329 pub data: Vec<u8>,
330 pub media_type: AudioMediaType,
332}
333
334impl BinaryAudio {
335 #[must_use]
337 pub fn new(data: Vec<u8>, media_type: AudioMediaType) -> Self {
338 Self { data, media_type }
339 }
340}
341
342#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
344#[serde(untagged)]
345pub enum VideoContent {
346 Url(VideoUrl),
348 Binary(BinaryVideo),
350}
351
352impl VideoContent {
353 #[must_use]
355 pub fn url(url: impl Into<String>) -> Self {
356 Self::Url(VideoUrl::new(url))
357 }
358
359 #[must_use]
361 pub fn binary(data: Vec<u8>, media_type: VideoMediaType) -> Self {
362 Self::Binary(BinaryVideo::new(data, media_type))
363 }
364}
365
366#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
368pub struct VideoUrl {
369 pub url: String,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub media_type: Option<VideoMediaType>,
374 #[serde(default)]
376 pub force_download: bool,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub vendor_metadata: Option<serde_json::Value>,
380}
381
382impl VideoUrl {
383 #[must_use]
385 pub fn new(url: impl Into<String>) -> Self {
386 Self {
387 url: url.into(),
388 media_type: None,
389 force_download: false,
390 vendor_metadata: None,
391 }
392 }
393
394 #[must_use]
396 pub fn with_media_type(mut self, media_type: VideoMediaType) -> Self {
397 self.media_type = Some(media_type);
398 self
399 }
400}
401
402#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
404pub struct BinaryVideo {
405 #[serde(with = "base64_serde")]
407 pub data: Vec<u8>,
408 pub media_type: VideoMediaType,
410}
411
412impl BinaryVideo {
413 #[must_use]
415 pub fn new(data: Vec<u8>, media_type: VideoMediaType) -> Self {
416 Self { data, media_type }
417 }
418}
419
420#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
422#[serde(untagged)]
423pub enum DocumentContent {
424 Url(DocumentUrl),
426 Binary(BinaryDocument),
428}
429
430impl DocumentContent {
431 #[must_use]
433 pub fn url(url: impl Into<String>) -> Self {
434 Self::Url(DocumentUrl::new(url))
435 }
436
437 #[must_use]
439 pub fn binary(data: Vec<u8>, media_type: DocumentMediaType) -> Self {
440 Self::Binary(BinaryDocument::new(data, media_type))
441 }
442}
443
444#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
446pub struct DocumentUrl {
447 pub url: String,
449 #[serde(skip_serializing_if = "Option::is_none")]
451 pub media_type: Option<DocumentMediaType>,
452 #[serde(default)]
454 pub force_download: bool,
455 #[serde(skip_serializing_if = "Option::is_none")]
457 pub vendor_metadata: Option<serde_json::Value>,
458}
459
460impl DocumentUrl {
461 #[must_use]
463 pub fn new(url: impl Into<String>) -> Self {
464 Self {
465 url: url.into(),
466 media_type: None,
467 force_download: false,
468 vendor_metadata: None,
469 }
470 }
471
472 #[must_use]
474 pub fn with_media_type(mut self, media_type: DocumentMediaType) -> Self {
475 self.media_type = Some(media_type);
476 self
477 }
478}
479
480#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
482pub struct BinaryDocument {
483 #[serde(with = "base64_serde")]
485 pub data: Vec<u8>,
486 pub media_type: DocumentMediaType,
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub filename: Option<String>,
491}
492
493impl BinaryDocument {
494 #[must_use]
496 pub fn new(data: Vec<u8>, media_type: DocumentMediaType) -> Self {
497 Self {
498 data,
499 media_type,
500 filename: None,
501 }
502 }
503
504 #[must_use]
506 pub fn with_filename(mut self, filename: impl Into<String>) -> Self {
507 self.filename = Some(filename.into());
508 self
509 }
510}
511
512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
514#[serde(untagged)]
515pub enum FileContent {
516 Url(FileUrl),
518 Binary(BinaryFile),
520}
521
522impl FileContent {
523 #[must_use]
525 pub fn url(url: impl Into<String>) -> Self {
526 Self::Url(FileUrl::new(url))
527 }
528
529 #[must_use]
531 pub fn binary(data: Vec<u8>, mime_type: impl Into<String>) -> Self {
532 Self::Binary(BinaryFile::new(data, mime_type))
533 }
534}
535
536#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
538pub struct FileUrl {
539 pub url: String,
541 #[serde(skip_serializing_if = "Option::is_none")]
543 pub mime_type: Option<String>,
544 #[serde(default)]
546 pub force_download: bool,
547 #[serde(skip_serializing_if = "Option::is_none")]
549 pub vendor_metadata: Option<serde_json::Value>,
550}
551
552impl FileUrl {
553 #[must_use]
555 pub fn new(url: impl Into<String>) -> Self {
556 Self {
557 url: url.into(),
558 mime_type: None,
559 force_download: false,
560 vendor_metadata: None,
561 }
562 }
563
564 #[must_use]
566 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
567 self.mime_type = Some(mime_type.into());
568 self
569 }
570}
571
572#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
574pub struct BinaryFile {
575 #[serde(with = "base64_serde")]
577 pub data: Vec<u8>,
578 pub mime_type: String,
580 #[serde(skip_serializing_if = "Option::is_none")]
582 pub filename: Option<String>,
583}
584
585impl BinaryFile {
586 #[must_use]
588 pub fn new(data: Vec<u8>, mime_type: impl Into<String>) -> Self {
589 Self {
590 data,
591 mime_type: mime_type.into(),
592 filename: None,
593 }
594 }
595
596 #[must_use]
598 pub fn with_filename(mut self, filename: impl Into<String>) -> Self {
599 self.filename = Some(filename.into());
600 self
601 }
602}
603
604mod base64_serde {
606 use base64::{engine::general_purpose::STANDARD, Engine};
607 use serde::{Deserialize, Deserializer, Serializer};
608
609 pub fn serialize<S>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error>
610 where
611 S: Serializer,
612 {
613 serializer.serialize_str(&STANDARD.encode(data))
614 }
615
616 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
617 where
618 D: Deserializer<'de>,
619 {
620 let s = String::deserialize(deserializer)?;
621 STANDARD.decode(s).map_err(serde::de::Error::custom)
622 }
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628
629 #[test]
630 fn test_user_content_text() {
631 let content = UserContent::text("Hello, world!");
632 assert!(content.is_text());
633 assert_eq!(content.as_text(), Some("Hello, world!"));
634 }
635
636 #[test]
637 fn test_user_content_from_string() {
638 let content: UserContent = "Hello".into();
639 assert!(content.is_text());
640 }
641
642 #[test]
643 fn test_image_url() {
644 let img =
645 ImageUrl::new("https://example.com/image.png").with_media_type(ImageMediaType::Png);
646 assert_eq!(img.url, "https://example.com/image.png");
647 assert_eq!(img.media_type, Some(ImageMediaType::Png));
648 }
649
650 #[test]
651 fn test_binary_image_to_data_url() {
652 let img = BinaryImage::new(vec![1, 2, 3, 4], ImageMediaType::Png);
653 let data_url = img.to_data_url();
654 assert!(data_url.starts_with("data:image/png;base64,"));
655 }
656
657 #[test]
658 fn test_serde_roundtrip() {
659 let content = UserContent::parts(vec![
660 UserContentPart::text("Hello"),
661 UserContentPart::image_url("https://example.com/img.jpg"),
662 ]);
663 let json = serde_json::to_string(&content).unwrap();
664 let parsed: UserContent = serde_json::from_str(&json).unwrap();
665 assert_eq!(content, parsed);
666 }
667}