anthropic_sdk/types/
messages.rs

1use serde::{Deserialize, Serialize};
2use crate::types::shared::{RequestId, Usage};
3use crate::files::{File, FileError};
4
5/// A message from Claude
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct Message {
8    /// Unique object identifier
9    pub id: String,
10    
11    /// Object type - always "message" for Messages
12    #[serde(rename = "type")]
13    pub type_: String,
14    
15    /// Conversational role - always "assistant" for responses
16    pub role: Role,
17    
18    /// Content generated by the model
19    pub content: Vec<ContentBlock>,
20    
21    /// The model that completed the prompt
22    pub model: String,
23    
24    /// The reason that generation stopped
25    pub stop_reason: Option<StopReason>,
26    
27    /// Which custom stop sequence was generated, if any
28    pub stop_sequence: Option<String>,
29    
30    /// Billing and rate-limit usage
31    pub usage: Usage,
32    
33    /// Request ID for tracking (extracted from headers)
34    #[serde(skip)]
35    pub request_id: Option<RequestId>,
36}
37
38/// Conversational role
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
40#[serde(rename_all = "lowercase")]
41pub enum Role {
42    User,
43    Assistant,
44}
45
46/// Content blocks that can appear in messages
47#[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/// Image source for image content blocks
72#[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/// Reasons why the model stopped generating
88#[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/// Parameters for creating a new message
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct MessageCreateParams {
100    /// The model to use for completion
101    pub model: String,
102    
103    /// Maximum number of tokens to generate
104    pub max_tokens: u32,
105    
106    /// Input messages for the conversation
107    pub messages: Vec<MessageParam>,
108    
109    /// System prompt (optional)
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub system: Option<String>,
112    
113    /// Amount of randomness (0.0 to 1.0)
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub temperature: Option<f32>,
116    
117    /// Use nucleus sampling
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub top_p: Option<f32>,
120    
121    /// Only sample from top K options
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub top_k: Option<u32>,
124    
125    /// Custom stop sequences
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub stop_sequences: Option<Vec<String>>,
128    
129    /// Whether to stream the response
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub stream: Option<bool>,
132    
133    /// Tools available for the model to use
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub tools: Option<Vec<crate::types::Tool>>,
136    
137    /// Tool choice strategy
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub tool_choice: Option<crate::types::ToolChoice>,
140    
141    /// Additional metadata
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub metadata: Option<std::collections::HashMap<String, String>>,
144}
145
146/// A single message in the conversation
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct MessageParam {
149    pub role: Role,
150    pub content: MessageContent,
151}
152
153/// Content for a message parameter  
154#[derive(Debug, Clone, Serialize, Deserialize)]
155#[serde(untagged)]
156pub enum MessageContent {
157    /// Simple text content
158    Text(String),
159    /// Array of content blocks (text, images, etc.)
160    Blocks(Vec<ContentBlockParam>),
161}
162
163/// Content block parameters for input messages
164#[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/// Builder for creating message requests ergonomically
189#[derive(Debug, Clone)]
190pub struct MessageCreateBuilder {
191    params: MessageCreateParams,
192}
193
194impl MessageCreateBuilder {
195    /// Create a new message builder
196    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    /// Add a message to the conversation
216    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    /// Add a user message
225    pub fn user(self, content: impl Into<MessageContent>) -> Self {
226        self.message(Role::User, content)
227    }
228    
229    /// Add an assistant message
230    pub fn assistant(self, content: impl Into<MessageContent>) -> Self {
231        self.message(Role::Assistant, content)
232    }
233    
234    /// Set the system prompt
235    pub fn system(mut self, system: impl Into<String>) -> Self {
236        self.params.system = Some(system.into());
237        self
238    }
239    
240    /// Set the temperature
241    pub fn temperature(mut self, temperature: f32) -> Self {
242        self.params.temperature = Some(temperature);
243        self
244    }
245    
246    /// Set top_p
247    pub fn top_p(mut self, top_p: f32) -> Self {
248        self.params.top_p = Some(top_p);
249        self
250    }
251    
252    /// Set top_k
253    pub fn top_k(mut self, top_k: u32) -> Self {
254        self.params.top_k = Some(top_k);
255        self
256    }
257    
258    /// Set custom stop sequences
259    pub fn stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
260        self.params.stop_sequences = Some(stop_sequences);
261        self
262    }
263    
264    /// Enable streaming
265    pub fn stream(mut self, stream: bool) -> Self {
266        self.params.stream = Some(stream);
267        self
268    }
269    
270    /// Set tools available for the model to use
271    pub fn tools(mut self, tools: Vec<crate::types::Tool>) -> Self {
272        self.params.tools = Some(tools);
273        self
274    }
275    
276    /// Set tool choice strategy
277    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    /// Set metadata
283    pub fn metadata(mut self, metadata: std::collections::HashMap<String, String>) -> Self {
284        self.params.metadata = Some(metadata);
285        self
286    }
287    
288    /// Build the message creation parameters
289    pub fn build(self) -> MessageCreateParams {
290        self.params
291    }
292}
293
294// Convenient conversions for MessageContent
295impl 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
313// Helper constructors for ContentBlockParam
314impl ContentBlockParam {
315    /// Create a text content block
316    pub fn text(text: impl Into<String>) -> Self {
317        Self::Text { text: text.into() }
318    }
319    
320    /// Create an image content block from base64 data
321    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    /// Create an image content block from URL
331    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    /// Create an image content block from a File
340    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    /// Create an image content block from a File (convenience method)
358    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}