use serde_json::{Value, json};
use crate::error::AnthropicError;
use crate::server_tool::ServerToolHandle;
pub const DEFAULT_ENDPOINT: &str = "https://api.anthropic.com/v1/messages";
pub const DEFAULT_ANTHROPIC_VERSION: &str = "2023-06-01";
#[derive(Clone, Debug)]
pub enum ThinkingConfig {
Disabled,
Enabled {
budget_tokens: u32,
},
Adaptive,
}
impl ThinkingConfig {
pub(crate) fn to_json(&self) -> Value {
match self {
Self::Disabled => json!({ "type": "disabled" }),
Self::Enabled { budget_tokens } => {
json!({ "type": "enabled", "budget_tokens": budget_tokens })
}
Self::Adaptive => json!({ "type": "adaptive" }),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum ServiceTier {
Auto,
StandardOnly,
}
impl ServiceTier {
pub(crate) fn as_str(&self) -> &'static str {
match self {
Self::Auto => "auto",
Self::StandardOnly => "standard_only",
}
}
}
#[derive(Clone, Debug)]
pub enum ToolChoice {
Auto,
Any,
None,
Tool {
name: String,
},
}
impl ToolChoice {
pub(crate) fn to_json(&self, disable_parallel: Option<bool>) -> Value {
let mut obj = match self {
Self::Auto => json!({ "type": "auto" }),
Self::Any => json!({ "type": "any" }),
Self::None => json!({ "type": "none" }),
Self::Tool { name } => json!({ "type": "tool", "name": name }),
};
if let Some(flag) = disable_parallel
&& let Some(obj) = obj.as_object_mut()
{
obj.insert("disable_parallel_tool_use".into(), Value::Bool(flag));
}
obj
}
}
#[derive(Clone, Debug)]
pub enum OutputFormat {
JsonSchema {
schema: Value,
},
}
impl OutputFormat {
pub(crate) fn to_json(&self) -> Value {
match self {
Self::JsonSchema { schema } => json!({
"type": "json_schema",
"schema": schema,
}),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum OutputEffort {
Low,
Medium,
High,
XHigh,
Max,
}
impl OutputEffort {
pub(crate) fn as_str(&self) -> &'static str {
match self {
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::XHigh => "xhigh",
Self::Max => "max",
}
}
}
#[derive(Clone, Debug)]
pub struct AnthropicMcpServer(pub Value);
#[derive(Clone)]
pub struct AnthropicConfig {
pub api_key: Option<String>,
pub auth_token: Option<String>,
pub base_url: String,
pub anthropic_version: String,
pub anthropic_beta: Vec<String>,
pub model: String,
pub max_tokens: u32,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub top_k: Option<u32>,
pub stop_sequences: Option<Vec<String>>,
pub thinking: Option<ThinkingConfig>,
pub service_tier: Option<ServiceTier>,
pub metadata_user_id: Option<String>,
pub tool_choice: Option<ToolChoice>,
pub disable_parallel_tool_use: Option<bool>,
pub server_tools: Vec<ServerToolHandle>,
pub container: Option<String>,
pub output_format: Option<OutputFormat>,
pub output_effort: Option<OutputEffort>,
pub mcp_servers: Vec<AnthropicMcpServer>,
pub streaming: bool,
}
impl AnthropicConfig {
pub fn new(
api_key: impl Into<String>,
model: impl Into<String>,
max_tokens: u32,
) -> Result<Self, AnthropicError> {
if max_tokens == 0 {
return Err(AnthropicError::InvalidMaxTokens);
}
Ok(Self {
api_key: Some(api_key.into()),
auth_token: None,
base_url: DEFAULT_ENDPOINT.into(),
anthropic_version: DEFAULT_ANTHROPIC_VERSION.into(),
anthropic_beta: Vec::new(),
model: model.into(),
max_tokens,
temperature: None,
top_p: None,
top_k: None,
stop_sequences: None,
thinking: None,
service_tier: None,
metadata_user_id: None,
tool_choice: None,
disable_parallel_tool_use: None,
server_tools: Vec::new(),
container: None,
output_format: None,
output_effort: None,
mcp_servers: Vec::new(),
streaming: true,
})
}
pub fn with_auth_token(
auth_token: impl Into<String>,
model: impl Into<String>,
max_tokens: u32,
) -> Result<Self, AnthropicError> {
if max_tokens == 0 {
return Err(AnthropicError::InvalidMaxTokens);
}
Ok(Self {
api_key: None,
auth_token: Some(auth_token.into()),
base_url: DEFAULT_ENDPOINT.into(),
anthropic_version: DEFAULT_ANTHROPIC_VERSION.into(),
anthropic_beta: Vec::new(),
model: model.into(),
max_tokens,
temperature: None,
top_p: None,
top_k: None,
stop_sequences: None,
thinking: None,
service_tier: None,
metadata_user_id: None,
tool_choice: None,
disable_parallel_tool_use: None,
server_tools: Vec::new(),
container: None,
output_format: None,
output_effort: None,
mcp_servers: Vec::new(),
streaming: true,
})
}
pub fn from_env() -> Result<Self, AnthropicError> {
let model = std::env::var("ANTHROPIC_MODEL")
.map_err(|_| AnthropicError::MissingEnv("ANTHROPIC_MODEL"))?;
let max_tokens: u32 = std::env::var("ANTHROPIC_MAX_TOKENS")
.map_err(|_| AnthropicError::MissingEnv("ANTHROPIC_MAX_TOKENS"))?
.parse()
.map_err(|_| AnthropicError::MissingEnv("ANTHROPIC_MAX_TOKENS"))?;
let mut config = match (
std::env::var("ANTHROPIC_AUTH_TOKEN").ok(),
std::env::var("ANTHROPIC_API_KEY").ok(),
) {
(Some(token), _) => Self::with_auth_token(token, model, max_tokens)?,
(None, Some(key)) => Self::new(key, model, max_tokens)?,
(None, None) => return Err(AnthropicError::MissingCredentials),
};
if let Ok(url) = std::env::var("ANTHROPIC_BASE_URL") {
config = config.with_base_url(url);
}
if let Ok(ver) = std::env::var("ANTHROPIC_VERSION") {
config = config.with_anthropic_version(ver);
}
if let Ok(betas) = std::env::var("ANTHROPIC_BETA") {
for flag in betas.split(',').map(str::trim).filter(|s| !s.is_empty()) {
config = config.with_beta(flag.to_string());
}
}
Ok(config)
}
pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn with_anthropic_version(mut self, v: impl Into<String>) -> Self {
self.anthropic_version = v.into();
self
}
pub fn with_beta(mut self, flag: impl Into<String>) -> Self {
self.anthropic_beta.push(flag.into());
self
}
pub fn with_temperature(mut self, v: f32) -> Self {
self.temperature = Some(v);
self
}
pub fn with_top_p(mut self, v: f32) -> Self {
self.top_p = Some(v);
self
}
pub fn with_top_k(mut self, v: u32) -> Self {
self.top_k = Some(v);
self
}
pub fn with_stop_sequences(mut self, sequences: impl IntoIterator<Item = String>) -> Self {
self.stop_sequences = Some(sequences.into_iter().collect());
self
}
pub fn with_thinking(mut self, thinking: ThinkingConfig) -> Self {
self.thinking = Some(thinking);
self
}
pub fn with_service_tier(mut self, tier: ServiceTier) -> Self {
self.service_tier = Some(tier);
self
}
pub fn with_metadata_user_id(mut self, user_id: impl Into<String>) -> Self {
self.metadata_user_id = Some(user_id.into());
self
}
pub fn with_tool_choice(mut self, choice: ToolChoice) -> Self {
self.tool_choice = Some(choice);
self
}
pub fn disable_parallel_tool_use(mut self, flag: bool) -> Self {
self.disable_parallel_tool_use = Some(flag);
self
}
pub fn with_server_tool(mut self, tool: ServerToolHandle) -> Self {
self.server_tools.push(tool);
self
}
pub fn with_container(mut self, id: impl Into<String>) -> Self {
self.container = Some(id.into());
self
}
pub fn with_output_format(mut self, format: OutputFormat) -> Self {
self.output_format = Some(format);
self
}
pub fn with_output_effort(mut self, effort: OutputEffort) -> Self {
self.output_effort = Some(effort);
self
}
pub fn with_mcp_server(mut self, server: AnthropicMcpServer) -> Self {
self.mcp_servers.push(server);
self
}
pub fn with_streaming(mut self, streaming: bool) -> Self {
self.streaming = streaming;
self
}
}