anthropic_api/
messages.rs

1//! # Messages API
2//!
3//! This module provides a Rust interface to Anthropic's Messages API, which allows you to interact
4//! with Claude models in a conversational manner.
5//!
6//! ## Key Features
7//!
8//! - Send messages to Claude models and receive responses
9//! - Support for streaming responses
10//! - Tool usage capabilities
11//! - Image input support
12//!
13//! ## Basic Usage
14//!
15//! ```no_run
16//! use anthropic_api::{messages::*, Credentials};
17//!
18//! #[tokio::main]
19//! async fn main() {
20//!     let credentials = Credentials::from_env();
21//!
22//!     let response =MessagesBuilder::builder(
23//!         "claude-3-7-sonnet-20250219",
24//!         vec![Message {
25//!             role: MessageRole::User,
26//!             content: MessageContent::Text("Hello, Claude!".to_string()),
27//!         }],
28//!         1024,
29//!     )
30//!     .credentials(credentials)
31//!     .create()
32//!     .await
33//!     .unwrap();
34//!
35//!     println!("Claude says: {:?}", response.content);
36//! }
37//! ```
38
39use crate::{anthropic_post, anthropic_request_stream, ApiResponseOrError, Credentials, Usage};
40use anyhow::Result;
41use derive_builder::Builder;
42use futures_util::StreamExt;
43use reqwest::Method;
44use reqwest_eventsource::{CannotCloneRequestError, Event, EventSource};
45use serde::{Deserialize, Serialize};
46use serde_json::Value;
47use tokio::sync::mpsc::{channel, Receiver, Sender};
48
49/// Represents a full message response from the Anthropic API.
50///
51/// This struct contains the complete response from a message request, including
52/// the model's generated content and usage statistics.
53#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
54pub struct MessagesResponse {
55    /// Unique identifier for this message
56    pub id: String,
57    /// The model that generated the response
58    pub model: String,
59    /// The role of the message sender (always Assistant for responses)
60    pub role: MessageRole,
61    /// The content blocks in the response (text, tool use, thinking, redacted thinking)
62    pub content: Vec<ResponseContentBlock>,
63    /// Reason why the model stopped generating, if applicable
64    pub stop_reason: Option<String>,
65    /// The specific sequence that caused generation to stop, if applicable
66    pub stop_sequence: Option<String>,
67    /// The type of the response (always "message")
68    #[serde(rename = "type")]
69    pub typ: String,
70    /// Token usage statistics for the request and response
71    pub usage: Usage,
72}
73
74/// Content block in a response, can be text or tool use.
75///
76/// Claude's responses can contain different types of content blocks.
77/// Currently, this can be either text, a tool use request, a thinking block, or a redacted thinking block.
78#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
79#[serde(tag = "type")]
80pub enum ResponseContentBlock {
81    /// A text content block containing natural language
82    #[serde(rename = "text")]
83    Text { text: String },
84    /// A tool use request from the model
85    #[serde(rename = "tool_use")]
86    ToolUse {
87        id: String,
88        name: String,
89        input: Value,
90    },
91    /// A thinking block from the model
92    #[serde(rename = "thinking")]
93    Thinking { signature: String, thinking: String },
94    /// A redacted thinking block from the model
95    #[serde(rename = "redacted_thinking")]
96    RedactedThinking { data: String },
97}
98
99/// Streaming events from the Anthropic API.
100///
101/// When using streaming mode, the API returns a series of events that
102/// incrementally build up the complete response.
103#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
104#[serde(tag = "type")]
105pub enum StreamEvent {
106    /// Indicates the start of a message
107    #[serde(rename = "message_start")]
108    MessageStart { message: MessageStart },
109    /// Indicates the start of a content block
110    #[serde(rename = "content_block_start")]
111    ContentBlockStart {
112        index: u32,
113        content_block: ContentBlockStart,
114    },
115    /// Contains a delta (incremental update) to a content block
116    #[serde(rename = "content_block_delta")]
117    ContentBlockDelta {
118        index: u32,
119        delta: ContentBlockDelta,
120    },
121    /// Indicates the end of a content block
122    #[serde(rename = "content_block_stop")]
123    ContentBlockStop { index: u32 },
124    /// Contains final message information like stop reason
125    #[serde(rename = "message_delta")]
126    MessageDelta { delta: MessageDelta, usage: Usage },
127    /// Indicates the end of the message
128    #[serde(rename = "message_stop")]
129    MessageStop,
130    /// A keepalive event that can be ignored
131    #[serde(rename = "ping")]
132    Ping,
133}
134
135/// Initial message information in a streaming response.
136#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
137pub struct MessageStart {
138    /// Unique identifier for this message
139    pub id: String,
140    /// The model generating the response
141    pub model: String,
142    /// The role of the message sender (always Assistant for responses)
143    pub role: MessageRole,
144    /// Initial content blocks in the response
145    pub content: Vec<ContentBlockStart>,
146}
147
148/// Initial content block in a streaming response.
149#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
150#[serde(untagged)]
151pub enum ContentBlockStart {
152    /// A text content block
153    Text { text: String },
154    /// A tool use request
155    ToolUse {
156        id: String,
157        name: String,
158        input: Value,
159    },
160}
161
162/// Incremental update to a content block in a streaming response.
163#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
164#[serde(untagged)]
165pub enum ContentBlockDelta {
166    /// Text delta for a text content block
167    Text { text: String },
168    /// JSON delta for a tool use input
169    InputJsonDelta { partial_json: String },
170}
171
172/// Final message information in a streaming response.
173#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
174pub struct MessageDelta {
175    /// Reason why the model stopped generating, if applicable
176    pub stop_reason: Option<String>,
177    /// The specific sequence that caused generation to stop, if applicable
178    pub stop_sequence: Option<String>,
179}
180
181/// Request to the Anthropic Messages API.
182///
183/// This struct represents a complete request to the Messages API,
184/// including all parameters that control generation behavior.
185#[derive(Serialize, Builder, Debug, Clone)]
186#[builder(derive(Clone, Debug, PartialEq))]
187#[builder(pattern = "owned")]
188#[builder(name = "MessagesBuilder")]
189#[builder(setter(strip_option, into))]
190pub struct MessagesRequest {
191    /// The model to use (e.g., "claude-3-7-sonnet-20250219").
192    pub model: String,
193    /// The conversation messages.
194    pub messages: Vec<Message>,
195    /// Maximum number of tokens to generate.
196    pub max_tokens: u64,
197    /// Optional metadata.
198    #[builder(default)]
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub metadata: Option<Metadata>,
201    /// Sequences where generation should stop.
202    #[builder(default)]
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub stop_sequences: Option<Vec<String>>,
205    /// Whether to stream the response.
206    #[builder(default)]
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub stream: Option<bool>,
209    /// System prompt to guide the assistant's behavior.
210    #[builder(default)]
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub system: Option<String>,
213    /// Sampling temperature (0.0 to 1.0).
214    #[builder(default)]
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub temperature: Option<f64>,
217    #[builder(default)]
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub thinking: Option<Thinking>,
220    /// Tool choice specification.
221    #[builder(default)]
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub tool_choice: Option<ToolChoice>,
224    /// Tools the assistant can use.
225    #[builder(default)]
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub tools: Option<Vec<Tool>>,
228    /// Top-k sampling parameter.
229    #[builder(default)]
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub top_k: Option<u32>,
232    /// Top-p (nucleus) sampling parameter.
233    #[builder(default)]
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub top_p: Option<f64>,
236    /// Credentials for authentication (not serialized).
237    #[serde(skip_serializing)]
238    #[builder(default)]
239    pub credentials: Option<Credentials>,
240}
241
242/// Message in the conversation.
243///
244/// Represents a single message in the conversation history,
245/// with a role (user or assistant) and content.
246#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
247pub struct Message {
248    /// The role of the message sender (user or assistant)
249    pub role: MessageRole,
250    /// The content of the message (text or content blocks)
251    pub content: MessageContent,
252}
253
254/// Role of the message sender.
255///
256/// In the Messages API, messages can be from either the user or the assistant.
257#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
258#[serde(rename_all = "lowercase")]
259pub enum MessageRole {
260    /// Message from the user
261    User,
262    /// Message from the assistant (Claude)
263    Assistant,
264}
265
266/// Content of a message, either text or content blocks.
267///
268/// Messages can contain either simple text or structured content blocks
269/// that can include text and images.
270#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
271#[serde(untagged)]
272pub enum MessageContent {
273    /// Simple text content
274    Text(String),
275    /// Structured content blocks (text and images)
276    ContentBlocks(Vec<RequestContentBlock>),
277}
278
279/// Content block in a request.
280///
281/// Request content blocks can be either text or images.
282#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
283#[serde(tag = "type")]
284pub enum RequestContentBlock {
285    /// A text content block
286    #[serde(rename = "text")]
287    Text { text: String },
288    /// An image content block
289    #[serde(rename = "image")]
290    Image { source: ImageSource },
291}
292
293/// Source of an image content block.
294///
295/// Currently, images must be provided as base64-encoded data.
296#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
297pub struct ImageSource {
298    /// The type of image source (currently only "base64" is supported)
299    #[serde(rename = "type")]
300    pub source_type: String,
301    /// The MIME type of the image (e.g., "image/png", "image/jpeg")
302    pub media_type: String,
303    /// The base64-encoded image data
304    pub data: String,
305}
306
307#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
308pub enum ThinkingType {
309    /// Whether Claude is to use thinking
310    #[serde(rename = "enabled")]
311    Enabled,
312    /// Whether Claude is not to use thinking
313    #[serde(rename = "disabled")]
314    Disabled,
315}
316
317#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
318pub struct Thinking {
319    #[serde(rename = "type")]
320    pub thinking_type: ThinkingType,
321    /// The budget for the thinking in tokens must
322    /// be at least 1024 and less than max_tokens
323    #[serde(rename = "budget_tokens")]
324    pub budget_tokens: u64,
325}
326
327/// Tool definition.
328///
329/// Tools allow Claude to perform actions outside its context,
330/// such as calculations or API calls.
331#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
332pub struct Tool {
333    /// The name of the tool
334    pub name: String,
335    /// A description of what the tool does
336    pub description: String,
337    /// JSON Schema defining the input format for the tool
338    pub input_schema: Value,
339}
340
341/// Tool choice specification.
342///
343/// Controls how Claude decides whether to use tools.
344#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
345#[serde(tag = "type")]
346pub enum ToolChoice {
347    /// Claude decides whether to use tools
348    #[serde(rename = "auto")]
349    Auto,
350    /// Claude can use any available tool
351    #[serde(rename = "any")]
352    Any,
353    /// Claude must use the specified tool
354    #[serde(rename = "tool")]
355    Tool { name: String },
356    /// Claude must not use any tools
357    #[serde(rename = "none")]
358    None,
359}
360
361/// Metadata for the request.
362///
363/// Additional information about the request that isn't
364/// directly related to generation behavior.
365#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
366pub struct Metadata {
367    /// Optional user identifier for tracking purposes
368    pub user_id: Option<String>,
369}
370
371// Implementation for non-streaming response
372impl MessagesResponse {
373    /// Creates a new message request and returns the response.
374    ///
375    /// This method sends a request to the Messages API and returns
376    /// the complete response.
377    ///
378    /// # Example
379    ///
380    /// ```no_run
381    /// # use anthropic_api::{messages::*, Credentials};
382    /// # #[tokio::main]
383    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
384    /// let credentials = Credentials::from_env();
385    /// let request = MessagesRequest {
386    ///     model: "claude-3-7-sonnet-20250219".to_string(),
387    ///     messages: vec![Message {
388    ///         role: MessageRole::User,
389    ///         content: MessageContent::Text("Hello!".to_string()),
390    ///     }],
391    ///     max_tokens: 100,
392    ///     credentials: Some(credentials),
393    ///     metadata: None,
394    ///     stop_sequences: None,
395    ///     stream: None,
396    ///     system: None,
397    ///     temperature: None,
398    ///     thinking: None,
399    ///     tool_choice: None,
400    ///     tools: None,
401    ///     top_k: None,
402    ///     top_p: None,
403    /// };
404    ///
405    /// let response = MessagesResponse::create(request).await?;
406    /// # Ok(())
407    /// # }
408    /// ```
409    pub async fn create(request: MessagesRequest) -> ApiResponseOrError<Self> {
410        let credentials_opt = request.credentials.clone();
411        anthropic_post("messages", &request, credentials_opt).await
412    }
413}
414
415// Implementation for streaming response
416impl StreamEvent {
417    /// Creates a new streaming message request and returns a channel of events.
418    ///
419    /// This method sends a request to the Messages API in streaming mode
420    /// and returns a channel that will receive the streaming events.
421    ///
422    /// # Example
423    ///
424    /// ```no_run
425    /// # use anthropic_api::{messages::*, Credentials};
426    /// # #[tokio::main]
427    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
428    /// let credentials = Credentials::from_env();
429    /// let mut request = MessagesRequest {
430    ///     model: "claude-3-7-sonnet-20250219".to_string(),
431    ///     messages: vec![Message {
432    ///         role: MessageRole::User,
433    ///         content: MessageContent::Text("Hello!".to_string()),
434    ///     }],
435    ///     max_tokens: 100,
436    ///     credentials: Some(credentials),
437    ///     metadata: None,
438    ///     stop_sequences: None,
439    ///     stream: Some(true),
440    ///     system: None,
441    ///     temperature: None,
442    ///     thinking: None,
443    ///     tool_choice: None,
444    ///     tools: None,
445    ///     top_k: None,
446    ///     top_p: None,
447    /// };
448    ///
449    /// let mut stream = StreamEvent::create_stream(request).await?;
450    ///
451    /// while let Some(event) = stream.recv().await {
452    ///     // Process streaming events
453    ///     println!("{:?}", event);
454    /// }
455    /// # Ok(())
456    /// # }
457    /// ```
458    pub async fn create_stream(
459        request: MessagesRequest,
460    ) -> Result<Receiver<Self>, CannotCloneRequestError> {
461        let credentials_opt = request.credentials.clone();
462        let stream = anthropic_request_stream(
463            Method::POST,
464            "messages",
465            |r| r.json(&request),
466            credentials_opt,
467        )
468        .await?;
469        let (tx, rx) = channel::<Self>(32);
470        tokio::spawn(forward_deserialized_anthropic_stream(stream, tx));
471        Ok(rx)
472    }
473}
474
475/// Processes the event stream and forwards events to the channel.
476///
477/// This internal function handles the raw event stream from the API
478/// and deserializes events into the `StreamEvent` enum.
479async fn forward_deserialized_anthropic_stream(
480    mut stream: EventSource,
481    tx: Sender<StreamEvent>,
482) -> anyhow::Result<()> {
483    while let Some(event) = stream.next().await {
484        let event = event?;
485        if let Event::Message(event) = event {
486            let stream_event = serde_json::from_str::<StreamEvent>(&event.data)?;
487            if matches!(stream_event, StreamEvent::Ping) {
488                continue; // Ignore ping events
489            }
490            tx.send(stream_event).await?;
491        }
492    }
493    Ok(())
494}
495
496// Builder convenience methods
497impl MessagesBuilder {
498    pub fn builder(model: &str, messages: impl Into<Vec<Message>>, max_tokens: u64) -> Self {
499        Self::create_empty()
500            .model(model)
501            .messages(messages)
502            .max_tokens(max_tokens)
503    }
504
505    /// Creates a new message request and returns the response.
506    ///
507    /// This is a convenience method that builds the request from the builder
508    /// and sends it to the Messages API.
509    ///
510    /// # Example
511    ///
512    /// ```no_run
513    /// # use anthropic_api::{messages::*, Credentials};
514    /// # #[tokio::main]
515    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
516    /// let credentials = Credentials::from_env();
517    ///
518    /// let response =MessagesBuilder::builder("claude-3-7-sonnet-20250219",[], 1024)
519    ///     .credentials(credentials.clone())
520    ///     .create()
521    ///     .await
522    ///     .unwrap();
523    /// # Ok(())
524    /// # }
525    /// ```
526    pub async fn create(self) -> ApiResponseOrError<MessagesResponse> {
527        let request = self.build().unwrap();
528        MessagesResponse::create(request).await
529    }
530
531    /// Creates a new streaming message request and returns a channel of events.
532    ///
533    /// This is a convenience method that builds the request from the builder
534    /// and sends it to the Messages API in streaming mode.
535    ///
536    /// # Example
537    ///
538    /// ```no_run
539    /// # use anthropic_api::{messages::*, Credentials};
540    /// # #[tokio::main]
541    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
542    /// let credentials = Credentials::from_env();
543    ///
544    /// let mut stream =MessagesBuilder::builder("claude-3-7-sonnet-20250219", [], 1024)
545    ///     .credentials(credentials)
546    ///     .create_stream()
547    ///     .await?;
548    ///
549    /// while let Some(event) = stream.recv().await {
550    ///     // Process streaming events
551    ///     println!("{:?}", event);
552    /// }
553    /// # Ok(())
554    /// # }
555    /// ```
556    pub async fn create_stream(self) -> Result<Receiver<StreamEvent>, CannotCloneRequestError> {
557        let mut request = self.build().expect("Failed to build MessagesRequest");
558        request.stream = Some(true);
559        StreamEvent::create_stream(request).await
560    }
561}
562
563// Helper to create a builder with required fields
564impl MessagesResponse {
565    /// Creates a new builder with the required fields.
566    ///
567    /// This is a convenience method to create a builder with the
568    /// minimum required fields for a message request.
569    ///
570    /// # Example
571    ///
572    /// ```no_run
573    /// # use anthropic_api::{messages::*, Credentials};
574    /// # #[tokio::main]
575    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
576    /// let credentials = Credentials::from_env();
577    ///
578    /// let response =MessagesBuilder::builder(
579    ///     "claude-3-7-sonnet-20250219",
580    ///     vec![Message {
581    ///         role: MessageRole::User,
582    ///         content: MessageContent::Text("Hello!".to_string()),
583    ///     }],
584    ///     100,
585    /// )
586    /// .credentials(credentials)
587    /// .create()
588    /// .await?;
589    /// # Ok(())
590    /// # }
591    /// ```
592    pub fn builder(
593        model: &str,
594        messages: impl Into<Vec<Message>>,
595        max_tokens: u64,
596    ) -> MessagesBuilder {
597        MessagesBuilder::create_empty()
598            .model(model)
599            .messages(messages)
600            .max_tokens(max_tokens)
601    }
602}
603
604#[cfg(test)]
605mod tests {
606    use super::*;
607
608    #[tokio::test]
609    async fn test_simple_message() {
610        let credentials = Credentials::from_env();
611
612        let response = MessagesResponse::builder(
613            "claude-3-7-sonnet-20250219",
614            vec![Message {
615                role: MessageRole::User,
616                content: MessageContent::Text("Hello!".to_string()),
617            }],
618            100,
619        )
620        .credentials(credentials)
621        .create()
622        .await
623        .unwrap();
624
625        assert!(!response.content.is_empty());
626    }
627
628    #[tokio::test]
629    async fn test_streaming_message() {
630        let credentials = Credentials::from_env();
631
632        let mut stream = MessagesResponse::builder(
633            "claude-3-7-sonnet-20250219",
634            vec![Message {
635                role: MessageRole::User,
636                content: MessageContent::Text("Hello!".to_string()),
637            }],
638            100,
639        )
640        .credentials(credentials)
641        .create_stream()
642        .await
643        .unwrap();
644
645        while let Some(event) = stream.recv().await {
646            match event {
647                StreamEvent::ContentBlockDelta { delta, .. } => {
648                    if let ContentBlockDelta::Text { text } = delta {
649                        print!("{}", text);
650                    }
651                }
652                _ => {}
653            }
654        }
655    }
656}