Skip to main content

conversation_api/
content.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5#[serde(tag = "kind", rename_all = "snake_case")]
6pub enum MediaSource {
7    Artifact { uri: String },
8}
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11#[serde(tag = "type", rename_all = "snake_case")]
12pub enum MessagePart {
13    Text {
14        text: String,
15    },
16    Image {
17        source: MediaSource,
18        #[serde(default, skip_serializing_if = "Option::is_none")]
19        mime_type: Option<String>,
20        #[serde(default, skip_serializing_if = "Option::is_none")]
21        alt_text: Option<String>,
22        #[serde(default, skip_serializing_if = "Option::is_none")]
23        detail: Option<String>,
24        #[serde(default, skip_serializing_if = "Option::is_none")]
25        width_px: Option<u32>,
26        #[serde(default, skip_serializing_if = "Option::is_none")]
27        height_px: Option<u32>,
28        #[serde(default, skip_serializing_if = "Option::is_none")]
29        size_bytes: Option<u64>,
30    },
31    Audio {
32        source: MediaSource,
33        #[serde(default, skip_serializing_if = "Option::is_none")]
34        mime_type: Option<String>,
35        #[serde(default, skip_serializing_if = "Option::is_none")]
36        transcript: Option<String>,
37        #[serde(default, skip_serializing_if = "Option::is_none")]
38        duration_ms: Option<u64>,
39        #[serde(default, skip_serializing_if = "Option::is_none")]
40        size_bytes: Option<u64>,
41    },
42    Video {
43        source: MediaSource,
44        #[serde(default, skip_serializing_if = "Option::is_none")]
45        mime_type: Option<String>,
46        #[serde(default, skip_serializing_if = "Option::is_none")]
47        alt_text: Option<String>,
48        #[serde(default, skip_serializing_if = "Option::is_none")]
49        duration_ms: Option<u64>,
50        #[serde(default, skip_serializing_if = "Option::is_none")]
51        width_px: Option<u32>,
52        #[serde(default, skip_serializing_if = "Option::is_none")]
53        height_px: Option<u32>,
54        #[serde(default, skip_serializing_if = "Option::is_none")]
55        size_bytes: Option<u64>,
56    },
57    File {
58        source: MediaSource,
59        #[serde(default, skip_serializing_if = "Option::is_none")]
60        mime_type: Option<String>,
61        #[serde(default, skip_serializing_if = "Option::is_none")]
62        name: Option<String>,
63        #[serde(default, skip_serializing_if = "Option::is_none")]
64        size_bytes: Option<u64>,
65    },
66    Json {
67        value: Value,
68    },
69}
70
71#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
72#[serde(rename_all = "camelCase")]
73pub struct MessageContent {
74    #[serde(default)]
75    pub parts: Vec<MessagePart>,
76}
77
78impl MessageContent {
79    pub fn text(text: impl Into<String>) -> Self {
80        Self {
81            parts: vec![MessagePart::Text { text: text.into() }],
82        }
83    }
84
85    pub fn plain_text(&self) -> String {
86        self.parts
87            .iter()
88            .filter_map(|part| match part {
89                MessagePart::Text { text } => Some(text.as_str()),
90                _ => None,
91            })
92            .collect::<Vec<_>>()
93            .join("\n")
94    }
95
96    pub fn has_non_text_parts(&self) -> bool {
97        self.parts
98            .iter()
99            .any(|part| !matches!(part, MessagePart::Text { .. }))
100    }
101}
102
103#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
104#[serde(rename_all = "snake_case")]
105pub enum ConversationMessageRole {
106    User,
107    Assistant,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
111#[serde(rename_all = "camelCase")]
112pub struct ConversationMessage {
113    pub message_id: String,
114    pub request_id: String,
115    pub role: ConversationMessageRole,
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub author_user_id: Option<String>,
118    pub content: MessageContent,
119    pub created_at: String,
120}