1use serde::{Deserialize, Serialize};
4
5use super::tools::{ToolResult, ToolUse};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum Role {
11 User,
12 Assistant,
13}
14
15impl std::fmt::Display for Role {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 write!(f, "{}", self.as_str())
18 }
19}
20
21impl Role {
22 pub fn as_str(&self) -> &'static str {
24 match self {
25 Role::User => "user",
26 Role::Assistant => "assistant",
27 }
28 }
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
33#[serde(rename_all = "camelCase")]
34pub struct ContentBlock {
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub text: Option<String>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub image: Option<ImageContent>,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub document: Option<DocumentContent>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub video: Option<VideoContent>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub tool_use: Option<ToolUse>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub tool_result: Option<ToolResult>,
52
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub reasoning_content: Option<ReasoningContentBlock>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub cache_point: Option<CachePoint>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub guard_content: Option<GuardContent>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub citations_content: Option<CitationsContentBlock>,
64}
65
66impl ContentBlock {
67 pub fn text(text: impl Into<String>) -> Self {
68 Self { text: Some(text.into()), ..Default::default() }
69 }
70
71 pub fn tool_use(tool_use: ToolUse) -> Self {
72 Self { tool_use: Some(tool_use), ..Default::default() }
73 }
74
75 pub fn tool_result(result: ToolResult) -> Self {
76 Self { tool_result: Some(result), ..Default::default() }
77 }
78
79 pub fn is_text(&self) -> bool { self.text.is_some() }
80 pub fn is_tool_use(&self) -> bool { self.tool_use.is_some() }
81 pub fn is_tool_result(&self) -> bool { self.tool_result.is_some() }
82 pub fn as_text(&self) -> Option<&str> { self.text.as_deref() }
83 pub fn as_tool_use(&self) -> Option<&ToolUse> { self.tool_use.as_ref() }
84 pub fn as_tool_result(&self) -> Option<&ToolResult> { self.tool_result.as_ref() }
85}
86
87impl From<String> for ContentBlock {
88 fn from(text: String) -> Self { Self::text(text) }
89}
90
91impl From<&str> for ContentBlock {
92 fn from(text: &str) -> Self { Self::text(text) }
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct ImageContent {
99 pub format: String,
100 pub source: ImageSource,
101}
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct ImageSource {
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub bytes: Option<String>,
109}
110
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct DocumentContent {
115 pub format: String,
116 pub name: String,
117 pub source: DocumentSource,
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct DocumentSource {
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub bytes: Option<String>,
126}
127
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct VideoContent {
132 pub format: String,
133 pub source: VideoSource,
134}
135
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct VideoSource {
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub bytes: Option<String>,
142}
143
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
146#[serde(rename_all = "camelCase")]
147pub struct ReasoningContentBlock {
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub reasoning_text: Option<ReasoningTextBlock>,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub redacted_content: Option<Vec<u8>>,
153}
154
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
157#[serde(rename_all = "camelCase")]
158pub struct ReasoningTextBlock {
159 pub text: String,
160
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub signature: Option<String>,
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167#[serde(rename_all = "camelCase")]
168pub struct CachePoint {
169 #[serde(rename = "type")]
170 pub cache_type: String,
171}
172
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub struct GuardContent {
177 pub text: GuardContentText,
178}
179
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct GuardContentText {
184 pub qualifiers: Vec<String>,
185 pub text: String,
186}
187
188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct Message {
192 pub role: Role,
193 pub content: Vec<ContentBlock>,
194}
195
196impl Message {
197 pub fn user(text: impl Into<String>) -> Self {
198 Self { role: Role::User, content: vec![ContentBlock::text(text)] }
199 }
200
201 pub fn assistant(text: impl Into<String>) -> Self {
202 Self { role: Role::Assistant, content: vec![ContentBlock::text(text)] }
203 }
204
205 pub fn new(role: Role, content: Vec<ContentBlock>) -> Self {
206 Self { role, content }
207 }
208
209 pub fn text_content(&self) -> String {
210 self.content.iter().filter_map(|b| b.as_text()).collect::<Vec<_>>().join("")
211 }
212
213 pub fn has_tool_use(&self) -> bool {
214 self.content.iter().any(|b| b.is_tool_use())
215 }
216
217 pub fn tool_uses(&self) -> Vec<&ToolUse> {
218 self.content.iter().filter_map(|b| b.as_tool_use()).collect()
219 }
220
221 pub fn has_tool_result(&self) -> bool {
222 self.content.iter().any(|b| b.is_tool_result())
223 }
224}
225
226pub type Messages = Vec<Message>;
228
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
231#[serde(rename_all = "camelCase")]
232pub struct SystemContentBlock {
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub text: Option<String>,
235
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub cache_point: Option<CachePoint>,
238}
239
240impl SystemContentBlock {
241 pub fn text(text: impl Into<String>) -> Self {
242 Self { text: Some(text.into()), cache_point: None }
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
248#[serde(rename_all = "camelCase")]
249pub struct CitationsContentBlock {
250 #[serde(skip_serializing_if = "Option::is_none")]
251 pub citations: Option<Vec<Citation>>,
252
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub content: Option<Vec<CitationGeneratedContent>>,
255}
256
257#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
259#[serde(rename_all = "camelCase")]
260pub struct Citation {
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub location: Option<serde_json::Value>,
263
264 #[serde(skip_serializing_if = "Option::is_none")]
265 pub source_content: Option<Vec<CitationSourceContent>>,
266
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub title: Option<String>,
269}
270
271#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
273#[serde(rename_all = "camelCase")]
274pub struct CitationSourceContent {
275 #[serde(skip_serializing_if = "Option::is_none")]
276 pub text: Option<String>,
277}
278
279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
281#[serde(rename_all = "camelCase")]
282pub struct CitationGeneratedContent {
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub text: Option<String>,
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn test_role_serialization() {
293 assert_eq!(serde_json::to_string(&Role::User).unwrap(), "\"user\"");
294 assert_eq!(serde_json::to_string(&Role::Assistant).unwrap(), "\"assistant\"");
295 }
296
297 #[test]
298 fn test_message_creation() {
299 let msg = Message::user("Hello, world!");
300 assert_eq!(msg.role, Role::User);
301 assert_eq!(msg.text_content(), "Hello, world!");
302 }
303
304 #[test]
305 fn test_content_block_from_string() {
306 let block: ContentBlock = "test".into();
307 assert!(block.is_text());
308 assert_eq!(block.as_text(), Some("test"));
309 }
310
311 #[test]
312 fn test_content_block_serialization() {
313 let block = ContentBlock::text("hello");
314 let json = serde_json::to_string(&block).unwrap();
315 assert_eq!(json, r#"{"text":"hello"}"#);
316 }
317}