Skip to main content

anthropic_async/resources/
messages.rs

1use crate::{
2    client::Client,
3    config::Config,
4    error::AnthropicError,
5    types::common::validate_mixed_ttl_order,
6    types::content::{ContentBlockParam, MessageContentParam, SystemParam},
7    types::messages::{
8        MessageTokensCountRequest, MessageTokensCountResponse, MessagesCreateRequest,
9        MessagesCreateResponse,
10    },
11};
12
13/// Validate a messages create request
14///
15/// Checks TTL ordering across system+messages content blocks and validates sampling parameters.
16fn validate_messages_create_request(req: &MessagesCreateRequest) -> Result<(), AnthropicError> {
17    // Validate TTL ordering across system+messages content blocks
18    let mut ttls = Vec::new();
19
20    // Scan system blocks
21    if let Some(system) = &req.system
22        && let SystemParam::Blocks(blocks) = system
23    {
24        for tb in blocks {
25            if let Some(cc) = &tb.cache_control
26                && let Some(ttl) = &cc.ttl
27            {
28                ttls.push(ttl.clone());
29            }
30        }
31    }
32
33    // Scan message blocks
34    for message in &req.messages {
35        if let MessageContentParam::Blocks(blocks) = &message.content {
36            for block in blocks {
37                match block {
38                    ContentBlockParam::Text {
39                        cache_control: Some(cc),
40                        ..
41                    }
42                    | ContentBlockParam::Image {
43                        cache_control: Some(cc),
44                        ..
45                    }
46                    | ContentBlockParam::Document {
47                        cache_control: Some(cc),
48                        ..
49                    }
50                    | ContentBlockParam::ToolResult {
51                        cache_control: Some(cc),
52                        ..
53                    } => {
54                        if let Some(ttl) = &cc.ttl {
55                            ttls.push(ttl.clone());
56                        }
57                    }
58                    _ => {}
59                }
60            }
61        }
62    }
63
64    if !validate_mixed_ttl_order(ttls) {
65        return Err(AnthropicError::Config(
66            "Invalid cache_control TTL ordering: 1h must precede 5m".into(),
67        ));
68    }
69
70    // Validate sampling parameters
71    if let Some(t) = req.temperature
72        && !(0.0..=1.0).contains(&t)
73    {
74        return Err(AnthropicError::Config(format!(
75            "Invalid temperature {t}: must be in [0.0, 1.0]"
76        )));
77    }
78
79    if let Some(p) = req.top_p
80        && (!(0.0..=1.0).contains(&p) || p == 0.0)
81    {
82        return Err(AnthropicError::Config(format!(
83            "Invalid top_p {p}: must be in (0.0, 1.0]"
84        )));
85    }
86
87    if let Some(k) = req.top_k
88        && k < 1
89    {
90        return Err(AnthropicError::Config(format!(
91            "Invalid top_k {k}: must be >= 1"
92        )));
93    }
94
95    if req.max_tokens == 0 {
96        return Err(AnthropicError::Config(
97            "max_tokens must be greater than 0".into(),
98        ));
99    }
100
101    Ok(())
102}
103
104/// API resource for the `/v1/messages` endpoints
105///
106/// Provides methods to create messages and count tokens.
107pub struct Messages<'c, C: Config> {
108    client: &'c Client<C>,
109}
110
111impl<'c, C: Config> Messages<'c, C> {
112    /// Creates a new Messages resource
113    #[must_use]
114    pub const fn new(client: &'c Client<C>) -> Self {
115        Self { client }
116    }
117
118    /// Create a new message
119    ///
120    /// # Errors
121    ///
122    /// Returns an error if:
123    /// - The request fails to send
124    /// - The `cache_control` TTL ordering is invalid (1h must precede 5m)
125    /// - The API returns an error
126    pub async fn create(
127        &self,
128        req: MessagesCreateRequest,
129    ) -> Result<MessagesCreateResponse, AnthropicError> {
130        // Centralized validation
131        validate_messages_create_request(&req)?;
132
133        self.client.post("/v1/messages", req).await
134    }
135
136    /// Count tokens for a message request
137    ///
138    /// # Errors
139    ///
140    /// Returns an error if:
141    /// - The request fails to send
142    /// - The API returns an error
143    pub async fn count_tokens(
144        &self,
145        req: MessageTokensCountRequest,
146    ) -> Result<MessageTokensCountResponse, AnthropicError> {
147        // No TTL validation needed for token counting
148        self.client.post("/v1/messages/count_tokens", req).await
149    }
150
151    /// Create a new message with streaming response
152    ///
153    /// Returns a stream of SSE events that can be processed as they arrive.
154    /// The request will automatically have `stream: true` set.
155    ///
156    /// # Example
157    ///
158    /// ```ignore
159    /// use futures::StreamExt;
160    ///
161    /// let mut stream = client.messages().create_stream(req).await?;
162    /// while let Some(event) = stream.next().await {
163    ///     match event? {
164    ///         Event::ContentBlockDelta { delta, .. } => {
165    ///             if let ContentBlockDeltaData::TextDelta { text } = delta {
166    ///                 print!("{}", text);
167    ///             }
168    ///         }
169    ///         Event::MessageStop => break,
170    ///         _ => {}
171    ///     }
172    /// }
173    /// ```
174    ///
175    /// # Errors
176    ///
177    /// Returns an error if:
178    /// - The request fails to send
179    /// - The API returns an error (non-2xx status)
180    #[cfg(feature = "streaming")]
181    pub async fn create_stream(
182        &self,
183        mut req: MessagesCreateRequest,
184    ) -> Result<crate::streaming::EventStream, AnthropicError> {
185        // Force streaming mode
186        req.stream = Some(true);
187
188        // Centralized validation
189        validate_messages_create_request(&req)?;
190
191        let response = self.client.post_stream("/v1/messages", req).await?;
192        Ok(crate::sse::streaming::event_stream_from_response(response))
193    }
194}
195
196// Add to client
197impl<C: Config> crate::Client<C> {
198    /// Returns the Messages API resource
199    #[must_use]
200    pub const fn messages(&self) -> Messages<'_, C> {
201        Messages::new(self)
202    }
203}