1use serde::{Deserialize, Serialize};
2
3use super::common::CacheControl;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(untagged)]
11pub enum ToolResultContent {
12 String(String),
14 Blocks(Vec<ToolResultContentBlock>),
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
22#[serde(tag = "type", rename_all = "snake_case")]
23pub enum ToolResultContentBlock {
24 Text {
26 text: String,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 cache_control: Option<CacheControl>,
31 },
32 Image {
34 source: ImageSource,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 cache_control: Option<CacheControl>,
39 },
40}
41
42impl From<&str> for ToolResultContent {
43 fn from(s: &str) -> Self {
44 Self::String(s.to_string())
45 }
46}
47
48impl From<String> for ToolResultContent {
49 fn from(s: String) -> Self {
50 Self::String(s)
51 }
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
56#[serde(tag = "type", rename_all = "snake_case")]
57pub enum ImageSource {
58 Base64 {
60 media_type: String,
62 data: String,
64 },
65 Url {
67 url: String,
69 },
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
74#[serde(tag = "type", rename_all = "snake_case")]
75pub enum DocumentSource {
76 Base64 {
78 media_type: String,
80 data: String,
82 },
83 Url {
85 url: String,
87 },
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
92#[serde(rename_all = "snake_case")]
93pub enum MessageRole {
94 User,
96 Assistant,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
108#[serde(tag = "type", rename_all = "snake_case")]
109pub enum ContentBlockParam {
110 Text {
112 text: String,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 cache_control: Option<CacheControl>,
117 },
118 ToolResult {
120 tool_use_id: String,
122 #[serde(skip_serializing_if = "Option::is_none")]
124 content: Option<ToolResultContent>,
125 #[serde(skip_serializing_if = "Option::is_none")]
127 is_error: Option<bool>,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 cache_control: Option<CacheControl>,
131 },
132 Image {
134 source: ImageSource,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 cache_control: Option<CacheControl>,
139 },
140 Document {
142 source: DocumentSource,
144 #[serde(skip_serializing_if = "Option::is_none")]
146 cache_control: Option<CacheControl>,
147 },
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
157#[serde(tag = "type", rename_all = "snake_case")]
158pub enum ContentBlock {
159 Text {
161 text: String,
163 },
164 ToolUse {
166 id: String,
168 name: String,
170 input: serde_json::Value,
172 },
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
179#[serde(untagged)]
180pub enum SystemParam {
181 String(String),
183 Blocks(Vec<TextBlockParam>),
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
191#[serde(untagged)]
192pub enum MessageContentParam {
193 String(String),
195 Blocks(Vec<ContentBlockParam>),
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
201pub struct TextBlockParam {
202 pub text: String,
204 #[serde(
206 rename = "type",
207 default = "text_type",
208 skip_serializing_if = "is_text"
209 )]
210 pub kind: String,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub cache_control: Option<CacheControl>,
214}
215
216fn text_type() -> String {
217 "text".to_string()
218}
219
220fn is_text(s: &str) -> bool {
221 s == "text"
222}
223
224impl TextBlockParam {
225 #[must_use]
227 pub fn new(text: impl Into<String>) -> Self {
228 Self {
229 text: text.into(),
230 kind: "text".to_string(),
231 cache_control: None,
232 }
233 }
234
235 #[must_use]
237 pub fn with_cache_control(text: impl Into<String>, cache_control: CacheControl) -> Self {
238 Self {
239 text: text.into(),
240 kind: "text".to_string(),
241 cache_control: Some(cache_control),
242 }
243 }
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
248pub struct MessageParam {
249 pub role: MessageRole,
251 pub content: MessageContentParam,
253}
254
255impl From<&str> for MessageContentParam {
257 fn from(s: &str) -> Self {
258 Self::String(s.to_string())
259 }
260}
261
262impl From<String> for MessageContentParam {
263 fn from(s: String) -> Self {
264 Self::String(s)
265 }
266}
267
268impl From<&str> for SystemParam {
269 fn from(s: &str) -> Self {
270 Self::String(s.to_string())
271 }
272}
273
274impl From<String> for SystemParam {
275 fn from(s: String) -> Self {
276 Self::String(s)
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn message_role_ser() {
286 assert_eq!(
287 serde_json::to_string(&MessageRole::User).unwrap(),
288 r#""user""#
289 );
290 assert_eq!(
291 serde_json::to_string(&MessageRole::Assistant).unwrap(),
292 r#""assistant""#
293 );
294 }
295
296 #[test]
297 fn content_block_param_text_ser() {
298 let cb = ContentBlockParam::Text {
299 text: "hello".into(),
300 cache_control: None,
301 };
302 let s = serde_json::to_string(&cb).unwrap();
303 assert!(s.contains(r#""type":"text""#));
304 assert!(s.contains(r#""text":"hello""#));
305 assert!(!s.contains("cache_control"));
306 }
307
308 #[test]
309 fn content_block_response_text_ser() {
310 let cb = ContentBlock::Text {
311 text: "response".into(),
312 };
313 let s = serde_json::to_string(&cb).unwrap();
314 assert!(s.contains(r#""type":"text""#));
315 assert!(s.contains(r#""text":"response""#));
316 }
317
318 #[test]
319 fn system_param_string() {
320 let sys: SystemParam = "You are helpful".into();
321 let s = serde_json::to_string(&sys).unwrap();
322 assert_eq!(s, r#""You are helpful""#);
323 }
324
325 #[test]
326 fn system_param_blocks() {
327 let sys = SystemParam::Blocks(vec![TextBlockParam::new("test")]);
328 let s = serde_json::to_string(&sys).unwrap();
329 assert!(s.contains(r#""text":"test""#));
330 }
331
332 #[test]
333 fn message_content_param_string() {
334 let content: MessageContentParam = "hello".into();
335 let s = serde_json::to_string(&content).unwrap();
336 assert_eq!(s, r#""hello""#);
337 }
338
339 #[test]
340 fn message_content_param_blocks() {
341 let content = MessageContentParam::Blocks(vec![ContentBlockParam::Text {
342 text: "test".into(),
343 cache_control: None,
344 }]);
345 let s = serde_json::to_string(&content).unwrap();
346 assert!(s.contains(r#""type":"text""#));
347 assert!(s.contains(r#""text":"test""#));
348 }
349
350 #[test]
351 fn text_block_param_with_cache() {
352 let tb = TextBlockParam::with_cache_control("cached", CacheControl::ephemeral_1h());
353 let s = serde_json::to_string(&tb).unwrap();
354 assert!(s.contains(r#""text":"cached""#));
355 assert!(s.contains(r#""cache_control""#));
356 }
357}