1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(tag = "type")]
14#[non_exhaustive]
15pub enum ProviderConfig {
16 #[serde(rename = "openai")]
18 OpenAI {
19 api_key_env: String,
21 #[serde(default = "default_openai_base")]
23 api_base: String,
24 default_model: String,
26 },
27 #[serde(rename = "azure")]
29 Azure {
30 #[serde(default = "default_azure_api_key_env")]
32 api_key_env: String,
33 #[serde(default = "default_azure_base_url_env")]
35 base_url_env: String,
36 #[serde(default = "default_azure_default_model")]
38 default_model: String,
39 },
40 #[serde(rename = "anthropic")]
42 Anthropic {
43 #[serde(default = "default_anthropic_api_key_env")]
45 api_key_env: String,
46 #[serde(default = "default_anthropic_default_model")]
48 default_model: String,
49 },
50 #[serde(rename = "bedrock")]
52 Bedrock {
53 #[serde(default = "default_bedrock_api_key_env")]
55 api_key_env: String,
56 #[serde(default = "default_bedrock_region_env")]
58 region_env: String,
59 #[serde(default = "default_bedrock_default_model")]
61 default_model: String,
62 },
63 #[serde(rename = "ollama")]
65 Ollama {
66 #[serde(default = "default_ollama_api_key_env")]
68 api_key_env: String,
69 #[serde(default = "default_ollama_base_url")]
71 base_url: String,
72 #[serde(default = "default_ollama_default_model")]
74 default_model: String,
75 },
76 #[serde(rename = "vertex")]
78 Vertex {
79 #[serde(default = "default_vertex_api_key_env")]
81 api_key_env: String,
82 #[serde(default = "default_vertex_project_env")]
84 project_env: String,
85 #[serde(default = "default_vertex_location_env")]
87 location_env: String,
88 #[serde(default)]
90 default_model: String,
91 },
92 #[serde(rename = "custom")]
94 Custom {
95 index: u8,
97 endpoint: String,
99 #[serde(default)]
101 api_key_env: Option<String>,
102 #[serde(default)]
104 default_model: String,
105 },
106 #[serde(rename = "openai_resp")]
107 OpenAIResp {
108 #[serde(default = "default_openai_api_key_env")]
109 api_key_env: String,
110 #[serde(default)]
111 api_base: Option<String>,
112 #[serde(default)]
113 default_model: String,
114 },
115 #[serde(rename = "gemini")]
116 Gemini {
117 #[serde(default = "default_gemini_api_key_env")]
118 api_key_env: String,
119 #[serde(default)]
120 api_base: Option<String>,
121 #[serde(default)]
122 default_model: String,
123 },
124 #[serde(rename = "fireworks")]
125 Fireworks {
126 #[serde(default = "default_fireworks_api_key_env")]
127 api_key_env: String,
128 #[serde(default)]
129 api_base: Option<String>,
130 #[serde(default)]
131 default_model: String,
132 },
133 #[serde(rename = "together")]
134 Together {
135 #[serde(default = "default_together_api_key_env")]
136 api_key_env: String,
137 #[serde(default)]
138 api_base: Option<String>,
139 #[serde(default)]
140 default_model: String,
141 },
142 #[serde(rename = "groq")]
143 Groq {
144 #[serde(default = "default_groq_api_key_env")]
145 api_key_env: String,
146 #[serde(default)]
147 api_base: Option<String>,
148 #[serde(default)]
149 default_model: String,
150 },
151 #[serde(rename = "aihubmix")]
152 Aihubmix {
153 #[serde(default = "default_aihubmix_api_key_env")]
154 api_key_env: String,
155 #[serde(default)]
156 api_base: Option<String>,
157 #[serde(default)]
158 default_model: String,
159 },
160 #[serde(rename = "kimi")]
161 Kimi {
162 #[serde(default = "default_kimi_api_key_env")]
163 api_key_env: String,
164 #[serde(default)]
165 api_base: Option<String>,
166 #[serde(default)]
167 default_model: String,
168 },
169 #[serde(rename = "mimo")]
170 Mimo {
171 #[serde(default = "default_mimo_api_key_env")]
172 api_key_env: String,
173 #[serde(default)]
174 api_base: Option<String>,
175 #[serde(default)]
176 default_model: String,
177 },
178 #[serde(rename = "moonshot")]
179 Moonshot {
180 #[serde(default = "default_moonshot_api_key_env")]
181 api_key_env: String,
182 #[serde(default)]
183 api_base: Option<String>,
184 #[serde(default)]
185 default_model: String,
186 },
187 #[serde(rename = "nebius")]
188 Nebius {
189 #[serde(default = "default_nebius_api_key_env")]
190 api_key_env: String,
191 #[serde(default)]
192 api_base: Option<String>,
193 #[serde(default)]
194 default_model: String,
195 },
196 #[serde(rename = "xai")]
197 Xai {
198 #[serde(default = "default_xai_api_key_env")]
199 api_key_env: String,
200 #[serde(default)]
201 api_base: Option<String>,
202 #[serde(default)]
203 default_model: String,
204 },
205 #[serde(rename = "deepseek")]
206 DeepSeek {
207 #[serde(default = "default_deepseek_api_key_env")]
208 api_key_env: String,
209 #[serde(default)]
210 api_base: Option<String>,
211 #[serde(default)]
212 default_model: String,
213 },
214 #[serde(rename = "zai")]
215 Zai {
216 #[serde(default = "default_zai_api_key_env")]
217 api_key_env: String,
218 #[serde(default)]
219 api_base: Option<String>,
220 #[serde(default)]
221 default_model: String,
222 },
223 #[serde(rename = "bigmodel")]
224 BigModel {
225 #[serde(default = "default_bigmodel_api_key_env")]
226 api_key_env: String,
227 #[serde(default)]
228 api_base: Option<String>,
229 #[serde(default)]
230 default_model: String,
231 },
232 #[serde(rename = "aliyun")]
233 Aliyun {
234 #[serde(default = "default_aliyun_api_key_env")]
235 api_key_env: String,
236 #[serde(default)]
237 api_base: Option<String>,
238 #[serde(default)]
239 default_model: String,
240 },
241 #[serde(rename = "qwen_cloud")]
242 QwenCloud {
243 #[serde(default = "default_qwen_cloud_api_key_env")]
244 api_key_env: String,
245 #[serde(default)]
246 api_base: Option<String>,
247 #[serde(default)]
248 default_model: String,
249 },
250 #[serde(rename = "baidu")]
251 Baidu {
252 #[serde(default = "default_baidu_api_key_env")]
253 api_key_env: String,
254 #[serde(default)]
255 api_base: Option<String>,
256 #[serde(default)]
257 default_model: String,
258 },
259 #[serde(rename = "cohere")]
260 Cohere {
261 #[serde(default = "default_cohere_api_key_env")]
262 api_key_env: String,
263 #[serde(default)]
264 api_base: Option<String>,
265 #[serde(default)]
266 default_model: String,
267 },
268 #[serde(rename = "ollama_cloud")]
269 OllamaCloud {
270 #[serde(default = "default_ollama_cloud_api_key_env")]
271 api_key_env: String,
272 #[serde(default)]
273 api_base: Option<String>,
274 #[serde(default)]
275 default_model: String,
276 },
277 #[serde(rename = "omlx")]
278 Omlx {
279 #[serde(default = "default_omlx_api_key_env")]
280 api_key_env: String,
281 #[serde(default)]
282 api_base: Option<String>,
283 #[serde(default)]
284 default_model: String,
285 },
286 #[serde(rename = "github_copilot", alias = "github")]
287 GithubCopilot {
288 #[serde(default = "default_github_copilot_api_key_env")]
289 api_key_env: String,
290 #[serde(default)]
291 api_base: Option<String>,
292 #[serde(default)]
293 default_model: String,
294 },
295 #[serde(rename = "opencode_go")]
296 OpenCodeGo {
297 #[serde(default = "default_opencode_go_api_key_env")]
298 api_key_env: String,
299 #[serde(default)]
300 api_base: Option<String>,
301 #[serde(default)]
302 default_model: String,
303 },
304 #[serde(rename = "bedrock_api")]
305 BedrockApi {
306 #[serde(default = "default_bedrock_api_kind_api_key_env")]
307 api_key_env: String,
308 #[serde(default)]
309 api_base: Option<String>,
310 #[serde(default)]
311 default_model: String,
312 },
313 #[serde(rename = "open_router", alias = "openrouter")]
314 OpenRouter {
315 #[serde(default = "default_open_router_api_key_env")]
316 api_key_env: String,
317 #[serde(default)]
318 api_base: Option<String>,
319 #[serde(default)]
320 default_model: String,
321 },
322 #[serde(rename = "atlascloud")]
323 AtlasCloud {
324 #[serde(default = "default_atlascloud_api_key_env")]
325 api_key_env: String,
326 #[serde(default)]
327 api_base: Option<String>,
328 #[serde(default)]
329 default_model: String,
330 },
331 #[serde(rename = "minimax")]
332 MiniMax {
333 #[serde(default = "default_minimax_api_key_env")]
334 api_key_env: String,
335 #[serde(default)]
336 api_base: Option<String>,
337 #[serde(default)]
338 default_model: String,
339 },
340}
341
342macro_rules! type_name_match {
343 ($self:expr, $($variant:ident => $name:literal),* $(,)?) => {
344 match $self {
345 $(ProviderConfig::$variant { .. } => $name,)*
346 }
347 };
348}
349
350impl ProviderConfig {
351 pub fn type_name(&self) -> &'static str {
353 type_name_match!(
354 self,
355 OpenAI => "openai",
356 Azure => "azure",
357 Anthropic => "anthropic",
358 Bedrock => "bedrock",
359 Ollama => "ollama",
360 Vertex => "vertex",
361 Custom => "custom",
362 OpenAIResp => "openai_resp",
363 Gemini => "gemini",
364 Fireworks => "fireworks",
365 Together => "together",
366 Groq => "groq",
367 Aihubmix => "aihubmix",
368 Kimi => "kimi",
369 Mimo => "mimo",
370 Moonshot => "moonshot",
371 Nebius => "nebius",
372 Xai => "xai",
373 DeepSeek => "deepseek",
374 Zai => "zai",
375 BigModel => "bigmodel",
376 Aliyun => "aliyun",
377 QwenCloud => "qwen_cloud",
378 Baidu => "baidu",
379 Cohere => "cohere",
380 OllamaCloud => "ollama_cloud",
381 Omlx => "omlx",
382 GithubCopilot => "github_copilot",
383 OpenCodeGo => "opencode_go",
384 BedrockApi => "bedrock_api",
385 OpenRouter => "open_router",
386 AtlasCloud => "atlascloud",
387 MiniMax => "minimax",
388 )
389 }
390
391 pub fn default_model(&self) -> &str {
393 match self {
394 ProviderConfig::OpenAI { default_model, .. }
395 | ProviderConfig::Azure { default_model, .. }
396 | ProviderConfig::Anthropic { default_model, .. }
397 | ProviderConfig::Bedrock { default_model, .. }
398 | ProviderConfig::Ollama { default_model, .. }
399 | ProviderConfig::Vertex { default_model, .. }
400 | ProviderConfig::Custom { default_model, .. }
401 | ProviderConfig::OpenAIResp { default_model, .. }
402 | ProviderConfig::Gemini { default_model, .. }
403 | ProviderConfig::Fireworks { default_model, .. }
404 | ProviderConfig::Together { default_model, .. }
405 | ProviderConfig::Groq { default_model, .. }
406 | ProviderConfig::Aihubmix { default_model, .. }
407 | ProviderConfig::Kimi { default_model, .. }
408 | ProviderConfig::Mimo { default_model, .. }
409 | ProviderConfig::Moonshot { default_model, .. }
410 | ProviderConfig::Nebius { default_model, .. }
411 | ProviderConfig::Xai { default_model, .. }
412 | ProviderConfig::DeepSeek { default_model, .. }
413 | ProviderConfig::Zai { default_model, .. }
414 | ProviderConfig::BigModel { default_model, .. }
415 | ProviderConfig::Aliyun { default_model, .. }
416 | ProviderConfig::QwenCloud { default_model, .. }
417 | ProviderConfig::Baidu { default_model, .. }
418 | ProviderConfig::Cohere { default_model, .. }
419 | ProviderConfig::OllamaCloud { default_model, .. }
420 | ProviderConfig::Omlx { default_model, .. }
421 | ProviderConfig::GithubCopilot { default_model, .. }
422 | ProviderConfig::OpenCodeGo { default_model, .. }
423 | ProviderConfig::BedrockApi { default_model, .. }
424 | ProviderConfig::OpenRouter { default_model, .. }
425 | ProviderConfig::AtlasCloud { default_model, .. }
426 | ProviderConfig::MiniMax { default_model, .. } => default_model,
427 }
428 }
429}
430
431impl std::str::FromStr for ProviderConfig {
432 type Err = String;
433
434 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
437 let key = s.trim().to_lowercase();
438 let key = match key.as_str() {
439 "openrouter" => "open_router",
440 "github" => "github_copilot",
441 other => other,
442 };
443 match key {
444 "openai" => Ok(ProviderConfig::OpenAI {
445 api_key_env: default_openai_api_key_env(),
446 api_base: default_openai_base(),
447 default_model: "gpt-4".to_string(),
448 }),
449 "nvidia" => Ok(ProviderConfig::OpenAI {
450 api_key_env: default_nvidia_api_key_env(),
451 api_base: default_nvidia_api_base(),
452 default_model: default_nvidia_default_model(),
453 }),
454 "azure" => Ok(ProviderConfig::Azure {
455 api_key_env: default_azure_api_key_env(),
456 base_url_env: default_azure_base_url_env(),
457 default_model: default_azure_default_model(),
458 }),
459 "anthropic" => Ok(ProviderConfig::Anthropic {
460 api_key_env: default_anthropic_api_key_env(),
461 default_model: default_anthropic_default_model(),
462 }),
463 "bedrock" => Ok(ProviderConfig::Bedrock {
464 api_key_env: default_bedrock_api_key_env(),
465 region_env: default_bedrock_region_env(),
466 default_model: default_bedrock_default_model(),
467 }),
468 "ollama" => Ok(ProviderConfig::Ollama {
469 api_key_env: default_ollama_api_key_env(),
470 base_url: default_ollama_base_url(),
471 default_model: default_ollama_default_model(),
472 }),
473 "vertex" => Ok(ProviderConfig::Vertex {
474 api_key_env: default_vertex_api_key_env(),
475 project_env: default_vertex_project_env(),
476 location_env: default_vertex_location_env(),
477 default_model: String::new(),
478 }),
479 "custom" => Ok(ProviderConfig::Custom {
480 index: 1,
481 endpoint: String::new(),
482 api_key_env: None,
483 default_model: String::new(),
484 }),
485 other => {
486 let json = format!(r#"{{"type":"{other}"}}"#);
487 serde_json::from_str(&json).map_err(|_| {
488 format!(
489 "Unknown provider type: {other}. Use: openai (or nvidia), azure, anthropic, bedrock, ollama, or a genai adapter kind"
490 )
491 })
492 }
493 }
494 }
495}
496
497fn default_openai_base() -> String {
498 "https://api.openai.com/v1".to_string()
499}
500
501fn default_openai_api_key_env() -> String {
502 "OPENAI_API_KEY".to_string()
503}
504
505fn default_nvidia_api_key_env() -> String {
506 "NVIDIA_API_KEY".to_string()
507}
508
509fn default_nvidia_api_base() -> String {
510 "https://integrate.api.nvidia.com/v1".to_string()
511}
512
513fn default_nvidia_default_model() -> String {
514 "nvidia/nemotron-3-ultra-550b-a55b".to_string()
515}
516
517fn default_azure_api_key_env() -> String {
518 "AZURE_FOUNDRY_API_KEY".to_string()
519}
520
521fn default_azure_base_url_env() -> String {
522 "AZURE_FOUNDRY_BASE_URL".to_string()
523}
524
525fn default_azure_default_model() -> String {
526 "DeepSeek-V4-Flash".to_string()
527}
528
529fn default_anthropic_api_key_env() -> String {
530 "ANTHROPIC_API_KEY".to_string()
531}
532
533fn default_anthropic_default_model() -> String {
534 "claude-3-5-sonnet-20241022".to_string()
535}
536
537fn default_bedrock_api_key_env() -> String {
538 "AWS_BEARER_TOKEN_BEDROCK".to_string()
539}
540
541fn default_bedrock_region_env() -> String {
542 "AWS_REGION".to_string()
543}
544
545fn default_bedrock_default_model() -> String {
546 "us.anthropic.claude-haiku-4-5-20251001-v1:0".to_string()
547}
548
549fn default_ollama_api_key_env() -> String {
550 "OLLAMA_API_KEY".to_string()
551}
552
553fn default_ollama_base_url() -> String {
554 "http://localhost:11434".to_string()
555}
556
557fn default_ollama_default_model() -> String {
558 "ministral-3:3b".to_string()
559}
560
561fn default_vertex_api_key_env() -> String {
562 "VERTEX_API_KEY".to_string()
563}
564
565fn default_vertex_project_env() -> String {
566 "VERTEX_PROJECT_ID".to_string()
567}
568
569fn default_vertex_location_env() -> String {
570 "VERTEX_LOCATION".to_string()
571}
572
573fn default_gemini_api_key_env() -> String {
574 "GEMINI_API_KEY".to_string()
575}
576fn default_fireworks_api_key_env() -> String {
577 "FIREWORKS_API_KEY".to_string()
578}
579fn default_together_api_key_env() -> String {
580 "TOGETHER_API_KEY".to_string()
581}
582fn default_groq_api_key_env() -> String {
583 "GROQ_API_KEY".to_string()
584}
585fn default_aihubmix_api_key_env() -> String {
586 "AIHUBMIX_API_KEY".to_string()
587}
588fn default_kimi_api_key_env() -> String {
589 "KIMI_API_KEY".to_string()
590}
591fn default_mimo_api_key_env() -> String {
592 "MIMO_API_KEY".to_string()
593}
594fn default_moonshot_api_key_env() -> String {
595 "MOONSHOT_API_KEY".to_string()
596}
597fn default_nebius_api_key_env() -> String {
598 "NEBIUS_API_KEY".to_string()
599}
600fn default_xai_api_key_env() -> String {
601 "XAI_API_KEY".to_string()
602}
603fn default_deepseek_api_key_env() -> String {
604 "DEEPSEEK_API_KEY".to_string()
605}
606fn default_zai_api_key_env() -> String {
607 "ZAI_API_KEY".to_string()
608}
609fn default_bigmodel_api_key_env() -> String {
610 "BIGMODEL_API_KEY".to_string()
611}
612fn default_aliyun_api_key_env() -> String {
613 "ALIYUN_API_KEY".to_string()
614}
615fn default_qwen_cloud_api_key_env() -> String {
616 "QWEN_CLOUD_API_KEY".to_string()
617}
618fn default_baidu_api_key_env() -> String {
619 "BAIDU_API_KEY".to_string()
620}
621fn default_cohere_api_key_env() -> String {
622 "COHERE_API_KEY".to_string()
623}
624fn default_ollama_cloud_api_key_env() -> String {
625 "OLLAMA_API_KEY".to_string()
626}
627fn default_omlx_api_key_env() -> String {
628 "OMLX_API_KEY".to_string()
629}
630fn default_github_copilot_api_key_env() -> String {
631 "GITHUB_TOKEN".to_string()
632}
633fn default_opencode_go_api_key_env() -> String {
634 "OPENCODE_GO_API_KEY".to_string()
635}
636fn default_bedrock_api_kind_api_key_env() -> String {
637 "BEDROCK_API_KEY".to_string()
638}
639fn default_open_router_api_key_env() -> String {
640 "OPEN_ROUTER_API_KEY".to_string()
641}
642fn default_atlascloud_api_key_env() -> String {
643 "ATLASCLOUD_API_KEY".to_string()
644}
645fn default_minimax_api_key_env() -> String {
646 "MINIMAX_API_KEY".to_string()
647}
648
649#[derive(Debug, Clone, Serialize, Deserialize)]
653pub struct ModelConfig {
654 pub provider: String,
656
657 pub model: String,
659
660 #[serde(default = "default_temperature")]
662 pub temperature: f32,
663
664 #[serde(default = "default_model_max_tokens")]
666 pub max_tokens: u32,
667}
668
669fn default_temperature() -> f32 {
670 0.7
671}
672
673fn default_model_max_tokens() -> u32 {
674 512
675}
676
677#[cfg(test)]
678mod tests {
679 use super::*;
680 use std::str::FromStr;
681
682 #[test]
683 fn parses_gemini_groq_openai_resp_custom() {
684 let gemini: ProviderConfig = serde_json::from_str(r#"{"type":"gemini"}"#).unwrap();
685 assert_eq!(gemini.type_name(), "gemini");
686 assert!(matches!(gemini, ProviderConfig::Gemini { .. }));
687
688 let groq: ProviderConfig = serde_json::from_str(r#"{"type":"groq"}"#).unwrap();
689 assert_eq!(groq.type_name(), "groq");
690
691 let resp: ProviderConfig = serde_json::from_str(r#"{"type":"openai_resp"}"#).unwrap();
692 assert_eq!(resp.type_name(), "openai_resp");
693
694 let custom: ProviderConfig = serde_json::from_str(
695 r#"{"type":"custom","index":2,"endpoint":"http://127.0.0.1:8000/v1"}"#,
696 )
697 .unwrap();
698 match custom {
699 ProviderConfig::Custom {
700 index, endpoint, ..
701 } => {
702 assert_eq!(index, 2);
703 assert_eq!(endpoint, "http://127.0.0.1:8000/v1");
704 }
705 other => panic!("expected Custom, got {}", other.type_name()),
706 }
707
708 assert_eq!(
709 ProviderConfig::from_str("gemini").unwrap().type_name(),
710 "gemini"
711 );
712 assert_eq!(ProviderConfig::from_str("groq").unwrap().type_name(), "groq");
713 assert_eq!(
714 ProviderConfig::from_str("openai_resp").unwrap().type_name(),
715 "openai_resp"
716 );
717 assert_eq!(
718 ProviderConfig::from_str("custom").unwrap().type_name(),
719 "custom"
720 );
721 assert_eq!(
722 ProviderConfig::from_str("openrouter").unwrap().type_name(),
723 "open_router"
724 );
725 assert_eq!(
726 ProviderConfig::from_str("github").unwrap().type_name(),
727 "github_copilot"
728 );
729 assert_eq!(
730 ProviderConfig::from_str("nvidia").unwrap().type_name(),
731 "openai"
732 );
733 assert!(ProviderConfig::from_str("not-a-provider").is_err());
734 }
735}