Skip to main content

evo_common/
config.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct GatewayConfig {
6    pub server: ServerConfig,
7    pub providers: Vec<ProviderConfig>,
8}
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ServerConfig {
12    pub host: String,
13    pub port: u16,
14}
15
16/// Which wire protocol the provider speaks.
17#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
18#[serde(rename_all = "snake_case")]
19pub enum ProviderType {
20    /// OpenAI-compatible REST API (OpenAI, OpenRouter, Ollama, vLLM, etc.)
21    #[default]
22    OpenAiCompatible,
23    /// Anthropic Messages API — different auth headers and request format.
24    Anthropic,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ProviderConfig {
29    pub name: String,
30    pub base_url: String,
31    /// One or more env-var names whose values are API tokens.
32    /// Multiple tokens enable a round-robin pool: ["KEY_1", "KEY_2", ...].
33    /// Leave empty for unauthenticated providers (e.g. local Ollama).
34    #[serde(default)]
35    pub api_key_envs: Vec<String>,
36    pub enabled: bool,
37    /// Wire protocol this provider uses.
38    #[serde(default)]
39    pub provider_type: ProviderType,
40    /// Optional extra HTTP headers sent on every request (e.g. OpenRouter's
41    /// `HTTP-Referer` and `X-Title`).
42    #[serde(default)]
43    pub extra_headers: HashMap<String, String>,
44    #[serde(default)]
45    pub rate_limit: Option<RateLimitConfig>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct RateLimitConfig {
50    pub requests_per_minute: u32,
51    pub burst_size: u32,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct AgentConfig {
56    pub role: String,
57    pub skills: Vec<String>,
58    pub king_address: String,
59}
60
61impl GatewayConfig {
62    pub fn from_toml(content: &str) -> Result<Self, toml::de::Error> {
63        toml::from_str(content)
64    }
65
66    pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
67        toml::to_string_pretty(self)
68    }
69
70    pub fn from_json(content: &str) -> Result<Self, serde_json::Error> {
71        serde_json::from_str(content)
72    }
73
74    pub fn to_json(&self) -> Result<String, serde_json::Error> {
75        serde_json::to_string_pretty(self)
76    }
77}
78
79impl AgentConfig {
80    pub fn from_toml(content: &str) -> Result<Self, toml::de::Error> {
81        toml::from_str(content)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn parse_gateway_config_with_pool() {
91        let toml_str = r#"
92[server]
93host = "0.0.0.0"
94port = 8080
95
96[[providers]]
97name = "openai"
98base_url = "https://api.openai.com/v1"
99api_key_envs = ["OPENAI_API_KEY_1", "OPENAI_API_KEY_2"]
100enabled = true
101provider_type = "open_ai_compatible"
102
103[[providers]]
104name = "anthropic"
105base_url = "https://api.anthropic.com/v1"
106api_key_envs = ["ANTHROPIC_API_KEY"]
107enabled = true
108provider_type = "anthropic"
109
110[[providers]]
111name = "openrouter"
112base_url = "https://openrouter.ai/api/v1"
113api_key_envs = ["OPENROUTER_API_KEY"]
114enabled = true
115provider_type = "open_ai_compatible"
116
117[providers.extra_headers]
118"HTTP-Referer" = "https://github.com/ai-evo-agents"
119"X-Title" = "evo-gateway"
120"#;
121        let config = GatewayConfig::from_toml(toml_str).unwrap();
122        assert_eq!(config.server.port, 8080);
123        assert_eq!(config.providers.len(), 3);
124        assert_eq!(config.providers[0].api_key_envs.len(), 2);
125        assert_eq!(config.providers[1].provider_type, ProviderType::Anthropic);
126        assert!(
127            config.providers[2]
128                .extra_headers
129                .contains_key("HTTP-Referer")
130        );
131    }
132
133    #[test]
134    fn roundtrip_gateway_config_toml() {
135        let config = GatewayConfig {
136            server: ServerConfig {
137                host: "127.0.0.1".into(),
138                port: 3000,
139            },
140            providers: vec![ProviderConfig {
141                name: "test".into(),
142                base_url: "http://localhost:11434".into(),
143                api_key_envs: vec![],
144                enabled: true,
145                provider_type: ProviderType::OpenAiCompatible,
146                extra_headers: HashMap::new(),
147                rate_limit: None,
148            }],
149        };
150        let toml_str = config.to_toml().unwrap();
151        let parsed = GatewayConfig::from_toml(&toml_str).unwrap();
152        assert_eq!(parsed.server.port, 3000);
153        assert_eq!(parsed.providers[0].api_key_envs.len(), 0);
154    }
155
156    #[test]
157    fn roundtrip_gateway_config_json() {
158        let config = GatewayConfig {
159            server: ServerConfig {
160                host: "0.0.0.0".into(),
161                port: 8080,
162            },
163            providers: vec![
164                ProviderConfig {
165                    name: "openai".into(),
166                    base_url: "https://api.openai.com/v1".into(),
167                    api_key_envs: vec!["OPENAI_API_KEY".into()],
168                    enabled: true,
169                    provider_type: ProviderType::OpenAiCompatible,
170                    extra_headers: HashMap::new(),
171                    rate_limit: None,
172                },
173                ProviderConfig {
174                    name: "anthropic".into(),
175                    base_url: "https://api.anthropic.com/v1".into(),
176                    api_key_envs: vec!["ANTHROPIC_API_KEY".into()],
177                    enabled: true,
178                    provider_type: ProviderType::Anthropic,
179                    extra_headers: HashMap::new(),
180                    rate_limit: None,
181                },
182            ],
183        };
184        let json_str = config.to_json().unwrap();
185        let parsed = GatewayConfig::from_json(&json_str).unwrap();
186        assert_eq!(parsed.server.port, 8080);
187        assert_eq!(parsed.providers.len(), 2);
188        assert_eq!(parsed.providers[1].provider_type, ProviderType::Anthropic);
189        assert_eq!(parsed.providers[0].api_key_envs[0], "OPENAI_API_KEY");
190    }
191}