Skip to main content

anthropic_async/
client.rs

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