Skip to main content

anthropic_async/
client.rs

1use backon::ExponentialBuilder;
2use backon::Retryable;
3use serde::Serialize;
4use serde::de::DeserializeOwned;
5
6use crate::config::Config;
7use crate::error::AnthropicError;
8use crate::retry;
9
10/// Anthropic API client
11///
12/// The client is generic over a [`Config`] implementation that provides authentication
13/// and API configuration.
14#[derive(Debug, Clone)]
15pub struct Client<C: Config> {
16    http: reqwest::Client,
17    config: C,
18    backoff: ExponentialBuilder,
19}
20
21impl Client<crate::config::AnthropicConfig> {
22    /// Creates a new client with default configuration
23    ///
24    /// Uses environment variables for authentication:
25    /// - `ANTHROPIC_API_KEY` for API key authentication
26    /// - `ANTHROPIC_AUTH_TOKEN` for bearer token authentication
27    #[must_use]
28    pub fn new() -> Self {
29        Self::with_config(crate::config::AnthropicConfig::new())
30    }
31}
32
33impl<C: Config + Default> Default for Client<C> {
34    fn default() -> Self {
35        Self::with_config(C::default())
36    }
37}
38
39impl<C: Config> Client<C> {
40    /// Creates a new client with the given configuration.
41    ///
42    /// # Panics
43    ///
44    /// Panics if the reqwest client cannot be built.
45    #[must_use]
46    #[expect(
47        clippy::expect_used,
48        reason = "reqwest client build failure is rare (TLS/resolver init) and fatal; matches reqwest::Client::new() pattern"
49    )]
50    pub fn with_config(config: C) -> Self {
51        Self {
52            http: reqwest::Client::builder()
53                .connect_timeout(std::time::Duration::from_secs(5))
54                .timeout(std::time::Duration::from_secs(600))
55                .build()
56                .expect("reqwest client"),
57            config,
58            backoff: retry::default_backoff_builder(),
59        }
60    }
61
62    /// Replaces the HTTP client with a custom one
63    ///
64    /// Useful for setting custom timeouts, proxies, or other HTTP configuration.
65    #[must_use]
66    pub fn with_http_client(mut self, http: reqwest::Client) -> Self {
67        self.http = http;
68        self
69    }
70
71    /// Replaces the backoff configuration for retry logic
72    ///
73    /// By default, the client uses exponential backoff with jitter.
74    #[must_use]
75    pub const fn with_backoff(mut self, backoff: ExponentialBuilder) -> Self {
76        self.backoff = backoff;
77        self
78    }
79
80    /// Returns a reference to the client's configuration
81    #[must_use]
82    pub const fn config(&self) -> &C {
83        &self.config
84    }
85
86    pub(crate) async fn get<O: DeserializeOwned>(&self, path: &str) -> Result<O, AnthropicError> {
87        let mk = || async {
88            let headers = self.config.headers()?;
89            Ok(self
90                .http
91                .get(self.config.url(path))
92                .headers(headers)
93                .query(&self.config.query())
94                .build()?)
95        };
96        self.execute(mk).await
97    }
98
99    pub(crate) async fn get_with_query<Q, O>(
100        &self,
101        path: &str,
102        query: &Q,
103    ) -> Result<O, AnthropicError>
104    where
105        Q: Serialize + Sync + ?Sized,
106        O: DeserializeOwned,
107    {
108        let mk = || async {
109            let headers = self.config.headers()?;
110            Ok(self
111                .http
112                .get(self.config.url(path))
113                .headers(headers)
114                .query(&self.config.query())
115                .query(query)
116                .build()?)
117        };
118        self.execute(mk).await
119    }
120
121    pub(crate) async fn post<I, O>(&self, path: &str, body: I) -> Result<O, AnthropicError>
122    where
123        I: Serialize + Send + Sync,
124        O: DeserializeOwned,
125    {
126        let mk = || async {
127            let headers = self.config.headers()?;
128            Ok(self
129                .http
130                .post(self.config.url(path))
131                .headers(headers)
132                .query(&self.config.query())
133                .json(&body)
134                .build()?)
135        };
136        self.execute(mk).await
137    }
138
139    /// Sends a POST request and returns the raw response for streaming.
140    ///
141    /// This method does not retry on error, as streaming responses cannot be replayed.
142    #[cfg(feature = "streaming")]
143    pub(crate) async fn post_stream<I: Serialize + Send + Sync>(
144        &self,
145        path: &str,
146        body: I,
147    ) -> Result<reqwest::Response, AnthropicError> {
148        // Validate auth before any request
149        self.config.validate_auth()?;
150
151        let headers = self.config.headers()?;
152        let request = self
153            .http
154            .post(self.config.url(path))
155            .headers(headers)
156            .query(&self.config.query())
157            .json(&body)
158            .build()?;
159
160        let response = self
161            .http
162            .execute(request)
163            .await
164            .map_err(AnthropicError::Reqwest)?;
165
166        let status = response.status();
167        if status.is_success() {
168            Ok(response)
169        } else {
170            let bytes = response.bytes().await.map_err(AnthropicError::Reqwest)?;
171            Err(crate::error::deserialize_api_error(status, &bytes))
172        }
173    }
174
175    async fn execute<O, M, Fut>(&self, mk: M) -> Result<O, AnthropicError>
176    where
177        O: DeserializeOwned,
178        M: Fn() -> Fut + Send + Sync,
179        Fut: core::future::Future<Output = Result<reqwest::Request, AnthropicError>> + Send,
180    {
181        // Validate auth before any request
182        self.config.validate_auth()?;
183
184        let bytes = self.execute_raw(mk).await?;
185        let resp: O =
186            serde_json::from_slice(&bytes).map_err(|e| crate::error::map_deser(&e, &bytes))?;
187        Ok(resp)
188    }
189
190    async fn execute_raw<M, Fut>(&self, mk: M) -> Result<bytes::Bytes, AnthropicError>
191    where
192        M: Fn() -> Fut + Send + Sync,
193        Fut: core::future::Future<Output = Result<reqwest::Request, AnthropicError>> + Send,
194    {
195        let http_client = self.http.clone();
196
197        (|| async {
198            let request = mk().await?;
199            let response = http_client
200                .execute(request)
201                .await
202                .map_err(AnthropicError::Reqwest)?;
203
204            let status = response.status();
205            let bytes = response.bytes().await.map_err(AnthropicError::Reqwest)?;
206
207            if status.is_success() {
208                return Ok(bytes);
209            }
210
211            Err(crate::error::deserialize_api_error(status, &bytes))
212        })
213        .retry(self.backoff)
214        .when(AnthropicError::is_retryable)
215        .await
216    }
217}