claude_agent/client/
gateway.rs

1//! LLM Gateway configuration for custom proxy/endpoint support.
2
3use std::env;
4
5/// LLM Gateway configuration.
6///
7/// Provides a centralized proxy layer between the SDK and model providers,
8/// supporting custom endpoints, authentication, and header injection.
9#[derive(Clone, Debug, Default)]
10pub struct GatewayConfig {
11    /// Custom base URL (overrides provider URL)
12    pub base_url: Option<String>,
13    /// Custom auth token (overrides provider auth)
14    pub auth_token: Option<String>,
15    /// Custom headers (key-value pairs)
16    pub custom_headers: Vec<(String, String)>,
17    /// Disable experimental beta flags for gateway compatibility
18    pub disable_betas: bool,
19}
20
21impl GatewayConfig {
22    /// Create from environment variables.
23    pub fn from_env() -> Option<Self> {
24        let base_url = env::var("ANTHROPIC_BASE_URL").ok();
25        let auth_token = env::var("ANTHROPIC_AUTH_TOKEN").ok();
26        let custom_headers = parse_custom_headers();
27        let disable_betas = env::var("CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS")
28            .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
29            .unwrap_or(false);
30
31        if base_url.is_some() || auth_token.is_some() || !custom_headers.is_empty() {
32            Some(Self {
33                base_url,
34                auth_token,
35                custom_headers,
36                disable_betas,
37            })
38        } else {
39            None
40        }
41    }
42
43    /// Create with custom base URL.
44    pub fn with_base_url(url: impl Into<String>) -> Self {
45        Self {
46            base_url: Some(url.into()),
47            ..Default::default()
48        }
49    }
50
51    /// Set auth token.
52    pub fn auth_token(mut self, token: impl Into<String>) -> Self {
53        self.auth_token = Some(token.into());
54        self
55    }
56
57    /// Add a custom header.
58    pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
59        self.custom_headers.push((name.into(), value.into()));
60        self
61    }
62
63    /// Add multiple headers.
64    pub fn headers(mut self, headers: impl IntoIterator<Item = (String, String)>) -> Self {
65        self.custom_headers.extend(headers);
66        self
67    }
68
69    /// Disable experimental beta flags.
70    pub fn disable_betas(mut self) -> Self {
71        self.disable_betas = true;
72        self
73    }
74
75    /// Check if gateway is configured.
76    pub fn is_active(&self) -> bool {
77        self.base_url.is_some() || self.auth_token.is_some()
78    }
79
80    /// Get effective base URL.
81    pub fn effective_base_url(&self, default: &str) -> String {
82        self.base_url.clone().unwrap_or_else(|| default.to_string())
83    }
84
85    /// Get all headers including custom ones.
86    pub fn all_headers(&self) -> Vec<(String, String)> {
87        self.custom_headers.clone()
88    }
89}
90
91/// Parse ANTHROPIC_CUSTOM_HEADERS environment variable.
92/// Format: "Header1: Value1\nHeader2: Value2"
93fn parse_custom_headers() -> Vec<(String, String)> {
94    env::var("ANTHROPIC_CUSTOM_HEADERS")
95        .ok()
96        .map(|s| {
97            s.lines()
98                .filter_map(|line| {
99                    let mut parts = line.splitn(2, ':');
100                    match (parts.next(), parts.next()) {
101                        (Some(key), Some(value)) => {
102                            Some((key.trim().to_string(), value.trim().to_string()))
103                        }
104                        _ => None,
105                    }
106                })
107                .collect()
108        })
109        .unwrap_or_default()
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_gateway_config_builder() {
118        let config = GatewayConfig::with_base_url("https://my-gateway.com")
119            .auth_token("my-token")
120            .header("X-Custom", "value")
121            .disable_betas();
122
123        assert_eq!(config.base_url, Some("https://my-gateway.com".to_string()));
124        assert_eq!(config.auth_token, Some("my-token".to_string()));
125        assert!(config.disable_betas);
126        assert_eq!(config.custom_headers.len(), 1);
127    }
128
129    #[test]
130    fn test_effective_base_url() {
131        let config = GatewayConfig::default();
132        assert_eq!(
133            config.effective_base_url("https://default.com"),
134            "https://default.com"
135        );
136
137        let config = GatewayConfig::with_base_url("https://custom.com");
138        assert_eq!(
139            config.effective_base_url("https://default.com"),
140            "https://custom.com"
141        );
142    }
143}