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#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
18#[serde(rename_all = "snake_case")]
19pub enum ProviderType {
20 #[default]
22 OpenAiCompatible,
23 Anthropic,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ProviderConfig {
29 pub name: String,
30 pub base_url: String,
31 #[serde(default)]
35 pub api_key_envs: Vec<String>,
36 pub enabled: bool,
37 #[serde(default)]
39 pub provider_type: ProviderType,
40 #[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}