Skip to main content

anthropic/
client.rs

1//! HTTP client for the Anthropic API.
2//!
3//! Construct a [`Client`] with [`Client::from_env`] (reads `ANTHROPIC_API_KEY`
4//! and optionally `ANTHROPIC_BASE_URL`) or [`Client::builder`] for explicit
5//! configuration. The client is cheap to clone — internally it shares a
6//! reference-counted `reqwest::Client` and credential set.
7
8use std::sync::Arc;
9use std::time::Duration;
10
11use url::Url;
12
13use crate::auth::Credentials;
14use crate::config::{DEFAULT_BASE_URL, ENV_API_KEY, ENV_BASE_URL};
15use crate::error::{Error, Result};
16use crate::messages::MessagesService;
17
18/// Top-level handle for the Anthropic API.
19#[derive(Debug, Clone)]
20pub struct Client {
21    pub(crate) inner: Arc<ClientInner>,
22}
23
24#[derive(Debug)]
25pub(crate) struct ClientInner {
26    pub(crate) http: reqwest::Client,
27    pub(crate) base_url: Url,
28    pub(crate) credentials: Credentials,
29}
30
31impl Client {
32    /// Build a [`Client`] from environment variables.
33    ///
34    /// Required: `ANTHROPIC_API_KEY`.
35    /// Optional: `ANTHROPIC_BASE_URL` (defaults to `https://api.anthropic.com`).
36    pub fn from_env() -> Result<Self> {
37        let api_key = std::env::var(ENV_API_KEY)
38            .map_err(|_| Error::Config(format!("environment variable {ENV_API_KEY} is not set")))?;
39        let mut builder = Self::builder().api_key(api_key);
40        if let Ok(url) = std::env::var(ENV_BASE_URL) {
41            builder = builder.base_url(
42                Url::parse(&url)
43                    .map_err(|e| Error::Config(format!("invalid {ENV_BASE_URL}: {e}")))?,
44            );
45        }
46        builder.build()
47    }
48
49    /// Start a fluent builder for explicit construction.
50    pub fn builder() -> ClientBuilder {
51        ClientBuilder::default()
52    }
53
54    /// Access the Messages API.
55    pub fn messages(&self) -> MessagesService<'_> {
56        MessagesService::new(self)
57    }
58}
59
60/// Builder for [`Client`]. Use [`Client::builder`] to start one.
61#[derive(Debug, Default)]
62pub struct ClientBuilder {
63    api_key: Option<String>,
64    base_url: Option<Url>,
65    timeout: Option<Duration>,
66    http_client: Option<reqwest::Client>,
67}
68
69impl ClientBuilder {
70    /// Set the API key. Required.
71    pub fn api_key(mut self, key: impl Into<String>) -> Self {
72        self.api_key = Some(key.into());
73        self
74    }
75
76    /// Override the API base URL. Defaults to `https://api.anthropic.com`.
77    pub fn base_url(mut self, url: Url) -> Self {
78        self.base_url = Some(url);
79        self
80    }
81
82    /// Override the request timeout. Defaults to whatever `reqwest` uses
83    /// when no explicit `http_client` is supplied (no timeout).
84    pub fn timeout(mut self, timeout: Duration) -> Self {
85        self.timeout = Some(timeout);
86        self
87    }
88
89    /// Provide a fully-configured `reqwest::Client` (for custom proxies,
90    /// connection pools, root certs, etc.). When set, [`ClientBuilder::timeout`]
91    /// is ignored.
92    pub fn http_client(mut self, http: reqwest::Client) -> Self {
93        self.http_client = Some(http);
94        self
95    }
96
97    /// Finalize the builder.
98    pub fn build(self) -> Result<Client> {
99        let api_key = self
100            .api_key
101            .ok_or_else(|| Error::Config("api_key is required".into()))?;
102        if api_key.trim().is_empty() {
103            return Err(Error::Config("api_key must not be empty".into()));
104        }
105
106        let base_url = match self.base_url {
107            Some(u) => u,
108            None => Url::parse(DEFAULT_BASE_URL)
109                .map_err(|e| Error::Config(format!("default base URL is invalid: {e}")))?,
110        };
111
112        let http = match self.http_client {
113            Some(c) => c,
114            None => {
115                let mut b = reqwest::Client::builder();
116                if let Some(t) = self.timeout {
117                    b = b.timeout(t);
118                }
119                b.build().map_err(Error::Http)?
120            }
121        };
122
123        Ok(Client {
124            inner: Arc::new(ClientInner {
125                http,
126                base_url,
127                credentials: Credentials::from_api_key(api_key),
128            }),
129        })
130    }
131}