1use serde::{Deserialize, Serialize};
2use crate::types::shared::{RequestId, Usage};
3use crate::files::{File, FileError};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct Message {
8 pub id: String,
10
11 #[serde(rename = "type")]
13 pub type_: String,
14
15 pub role: Role,
17
18 pub content: Vec<ContentBlock>,
20
21 pub model: String,
23
24 pub stop_reason: Option<StopReason>,
26
27 pub stop_sequence: Option<String>,
29
30 pub usage: Usage,
32
33 #[serde(skip)]
35 pub request_id: Option<RequestId>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
40#[serde(rename_all = "lowercase")]
41pub enum Role {
42 User,
43 Assistant,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
48#[serde(tag = "type")]
49pub enum ContentBlock {
50 #[serde(rename = "text")]
51 Text { text: String },
52
53 #[serde(rename = "image")]
54 Image { source: ImageSource },
55
56 #[serde(rename = "tool_use")]
57 ToolUse {
58 id: String,
59 name: String,
60 input: serde_json::Value,
61 },
62
63 #[serde(rename = "tool_result")]
64 ToolResult {
65 tool_use_id: String,
66 content: Option<String>,
67 is_error: Option<bool>,
68 },
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
73#[serde(tag = "type")]
74pub enum ImageSource {
75 #[serde(rename = "base64")]
76 Base64 {
77 media_type: String,
78 data: String,
79 },
80
81 #[serde(rename = "url")]
82 Url {
83 url: String,
84 },
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
89#[serde(rename_all = "snake_case")]
90pub enum StopReason {
91 EndTurn,
92 MaxTokens,
93 StopSequence,
94 ToolUse,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct MessageCreateParams {
100 pub model: String,
102
103 pub max_tokens: u32,
105
106 pub messages: Vec<MessageParam>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub system: Option<String>,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub temperature: Option<f32>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub top_p: Option<f32>,
120
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub top_k: Option<u32>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub stop_sequences: Option<Vec<String>>,
128
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub stream: Option<bool>,
132
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub tools: Option<Vec<crate::types::Tool>>,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub tool_choice: Option<crate::types::ToolChoice>,
140
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub metadata: Option<std::collections::HashMap<String, String>>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct MessageParam {
149 pub role: Role,
150 pub content: MessageContent,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155#[serde(untagged)]
156pub enum MessageContent {
157 Text(String),
159 Blocks(Vec<ContentBlockParam>),
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165#[serde(tag = "type")]
166pub enum ContentBlockParam {
167 #[serde(rename = "text")]
168 Text { text: String },
169
170 #[serde(rename = "image")]
171 Image { source: ImageSource },
172
173 #[serde(rename = "tool_use")]
174 ToolUse {
175 id: String,
176 name: String,
177 input: serde_json::Value,
178 },
179
180 #[serde(rename = "tool_result")]
181 ToolResult {
182 tool_use_id: String,
183 content: Option<String>,
184 is_error: Option<bool>,
185 },
186}
187
188#[derive(Debug, Clone)]
190pub struct MessageCreateBuilder {
191 params: MessageCreateParams,
192}
193
194impl MessageCreateBuilder {
195 pub fn new(model: impl Into<String>, max_tokens: u32) -> Self {
197 Self {
198 params: MessageCreateParams {
199 model: model.into(),
200 max_tokens,
201 messages: Vec::new(),
202 system: None,
203 temperature: None,
204 top_p: None,
205 top_k: None,
206 stop_sequences: None,
207 stream: None,
208 tools: None,
209 tool_choice: None,
210 metadata: None,
211 },
212 }
213 }
214
215 pub fn message(mut self, role: Role, content: impl Into<MessageContent>) -> Self {
217 self.params.messages.push(MessageParam {
218 role,
219 content: content.into(),
220 });
221 self
222 }
223
224 pub fn user(self, content: impl Into<MessageContent>) -> Self {
226 self.message(Role::User, content)
227 }
228
229 pub fn assistant(self, content: impl Into<MessageContent>) -> Self {
231 self.message(Role::Assistant, content)
232 }
233
234 pub fn system(mut self, system: impl Into<String>) -> Self {
236 self.params.system = Some(system.into());
237 self
238 }
239
240 pub fn temperature(mut self, temperature: f32) -> Self {
242 self.params.temperature = Some(temperature);
243 self
244 }
245
246 pub fn top_p(mut self, top_p: f32) -> Self {
248 self.params.top_p = Some(top_p);
249 self
250 }
251
252 pub fn top_k(mut self, top_k: u32) -> Self {
254 self.params.top_k = Some(top_k);
255 self
256 }
257
258 pub fn stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
260 self.params.stop_sequences = Some(stop_sequences);
261 self
262 }
263
264 pub fn stream(mut self, stream: bool) -> Self {
266 self.params.stream = Some(stream);
267 self
268 }
269
270 pub fn tools(mut self, tools: Vec<crate::types::Tool>) -> Self {
272 self.params.tools = Some(tools);
273 self
274 }
275
276 pub fn tool_choice(mut self, tool_choice: crate::types::ToolChoice) -> Self {
278 self.params.tool_choice = Some(tool_choice);
279 self
280 }
281
282 pub fn metadata(mut self, metadata: std::collections::HashMap<String, String>) -> Self {
284 self.params.metadata = Some(metadata);
285 self
286 }
287
288 pub fn build(self) -> MessageCreateParams {
290 self.params
291 }
292}
293
294impl From<String> for MessageContent {
296 fn from(text: String) -> Self {
297 Self::Text(text)
298 }
299}
300
301impl From<&str> for MessageContent {
302 fn from(text: &str) -> Self {
303 Self::Text(text.to_string())
304 }
305}
306
307impl From<Vec<ContentBlockParam>> for MessageContent {
308 fn from(blocks: Vec<ContentBlockParam>) -> Self {
309 Self::Blocks(blocks)
310 }
311}
312
313impl ContentBlockParam {
315 pub fn text(text: impl Into<String>) -> Self {
317 Self::Text { text: text.into() }
318 }
319
320 pub fn image_base64(media_type: impl Into<String>, data: impl Into<String>) -> Self {
322 Self::Image {
323 source: ImageSource::Base64 {
324 media_type: media_type.into(),
325 data: data.into(),
326 },
327 }
328 }
329
330 pub fn image_url(url: impl Into<String>) -> Self {
332 Self::Image {
333 source: ImageSource::Url {
334 url: url.into(),
335 },
336 }
337 }
338
339 pub async fn image_file(file: File) -> Result<Self, FileError> {
341 if !file.is_image() {
342 return Err(FileError::InvalidMimeType {
343 mime_type: file.mime_type.to_string(),
344 allowed: vec!["image/*".to_string()],
345 });
346 }
347
348 let base64_data = file.to_base64().await?;
349 Ok(Self::Image {
350 source: ImageSource::Base64 {
351 media_type: file.mime_type.to_string(),
352 data: base64_data,
353 },
354 })
355 }
356
357 pub async fn from_file(file: File) -> Result<Self, FileError> {
359 Self::image_file(file).await
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_message_builder() {
369 let params = MessageCreateBuilder::new("claude-3-5-sonnet-latest", 1024)
370 .user("Hello, Claude!")
371 .system("You are a helpful assistant.")
372 .temperature(0.7)
373 .build();
374
375 assert_eq!(params.model, "claude-3-5-sonnet-latest");
376 assert_eq!(params.max_tokens, 1024);
377 assert_eq!(params.messages.len(), 1);
378 assert_eq!(params.messages[0].role, Role::User);
379 assert_eq!(params.system, Some("You are a helpful assistant.".to_string()));
380 assert_eq!(params.temperature, Some(0.7));
381 }
382
383 #[test]
384 fn test_content_block_creation() {
385 let text_block = ContentBlockParam::text("Hello world");
386 match text_block {
387 ContentBlockParam::Text { text } => assert_eq!(text, "Hello world"),
388 _ => panic!("Expected text block"),
389 }
390
391 let image_block = ContentBlockParam::image_base64("image/jpeg", "base64data");
392 match image_block {
393 ContentBlockParam::Image { source } => match source {
394 ImageSource::Base64 { media_type, data } => {
395 assert_eq!(media_type, "image/jpeg");
396 assert_eq!(data, "base64data");
397 },
398 _ => panic!("Expected base64 image source"),
399 },
400 _ => panic!("Expected image block"),
401 }
402 }
403
404 #[test]
405 fn test_message_content_from_string() {
406 let content: MessageContent = "Hello".into();
407 match content {
408 MessageContent::Text(text) => assert_eq!(text, "Hello"),
409 _ => panic!("Expected text content"),
410 }
411 }
412}