Skip to main content

agent_proxy_rust_core/
config.rs

1//! Proxy configuration types.
2
3use std::{
4    collections::HashMap,
5    net::{IpAddr, Ipv4Addr, SocketAddr},
6    time::Duration,
7};
8
9use secrecy::SecretString;
10
11/// Auth configuration for a single role-mapped key.
12#[derive(Debug, Clone)]
13pub struct AuthKeyEntry {
14    /// The role assigned to this key (e.g., "architect", "coder").
15    pub role: String,
16}
17
18/// Core proxy configuration.
19#[derive(Debug, Clone)]
20pub struct ProxyConfig {
21    /// Address to listen on.
22    pub listen: SocketAddr,
23    /// Maximum request body size in bytes (default 16 MB).
24    pub max_body_size: usize,
25    /// Per-read timeout for upstream HTTP responses.
26    ///
27    /// This is the maximum idle time between successive socket reads, **not**
28    /// the total request duration. A single read that takes longer than this
29    /// will fail, but long-running streaming responses (SSE) are fine as long
30    /// as the upstream sends data within this window.
31    pub upstream_read_timeout: Duration,
32    /// Timeout for establishing upstream TCP connections.
33    pub upstream_connect_timeout: Duration,
34    /// Optional simple auth key (check `Authorization: Bearer <key>`).
35    pub proxy_api_key: Option<SecretString>,
36    /// Optional simple token auth (check `X-Proxy-Token: <token>`).
37    pub proxy_token: Option<SecretString>,
38    /// Role-based auth: maps API keys to roles.
39    pub proxy_auth_keys: HashMap<String, AuthKeyEntry>,
40}
41
42impl Default for ProxyConfig {
43    fn default() -> Self {
44        Self {
45            listen: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8787),
46            max_body_size: 16 * 1024 * 1024, // 16 MB
47            upstream_read_timeout: Duration::from_secs(600),
48            upstream_connect_timeout: Duration::from_secs(10),
49            proxy_api_key: None,
50            proxy_token: None,
51            proxy_auth_keys: HashMap::new(),
52        }
53    }
54}
55
56impl ProxyConfig {
57    /// Creates a new [`ProxyConfig`] with the given listen address.
58    #[must_use]
59    pub fn new(listen: SocketAddr) -> Self {
60        Self {
61            listen,
62            ..Default::default()
63        }
64    }
65
66    /// Returns `true` if any authentication mechanism is configured.
67    #[must_use]
68    pub fn has_auth(&self) -> bool {
69        self.proxy_api_key.is_some()
70            || self.proxy_token.is_some()
71            || !self.proxy_auth_keys.is_empty()
72    }
73}
74
75#[cfg(test)]
76#[allow(clippy::unwrap_used)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_default_config_values() {
82        let config = ProxyConfig::default();
83        assert_eq!(config.max_body_size, 16 * 1024 * 1024);
84        assert_eq!(config.upstream_read_timeout, Duration::from_secs(600));
85        assert_eq!(config.upstream_connect_timeout, Duration::from_secs(10));
86        assert!(!config.has_auth());
87    }
88
89    #[test]
90    fn test_has_auth_with_api_key() {
91        let config = ProxyConfig {
92            proxy_api_key: Some(SecretString::new("sk-test".into())),
93            ..Default::default()
94        };
95        assert!(config.has_auth());
96    }
97
98    #[test]
99    fn test_has_auth_with_token() {
100        let config = ProxyConfig {
101            proxy_token: Some(SecretString::new("token-test".into())),
102            ..Default::default()
103        };
104        assert!(config.has_auth());
105    }
106
107    #[test]
108    fn test_has_auth_with_role_mapping() {
109        let config = ProxyConfig {
110            proxy_auth_keys: HashMap::from([(
111                "sk-test".into(),
112                AuthKeyEntry {
113                    role: "coder".into(),
114                },
115            )]),
116            ..Default::default()
117        };
118        assert!(config.has_auth());
119    }
120}