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 #[serde(default)]
20 pub content: Vec<ContentBlock>,
21
22 pub model: String,
24
25 pub stop_reason: Option<StopReason>,
27
28 pub stop_sequence: Option<String>,
30
31 pub usage: Usage,
33
34 #[serde(skip)]
36 pub request_id: Option<RequestId>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
41#[serde(rename_all = "lowercase")]
42pub enum Role {
43 User,
44 Assistant,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
49#[serde(tag = "type")]
50pub enum ContentBlock {
51 #[serde(rename = "text")]
52 Text { text: String },
53
54 #[serde(rename = "thinking")]
55 Thinking {
56 thinking: String,
57 signature: String,
58 },
59
60 #[serde(rename = "image")]
61 Image { source: ImageSource },
62
63 #[serde(rename = "tool_use")]
64 ToolUse {
65 id: String,
66 name: String,
67 input: serde_json::Value,
68 },
69
70 #[serde(rename = "tool_result")]
71 ToolResult {
72 tool_use_id: String,
73 content: Option<String>,
74 is_error: Option<bool>,
75 },
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80#[serde(tag = "type")]
81pub enum ImageSource {
82 #[serde(rename = "base64")]
83 Base64 {
84 media_type: String,
85 data: String,
86 },
87
88 #[serde(rename = "url")]
89 Url {
90 url: String,
91 },
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
96#[serde(rename_all = "snake_case")]
97pub enum StopReason {
98 EndTurn,
99 MaxTokens,
100 StopSequence,
101 ToolUse,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct MessageCreateParams {
107 pub model: String,
109
110 pub max_tokens: u32,
112
113 pub messages: Vec<MessageParam>,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub system: Option<String>,
119
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub temperature: Option<f32>,
123
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub top_p: Option<f32>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub top_k: Option<u32>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub stop_sequences: Option<Vec<String>>,
135
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub stream: Option<bool>,
139
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub tools: Option<Vec<crate::types::Tool>>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub tool_choice: Option<crate::types::ToolChoice>,
147
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub metadata: Option<std::collections::HashMap<String, String>>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct MessageParam {
156 pub role: Role,
157 pub content: MessageContent,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(untagged)]
163pub enum MessageContent {
164 Text(String),
166 Blocks(Vec<ContentBlockParam>),
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(tag = "type")]
173pub enum ContentBlockParam {
174 #[serde(rename = "text")]
175 Text { text: String },
176
177 #[serde(rename = "thinking")]
178 Thinking {
179 thinking: String,
180 signature: String,
181 },
182
183 #[serde(rename = "image")]
184 Image { source: ImageSource },
185
186 #[serde(rename = "tool_use")]
187 ToolUse {
188 id: String,
189 name: String,
190 input: serde_json::Value,
191 },
192
193 #[serde(rename = "tool_result")]
194 ToolResult {
195 tool_use_id: String,
196 content: Option<String>,
197 is_error: Option<bool>,
198 },
199}
200
201#[derive(Debug, Clone)]
203pub struct MessageCreateBuilder {
204 params: MessageCreateParams,
205}
206
207impl MessageCreateBuilder {
208 pub fn new(model: impl Into<String>, max_tokens: u32) -> Self {
210 Self {
211 params: MessageCreateParams {
212 model: model.into(),
213 max_tokens,
214 messages: Vec::new(),
215 system: None,
216 temperature: None,
217 top_p: None,
218 top_k: None,
219 stop_sequences: None,
220 stream: None,
221 tools: None,
222 tool_choice: None,
223 metadata: None,
224 },
225 }
226 }
227
228 pub fn message(mut self, role: Role, content: impl Into<MessageContent>) -> Self {
230 self.params.messages.push(MessageParam {
231 role,
232 content: content.into(),
233 });
234 self
235 }
236
237 pub fn user(self, content: impl Into<MessageContent>) -> Self {
239 self.message(Role::User, content)
240 }
241
242 pub fn assistant(self, content: impl Into<MessageContent>) -> Self {
244 self.message(Role::Assistant, content)
245 }
246
247 pub fn system(mut self, system: impl Into<String>) -> Self {
249 self.params.system = Some(system.into());
250 self
251 }
252
253 pub fn temperature(mut self, temperature: f32) -> Self {
255 self.params.temperature = Some(temperature);
256 self
257 }
258
259 pub fn top_p(mut self, top_p: f32) -> Self {
261 self.params.top_p = Some(top_p);
262 self
263 }
264
265 pub fn top_k(mut self, top_k: u32) -> Self {
267 self.params.top_k = Some(top_k);
268 self
269 }
270
271 pub fn stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
273 self.params.stop_sequences = Some(stop_sequences);
274 self
275 }
276
277 pub fn stream(mut self, stream: bool) -> Self {
279 self.params.stream = Some(stream);
280 self
281 }
282
283 pub fn tools(mut self, tools: Vec<crate::types::Tool>) -> Self {
285 self.params.tools = Some(tools);
286 self
287 }
288
289 pub fn tool_choice(mut self, tool_choice: crate::types::ToolChoice) -> Self {
291 self.params.tool_choice = Some(tool_choice);
292 self
293 }
294
295 pub fn metadata(mut self, metadata: std::collections::HashMap<String, String>) -> Self {
297 self.params.metadata = Some(metadata);
298 self
299 }
300
301 pub fn build(self) -> MessageCreateParams {
303 self.params
304 }
305}
306
307impl From<String> for MessageContent {
309 fn from(text: String) -> Self {
310 Self::Text(text)
311 }
312}
313
314impl From<&str> for MessageContent {
315 fn from(text: &str) -> Self {
316 Self::Text(text.to_string())
317 }
318}
319
320impl From<Vec<ContentBlockParam>> for MessageContent {
321 fn from(blocks: Vec<ContentBlockParam>) -> Self {
322 Self::Blocks(blocks)
323 }
324}
325
326impl ContentBlockParam {
328 pub fn text(text: impl Into<String>) -> Self {
330 Self::Text { text: text.into() }
331 }
332
333 pub fn image_base64(media_type: impl Into<String>, data: impl Into<String>) -> Self {
335 Self::Image {
336 source: ImageSource::Base64 {
337 media_type: media_type.into(),
338 data: data.into(),
339 },
340 }
341 }
342
343 pub fn image_url(url: impl Into<String>) -> Self {
345 Self::Image {
346 source: ImageSource::Url {
347 url: url.into(),
348 },
349 }
350 }
351
352 pub async fn image_file(file: File) -> Result<Self, FileError> {
354 if !file.is_image() {
355 return Err(FileError::InvalidMimeType {
356 mime_type: file.mime_type.to_string(),
357 allowed: vec!["image/*".to_string()],
358 });
359 }
360
361 let base64_data = file.to_base64().await?;
362 Ok(Self::Image {
363 source: ImageSource::Base64 {
364 media_type: file.mime_type.to_string(),
365 data: base64_data,
366 },
367 })
368 }
369
370 pub async fn from_file(file: File) -> Result<Self, FileError> {
372 Self::image_file(file).await
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn test_message_builder() {
382 let params = MessageCreateBuilder::new("claude-3-5-sonnet-latest", 1024)
383 .user("Hello, Claude!")
384 .system("You are a helpful assistant.")
385 .temperature(0.7)
386 .build();
387
388 assert_eq!(params.model, "claude-3-5-sonnet-latest");
389 assert_eq!(params.max_tokens, 1024);
390 assert_eq!(params.messages.len(), 1);
391 assert_eq!(params.messages[0].role, Role::User);
392 assert_eq!(params.system, Some("You are a helpful assistant.".to_string()));
393 assert_eq!(params.temperature, Some(0.7));
394 }
395
396 #[test]
397 fn test_content_block_creation() {
398 let text_block = ContentBlockParam::text("Hello world");
399 match text_block {
400 ContentBlockParam::Text { text } => assert_eq!(text, "Hello world"),
401 _ => panic!("Expected text block"),
402 }
403
404 let image_block = ContentBlockParam::image_base64("image/jpeg", "base64data");
405 match image_block {
406 ContentBlockParam::Image { source } => match source {
407 ImageSource::Base64 { media_type, data } => {
408 assert_eq!(media_type, "image/jpeg");
409 assert_eq!(data, "base64data");
410 },
411 _ => panic!("Expected base64 image source"),
412 },
413 _ => panic!("Expected image block"),
414 }
415 }
416
417 #[test]
418 fn test_message_content_from_string() {
419 let content: MessageContent = "Hello".into();
420 match content {
421 MessageContent::Text(text) => assert_eq!(text, "Hello"),
422 _ => panic!("Expected text content"),
423 }
424 }
425}