use crate::error::ProviderError;
use crate::providers::types::build_http_client;
use crate::providers::types::ServiceType;
use http::{header, HeaderMap};
use potato_type::anthropic::v1::response::AnthropicMessageResponse;
use potato_type::prompt::Prompt;
use potato_type::{Common, Provider};
use reqwest::Client;
use reqwest::Response;
use serde_json::Value;
use tracing::{debug, error, instrument};
const ANTHROPIC_BETA: &str = "anthropic-beta";
const ANTHROPIC_STRUCTURED_OUTPUT: &str = "structured-outputs-2025-11-13";
const ANTHROPIC_VERSION_KEY: &str = "anthropic-version";
const ANTHROPIC_VERSION_VALUE: &str = "2023-06-01";
#[derive(Debug, PartialEq)]
pub enum AnthropicAuth {
ApiKey(String),
NotSet,
}
impl AnthropicAuth {
#[instrument(skip_all)]
pub fn from_env() -> Self {
let api_key =
std::env::var("ANTHROPIC_API_KEY").unwrap_or_else(|_| Common::Undefined.to_string());
if api_key != Common::Undefined.to_string() {
debug!("Using Anthropic API key from environment variable");
return Self::ApiKey(api_key);
}
Self::NotSet
}
}
struct AnthropicPaths {}
impl AnthropicPaths {
fn base_url() -> String {
"https://api.anthropic.com/v1".to_string()
}
}
#[derive(Debug, PartialEq)]
pub struct AnthropicApiConfig {
base_url: String,
service_type: ServiceType,
auth: AnthropicAuth,
}
impl AnthropicApiConfig {
fn new(service_type: ServiceType) -> Result<Self, ProviderError> {
let env_base_url = std::env::var("ANTHROPIC_API_URL").ok();
let base_url = env_base_url.unwrap_or_else(AnthropicPaths::base_url);
let auth = AnthropicAuth::from_env();
Ok(Self {
base_url,
service_type,
auth,
})
}
fn build_url(&self) -> String {
let endpoint = self.get_endpoint();
format!("{}/{}", self.base_url, endpoint)
}
async fn set_auth_header(
&self,
req: reqwest::RequestBuilder,
) -> Result<reqwest::RequestBuilder, ProviderError> {
match &self.auth {
AnthropicAuth::ApiKey(api_key) => Ok(req.header("x-api-key", api_key)),
AnthropicAuth::NotSet => Ok(req),
}
}
fn get_endpoint(&self) -> &'static str {
self.service_type.anthropic_endpoint()
}
}
#[derive(Debug)]
pub struct AnthropicClient {
client: Client,
config: AnthropicApiConfig,
pub provider: Provider,
}
impl PartialEq for AnthropicClient {
fn eq(&self, other: &Self) -> bool {
self.config == other.config && self.provider == other.provider
}
}
impl AnthropicClient {
pub fn new(service_type: ServiceType) -> Result<Self, ProviderError> {
let mut default_headers = HeaderMap::new();
default_headers.insert(
ANTHROPIC_VERSION_KEY,
header::HeaderValue::from_static(ANTHROPIC_VERSION_VALUE),
);
let client = build_http_client(Some(default_headers))?;
let config = AnthropicApiConfig::new(service_type)?;
Ok(Self {
client,
config,
provider: Provider::Anthropic,
})
}
async fn make_request(
&self,
object: &Value,
additional_headers: HeaderMap,
) -> Result<Response, ProviderError> {
let request = self.client.post(self.config.build_url()).json(&object);
let request = self.config.set_auth_header(request).await?;
let request = request.headers(additional_headers);
let response = request.send().await.map_err(ProviderError::RequestError)?;
let status = response.status();
if !status.is_success() {
error!("Anthropic API request failed with status: {}", status);
let body = response
.text()
.await
.unwrap_or_else(|_| "No response body".to_string());
return Err(ProviderError::CompletionError(body, status));
}
Ok(response)
}
#[instrument(name = "anthropic_chat_completion", skip_all)]
pub async fn message(
&self,
prompt: &Prompt,
) -> Result<AnthropicMessageResponse, ProviderError> {
if let AnthropicAuth::NotSet = self.config.auth {
return Err(ProviderError::MissingAuthenticationError);
}
let mut additional_headers = HeaderMap::new();
if prompt.request.has_structured_output() {
additional_headers.insert(
ANTHROPIC_BETA,
header::HeaderValue::from_static(ANTHROPIC_STRUCTURED_OUTPUT),
);
}
let request_body = prompt.request.to_request(&self.provider)?;
debug!(
"Sending message request to Anthropic API: {:?}",
request_body
);
let response = self.make_request(&request_body, additional_headers).await?;
let chat_response: AnthropicMessageResponse = response.json().await?;
debug!("Message successful");
Ok(chat_response)
}
}