Skip to main content

llm_stack_anthropic/
config.rs

1//! Anthropic provider configuration.
2
3use std::time::Duration;
4
5/// Configuration for the Anthropic provider.
6///
7/// Use struct update syntax with [`Default`] for ergonomic construction:
8///
9/// ```rust
10/// use llm_stack_anthropic::AnthropicConfig;
11///
12/// let config = AnthropicConfig {
13///     api_key: "sk-ant-...".into(),
14///     model: "claude-sonnet-4-20250514".into(),
15///     ..Default::default()
16/// };
17/// ```
18#[derive(Clone)]
19pub struct AnthropicConfig {
20    /// Anthropic API key. Required.
21    pub api_key: String,
22    /// Model identifier (e.g. `"claude-sonnet-4-20250514"`).
23    pub model: String,
24    /// Base URL for the API. Override for proxies or testing.
25    pub base_url: String,
26    /// Default max tokens for responses when not specified in `ChatParams`.
27    pub max_tokens: u32,
28    /// Anthropic API version header.
29    pub api_version: String,
30    /// Request timeout. `None` uses reqwest's default.
31    pub timeout: Option<Duration>,
32    /// Pre-configured HTTP client for connection pooling across providers.
33    /// When `None`, a new client is created.
34    pub client: Option<reqwest::Client>,
35}
36
37impl std::fmt::Debug for AnthropicConfig {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        f.debug_struct("AnthropicConfig")
40            .field("api_key", &"[REDACTED]")
41            .field("model", &self.model)
42            .field("base_url", &self.base_url)
43            .field("max_tokens", &self.max_tokens)
44            .field("api_version", &self.api_version)
45            .field("timeout", &self.timeout)
46            .field("client", &self.client.as_ref().map(|_| "..."))
47            .finish()
48    }
49}
50
51impl Default for AnthropicConfig {
52    fn default() -> Self {
53        Self {
54            api_key: String::new(),
55            model: "claude-sonnet-4-20250514".into(),
56            base_url: "https://api.anthropic.com".into(),
57            max_tokens: 4096,
58            api_version: "2023-06-01".into(),
59            timeout: None,
60            client: None,
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_default_config() {
71        let config = AnthropicConfig::default();
72        assert_eq!(config.model, "claude-sonnet-4-20250514");
73        assert_eq!(config.base_url, "https://api.anthropic.com");
74        assert_eq!(config.max_tokens, 4096);
75        assert_eq!(config.api_version, "2023-06-01");
76        assert!(config.api_key.is_empty());
77        assert!(config.timeout.is_none());
78        assert!(config.client.is_none());
79    }
80
81    #[test]
82    fn test_debug_redacts_api_key() {
83        let config = AnthropicConfig {
84            api_key: "sk-ant-super-secret".into(),
85            ..Default::default()
86        };
87        let debug_output = format!("{config:?}");
88        assert!(
89            !debug_output.contains("sk-ant-super-secret"),
90            "Debug output should not contain the API key"
91        );
92        assert!(debug_output.contains("[REDACTED]"));
93    }
94
95    #[test]
96    fn test_config_override() {
97        let config = AnthropicConfig {
98            api_key: "test-key".into(),
99            model: "claude-3-5-haiku-20241022".into(),
100            max_tokens: 1024,
101            ..Default::default()
102        };
103        assert_eq!(config.api_key, "test-key");
104        assert_eq!(config.model, "claude-3-5-haiku-20241022");
105        assert_eq!(config.max_tokens, 1024);
106        assert_eq!(config.base_url, "https://api.anthropic.com");
107    }
108}