1use super::{
8 DEFAULT_ARCEE_BASE_URL, DEFAULT_ARCEE_MODEL, DEFAULT_ATLASCLOUD_BASE_URL,
9 DEFAULT_ATLASCLOUD_MODEL, DEFAULT_DEEPINFRA_BASE_URL, DEFAULT_DEEPINFRA_MODEL,
10 DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL, DEFAULT_DEEPSEEK_ANTHROPIC_MODEL,
11 DEFAULT_DEEPSEEK_BASE_URL, DEFAULT_DEEPSEEK_MODEL, DEFAULT_FIREWORKS_BASE_URL,
12 DEFAULT_FIREWORKS_MODEL, DEFAULT_HUGGINGFACE_BASE_URL, DEFAULT_HUGGINGFACE_MODEL,
13 DEFAULT_LONGCAT_BASE_URL, DEFAULT_LONGCAT_MODEL, DEFAULT_META_BASE_URL, DEFAULT_META_MODEL,
14 DEFAULT_MINIMAX_ANTHROPIC_BASE_URL, DEFAULT_MINIMAX_BASE_URL, DEFAULT_MINIMAX_MODEL,
15 DEFAULT_MOONSHOT_BASE_URL, DEFAULT_MOONSHOT_MODEL, DEFAULT_NOVITA_BASE_URL,
16 DEFAULT_NOVITA_MODEL, DEFAULT_NVIDIA_NIM_BASE_URL, DEFAULT_NVIDIA_NIM_MODEL,
17 DEFAULT_OLLAMA_BASE_URL, DEFAULT_OLLAMA_MODEL, DEFAULT_OPENAI_BASE_URL,
18 DEFAULT_OPENAI_CODEX_BASE_URL, DEFAULT_OPENAI_CODEX_MODEL, DEFAULT_OPENAI_MODEL,
19 DEFAULT_OPENMODEL_BASE_URL, DEFAULT_OPENMODEL_MODEL, DEFAULT_OPENROUTER_BASE_URL,
20 DEFAULT_OPENROUTER_MODEL, DEFAULT_QIANFAN_BASE_URL, DEFAULT_QIANFAN_MODEL,
21 DEFAULT_SAKANA_BASE_URL, DEFAULT_SAKANA_MODEL, DEFAULT_SGLANG_BASE_URL, DEFAULT_SGLANG_MODEL,
22 DEFAULT_SILICONFLOW_BASE_URL, DEFAULT_SILICONFLOW_CN_BASE_URL, DEFAULT_SILICONFLOW_MODEL,
23 DEFAULT_STEPFUN_BASE_URL, DEFAULT_STEPFUN_MODEL, DEFAULT_TOGETHER_BASE_URL,
24 DEFAULT_TOGETHER_MODEL, DEFAULT_VLLM_BASE_URL, DEFAULT_VLLM_MODEL, DEFAULT_VOLCENGINE_BASE_URL,
25 DEFAULT_VOLCENGINE_MODEL, DEFAULT_WANJIE_ARK_BASE_URL, DEFAULT_WANJIE_ARK_MODEL,
26 DEFAULT_XAI_BASE_URL, DEFAULT_XAI_MODEL, DEFAULT_XIAOMI_MIMO_BASE_URL,
27 DEFAULT_XIAOMI_MIMO_MODEL, DEFAULT_ZAI_BASE_URL, DEFAULT_ZAI_MODEL, ProviderKind,
28};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum WireFormat {
34 ChatCompletions,
36 Responses,
38 AnthropicMessages,
40}
41
42pub trait Provider: Send + Sync {
44 fn kind(&self) -> ProviderKind;
46
47 fn id(&self) -> &'static str {
49 self.kind().as_str()
50 }
51
52 fn display_name(&self) -> &'static str;
54
55 fn default_base_url(&self) -> &'static str;
57
58 fn default_model(&self) -> &'static str;
60
61 fn env_vars(&self) -> &'static [&'static str];
63
64 fn provider_config_key(&self) -> &'static str;
66
67 fn aliases(&self) -> &'static [&'static str] {
69 &[]
70 }
71
72 fn wire(&self) -> WireFormat {
74 WireFormat::ChatCompletions
75 }
76}
77
78macro_rules! provider {
79 (
80 $struct_name:ident,
81 $kind:ident,
82 $id:literal,
83 $display_name:literal,
84 $base_url:ident,
85 $model:ident,
86 [$($env_var:literal),* $(,)?],
87 $config_key:literal,
88 aliases: [$($alias:literal),* $(,)?]
89 ) => {
90 pub struct $struct_name;
92
93 impl Provider for $struct_name {
94 fn id(&self) -> &'static str {
95 $id
96 }
97
98 fn kind(&self) -> ProviderKind {
99 ProviderKind::$kind
100 }
101
102 fn display_name(&self) -> &'static str {
103 $display_name
104 }
105
106 fn default_base_url(&self) -> &'static str {
107 $base_url
108 }
109
110 fn default_model(&self) -> &'static str {
111 $model
112 }
113
114 fn env_vars(&self) -> &'static [&'static str] {
115 &[$($env_var),*]
116 }
117
118 fn provider_config_key(&self) -> &'static str {
119 $config_key
120 }
121
122 fn aliases(&self) -> &'static [&'static str] {
123 &[$($alias),*]
124 }
125 }
126 };
127}
128
129provider!(
130 Deepseek,
131 Deepseek,
132 "deepseek",
133 "DeepSeek",
134 DEFAULT_DEEPSEEK_BASE_URL,
135 DEFAULT_DEEPSEEK_MODEL,
136 ["DEEPSEEK_API_KEY"],
137 "deepseek",
138 aliases: ["deep-seek", "deepseek-cn", "deepseek_china", "deepseekcn", "deepseek-china"]
139);
140
141pub struct DeepseekAnthropic;
143
144impl Provider for DeepseekAnthropic {
145 fn id(&self) -> &'static str {
146 "deepseek-anthropic"
147 }
148
149 fn kind(&self) -> ProviderKind {
150 ProviderKind::DeepseekAnthropic
151 }
152
153 fn display_name(&self) -> &'static str {
154 "DeepSeek (Anthropic-compatible)"
155 }
156
157 fn default_base_url(&self) -> &'static str {
158 DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL
159 }
160
161 fn default_model(&self) -> &'static str {
162 DEFAULT_DEEPSEEK_ANTHROPIC_MODEL
163 }
164
165 fn env_vars(&self) -> &'static [&'static str] {
166 &["DEEPSEEK_API_KEY"]
167 }
168
169 fn provider_config_key(&self) -> &'static str {
170 "deepseek_anthropic"
171 }
172
173 fn aliases(&self) -> &'static [&'static str] {
174 &["deepseek_anthropic", "deepseek-claude", "deepseek_claude"]
175 }
176
177 fn wire(&self) -> WireFormat {
178 WireFormat::AnthropicMessages
179 }
180}
181provider!(
182 NvidiaNim,
183 NvidiaNim,
184 "nvidia-nim",
185 "NVIDIA NIM",
186 DEFAULT_NVIDIA_NIM_BASE_URL,
187 DEFAULT_NVIDIA_NIM_MODEL,
188 ["NVIDIA_API_KEY", "NVIDIA_NIM_API_KEY", "DEEPSEEK_API_KEY"],
189 "nvidia_nim",
190 aliases: ["nvidia", "nvidia_nim", "nim"]
191);
192provider!(
193 Openai,
194 Openai,
195 "openai",
196 "OpenAI-compatible",
197 DEFAULT_OPENAI_BASE_URL,
198 DEFAULT_OPENAI_MODEL,
199 ["OPENAI_API_KEY"],
200 "openai",
201 aliases: ["open-ai"]
202);
203provider!(
204 Atlascloud,
205 Atlascloud,
206 "atlascloud",
207 "AtlasCloud",
208 DEFAULT_ATLASCLOUD_BASE_URL,
209 DEFAULT_ATLASCLOUD_MODEL,
210 ["ATLASCLOUD_API_KEY"],
211 "atlascloud",
212 aliases: ["atlas-cloud", "atlas_cloud", "atlas"]
213);
214provider!(
215 WanjieArk,
216 WanjieArk,
217 "wanjie-ark",
218 "Wanjie Ark",
219 DEFAULT_WANJIE_ARK_BASE_URL,
220 DEFAULT_WANJIE_ARK_MODEL,
221 [
222 "WANJIE_ARK_API_KEY",
223 "WANJIE_API_KEY",
224 "WANJIE_MAAS_API_KEY"
225 ],
226 "wanjie_ark",
227 aliases: ["wanjie", "wanjie_ark", "ark-wanjie", "ark_wanjie", "wanjieark", "wanjie-maas", "wanjie_maas", "wanjiemaas"]
228);
229provider!(
230 Volcengine,
231 Volcengine,
232 "volcengine",
233 "Volcengine Ark",
234 DEFAULT_VOLCENGINE_BASE_URL,
235 DEFAULT_VOLCENGINE_MODEL,
236 [
237 "VOLCENGINE_API_KEY",
238 "VOLCENGINE_ARK_API_KEY",
239 "ARK_API_KEY"
240 ],
241 "volcengine",
242 aliases: ["volcengine-ark", "volcengine_ark", "ark", "volc-ark", "volcengineark"]
243);
244provider!(
245 Openrouter,
246 Openrouter,
247 "openrouter",
248 "OpenRouter",
249 DEFAULT_OPENROUTER_BASE_URL,
250 DEFAULT_OPENROUTER_MODEL,
251 ["OPENROUTER_API_KEY"],
252 "openrouter",
253 aliases: ["open_router"]
254);
255provider!(
256 XiaomiMimo,
257 XiaomiMimo,
258 "xiaomi-mimo",
259 "Xiaomi MiMo",
260 DEFAULT_XIAOMI_MIMO_BASE_URL,
261 DEFAULT_XIAOMI_MIMO_MODEL,
262 [
263 "XIAOMI_MIMO_TOKEN_PLAN_API_KEY",
264 "MIMO_TOKEN_PLAN_API_KEY",
265 "XIAOMI_MIMO_API_KEY",
266 "XIAOMI_API_KEY",
267 "MIMO_API_KEY",
268 ],
269 "xiaomi_mimo",
270 aliases: ["xiaomi_mimo", "xiaomimimo", "mimo", "xiaomi"]
271);
272provider!(
273 Novita,
274 Novita,
275 "novita",
276 "Novita AI",
277 DEFAULT_NOVITA_BASE_URL,
278 DEFAULT_NOVITA_MODEL,
279 ["NOVITA_API_KEY"],
280 "novita",
281 aliases: ["novita-ai", "novita_ai"]
285);
286provider!(
287 Fireworks,
288 Fireworks,
289 "fireworks",
290 "Fireworks AI",
291 DEFAULT_FIREWORKS_BASE_URL,
292 DEFAULT_FIREWORKS_MODEL,
293 ["FIREWORKS_API_KEY"],
294 "fireworks",
295 aliases: ["fireworks-ai"]
296);
297provider!(
298 Siliconflow,
299 Siliconflow,
300 "siliconflow",
301 "SiliconFlow",
302 DEFAULT_SILICONFLOW_BASE_URL,
303 DEFAULT_SILICONFLOW_MODEL,
304 ["SILICONFLOW_API_KEY"],
305 "siliconflow",
306 aliases: ["silicon-flow", "silicon_flow"]
307);
308provider!(
309 SiliconflowCN,
310 SiliconflowCN,
311 "siliconflow-CN",
312 "SiliconFlow (China)",
313 DEFAULT_SILICONFLOW_CN_BASE_URL,
314 DEFAULT_SILICONFLOW_MODEL,
315 ["SILICONFLOW_API_KEY"],
316 "siliconflow_cn",
317 aliases: [
318 "silicon-flow-cn",
319 "silicon-flow-CN",
320 "silicon_flow_cn",
321 "silicon_flow_CN",
322 "siliconflow-china",
323 ]
324);
325provider!(
326 Arcee,
327 Arcee,
328 "arcee",
329 "Arcee AI",
330 DEFAULT_ARCEE_BASE_URL,
331 DEFAULT_ARCEE_MODEL,
332 ["ARCEE_API_KEY"],
333 "arcee",
334 aliases: ["arcee-ai", "arcee_ai"]
335);
336provider!(
337 Moonshot,
338 Moonshot,
339 "moonshot",
340 "Moonshot/Kimi",
341 DEFAULT_MOONSHOT_BASE_URL,
342 DEFAULT_MOONSHOT_MODEL,
343 ["MOONSHOT_API_KEY", "KIMI_API_KEY"],
344 "moonshot",
345 aliases: ["moonshot-ai", "moonshotai", "moonshot_ai", "kimi", "kimi-k2"]
349);
350provider!(
351 Sglang,
352 Sglang,
353 "sglang",
354 "SGLang",
355 DEFAULT_SGLANG_BASE_URL,
356 DEFAULT_SGLANG_MODEL,
357 ["SGLANG_API_KEY"],
358 "sglang",
359 aliases: ["sg-lang"]
360);
361provider!(
362 Vllm,
363 Vllm,
364 "vllm",
365 "vLLM",
366 DEFAULT_VLLM_BASE_URL,
367 DEFAULT_VLLM_MODEL,
368 ["VLLM_API_KEY"],
369 "vllm",
370 aliases: ["v-llm"]
371);
372provider!(
373 Ollama,
374 Ollama,
375 "ollama",
376 "Ollama",
377 DEFAULT_OLLAMA_BASE_URL,
378 DEFAULT_OLLAMA_MODEL,
379 ["OLLAMA_API_KEY"],
380 "ollama",
381 aliases: ["ollama-local"]
382);
383provider!(
384 Huggingface,
385 Huggingface,
386 "huggingface",
387 "Hugging Face",
388 DEFAULT_HUGGINGFACE_BASE_URL,
389 DEFAULT_HUGGINGFACE_MODEL,
390 ["HUGGINGFACE_API_KEY", "HF_TOKEN"],
391 "huggingface",
392 aliases: ["hugging-face", "hugging_face", "hf"]
393);
394provider!(
395 Together,
396 Together,
397 "together",
398 "Together AI",
399 DEFAULT_TOGETHER_BASE_URL,
400 DEFAULT_TOGETHER_MODEL,
401 ["TOGETHER_API_KEY"],
402 "together",
403 aliases: ["together-ai", "together_ai", "togetherai"]
408);
409provider!(
410 Qianfan,
411 Qianfan,
412 "qianfan",
413 "Baidu Qianfan",
414 DEFAULT_QIANFAN_BASE_URL,
415 DEFAULT_QIANFAN_MODEL,
416 ["QIANFAN_API_KEY", "BAIDU_QIANFAN_API_KEY"],
417 "qianfan",
418 aliases: ["baidu-qianfan", "baidu_qianfan", "baidu"]
419);
420
421pub struct OpenaiCodex;
423
424impl Provider for OpenaiCodex {
425 fn id(&self) -> &'static str {
426 "openai-codex"
427 }
428
429 fn kind(&self) -> ProviderKind {
430 ProviderKind::OpenaiCodex
431 }
432
433 fn display_name(&self) -> &'static str {
434 "OpenAI Codex (ChatGPT)"
435 }
436
437 fn default_base_url(&self) -> &'static str {
438 DEFAULT_OPENAI_CODEX_BASE_URL
439 }
440
441 fn default_model(&self) -> &'static str {
442 DEFAULT_OPENAI_CODEX_MODEL
443 }
444
445 fn env_vars(&self) -> &'static [&'static str] {
446 &["OPENAI_CODEX_ACCESS_TOKEN", "CODEX_ACCESS_TOKEN"]
447 }
448
449 fn provider_config_key(&self) -> &'static str {
450 "openai_codex"
451 }
452
453 fn aliases(&self) -> &'static [&'static str] {
454 &[
455 "openai_codex",
456 "openaicodex",
457 "codex",
458 "chatgpt",
459 "chatgpt-codex",
460 "chatgpt_codex",
461 "chatgptcodex",
462 ]
463 }
464
465 fn wire(&self) -> WireFormat {
466 WireFormat::Responses
467 }
468}
469
470pub struct Anthropic;
472
473impl Provider for Anthropic {
474 fn id(&self) -> &'static str {
475 "anthropic"
476 }
477
478 fn kind(&self) -> ProviderKind {
479 ProviderKind::Anthropic
480 }
481
482 fn display_name(&self) -> &'static str {
483 "Anthropic"
484 }
485
486 fn default_base_url(&self) -> &'static str {
487 crate::DEFAULT_ANTHROPIC_BASE_URL
488 }
489
490 fn default_model(&self) -> &'static str {
491 crate::DEFAULT_ANTHROPIC_MODEL
492 }
493
494 fn env_vars(&self) -> &'static [&'static str] {
495 &["ANTHROPIC_API_KEY"]
496 }
497
498 fn provider_config_key(&self) -> &'static str {
499 "anthropic"
500 }
501
502 fn wire(&self) -> WireFormat {
503 WireFormat::AnthropicMessages
504 }
505}
506
507pub struct Openmodel;
509
510impl Provider for Openmodel {
511 fn id(&self) -> &'static str {
512 "openmodel"
513 }
514
515 fn kind(&self) -> ProviderKind {
516 ProviderKind::Openmodel
517 }
518
519 fn display_name(&self) -> &'static str {
520 "OpenModel"
521 }
522
523 fn default_base_url(&self) -> &'static str {
524 DEFAULT_OPENMODEL_BASE_URL
525 }
526
527 fn default_model(&self) -> &'static str {
528 DEFAULT_OPENMODEL_MODEL
529 }
530
531 fn env_vars(&self) -> &'static [&'static str] {
532 &["OPENMODEL_API_KEY"]
533 }
534
535 fn provider_config_key(&self) -> &'static str {
536 "openmodel"
537 }
538
539 fn aliases(&self) -> &'static [&'static str] {
540 &["open-model", "open_model"]
541 }
542
543 fn wire(&self) -> WireFormat {
544 WireFormat::AnthropicMessages
545 }
546}
547
548provider!(
549 Zai,
550 Zai,
551 "zai",
552 "Zhipu AI / Z.ai",
553 DEFAULT_ZAI_BASE_URL,
554 DEFAULT_ZAI_MODEL,
555 ["ZAI_API_KEY", "Z_AI_API_KEY", "ZHIPU_API_KEY", "GLM_API_KEY"],
556 "zai",
557 aliases: ["z-ai", "z_ai", "z.ai", "zhipu", "zhipuai", "bigmodel", "big-model"]
558);
559
560provider!(
561 Stepfun,
562 Stepfun,
563 "stepfun",
564 "StepFun / StepFlash",
565 DEFAULT_STEPFUN_BASE_URL,
566 DEFAULT_STEPFUN_MODEL,
567 ["STEPFUN_API_KEY", "STEP_API_KEY"],
568 "stepfun",
569 aliases: ["step-fun", "step_fun", "stepflash", "step-flash", "step_flash"]
570);
571
572provider!(
573 Minimax,
574 Minimax,
575 "minimax",
576 "MiniMax",
577 DEFAULT_MINIMAX_BASE_URL,
578 DEFAULT_MINIMAX_MODEL,
579 ["MINIMAX_API_KEY"],
580 "minimax",
581 aliases: ["mini-max", "mini_max"]
582);
583
584pub struct MinimaxAnthropic;
586
587impl Provider for MinimaxAnthropic {
588 fn id(&self) -> &'static str {
589 "minimax-anthropic"
590 }
591
592 fn kind(&self) -> ProviderKind {
593 ProviderKind::MinimaxAnthropic
594 }
595
596 fn display_name(&self) -> &'static str {
597 "MiniMax (Anthropic-compatible)"
598 }
599
600 fn default_base_url(&self) -> &'static str {
601 DEFAULT_MINIMAX_ANTHROPIC_BASE_URL
602 }
603
604 fn default_model(&self) -> &'static str {
605 DEFAULT_MINIMAX_MODEL
606 }
607
608 fn env_vars(&self) -> &'static [&'static str] {
609 &["MINIMAX_API_KEY"]
610 }
611
612 fn provider_config_key(&self) -> &'static str {
613 "minimax_anthropic"
614 }
615
616 fn aliases(&self) -> &'static [&'static str] {
617 &[
618 "minimax_anthropic",
619 "mini-max-anthropic",
620 "mini_max_anthropic",
621 ]
622 }
623
624 fn wire(&self) -> WireFormat {
625 WireFormat::AnthropicMessages
626 }
627}
628
629provider!(
630 Deepinfra,
631 Deepinfra,
632 "deepinfra",
633 "DeepInfra",
634 DEFAULT_DEEPINFRA_BASE_URL,
635 DEFAULT_DEEPINFRA_MODEL,
636 ["DEEPINFRA_API_KEY", "DEEPINFRA_TOKEN"],
637 "deepinfra",
638 aliases: ["deep-infra", "deep_infra"]
639);
640
641provider!(
642 Sakana,
643 Sakana,
644 "sakana",
645 "Sakana AI (Fugu)",
646 DEFAULT_SAKANA_BASE_URL,
647 DEFAULT_SAKANA_MODEL,
648 ["FUGU_API_KEY", "SAKANA_API_KEY"],
649 "sakana",
650 aliases: ["sakana-ai", "sakana_ai", "fugu"]
651);
652
653provider!(
654 LongCat,
655 LongCat,
656 "longcat",
657 "Meituan LongCat",
658 DEFAULT_LONGCAT_BASE_URL,
659 DEFAULT_LONGCAT_MODEL,
660 ["LONGCAT_API_KEY"],
661 "longcat",
662 aliases: ["long-cat", "meituan-longcat", "meituan"]
663);
664
665provider!(
666 Meta,
667 Meta,
668 "meta",
669 "Meta Model API",
670 DEFAULT_META_BASE_URL,
671 DEFAULT_META_MODEL,
672 ["META_MODEL_API_KEY", "MODEL_API_KEY"],
673 "meta",
674 aliases: [
675 "meta-ai",
676 "meta_ai",
677 "meta-model-api",
678 "meta_model_api",
679 "muse",
680 "muse-spark"
681 ]
682);
683
684provider!(
685 Xai,
686 Xai,
687 "xai",
688 "xAI",
689 DEFAULT_XAI_BASE_URL,
690 DEFAULT_XAI_MODEL,
691 ["XAI_API_KEY"],
692 "xai",
693 aliases: ["x-ai", "x_ai", "grok"]
694);
695
696pub struct Custom;
707
708impl Provider for Custom {
709 fn id(&self) -> &'static str {
710 "custom"
711 }
712
713 fn kind(&self) -> ProviderKind {
714 ProviderKind::Custom
715 }
716
717 fn display_name(&self) -> &'static str {
718 "Custom (OpenAI-compatible)"
719 }
720
721 fn default_base_url(&self) -> &'static str {
722 "http://localhost/v1"
726 }
727
728 fn default_model(&self) -> &'static str {
729 "custom-model"
732 }
733
734 fn env_vars(&self) -> &'static [&'static str] {
735 &[]
738 }
739
740 fn provider_config_key(&self) -> &'static str {
741 "custom"
742 }
743
744 fn wire(&self) -> WireFormat {
745 WireFormat::ChatCompletions
746 }
747}
748
749static DEEPSEEK: Deepseek = Deepseek;
750static DEEPSEEK_ANTHROPIC: DeepseekAnthropic = DeepseekAnthropic;
751static NVIDIA_NIM: NvidiaNim = NvidiaNim;
752static OPENAI: Openai = Openai;
753static ATLASCLOUD: Atlascloud = Atlascloud;
754static WANJIE_ARK: WanjieArk = WanjieArk;
755static VOLCENGINE: Volcengine = Volcengine;
756static OPENROUTER: Openrouter = Openrouter;
757static XIAOMI_MIMO: XiaomiMimo = XiaomiMimo;
758static NOVITA: Novita = Novita;
759static FIREWORKS: Fireworks = Fireworks;
760static SILICONFLOW: Siliconflow = Siliconflow;
761static SILICONFLOW_CN: SiliconflowCN = SiliconflowCN;
762static ARCEE: Arcee = Arcee;
763static MOONSHOT: Moonshot = Moonshot;
764static SGLANG: Sglang = Sglang;
765static VLLM: Vllm = Vllm;
766static OLLAMA: Ollama = Ollama;
767static HUGGINGFACE: Huggingface = Huggingface;
768static TOGETHER: Together = Together;
769static QIANFAN: Qianfan = Qianfan;
770static OPENAI_CODEX: OpenaiCodex = OpenaiCodex;
771static ANTHROPIC: Anthropic = Anthropic;
772static OPENMODEL: Openmodel = Openmodel;
773static ZAI: Zai = Zai;
774static STEPFUN: Stepfun = Stepfun;
775static MINIMAX: Minimax = Minimax;
776static MINIMAX_ANTHROPIC: MinimaxAnthropic = MinimaxAnthropic;
777static DEEPINFRA: Deepinfra = Deepinfra;
778static SAKANA: Sakana = Sakana;
779static LONGCAT: LongCat = LongCat;
780static META: Meta = Meta;
781static XAI: Xai = Xai;
782static CUSTOM: Custom = Custom;
783
784static PROVIDER_REGISTRY: [&dyn Provider; 34] = [
785 &DEEPSEEK,
786 &DEEPSEEK_ANTHROPIC,
787 &NVIDIA_NIM,
788 &OPENAI,
789 &ATLASCLOUD,
790 &WANJIE_ARK,
791 &VOLCENGINE,
792 &OPENROUTER,
793 &XIAOMI_MIMO,
794 &NOVITA,
795 &FIREWORKS,
796 &SILICONFLOW,
797 &ARCEE,
798 &SILICONFLOW_CN,
799 &MOONSHOT,
800 &SGLANG,
801 &VLLM,
802 &OLLAMA,
803 &HUGGINGFACE,
804 &TOGETHER,
805 &QIANFAN,
806 &OPENAI_CODEX,
807 &ANTHROPIC,
808 &OPENMODEL,
809 &ZAI,
810 &STEPFUN,
811 &MINIMAX,
812 &MINIMAX_ANTHROPIC,
813 &DEEPINFRA,
814 &SAKANA,
815 &LONGCAT,
816 &META,
817 &XAI,
818 &CUSTOM,
819];
820
821#[must_use]
827pub fn all_providers() -> &'static [&'static dyn Provider] {
828 &PROVIDER_REGISTRY
829}
830
831#[must_use]
847pub fn providers_sorted_for_display() -> Vec<&'static dyn Provider> {
848 let mut providers = all_providers().to_vec();
849 providers.sort_by(|a, b| {
850 a.display_name()
851 .to_ascii_lowercase()
852 .cmp(&b.display_name().to_ascii_lowercase())
853 });
854 providers
855}
856
857#[must_use]
859pub fn lookup_provider(id: &str) -> Option<&'static dyn Provider> {
860 let id = id.trim();
861 all_providers()
862 .iter()
863 .copied()
864 .find(|provider| provider.id() == id)
865}
866
867#[must_use]
869pub fn resolve_provider(id_or_alias: &str) -> Option<&'static dyn Provider> {
870 ProviderKind::parse(id_or_alias).map(provider_for_kind)
871}
872
873#[must_use]
875pub fn provider_for_kind(kind: ProviderKind) -> &'static dyn Provider {
876 PROVIDER_REGISTRY
877 .iter()
878 .find(|p| p.kind() == kind)
879 .copied()
880 .expect("ProviderKind variant missing from PROVIDER_REGISTRY")
881}
882
883#[cfg(test)]
884mod tests {
885 use super::*;
886
887 #[test]
888 fn display_order_is_alphabetical_by_display_name() {
889 let display = providers_sorted_for_display();
890 let names: Vec<String> = display
891 .iter()
892 .map(|p| p.display_name().to_ascii_lowercase())
893 .collect();
894 let mut sorted = names.clone();
895 sorted.sort();
896 assert_eq!(
897 names, sorted,
898 "providers_sorted_for_display must be alphabetical (case-insensitive) by display name"
899 );
900 }
901
902 #[test]
903 fn display_order_differs_from_internal_all_order() {
904 let display_ids: Vec<&str> = providers_sorted_for_display()
907 .iter()
908 .map(|p| p.id())
909 .collect();
910 let internal_ids: Vec<&str> = all_providers().iter().map(|p| p.id()).collect();
911 assert_ne!(
912 display_ids, internal_ids,
913 "display order should not match internal ALL order"
914 );
915 }
916
917 #[test]
918 fn display_order_is_complete_and_unique() {
919 let display = providers_sorted_for_display();
921 assert_eq!(
922 display.len(),
923 all_providers().len(),
924 "display order must include every built-in provider"
925 );
926 let mut ids: Vec<&str> = display.iter().map(|p| p.id()).collect();
927 ids.sort_unstable();
928 let before = ids.len();
929 ids.dedup();
930 assert_eq!(
931 before,
932 ids.len(),
933 "display order must not contain duplicates"
934 );
935 }
936
937 #[test]
938 fn deepseek_is_present_but_not_first_in_display_order() {
939 let display = providers_sorted_for_display();
942 assert_eq!(
943 all_providers()[0].kind(),
944 ProviderKind::Deepseek,
945 "DeepSeek is expected to remain first in the stable internal order"
946 );
947 assert!(
948 display.iter().any(|p| p.kind() == ProviderKind::Deepseek),
949 "DeepSeek must remain present in display order"
950 );
951 assert_ne!(
952 display[0].kind(),
953 ProviderKind::Deepseek,
954 "DeepSeek must not be hard-coded first in display order"
955 );
956 assert_eq!(
959 display[0].display_name(),
960 "Anthropic",
961 "alphabetical display order should lead with Anthropic"
962 );
963 }
964}