1use reqwest::{header::CONTENT_TYPE, Client, Method, RequestBuilder, Response};
45use reqwest_eventsource::{CannotCloneRequestError, EventSource, RequestBuilderExt};
46use serde::{de::DeserializeOwned, Deserialize, Serialize};
47use std::env;
48use std::env::VarError;
49use std::sync::{LazyLock, RwLock};
50
51pub mod admin;
52pub mod messages;
53pub mod models;
54pub static DEFAULT_BASE_URL: LazyLock<String> =
56 LazyLock::new(|| String::from("https://api.anthropic.com/v1/"));
57static DEFAULT_CREDENTIALS: LazyLock<RwLock<Credentials>> =
59 LazyLock::new(|| RwLock::new(Credentials::from_env()));
60
61#[derive(Debug, Clone, Eq, PartialEq)]
66pub struct Credentials {
67 api_key: String,
68 base_url: String,
69}
70
71impl Credentials {
72 pub fn new(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
84 let base_url = base_url.into();
85 let base_url = if base_url.is_empty() {
86 DEFAULT_BASE_URL.clone()
87 } else {
88 parse_base_url(base_url)
89 };
90 Self {
91 api_key: api_key.into(),
92 base_url,
93 }
94 }
95
96 pub fn from_env() -> Credentials {
113 let api_key = env::var("ANTHROPIC_API_KEY").unwrap();
114 let base_url_unparsed = env::var("ANTHROPIC_BASE_URL").unwrap_or_else(|e| match e {
115 VarError::NotPresent => DEFAULT_BASE_URL.clone(),
116 VarError::NotUnicode(v) => panic!("ANTHROPIC_BASE_URL is not unicode: {v:#?}"),
117 });
118 let base_url = parse_base_url(base_url_unparsed);
119 Credentials { api_key, base_url }
120 }
121
122 pub fn api_key(&self) -> &str {
124 &self.api_key
125 }
126
127 pub fn base_url(&self) -> &str {
129 &self.base_url
130 }
131}
132
133#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
135pub struct AnthropicError {
136 #[serde(rename = "type")]
138 pub error_type: String,
139 pub message: String,
141}
142
143#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
145pub struct AnthropicErrorResponse {
146 #[serde(rename = "type")]
148 pub response_type: String,
149 pub error: AnthropicError,
151}
152
153impl AnthropicErrorResponse {
154 fn new(message: String, error_type: String) -> AnthropicErrorResponse {
156 AnthropicErrorResponse {
157 response_type: "error".to_string(),
158 error: AnthropicError {
159 message,
160 error_type,
161 },
162 }
163 }
164}
165
166impl std::fmt::Display for AnthropicErrorResponse {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 f.write_str(&self.error.message)
169 }
170}
171
172impl std::error::Error for AnthropicErrorResponse {}
173
174#[derive(Deserialize, Clone)]
176#[serde(untagged)]
177pub enum ApiResponse<T> {
178 Err { error: AnthropicErrorResponse },
180 Ok(T),
182}
183
184#[derive(Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
186pub struct Usage {
187 pub input_tokens: u32,
189 pub output_tokens: u32,
191 pub cache_creation_input_tokens: Option<u32>,
193 pub cache_read_input_tokens: Option<u32>,
195}
196
197pub type ApiResponseOrError<T> = Result<T, AnthropicErrorResponse>;
199
200impl From<reqwest::Error> for AnthropicErrorResponse {
201 fn from(value: reqwest::Error) -> Self {
202 AnthropicErrorResponse::new(value.to_string(), "reqwest".to_string())
203 }
204}
205
206impl From<std::io::Error> for AnthropicErrorResponse {
207 fn from(value: std::io::Error) -> Self {
208 AnthropicErrorResponse::new(value.to_string(), "io".to_string())
209 }
210}
211
212async fn anthropic_request_json<F, T>(
214 method: Method,
215 route: &str,
216 builder: F,
217 credentials_opt: Option<Credentials>,
218) -> ApiResponseOrError<T>
219where
220 F: FnOnce(RequestBuilder) -> RequestBuilder,
221 T: DeserializeOwned,
222{
223 let api_response = anthropic_request(method, route, builder, credentials_opt)
224 .await?
225 .json()
226 .await?;
227
228 match api_response {
229 ApiResponse::Ok(t) => Ok(t),
230 ApiResponse::Err { error } => Err(error),
231 }
232}
233
234async fn anthropic_request<F>(
236 method: Method,
237 route: &str,
238 builder: F,
239 credentials_opt: Option<Credentials>,
240) -> ApiResponseOrError<Response>
241where
242 F: FnOnce(RequestBuilder) -> RequestBuilder,
243{
244 let client = Client::new();
245 let credentials =
246 credentials_opt.unwrap_or_else(|| DEFAULT_CREDENTIALS.read().unwrap().clone());
247 let mut request = client.request(method, format!("{}{route}", credentials.base_url));
248
249 request = builder(request);
250
251 let response = request
252 .header("x-api-key", credentials.api_key)
253 .header("anthropic-version", "2023-06-01")
254 .header(CONTENT_TYPE, "application/json")
255 .send()
256 .await?;
257
258 Ok(response)
259}
260
261async fn anthropic_request_stream<F>(
263 method: Method,
264 route: &str,
265 builder: F,
266 credentials_opt: Option<Credentials>,
267) -> Result<EventSource, CannotCloneRequestError>
268where
269 F: FnOnce(RequestBuilder) -> RequestBuilder,
270{
271 let client = Client::new();
272 let credentials =
273 credentials_opt.unwrap_or_else(|| DEFAULT_CREDENTIALS.read().unwrap().clone());
274 let mut request = client.request(method, format!("{}{route}", credentials.base_url));
275 request = builder(request);
276 let stream = request
277 .header("x-api-key", credentials.api_key)
278 .header("anthropic-version", "2023-06-01")
279 .header(CONTENT_TYPE, "application/json")
280 .eventsource()?;
281 Ok(stream)
282}
283
284async fn anthropic_post<J, T>(
286 route: &str,
287 json: &J,
288 credentials_opt: Option<Credentials>,
289) -> ApiResponseOrError<T>
290where
291 J: Serialize + ?Sized,
292 T: DeserializeOwned,
293{
294 anthropic_request_json(
295 Method::POST,
296 route,
297 |request| request.json(json),
298 credentials_opt,
299 )
300 .await
301}
302
303fn parse_base_url(mut value: String) -> String {
305 if !value.ends_with('/') {
306 value += "/";
307 }
308 value
309}
310
311#[cfg(test)]
313pub mod tests {
314 pub const DEFAULT_LEGACY_MODEL: &str = "claude-3-5-sonnet-20240620";
316}