hehe_core/message/
content.rs

1use bytes::Bytes;
2use camino::Utf8PathBuf;
3use serde::{Deserialize, Serialize};
4use url::Url;
5
6#[derive(Clone, Debug, Serialize, Deserialize)]
7#[serde(tag = "type", rename_all = "snake_case")]
8pub enum Source {
9    Base64 { data: String },
10    Url { url: Url },
11    File { path: Utf8PathBuf },
12    #[serde(skip)]
13    Bytes(Bytes),
14}
15
16impl Source {
17    pub fn base64(data: impl Into<String>) -> Self {
18        Self::Base64 { data: data.into() }
19    }
20
21    pub fn url(url: Url) -> Self {
22        Self::Url { url }
23    }
24
25    pub fn file(path: impl Into<Utf8PathBuf>) -> Self {
26        Self::File { path: path.into() }
27    }
28
29    pub fn bytes(data: impl Into<Bytes>) -> Self {
30        Self::Bytes(data.into())
31    }
32}
33
34#[derive(Clone, Debug, Serialize, Deserialize)]
35pub struct ImageContent {
36    pub source: Source,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub media_type: Option<String>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub alt: Option<String>,
41}
42
43impl ImageContent {
44    pub fn new(source: Source) -> Self {
45        Self {
46            source,
47            media_type: None,
48            alt: None,
49        }
50    }
51
52    pub fn with_media_type(mut self, media_type: impl Into<String>) -> Self {
53        self.media_type = Some(media_type.into());
54        self
55    }
56
57    pub fn with_alt(mut self, alt: impl Into<String>) -> Self {
58        self.alt = Some(alt.into());
59        self
60    }
61}
62
63#[derive(Clone, Debug, Serialize, Deserialize)]
64pub struct AudioContent {
65    pub source: Source,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub media_type: Option<String>,
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub duration_ms: Option<u64>,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub transcript: Option<String>,
72}
73
74impl AudioContent {
75    pub fn new(source: Source) -> Self {
76        Self {
77            source,
78            media_type: None,
79            duration_ms: None,
80            transcript: None,
81        }
82    }
83}
84
85#[derive(Clone, Debug, Serialize, Deserialize)]
86pub struct VideoContent {
87    pub source: Source,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub media_type: Option<String>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub duration_ms: Option<u64>,
92}
93
94impl VideoContent {
95    pub fn new(source: Source) -> Self {
96        Self {
97            source,
98            media_type: None,
99            duration_ms: None,
100        }
101    }
102}
103
104#[derive(Clone, Debug, Serialize, Deserialize)]
105pub struct FileContent {
106    pub source: Source,
107    pub filename: String,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub media_type: Option<String>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub size: Option<u64>,
112}
113
114impl FileContent {
115    pub fn new(source: Source, filename: impl Into<String>) -> Self {
116        Self {
117            source,
118            filename: filename.into(),
119            media_type: None,
120            size: None,
121        }
122    }
123}
124
125#[derive(Clone, Debug, Serialize, Deserialize)]
126pub struct ToolUse {
127    pub id: String,
128    pub name: String,
129    pub input: serde_json::Value,
130}
131
132impl ToolUse {
133    pub fn new(id: impl Into<String>, name: impl Into<String>, input: serde_json::Value) -> Self {
134        Self {
135            id: id.into(),
136            name: name.into(),
137            input,
138        }
139    }
140}
141
142#[derive(Clone, Debug, Serialize, Deserialize)]
143pub struct ToolResult {
144    pub tool_use_id: String,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub content: Option<String>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub error: Option<String>,
149    #[serde(default)]
150    pub is_error: bool,
151}
152
153impl ToolResult {
154    pub fn success(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self {
155        Self {
156            tool_use_id: tool_use_id.into(),
157            content: Some(content.into()),
158            error: None,
159            is_error: false,
160        }
161    }
162
163    pub fn error(tool_use_id: impl Into<String>, error: impl Into<String>) -> Self {
164        Self {
165            tool_use_id: tool_use_id.into(),
166            content: None,
167            error: Some(error.into()),
168            is_error: true,
169        }
170    }
171}
172
173#[derive(Clone, Debug, Serialize, Deserialize)]
174#[serde(tag = "type", rename_all = "snake_case")]
175pub enum ContentBlock {
176    Text { text: String },
177    Image(ImageContent),
178    Audio(AudioContent),
179    Video(VideoContent),
180    File(FileContent),
181    ToolUse(ToolUse),
182    ToolResult(ToolResult),
183    #[serde(rename = "x-custom")]
184    Custom {
185        #[serde(rename = "x-type")]
186        custom_type: String,
187        data: serde_json::Value,
188    },
189}
190
191impl ContentBlock {
192    pub fn text(s: impl Into<String>) -> Self {
193        Self::Text { text: s.into() }
194    }
195
196    pub fn image(content: ImageContent) -> Self {
197        Self::Image(content)
198    }
199
200    pub fn audio(content: AudioContent) -> Self {
201        Self::Audio(content)
202    }
203
204    pub fn video(content: VideoContent) -> Self {
205        Self::Video(content)
206    }
207
208    pub fn file(content: FileContent) -> Self {
209        Self::File(content)
210    }
211
212    pub fn tool_use(tool_use: ToolUse) -> Self {
213        Self::ToolUse(tool_use)
214    }
215
216    pub fn tool_result(result: ToolResult) -> Self {
217        Self::ToolResult(result)
218    }
219
220    pub fn custom(custom_type: impl Into<String>, data: serde_json::Value) -> Self {
221        Self::Custom {
222            custom_type: custom_type.into(),
223            data,
224        }
225    }
226
227    pub fn is_text(&self) -> bool {
228        matches!(self, Self::Text { .. })
229    }
230
231    pub fn is_image(&self) -> bool {
232        matches!(self, Self::Image(_))
233    }
234
235    pub fn is_tool_use(&self) -> bool {
236        matches!(self, Self::ToolUse(_))
237    }
238
239    pub fn is_tool_result(&self) -> bool {
240        matches!(self, Self::ToolResult(_))
241    }
242
243    pub fn as_text(&self) -> Option<&str> {
244        match self {
245            Self::Text { text } => Some(text),
246            _ => None,
247        }
248    }
249
250    pub fn as_tool_use(&self) -> Option<&ToolUse> {
251        match self {
252            Self::ToolUse(tu) => Some(tu),
253            _ => None,
254        }
255    }
256
257    pub fn as_tool_result(&self) -> Option<&ToolResult> {
258        match self {
259            Self::ToolResult(tr) => Some(tr),
260            _ => None,
261        }
262    }
263}