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/// API resource for the `/v1/messages` endpoints
14///
15/// Provides methods to create messages and count tokens.
16pub struct Messages<'c, C: Config> {
17    client: &'c Client<C>,
18}
19
20impl<'c, C: Config> Messages<'c, C> {
21    /// Creates a new Messages resource
22    #[must_use]
23    pub const fn new(client: &'c Client<C>) -> Self {
24        Self { client }
25    }
26
27    /// Create a new message
28    ///
29    /// # Errors
30    ///
31    /// Returns an error if:
32    /// - The request fails to send
33    /// - The `cache_control` TTL ordering is invalid (1h must precede 5m)
34    /// - The API returns an error
35    pub async fn create(
36        &self,
37        req: MessagesCreateRequest,
38    ) -> Result<MessagesCreateResponse, AnthropicError> {
39        // Validate TTL ordering across system+messages content blocks
40        let mut ttls = Vec::new();
41
42        // Scan system blocks
43        if let Some(system) = &req.system
44            && let SystemParam::Blocks(blocks) = system
45        {
46            for tb in blocks {
47                if let Some(cc) = &tb.cache_control
48                    && let Some(ttl) = &cc.ttl
49                {
50                    ttls.push(ttl.clone());
51                }
52            }
53        }
54
55        // Scan message blocks
56        for message in &req.messages {
57            if let MessageContentParam::Blocks(blocks) = &message.content {
58                for block in blocks {
59                    match block {
60                        ContentBlockParam::Text {
61                            cache_control: Some(cc),
62                            ..
63                        }
64                        | ContentBlockParam::Image {
65                            cache_control: Some(cc),
66                            ..
67                        }
68                        | ContentBlockParam::Document {
69                            cache_control: Some(cc),
70                            ..
71                        }
72                        | ContentBlockParam::ToolResult {
73                            cache_control: Some(cc),
74                            ..
75                        } => {
76                            if let Some(ttl) = &cc.ttl {
77                                ttls.push(ttl.clone());
78                            }
79                        }
80                        _ => {}
81                    }
82                }
83            }
84        }
85
86        if !validate_mixed_ttl_order(ttls) {
87            return Err(AnthropicError::Config(
88                "Invalid cache_control TTL ordering: 1h must precede 5m".into(),
89            ));
90        }
91
92        // Validate sampling parameters
93        if let Some(t) = req.temperature
94            && !(0.0..=1.0).contains(&t)
95        {
96            return Err(AnthropicError::Config(format!(
97                "Invalid temperature {t}: must be in [0.0, 1.0]"
98            )));
99        }
100
101        if let Some(p) = req.top_p
102            && (!(0.0..=1.0).contains(&p) || p == 0.0)
103        {
104            return Err(AnthropicError::Config(format!(
105                "Invalid top_p {p}: must be in (0.0, 1.0]"
106            )));
107        }
108
109        if let Some(k) = req.top_k
110            && k < 1
111        {
112            return Err(AnthropicError::Config(format!(
113                "Invalid top_k {k}: must be >= 1"
114            )));
115        }
116
117        if req.max_tokens == 0 {
118            return Err(AnthropicError::Config(
119                "max_tokens must be greater than 0".into(),
120            ));
121        }
122
123        self.client.post("/v1/messages", req).await
124    }
125
126    /// Count tokens for a message request
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if:
131    /// - The request fails to send
132    /// - The API returns an error
133    pub async fn count_tokens(
134        &self,
135        req: MessageTokensCountRequest,
136    ) -> Result<MessageTokensCountResponse, AnthropicError> {
137        // No TTL validation needed for token counting
138        self.client.post("/v1/messages/count_tokens", req).await
139    }
140}
141
142// Add to client
143impl<C: Config> crate::Client<C> {
144    /// Returns the Messages API resource
145    #[must_use]
146    pub const fn messages(&self) -> Messages<'_, C> {
147        Messages::new(self)
148    }
149}