1use crate::provider::InputType;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ProviderOnboardingMode {
11 BuiltInNative,
12 OpenAICompatiblePreset,
13 NativeAdapterRequired,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[allow(clippy::struct_excessive_bools)]
18pub struct ProviderTestObligations {
19 pub unit: bool,
20 pub contract: bool,
21 pub conformance: bool,
22 pub e2e: bool,
23}
24
25#[derive(Debug, Clone, Copy)]
26pub struct ProviderRoutingDefaults {
27 pub api: &'static str,
28 pub base_url: &'static str,
29 pub auth_header: bool,
30 pub reasoning: bool,
31 pub input: &'static [InputType],
32 pub context_window: u32,
33 pub max_tokens: u32,
34}
35
36#[derive(Debug, Clone, Copy)]
37pub struct ProviderMetadata {
38 pub canonical_id: &'static str,
39 pub display_name: Option<&'static str>,
40 pub aliases: &'static [&'static str],
41 pub auth_env_keys: &'static [&'static str],
42 pub onboarding: ProviderOnboardingMode,
43 pub routing_defaults: Option<ProviderRoutingDefaults>,
44 pub test_obligations: ProviderTestObligations,
45}
46
47const INPUT_TEXT: [InputType; 1] = [InputType::Text];
48const INPUT_TEXT_IMAGE: [InputType; 2] = [InputType::Text, InputType::Image];
49
50const TEST_REQUIRED: ProviderTestObligations = ProviderTestObligations {
51 unit: true,
52 contract: true,
53 conformance: true,
54 e2e: true,
55};
56
57pub const PROVIDER_METADATA: &[ProviderMetadata] = &[
58 ProviderMetadata {
59 canonical_id: "anthropic",
60 display_name: Some("Anthropic"),
61 aliases: &[],
62 auth_env_keys: &["ANTHROPIC_API_KEY"],
63 onboarding: ProviderOnboardingMode::BuiltInNative,
64 routing_defaults: Some(ProviderRoutingDefaults {
65 api: "anthropic-messages",
66 base_url: "https://api.anthropic.com/v1/messages",
67 auth_header: false,
68 reasoning: true,
69 input: &INPUT_TEXT_IMAGE,
70 context_window: 200_000,
71 max_tokens: 8192,
72 }),
73 test_obligations: TEST_REQUIRED,
74 },
75 ProviderMetadata {
76 canonical_id: "openai",
77 display_name: Some("OpenAI"),
78 aliases: &[],
79 auth_env_keys: &["OPENAI_API_KEY"],
80 onboarding: ProviderOnboardingMode::BuiltInNative,
81 routing_defaults: Some(ProviderRoutingDefaults {
82 api: "openai-responses",
83 base_url: "https://api.openai.com/v1",
84 auth_header: true,
85 reasoning: true,
86 input: &INPUT_TEXT_IMAGE,
87 context_window: 128_000,
88 max_tokens: 16_384,
89 }),
90 test_obligations: TEST_REQUIRED,
91 },
92 ProviderMetadata {
93 canonical_id: "openai-codex",
94 display_name: Some("OpenAI Codex (ChatGPT)"),
95 aliases: &["codex", "chatgpt-codex"],
96 auth_env_keys: &[],
97 onboarding: ProviderOnboardingMode::NativeAdapterRequired,
98 routing_defaults: None,
99 test_obligations: TEST_REQUIRED,
100 },
101 ProviderMetadata {
102 canonical_id: "google",
103 display_name: Some("Google Gemini"),
104 aliases: &["gemini"],
105 auth_env_keys: &["GOOGLE_API_KEY", "GEMINI_API_KEY"],
106 onboarding: ProviderOnboardingMode::BuiltInNative,
107 routing_defaults: Some(ProviderRoutingDefaults {
108 api: "google-generative-ai",
109 base_url: "https://generativelanguage.googleapis.com/v1beta",
110 auth_header: false,
111 reasoning: true,
112 input: &INPUT_TEXT_IMAGE,
113 context_window: 128_000,
114 max_tokens: 8192,
115 }),
116 test_obligations: TEST_REQUIRED,
117 },
118 ProviderMetadata {
119 canonical_id: "google-gemini-cli",
120 display_name: Some("Google Cloud Code Assist"),
121 aliases: &["gemini-cli"],
122 auth_env_keys: &[],
123 onboarding: ProviderOnboardingMode::NativeAdapterRequired,
124 routing_defaults: None,
125 test_obligations: TEST_REQUIRED,
126 },
127 ProviderMetadata {
128 canonical_id: "google-antigravity",
129 display_name: Some("Google Antigravity"),
130 aliases: &["antigravity"],
131 auth_env_keys: &[],
132 onboarding: ProviderOnboardingMode::NativeAdapterRequired,
133 routing_defaults: None,
134 test_obligations: TEST_REQUIRED,
135 },
136 ProviderMetadata {
137 canonical_id: "cohere",
138 display_name: Some("Cohere"),
139 aliases: &[],
140 auth_env_keys: &["COHERE_API_KEY"],
141 onboarding: ProviderOnboardingMode::BuiltInNative,
142 routing_defaults: Some(ProviderRoutingDefaults {
143 api: "cohere-chat",
144 base_url: "https://api.cohere.com/v2",
145 auth_header: false,
146 reasoning: true,
147 input: &INPUT_TEXT,
148 context_window: 128_000,
149 max_tokens: 8192,
150 }),
151 test_obligations: TEST_REQUIRED,
152 },
153 ProviderMetadata {
154 canonical_id: "groq",
155 display_name: Some("Groq"),
156 aliases: &[],
157 auth_env_keys: &["GROQ_API_KEY"],
158 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
159 routing_defaults: Some(ProviderRoutingDefaults {
160 api: "openai-completions",
161 base_url: "https://api.groq.com/openai/v1",
162 auth_header: true,
163 reasoning: true,
164 input: &INPUT_TEXT,
165 context_window: 128_000,
166 max_tokens: 16_384,
167 }),
168 test_obligations: TEST_REQUIRED,
169 },
170 ProviderMetadata {
171 canonical_id: "deepinfra",
172 display_name: Some("Deep Infra"),
173 aliases: &["deep-infra"],
174 auth_env_keys: &["DEEPINFRA_API_KEY"],
175 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
176 routing_defaults: Some(ProviderRoutingDefaults {
177 api: "openai-completions",
178 base_url: "https://api.deepinfra.com/v1/openai",
179 auth_header: true,
180 reasoning: true,
181 input: &INPUT_TEXT,
182 context_window: 128_000,
183 max_tokens: 16_384,
184 }),
185 test_obligations: TEST_REQUIRED,
186 },
187 ProviderMetadata {
188 canonical_id: "cerebras",
189 display_name: Some("Cerebras"),
190 aliases: &[],
191 auth_env_keys: &["CEREBRAS_API_KEY"],
192 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
193 routing_defaults: Some(ProviderRoutingDefaults {
194 api: "openai-completions",
195 base_url: "https://api.cerebras.ai/v1",
196 auth_header: true,
197 reasoning: true,
198 input: &INPUT_TEXT,
199 context_window: 128_000,
200 max_tokens: 16_384,
201 }),
202 test_obligations: TEST_REQUIRED,
203 },
204 ProviderMetadata {
205 canonical_id: "openrouter",
206 display_name: Some("OpenRouter"),
207 aliases: &["open-router"],
208 auth_env_keys: &["OPENROUTER_API_KEY"],
209 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
210 routing_defaults: Some(ProviderRoutingDefaults {
211 api: "openai-completions",
212 base_url: "https://openrouter.ai/api/v1",
213 auth_header: true,
214 reasoning: true,
215 input: &INPUT_TEXT,
216 context_window: 128_000,
217 max_tokens: 16_384,
218 }),
219 test_obligations: TEST_REQUIRED,
220 },
221 ProviderMetadata {
222 canonical_id: "mistral",
223 display_name: Some("Mistral AI"),
224 aliases: &["mistralai"],
225 auth_env_keys: &["MISTRAL_API_KEY"],
226 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
227 routing_defaults: Some(ProviderRoutingDefaults {
228 api: "openai-completions",
229 base_url: "https://api.mistral.ai/v1",
230 auth_header: true,
231 reasoning: true,
232 input: &INPUT_TEXT,
233 context_window: 128_000,
234 max_tokens: 16_384,
235 }),
236 test_obligations: TEST_REQUIRED,
237 },
238 ProviderMetadata {
239 canonical_id: "moonshotai",
240 display_name: Some("Moonshot AI"),
241 aliases: &["moonshot", "kimi"],
242 auth_env_keys: &["MOONSHOT_API_KEY", "KIMI_API_KEY"],
243 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
244 routing_defaults: Some(ProviderRoutingDefaults {
245 api: "openai-completions",
246 base_url: "https://api.moonshot.ai/v1",
247 auth_header: true,
248 reasoning: true,
249 input: &INPUT_TEXT,
250 context_window: 128_000,
251 max_tokens: 16_384,
252 }),
253 test_obligations: TEST_REQUIRED,
254 },
255 ProviderMetadata {
256 canonical_id: "alibaba",
257 display_name: Some("Alibaba (Qwen)"),
258 aliases: &["dashscope", "qwen"],
259 auth_env_keys: &["DASHSCOPE_API_KEY", "QWEN_API_KEY"],
260 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
261 routing_defaults: Some(ProviderRoutingDefaults {
262 api: "openai-completions",
263 base_url: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
264 auth_header: true,
265 reasoning: true,
266 input: &INPUT_TEXT,
267 context_window: 128_000,
268 max_tokens: 16_384,
269 }),
270 test_obligations: TEST_REQUIRED,
271 },
272 ProviderMetadata {
273 canonical_id: "deepseek",
274 display_name: Some("DeepSeek"),
275 aliases: &["deep-seek"],
276 auth_env_keys: &["DEEPSEEK_API_KEY"],
277 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
278 routing_defaults: Some(ProviderRoutingDefaults {
279 api: "openai-completions",
280 base_url: "https://api.deepseek.com",
281 auth_header: true,
282 reasoning: true,
283 input: &INPUT_TEXT,
284 context_window: 128_000,
285 max_tokens: 16_384,
286 }),
287 test_obligations: TEST_REQUIRED,
288 },
289 ProviderMetadata {
290 canonical_id: "fireworks",
291 display_name: Some("Fireworks AI"),
292 aliases: &["fireworks-ai"],
293 auth_env_keys: &["FIREWORKS_API_KEY"],
294 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
295 routing_defaults: Some(ProviderRoutingDefaults {
296 api: "openai-completions",
297 base_url: "https://api.fireworks.ai/inference/v1",
298 auth_header: true,
299 reasoning: true,
300 input: &INPUT_TEXT,
301 context_window: 128_000,
302 max_tokens: 16_384,
303 }),
304 test_obligations: TEST_REQUIRED,
305 },
306 ProviderMetadata {
307 canonical_id: "togetherai",
308 display_name: Some("Together AI"),
309 aliases: &["together", "together-ai"],
310 auth_env_keys: &["TOGETHER_API_KEY", "TOGETHER_AI_API_KEY"],
311 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
312 routing_defaults: Some(ProviderRoutingDefaults {
313 api: "openai-completions",
314 base_url: "https://api.together.xyz/v1",
315 auth_header: true,
316 reasoning: true,
317 input: &INPUT_TEXT,
318 context_window: 128_000,
319 max_tokens: 16_384,
320 }),
321 test_obligations: TEST_REQUIRED,
322 },
323 ProviderMetadata {
324 canonical_id: "perplexity",
325 display_name: Some("Perplexity"),
326 aliases: &["pplx"],
327 auth_env_keys: &["PERPLEXITY_API_KEY"],
328 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
329 routing_defaults: Some(ProviderRoutingDefaults {
330 api: "openai-completions",
331 base_url: "https://api.perplexity.ai",
332 auth_header: true,
333 reasoning: true,
334 input: &INPUT_TEXT,
335 context_window: 128_000,
336 max_tokens: 16_384,
337 }),
338 test_obligations: TEST_REQUIRED,
339 },
340 ProviderMetadata {
341 canonical_id: "xai",
342 display_name: Some("xAI (Grok)"),
343 aliases: &["grok", "x-ai"],
344 auth_env_keys: &["XAI_API_KEY"],
345 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
346 routing_defaults: Some(ProviderRoutingDefaults {
347 api: "openai-completions",
348 base_url: "https://api.x.ai/v1",
349 auth_header: true,
350 reasoning: true,
351 input: &INPUT_TEXT,
352 context_window: 128_000,
353 max_tokens: 16_384,
354 }),
355 test_obligations: TEST_REQUIRED,
356 },
357 ProviderMetadata {
359 canonical_id: "302ai",
360 display_name: Some("302.AI"),
361 aliases: &[],
362 auth_env_keys: &["302AI_API_KEY"],
363 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
364 routing_defaults: Some(ProviderRoutingDefaults {
365 api: "openai-completions",
366 base_url: "https://api.302.ai/v1",
367 auth_header: true,
368 reasoning: true,
369 input: &INPUT_TEXT,
370 context_window: 128_000,
371 max_tokens: 16_384,
372 }),
373 test_obligations: TEST_REQUIRED,
374 },
375 ProviderMetadata {
376 canonical_id: "abacus",
377 display_name: Some("Abacus AI"),
378 aliases: &[],
379 auth_env_keys: &["ABACUS_API_KEY"],
380 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
381 routing_defaults: Some(ProviderRoutingDefaults {
382 api: "openai-completions",
383 base_url: "https://routellm.abacus.ai/v1",
384 auth_header: true,
385 reasoning: true,
386 input: &INPUT_TEXT,
387 context_window: 128_000,
388 max_tokens: 16_384,
389 }),
390 test_obligations: TEST_REQUIRED,
391 },
392 ProviderMetadata {
393 canonical_id: "aihubmix",
394 display_name: Some("AIHubMix"),
395 aliases: &[],
396 auth_env_keys: &["AIHUBMIX_API_KEY"],
397 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
398 routing_defaults: Some(ProviderRoutingDefaults {
399 api: "openai-completions",
400 base_url: "https://aihubmix.com/v1",
401 auth_header: true,
402 reasoning: true,
403 input: &INPUT_TEXT,
404 context_window: 128_000,
405 max_tokens: 16_384,
406 }),
407 test_obligations: TEST_REQUIRED,
408 },
409 ProviderMetadata {
410 canonical_id: "bailing",
411 display_name: Some("Bailing"),
412 aliases: &[],
413 auth_env_keys: &["BAILING_API_TOKEN"],
414 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
415 routing_defaults: Some(ProviderRoutingDefaults {
416 api: "openai-completions",
417 base_url: "https://api.tbox.cn/api/llm/v1",
418 auth_header: true,
419 reasoning: false,
420 input: &INPUT_TEXT,
421 context_window: 128_000,
422 max_tokens: 16_384,
423 }),
424 test_obligations: TEST_REQUIRED,
425 },
426 ProviderMetadata {
427 canonical_id: "berget",
428 display_name: Some("Berget"),
429 aliases: &[],
430 auth_env_keys: &["BERGET_API_KEY"],
431 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
432 routing_defaults: Some(ProviderRoutingDefaults {
433 api: "openai-completions",
434 base_url: "https://api.berget.ai/v1",
435 auth_header: true,
436 reasoning: true,
437 input: &INPUT_TEXT,
438 context_window: 128_000,
439 max_tokens: 16_384,
440 }),
441 test_obligations: TEST_REQUIRED,
442 },
443 ProviderMetadata {
444 canonical_id: "chutes",
445 display_name: Some("Chutes"),
446 aliases: &[],
447 auth_env_keys: &["CHUTES_API_KEY"],
448 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
449 routing_defaults: Some(ProviderRoutingDefaults {
450 api: "openai-completions",
451 base_url: "https://llm.chutes.ai/v1",
452 auth_header: true,
453 reasoning: true,
454 input: &INPUT_TEXT,
455 context_window: 128_000,
456 max_tokens: 16_384,
457 }),
458 test_obligations: TEST_REQUIRED,
459 },
460 ProviderMetadata {
461 canonical_id: "cortecs",
462 display_name: Some("Cortecs"),
463 aliases: &[],
464 auth_env_keys: &["CORTECS_API_KEY"],
465 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
466 routing_defaults: Some(ProviderRoutingDefaults {
467 api: "openai-completions",
468 base_url: "https://api.cortecs.ai/v1",
469 auth_header: true,
470 reasoning: true,
471 input: &INPUT_TEXT,
472 context_window: 128_000,
473 max_tokens: 16_384,
474 }),
475 test_obligations: TEST_REQUIRED,
476 },
477 ProviderMetadata {
478 canonical_id: "fastrouter",
479 display_name: Some("FastRouter"),
480 aliases: &[],
481 auth_env_keys: &["FASTROUTER_API_KEY"],
482 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
483 routing_defaults: Some(ProviderRoutingDefaults {
484 api: "openai-completions",
485 base_url: "https://go.fastrouter.ai/api/v1",
486 auth_header: true,
487 reasoning: true,
488 input: &INPUT_TEXT,
489 context_window: 128_000,
490 max_tokens: 16_384,
491 }),
492 test_obligations: TEST_REQUIRED,
493 },
494 ProviderMetadata {
496 canonical_id: "firmware",
497 display_name: Some("Firmware"),
498 aliases: &[],
499 auth_env_keys: &["FIRMWARE_API_KEY"],
500 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
501 routing_defaults: Some(ProviderRoutingDefaults {
502 api: "openai-completions",
503 base_url: "https://app.firmware.ai/api/v1",
504 auth_header: true,
505 reasoning: true,
506 input: &INPUT_TEXT,
507 context_window: 128_000,
508 max_tokens: 16_384,
509 }),
510 test_obligations: TEST_REQUIRED,
511 },
512 ProviderMetadata {
513 canonical_id: "friendli",
514 display_name: Some("Friendli"),
515 aliases: &[],
516 auth_env_keys: &["FRIENDLI_TOKEN"],
517 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
518 routing_defaults: Some(ProviderRoutingDefaults {
519 api: "openai-completions",
520 base_url: "https://api.friendli.ai/serverless/v1",
521 auth_header: true,
522 reasoning: true,
523 input: &INPUT_TEXT,
524 context_window: 128_000,
525 max_tokens: 16_384,
526 }),
527 test_obligations: TEST_REQUIRED,
528 },
529 ProviderMetadata {
530 canonical_id: "github-models",
531 display_name: Some("GitHub Models"),
532 aliases: &[],
533 auth_env_keys: &["GITHUB_TOKEN"],
534 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
535 routing_defaults: Some(ProviderRoutingDefaults {
536 api: "openai-completions",
537 base_url: "https://models.github.ai/inference",
538 auth_header: true,
539 reasoning: true,
540 input: &INPUT_TEXT,
541 context_window: 128_000,
542 max_tokens: 16_384,
543 }),
544 test_obligations: TEST_REQUIRED,
545 },
546 ProviderMetadata {
547 canonical_id: "helicone",
548 display_name: Some("Helicone"),
549 aliases: &[],
550 auth_env_keys: &["HELICONE_API_KEY"],
551 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
552 routing_defaults: Some(ProviderRoutingDefaults {
553 api: "openai-completions",
554 base_url: "https://ai-gateway.helicone.ai/v1",
555 auth_header: true,
556 reasoning: true,
557 input: &INPUT_TEXT,
558 context_window: 128_000,
559 max_tokens: 16_384,
560 }),
561 test_obligations: TEST_REQUIRED,
562 },
563 ProviderMetadata {
564 canonical_id: "huggingface",
565 display_name: Some("Hugging Face"),
566 aliases: &["hf", "hugging-face"],
567 auth_env_keys: &["HF_TOKEN"],
568 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
569 routing_defaults: Some(ProviderRoutingDefaults {
570 api: "openai-completions",
571 base_url: "https://router.huggingface.co/v1",
572 auth_header: true,
573 reasoning: true,
574 input: &INPUT_TEXT,
575 context_window: 128_000,
576 max_tokens: 16_384,
577 }),
578 test_obligations: TEST_REQUIRED,
579 },
580 ProviderMetadata {
581 canonical_id: "iflowcn",
582 display_name: Some("iFlow"),
583 aliases: &[],
584 auth_env_keys: &["IFLOW_API_KEY"],
585 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
586 routing_defaults: Some(ProviderRoutingDefaults {
587 api: "openai-completions",
588 base_url: "https://apis.iflow.cn/v1",
589 auth_header: true,
590 reasoning: true,
591 input: &INPUT_TEXT,
592 context_window: 128_000,
593 max_tokens: 16_384,
594 }),
595 test_obligations: TEST_REQUIRED,
596 },
597 ProviderMetadata {
598 canonical_id: "inception",
599 display_name: Some("Inception"),
600 aliases: &[],
601 auth_env_keys: &["INCEPTION_API_KEY"],
602 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
603 routing_defaults: Some(ProviderRoutingDefaults {
604 api: "openai-completions",
605 base_url: "https://api.inceptionlabs.ai/v1",
606 auth_header: true,
607 reasoning: false,
608 input: &INPUT_TEXT,
609 context_window: 128_000,
610 max_tokens: 16_384,
611 }),
612 test_obligations: TEST_REQUIRED,
613 },
614 ProviderMetadata {
615 canonical_id: "inference",
616 display_name: Some("Inference"),
617 aliases: &[],
618 auth_env_keys: &["INFERENCE_API_KEY"],
619 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
620 routing_defaults: Some(ProviderRoutingDefaults {
621 api: "openai-completions",
622 base_url: "https://inference.net/v1",
623 auth_header: true,
624 reasoning: true,
625 input: &INPUT_TEXT,
626 context_window: 128_000,
627 max_tokens: 16_384,
628 }),
629 test_obligations: TEST_REQUIRED,
630 },
631 ProviderMetadata {
633 canonical_id: "io-net",
634 display_name: Some("io.net"),
635 aliases: &[],
636 auth_env_keys: &["IOINTELLIGENCE_API_KEY"],
637 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
638 routing_defaults: Some(ProviderRoutingDefaults {
639 api: "openai-completions",
640 base_url: "https://api.intelligence.io.solutions/api/v1",
641 auth_header: true,
642 reasoning: true,
643 input: &INPUT_TEXT,
644 context_window: 128_000,
645 max_tokens: 16_384,
646 }),
647 test_obligations: TEST_REQUIRED,
648 },
649 ProviderMetadata {
650 canonical_id: "jiekou",
651 display_name: Some("Jiekou"),
652 aliases: &[],
653 auth_env_keys: &["JIEKOU_API_KEY"],
654 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
655 routing_defaults: Some(ProviderRoutingDefaults {
656 api: "openai-completions",
657 base_url: "https://api.jiekou.ai/openai",
658 auth_header: true,
659 reasoning: true,
660 input: &INPUT_TEXT,
661 context_window: 128_000,
662 max_tokens: 16_384,
663 }),
664 test_obligations: TEST_REQUIRED,
665 },
666 ProviderMetadata {
667 canonical_id: "lucidquery",
668 display_name: Some("LucidQuery"),
669 aliases: &[],
670 auth_env_keys: &["LUCIDQUERY_API_KEY"],
671 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
672 routing_defaults: Some(ProviderRoutingDefaults {
673 api: "openai-completions",
674 base_url: "https://lucidquery.com/api/v1",
675 auth_header: true,
676 reasoning: true,
677 input: &INPUT_TEXT,
678 context_window: 128_000,
679 max_tokens: 16_384,
680 }),
681 test_obligations: TEST_REQUIRED,
682 },
683 ProviderMetadata {
684 canonical_id: "moark",
685 display_name: Some("Moark"),
686 aliases: &[],
687 auth_env_keys: &["MOARK_API_KEY"],
688 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
689 routing_defaults: Some(ProviderRoutingDefaults {
690 api: "openai-completions",
691 base_url: "https://moark.com/v1",
692 auth_header: true,
693 reasoning: true,
694 input: &INPUT_TEXT,
695 context_window: 128_000,
696 max_tokens: 16_384,
697 }),
698 test_obligations: TEST_REQUIRED,
699 },
700 ProviderMetadata {
701 canonical_id: "morph",
702 display_name: Some("Morph"),
703 aliases: &[],
704 auth_env_keys: &["MORPH_API_KEY"],
705 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
706 routing_defaults: Some(ProviderRoutingDefaults {
707 api: "openai-completions",
708 base_url: "https://api.morphllm.com/v1",
709 auth_header: true,
710 reasoning: true,
711 input: &INPUT_TEXT,
712 context_window: 128_000,
713 max_tokens: 16_384,
714 }),
715 test_obligations: TEST_REQUIRED,
716 },
717 ProviderMetadata {
718 canonical_id: "nano-gpt",
719 display_name: Some("NanoGPT"),
720 aliases: &["nanogpt"],
721 auth_env_keys: &["NANO_GPT_API_KEY"],
722 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
723 routing_defaults: Some(ProviderRoutingDefaults {
724 api: "openai-completions",
725 base_url: "https://nano-gpt.com/api/v1",
726 auth_header: true,
727 reasoning: true,
728 input: &INPUT_TEXT,
729 context_window: 128_000,
730 max_tokens: 16_384,
731 }),
732 test_obligations: TEST_REQUIRED,
733 },
734 ProviderMetadata {
735 canonical_id: "nova",
736 display_name: Some("Nova"),
737 aliases: &[],
738 auth_env_keys: &["NOVA_API_KEY"],
739 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
740 routing_defaults: Some(ProviderRoutingDefaults {
741 api: "openai-completions",
742 base_url: "https://api.nova.amazon.com/v1",
743 auth_header: true,
744 reasoning: true,
745 input: &INPUT_TEXT,
746 context_window: 128_000,
747 max_tokens: 16_384,
748 }),
749 test_obligations: TEST_REQUIRED,
750 },
751 ProviderMetadata {
752 canonical_id: "novita-ai",
753 display_name: Some("Novita AI"),
754 aliases: &["novita"],
755 auth_env_keys: &["NOVITA_API_KEY"],
756 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
757 routing_defaults: Some(ProviderRoutingDefaults {
758 api: "openai-completions",
759 base_url: "https://api.novita.ai/openai",
760 auth_header: true,
761 reasoning: true,
762 input: &INPUT_TEXT,
763 context_window: 128_000,
764 max_tokens: 16_384,
765 }),
766 test_obligations: TEST_REQUIRED,
767 },
768 ProviderMetadata {
769 canonical_id: "nvidia",
770 display_name: Some("NVIDIA NIM"),
771 aliases: &["nim", "nvidia-nim"],
772 auth_env_keys: &["NVIDIA_API_KEY"],
773 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
774 routing_defaults: Some(ProviderRoutingDefaults {
775 api: "openai-completions",
776 base_url: "https://integrate.api.nvidia.com/v1",
777 auth_header: true,
778 reasoning: true,
779 input: &INPUT_TEXT,
780 context_window: 128_000,
781 max_tokens: 16_384,
782 }),
783 test_obligations: TEST_REQUIRED,
784 },
785 ProviderMetadata {
787 canonical_id: "poe",
788 display_name: Some("Poe"),
789 aliases: &[],
790 auth_env_keys: &["POE_API_KEY"],
791 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
792 routing_defaults: Some(ProviderRoutingDefaults {
793 api: "openai-completions",
794 base_url: "https://api.poe.com/v1",
795 auth_header: true,
796 reasoning: true,
797 input: &INPUT_TEXT,
798 context_window: 128_000,
799 max_tokens: 16_384,
800 }),
801 test_obligations: TEST_REQUIRED,
802 },
803 ProviderMetadata {
804 canonical_id: "privatemode-ai",
805 display_name: Some("PrivateMode AI"),
806 aliases: &[],
807 auth_env_keys: &["PRIVATEMODE_API_KEY"],
808 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
809 routing_defaults: Some(ProviderRoutingDefaults {
810 api: "openai-completions",
811 base_url: "http://localhost:8080/v1",
813 auth_header: true,
814 reasoning: true,
815 input: &INPUT_TEXT,
816 context_window: 128_000,
817 max_tokens: 16_384,
818 }),
819 test_obligations: TEST_REQUIRED,
820 },
821 ProviderMetadata {
822 canonical_id: "requesty",
823 display_name: Some("Requesty"),
824 aliases: &[],
825 auth_env_keys: &["REQUESTY_API_KEY"],
826 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
827 routing_defaults: Some(ProviderRoutingDefaults {
828 api: "openai-completions",
829 base_url: "https://router.requesty.ai/v1",
830 auth_header: true,
831 reasoning: true,
832 input: &INPUT_TEXT,
833 context_window: 128_000,
834 max_tokens: 16_384,
835 }),
836 test_obligations: TEST_REQUIRED,
837 },
838 ProviderMetadata {
839 canonical_id: "submodel",
840 display_name: Some("Submodel"),
841 aliases: &[],
842 auth_env_keys: &["SUBMODEL_INSTAGEN_ACCESS_KEY"],
843 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
844 routing_defaults: Some(ProviderRoutingDefaults {
845 api: "openai-completions",
846 base_url: "https://llm.submodel.ai/v1",
847 auth_header: true,
848 reasoning: true,
849 input: &INPUT_TEXT,
850 context_window: 128_000,
851 max_tokens: 16_384,
852 }),
853 test_obligations: TEST_REQUIRED,
854 },
855 ProviderMetadata {
856 canonical_id: "synthetic",
857 display_name: Some("Synthetic"),
858 aliases: &[],
859 auth_env_keys: &["SYNTHETIC_API_KEY"],
860 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
861 routing_defaults: Some(ProviderRoutingDefaults {
862 api: "openai-completions",
863 base_url: "https://api.synthetic.new/v1",
864 auth_header: true,
865 reasoning: true,
866 input: &INPUT_TEXT,
867 context_window: 128_000,
868 max_tokens: 16_384,
869 }),
870 test_obligations: TEST_REQUIRED,
871 },
872 ProviderMetadata {
873 canonical_id: "vivgrid",
874 display_name: Some("Vivgrid"),
875 aliases: &[],
876 auth_env_keys: &["VIVGRID_API_KEY"],
877 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
878 routing_defaults: Some(ProviderRoutingDefaults {
879 api: "openai-completions",
880 base_url: "https://api.vivgrid.com/v1",
881 auth_header: true,
882 reasoning: true,
883 input: &INPUT_TEXT,
884 context_window: 128_000,
885 max_tokens: 16_384,
886 }),
887 test_obligations: TEST_REQUIRED,
888 },
889 ProviderMetadata {
890 canonical_id: "vultr",
891 display_name: Some("Vultr"),
892 aliases: &[],
893 auth_env_keys: &["VULTR_API_KEY"],
894 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
895 routing_defaults: Some(ProviderRoutingDefaults {
896 api: "openai-completions",
897 base_url: "https://api.vultrinference.com/v1",
898 auth_header: true,
899 reasoning: true,
900 input: &INPUT_TEXT,
901 context_window: 128_000,
902 max_tokens: 16_384,
903 }),
904 test_obligations: TEST_REQUIRED,
905 },
906 ProviderMetadata {
907 canonical_id: "wandb",
908 display_name: Some("Weights & Biases"),
909 aliases: &[],
910 auth_env_keys: &["WANDB_API_KEY"],
911 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
912 routing_defaults: Some(ProviderRoutingDefaults {
913 api: "openai-completions",
914 base_url: "https://api.inference.wandb.ai/v1",
915 auth_header: true,
916 reasoning: true,
917 input: &INPUT_TEXT,
918 context_window: 128_000,
919 max_tokens: 16_384,
920 }),
921 test_obligations: TEST_REQUIRED,
922 },
923 ProviderMetadata {
924 canonical_id: "xiaomi",
925 display_name: Some("Xiaomi"),
926 aliases: &[],
927 auth_env_keys: &["XIAOMI_API_KEY"],
928 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
929 routing_defaults: Some(ProviderRoutingDefaults {
930 api: "openai-completions",
931 base_url: "https://api.xiaomimimo.com/v1",
932 auth_header: true,
933 reasoning: true,
934 input: &INPUT_TEXT,
935 context_window: 128_000,
936 max_tokens: 16_384,
937 }),
938 test_obligations: TEST_REQUIRED,
939 },
940 ProviderMetadata {
942 canonical_id: "alibaba-cn",
943 display_name: Some("Alibaba China"),
944 aliases: &[],
945 auth_env_keys: &["DASHSCOPE_API_KEY"],
946 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
947 routing_defaults: Some(ProviderRoutingDefaults {
948 api: "openai-completions",
949 base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1",
950 auth_header: true,
951 reasoning: true,
952 input: &INPUT_TEXT,
953 context_window: 128_000,
954 max_tokens: 16_384,
955 }),
956 test_obligations: TEST_REQUIRED,
957 },
958 ProviderMetadata {
959 canonical_id: "kimi-for-coding",
960 display_name: Some("Kimi for Coding"),
961 aliases: &["kimi-coding", "kimi-code"],
962 auth_env_keys: &["KIMI_API_KEY"],
963 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
964 routing_defaults: Some(ProviderRoutingDefaults {
965 api: "anthropic-messages",
966 base_url: "https://api.kimi.com/coding/v1/messages",
967 auth_header: false,
968 reasoning: true,
969 input: &INPUT_TEXT_IMAGE,
970 context_window: 262_144,
971 max_tokens: 32_768,
972 }),
973 test_obligations: TEST_REQUIRED,
974 },
975 ProviderMetadata {
976 canonical_id: "minimax",
977 display_name: Some("MiniMax"),
978 aliases: &[],
979 auth_env_keys: &["MINIMAX_API_KEY"],
980 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
981 routing_defaults: Some(ProviderRoutingDefaults {
982 api: "anthropic-messages",
983 base_url: "https://api.minimax.io/anthropic/v1/messages",
984 auth_header: false,
985 reasoning: true,
986 input: &INPUT_TEXT,
987 context_window: 204_800,
988 max_tokens: 131_072,
989 }),
990 test_obligations: TEST_REQUIRED,
991 },
992 ProviderMetadata {
993 canonical_id: "minimax-cn",
994 display_name: Some("MiniMax China"),
995 aliases: &[],
996 auth_env_keys: &["MINIMAX_CN_API_KEY"],
997 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
998 routing_defaults: Some(ProviderRoutingDefaults {
999 api: "anthropic-messages",
1000 base_url: "https://api.minimaxi.com/anthropic/v1/messages",
1001 auth_header: false,
1002 reasoning: true,
1003 input: &INPUT_TEXT,
1004 context_window: 204_800,
1005 max_tokens: 131_072,
1006 }),
1007 test_obligations: TEST_REQUIRED,
1008 },
1009 ProviderMetadata {
1010 canonical_id: "minimax-coding-plan",
1011 display_name: Some("MiniMax Coding Plan"),
1012 aliases: &[],
1013 auth_env_keys: &["MINIMAX_API_KEY"],
1014 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1015 routing_defaults: Some(ProviderRoutingDefaults {
1016 api: "anthropic-messages",
1017 base_url: "https://api.minimax.io/anthropic/v1/messages",
1018 auth_header: false,
1019 reasoning: true,
1020 input: &INPUT_TEXT,
1021 context_window: 204_800,
1022 max_tokens: 131_072,
1023 }),
1024 test_obligations: TEST_REQUIRED,
1025 },
1026 ProviderMetadata {
1027 canonical_id: "minimax-cn-coding-plan",
1028 display_name: Some("MiniMax China Coding Plan"),
1029 aliases: &[],
1030 auth_env_keys: &["MINIMAX_CN_API_KEY"],
1031 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1032 routing_defaults: Some(ProviderRoutingDefaults {
1033 api: "anthropic-messages",
1034 base_url: "https://api.minimaxi.com/anthropic/v1/messages",
1035 auth_header: false,
1036 reasoning: true,
1037 input: &INPUT_TEXT,
1038 context_window: 204_800,
1039 max_tokens: 131_072,
1040 }),
1041 test_obligations: TEST_REQUIRED,
1042 },
1043 ProviderMetadata {
1045 canonical_id: "modelscope",
1046 display_name: Some("ModelScope"),
1047 aliases: &[],
1048 auth_env_keys: &["MODELSCOPE_API_KEY"],
1049 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1050 routing_defaults: Some(ProviderRoutingDefaults {
1051 api: "openai-completions",
1052 base_url: "https://api-inference.modelscope.cn/v1",
1053 auth_header: true,
1054 reasoning: true,
1055 input: &INPUT_TEXT,
1056 context_window: 131_072,
1057 max_tokens: 98_304,
1058 }),
1059 test_obligations: TEST_REQUIRED,
1060 },
1061 ProviderMetadata {
1062 canonical_id: "moonshotai-cn",
1063 display_name: Some("Moonshot AI China"),
1064 aliases: &[],
1065 auth_env_keys: &["MOONSHOT_API_KEY"],
1066 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1067 routing_defaults: Some(ProviderRoutingDefaults {
1068 api: "openai-completions",
1069 base_url: "https://api.moonshot.cn/v1",
1070 auth_header: true,
1071 reasoning: true,
1072 input: &INPUT_TEXT,
1073 context_window: 262_144,
1074 max_tokens: 262_144,
1075 }),
1076 test_obligations: TEST_REQUIRED,
1077 },
1078 ProviderMetadata {
1079 canonical_id: "nebius",
1080 display_name: Some("Nebius"),
1081 aliases: &[],
1082 auth_env_keys: &["NEBIUS_API_KEY"],
1083 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1084 routing_defaults: Some(ProviderRoutingDefaults {
1085 api: "openai-completions",
1086 base_url: "https://api.tokenfactory.nebius.com/v1",
1087 auth_header: true,
1088 reasoning: true,
1089 input: &INPUT_TEXT,
1090 context_window: 128_000,
1091 max_tokens: 8192,
1092 }),
1093 test_obligations: TEST_REQUIRED,
1094 },
1095 ProviderMetadata {
1096 canonical_id: "ovhcloud",
1097 display_name: Some("OVHcloud"),
1098 aliases: &[],
1099 auth_env_keys: &["OVHCLOUD_API_KEY"],
1100 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1101 routing_defaults: Some(ProviderRoutingDefaults {
1102 api: "openai-completions",
1103 base_url: "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1",
1104 auth_header: true,
1105 reasoning: true,
1106 input: &INPUT_TEXT,
1107 context_window: 32_768,
1108 max_tokens: 32_768,
1109 }),
1110 test_obligations: TEST_REQUIRED,
1111 },
1112 ProviderMetadata {
1113 canonical_id: "scaleway",
1114 display_name: Some("Scaleway"),
1115 aliases: &[],
1116 auth_env_keys: &["SCALEWAY_API_KEY"],
1117 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1118 routing_defaults: Some(ProviderRoutingDefaults {
1119 api: "openai-completions",
1120 base_url: "https://api.scaleway.ai/v1",
1121 auth_header: true,
1122 reasoning: true,
1123 input: &INPUT_TEXT,
1124 context_window: 260_000,
1125 max_tokens: 8192,
1126 }),
1127 test_obligations: TEST_REQUIRED,
1128 },
1129 ProviderMetadata {
1130 canonical_id: "stackit",
1131 display_name: Some("STACKIT"),
1132 aliases: &[],
1133 auth_env_keys: &["STACKIT_API_KEY"],
1134 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1135 routing_defaults: Some(ProviderRoutingDefaults {
1136 api: "openai-completions",
1137 base_url: "https://api.openai-compat.model-serving.eu01.onstackit.cloud/v1",
1138 auth_header: true,
1139 reasoning: true,
1140 input: &INPUT_TEXT,
1141 context_window: 128_000,
1142 max_tokens: 8192,
1143 }),
1144 test_obligations: TEST_REQUIRED,
1145 },
1146 ProviderMetadata {
1148 canonical_id: "siliconflow",
1149 display_name: Some("SiliconFlow"),
1150 aliases: &["silicon-flow"],
1151 auth_env_keys: &["SILICONFLOW_API_KEY"],
1152 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1153 routing_defaults: Some(ProviderRoutingDefaults {
1154 api: "openai-completions",
1155 base_url: "https://api.siliconflow.com/v1",
1156 auth_header: true,
1157 reasoning: true,
1158 input: &INPUT_TEXT,
1159 context_window: 128_000,
1160 max_tokens: 16_384,
1161 }),
1162 test_obligations: TEST_REQUIRED,
1163 },
1164 ProviderMetadata {
1165 canonical_id: "siliconflow-cn",
1166 display_name: Some("SiliconFlow China"),
1167 aliases: &[],
1168 auth_env_keys: &["SILICONFLOW_CN_API_KEY"],
1169 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1170 routing_defaults: Some(ProviderRoutingDefaults {
1171 api: "openai-completions",
1172 base_url: "https://api.siliconflow.cn/v1",
1173 auth_header: true,
1174 reasoning: true,
1175 input: &INPUT_TEXT,
1176 context_window: 128_000,
1177 max_tokens: 16_384,
1178 }),
1179 test_obligations: TEST_REQUIRED,
1180 },
1181 ProviderMetadata {
1182 canonical_id: "upstage",
1183 display_name: Some("Upstage"),
1184 aliases: &[],
1185 auth_env_keys: &["UPSTAGE_API_KEY"],
1186 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1187 routing_defaults: Some(ProviderRoutingDefaults {
1188 api: "openai-completions",
1189 base_url: "https://api.upstage.ai/v1/solar",
1190 auth_header: true,
1191 reasoning: true,
1192 input: &INPUT_TEXT,
1193 context_window: 128_000,
1194 max_tokens: 16_384,
1195 }),
1196 test_obligations: TEST_REQUIRED,
1197 },
1198 ProviderMetadata {
1199 canonical_id: "venice",
1200 display_name: Some("Venice AI"),
1201 aliases: &[],
1202 auth_env_keys: &["VENICE_API_KEY"],
1203 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1204 routing_defaults: Some(ProviderRoutingDefaults {
1205 api: "openai-completions",
1206 base_url: "https://api.venice.ai/api/v1",
1207 auth_header: true,
1208 reasoning: true,
1209 input: &INPUT_TEXT,
1210 context_window: 128_000,
1211 max_tokens: 16_384,
1212 }),
1213 test_obligations: TEST_REQUIRED,
1214 },
1215 ProviderMetadata {
1216 canonical_id: "zai",
1217 display_name: Some("Zai"),
1218 aliases: &[],
1219 auth_env_keys: &["ZHIPU_API_KEY"],
1220 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1221 routing_defaults: Some(ProviderRoutingDefaults {
1222 api: "openai-completions",
1223 base_url: "https://api.z.ai/api/paas/v4",
1224 auth_header: true,
1225 reasoning: true,
1226 input: &INPUT_TEXT,
1227 context_window: 128_000,
1228 max_tokens: 16_384,
1229 }),
1230 test_obligations: TEST_REQUIRED,
1231 },
1232 ProviderMetadata {
1233 canonical_id: "zai-coding-plan",
1234 display_name: Some("Zai Coding Plan"),
1235 aliases: &[],
1236 auth_env_keys: &["ZHIPU_API_KEY"],
1237 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1238 routing_defaults: Some(ProviderRoutingDefaults {
1239 api: "openai-completions",
1240 base_url: "https://api.z.ai/api/coding/paas/v4",
1241 auth_header: true,
1242 reasoning: true,
1243 input: &INPUT_TEXT,
1244 context_window: 128_000,
1245 max_tokens: 16_384,
1246 }),
1247 test_obligations: TEST_REQUIRED,
1248 },
1249 ProviderMetadata {
1250 canonical_id: "zhipuai",
1251 display_name: Some("Zhipu AI"),
1252 aliases: &["zhipu", "glm"],
1253 auth_env_keys: &["ZHIPU_API_KEY"],
1254 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1255 routing_defaults: Some(ProviderRoutingDefaults {
1256 api: "openai-completions",
1257 base_url: "https://open.bigmodel.cn/api/paas/v4",
1258 auth_header: true,
1259 reasoning: true,
1260 input: &INPUT_TEXT,
1261 context_window: 128_000,
1262 max_tokens: 16_384,
1263 }),
1264 test_obligations: TEST_REQUIRED,
1265 },
1266 ProviderMetadata {
1267 canonical_id: "zhipuai-coding-plan",
1268 display_name: Some("Zhipu AI Coding Plan"),
1269 aliases: &[],
1270 auth_env_keys: &["ZHIPU_API_KEY"],
1271 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1272 routing_defaults: Some(ProviderRoutingDefaults {
1273 api: "openai-completions",
1274 base_url: "https://open.bigmodel.cn/api/coding/paas/v4",
1275 auth_header: true,
1276 reasoning: true,
1277 input: &INPUT_TEXT,
1278 context_window: 128_000,
1279 max_tokens: 16_384,
1280 }),
1281 test_obligations: TEST_REQUIRED,
1282 },
1283 ProviderMetadata {
1285 canonical_id: "baseten",
1286 display_name: Some("Baseten"),
1287 aliases: &[],
1288 auth_env_keys: &["BASETEN_API_KEY"],
1289 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1290 routing_defaults: Some(ProviderRoutingDefaults {
1291 api: "openai-completions",
1292 base_url: "https://inference.baseten.co/v1",
1293 auth_header: true,
1294 reasoning: true,
1295 input: &INPUT_TEXT,
1296 context_window: 262_144,
1297 max_tokens: 65_536,
1298 }),
1299 test_obligations: TEST_REQUIRED,
1300 },
1301 ProviderMetadata {
1302 canonical_id: "llama",
1303 display_name: Some("Meta Llama"),
1304 aliases: &[],
1305 auth_env_keys: &["LLAMA_API_KEY"],
1306 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1307 routing_defaults: Some(ProviderRoutingDefaults {
1308 api: "openai-completions",
1309 base_url: "https://api.llama.com/compat/v1",
1310 auth_header: true,
1311 reasoning: true,
1312 input: &INPUT_TEXT_IMAGE,
1313 context_window: 128_000,
1314 max_tokens: 4096,
1315 }),
1316 test_obligations: TEST_REQUIRED,
1317 },
1318 ProviderMetadata {
1319 canonical_id: "lmstudio",
1320 display_name: Some("LM Studio"),
1321 aliases: &["lm-studio"],
1322 auth_env_keys: &["LMSTUDIO_API_KEY"],
1323 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1324 routing_defaults: Some(ProviderRoutingDefaults {
1325 api: "openai-completions",
1326 base_url: "http://127.0.0.1:1234/v1",
1327 auth_header: true,
1328 reasoning: true,
1329 input: &INPUT_TEXT,
1330 context_window: 131_072,
1331 max_tokens: 32_768,
1332 }),
1333 test_obligations: TEST_REQUIRED,
1334 },
1335 ProviderMetadata {
1336 canonical_id: "ollama",
1337 display_name: Some("Ollama"),
1338 aliases: &[],
1339 auth_env_keys: &[],
1340 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1341 routing_defaults: Some(ProviderRoutingDefaults {
1342 api: "openai-completions",
1343 base_url: "http://127.0.0.1:11434/v1",
1344 auth_header: false,
1345 reasoning: true,
1346 input: &INPUT_TEXT,
1347 context_window: 131_072,
1348 max_tokens: 32_768,
1349 }),
1350 test_obligations: TEST_REQUIRED,
1351 },
1352 ProviderMetadata {
1353 canonical_id: "ollama-cloud",
1354 display_name: Some("Ollama Cloud"),
1355 aliases: &[],
1356 auth_env_keys: &["OLLAMA_API_KEY"],
1357 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1358 routing_defaults: Some(ProviderRoutingDefaults {
1359 api: "openai-completions",
1360 base_url: "https://ollama.com/v1",
1361 auth_header: true,
1362 reasoning: true,
1363 input: &INPUT_TEXT_IMAGE,
1364 context_window: 262_144,
1365 max_tokens: 131_072,
1366 }),
1367 test_obligations: TEST_REQUIRED,
1368 },
1369 ProviderMetadata {
1371 canonical_id: "opencode",
1372 display_name: Some("OpenCode"),
1373 aliases: &[],
1374 auth_env_keys: &["OPENCODE_API_KEY"],
1375 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1376 routing_defaults: Some(ProviderRoutingDefaults {
1377 api: "openai-completions",
1378 base_url: "https://opencode.ai/zen/v1",
1379 auth_header: true,
1380 reasoning: true,
1381 input: &INPUT_TEXT,
1382 context_window: 128_000,
1383 max_tokens: 16_384,
1384 }),
1385 test_obligations: TEST_REQUIRED,
1386 },
1387 ProviderMetadata {
1388 canonical_id: "vercel",
1389 display_name: Some("Vercel AI"),
1390 aliases: &["vercel-ai-gateway"],
1391 auth_env_keys: &["AI_GATEWAY_API_KEY"],
1392 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1393 routing_defaults: Some(ProviderRoutingDefaults {
1394 api: "openai-completions",
1395 base_url: "https://ai-gateway.vercel.sh/v1",
1396 auth_header: true,
1397 reasoning: true,
1398 input: &INPUT_TEXT,
1399 context_window: 128_000,
1400 max_tokens: 16_384,
1401 }),
1402 test_obligations: TEST_REQUIRED,
1403 },
1404 ProviderMetadata {
1405 canonical_id: "zenmux",
1406 display_name: Some("ZenMux"),
1407 aliases: &[],
1408 auth_env_keys: &["ZENMUX_API_KEY"],
1409 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1410 routing_defaults: Some(ProviderRoutingDefaults {
1411 api: "anthropic-messages",
1412 base_url: "https://zenmux.ai/api/anthropic/v1/messages",
1413 auth_header: false,
1414 reasoning: true,
1415 input: &INPUT_TEXT,
1416 context_window: 200_000,
1417 max_tokens: 8192,
1418 }),
1419 test_obligations: TEST_REQUIRED,
1420 },
1421 ProviderMetadata {
1423 canonical_id: "cloudflare-ai-gateway",
1424 display_name: Some("Cloudflare AI Gateway"),
1425 aliases: &[],
1426 auth_env_keys: &["CLOUDFLARE_API_TOKEN"],
1427 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1428 routing_defaults: Some(ProviderRoutingDefaults {
1429 api: "openai-completions",
1430 base_url: "https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/openai",
1431 auth_header: true,
1432 reasoning: true,
1433 input: &INPUT_TEXT,
1434 context_window: 128_000,
1435 max_tokens: 16_384,
1436 }),
1437 test_obligations: TEST_REQUIRED,
1438 },
1439 ProviderMetadata {
1440 canonical_id: "cloudflare-workers-ai",
1441 display_name: Some("Cloudflare Workers AI"),
1442 aliases: &[],
1443 auth_env_keys: &["CLOUDFLARE_API_TOKEN"],
1444 onboarding: ProviderOnboardingMode::OpenAICompatiblePreset,
1445 routing_defaults: Some(ProviderRoutingDefaults {
1446 api: "openai-completions",
1447 base_url: "https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1",
1448 auth_header: true,
1449 reasoning: true,
1450 input: &INPUT_TEXT,
1451 context_window: 128_000,
1452 max_tokens: 16_384,
1453 }),
1454 test_obligations: TEST_REQUIRED,
1455 },
1456 ProviderMetadata {
1458 canonical_id: "google-vertex",
1459 display_name: Some("Google Vertex AI"),
1460 aliases: &["vertexai", "google-vertex-anthropic"],
1461 auth_env_keys: &["GOOGLE_CLOUD_API_KEY", "VERTEX_API_KEY"],
1462 onboarding: ProviderOnboardingMode::BuiltInNative,
1463 routing_defaults: Some(ProviderRoutingDefaults {
1464 api: "google-vertex",
1465 base_url: "",
1466 auth_header: true,
1467 reasoning: true,
1468 input: &INPUT_TEXT_IMAGE,
1469 context_window: 1_000_000,
1470 max_tokens: 8192,
1471 }),
1472 test_obligations: TEST_REQUIRED,
1473 },
1474 ProviderMetadata {
1475 canonical_id: "amazon-bedrock",
1476 display_name: Some("Amazon Bedrock"),
1477 aliases: &["bedrock"],
1478 auth_env_keys: &[
1479 "AWS_ACCESS_KEY_ID",
1480 "AWS_SECRET_ACCESS_KEY",
1481 "AWS_SESSION_TOKEN",
1482 "AWS_BEARER_TOKEN_BEDROCK",
1483 "AWS_PROFILE",
1484 "AWS_REGION",
1485 ],
1486 onboarding: ProviderOnboardingMode::NativeAdapterRequired,
1487 routing_defaults: Some(ProviderRoutingDefaults {
1488 api: "bedrock-converse-stream",
1489 base_url: "",
1490 auth_header: false,
1491 reasoning: true,
1492 input: &INPUT_TEXT,
1493 context_window: 200_000,
1494 max_tokens: 8192,
1495 }),
1496 test_obligations: TEST_REQUIRED,
1497 },
1498 ProviderMetadata {
1499 canonical_id: "sap-ai-core",
1500 display_name: Some("SAP AI Core"),
1501 aliases: &["sap"],
1502 auth_env_keys: &[
1503 "AICORE_SERVICE_KEY",
1504 "SAP_AI_CORE_CLIENT_ID",
1505 "SAP_AI_CORE_CLIENT_SECRET",
1506 "SAP_AI_CORE_TOKEN_URL",
1507 "SAP_AI_CORE_SERVICE_URL",
1508 ],
1509 onboarding: ProviderOnboardingMode::NativeAdapterRequired,
1510 routing_defaults: None,
1511 test_obligations: TEST_REQUIRED,
1512 },
1513 ProviderMetadata {
1514 canonical_id: "v0",
1515 display_name: Some("v0 by Vercel"),
1516 aliases: &[],
1517 auth_env_keys: &["V0_API_KEY"],
1518 onboarding: ProviderOnboardingMode::NativeAdapterRequired,
1519 routing_defaults: None,
1520 test_obligations: TEST_REQUIRED,
1521 },
1522 ProviderMetadata {
1523 canonical_id: "azure-openai",
1524 display_name: Some("Azure OpenAI"),
1525 aliases: &[
1526 "azure",
1527 "azure-cognitive-services",
1528 "azure-openai-responses",
1529 ],
1530 auth_env_keys: &["AZURE_OPENAI_API_KEY"],
1531 onboarding: ProviderOnboardingMode::NativeAdapterRequired,
1532 routing_defaults: None,
1533 test_obligations: TEST_REQUIRED,
1534 },
1535 ProviderMetadata {
1536 canonical_id: "github-copilot",
1537 display_name: Some("GitHub Copilot"),
1538 aliases: &["copilot", "github-copilot-enterprise"],
1539 auth_env_keys: &["GITHUB_COPILOT_API_KEY", "GITHUB_TOKEN"],
1540 onboarding: ProviderOnboardingMode::NativeAdapterRequired,
1541 routing_defaults: None,
1542 test_obligations: TEST_REQUIRED,
1543 },
1544 ProviderMetadata {
1545 canonical_id: "gitlab",
1546 display_name: Some("GitLab Duo"),
1547 aliases: &["gitlab-duo"],
1548 auth_env_keys: &["GITLAB_TOKEN", "GITLAB_API_KEY"],
1549 onboarding: ProviderOnboardingMode::NativeAdapterRequired,
1550 routing_defaults: None,
1551 test_obligations: TEST_REQUIRED,
1552 },
1553];
1554
1555pub fn provider_metadata(provider_id: &str) -> Option<&'static ProviderMetadata> {
1556 if provider_id.is_empty() {
1557 return None;
1558 }
1559
1560 PROVIDER_METADATA.iter().find(|meta| {
1561 meta.canonical_id.eq_ignore_ascii_case(provider_id)
1562 || meta
1563 .aliases
1564 .iter()
1565 .any(|alias| alias.eq_ignore_ascii_case(provider_id))
1566 })
1567}
1568
1569pub fn canonical_provider_id(provider_id: &str) -> Option<&'static str> {
1570 provider_metadata(provider_id).map(|meta| meta.canonical_id)
1571}
1572
1573pub fn provider_auth_env_keys(provider_id: &str) -> &'static [&'static str] {
1574 provider_metadata(provider_id).map_or(&[], |meta| meta.auth_env_keys)
1575}
1576
1577pub fn provider_routing_defaults(provider_id: &str) -> Option<ProviderRoutingDefaults> {
1578 provider_metadata(provider_id).and_then(|meta| meta.routing_defaults)
1579}
1580
1581#[cfg(test)]
1582mod tests {
1583 use super::*;
1584
1585 #[test]
1586 fn metadata_resolves_canonical_and_alias_names() {
1587 let canonical = provider_metadata("moonshotai").expect("moonshot metadata");
1588 assert_eq!(canonical.canonical_id, "moonshotai");
1589 let alias = provider_metadata("kimi").expect("alias metadata");
1590 assert_eq!(alias.canonical_id, "moonshotai");
1591 let google_alias = provider_metadata("gemini").expect("gemini alias metadata");
1592 assert_eq!(google_alias.canonical_id, "google");
1593 let azure_alias = provider_metadata("azure").expect("azure alias metadata");
1594 assert_eq!(azure_alias.canonical_id, "azure-openai");
1595 let azure_cognitive_alias =
1596 provider_metadata("azure-cognitive-services").expect("azure-cognitive alias metadata");
1597 assert_eq!(azure_cognitive_alias.canonical_id, "azure-openai");
1598 let azure_responses_alias =
1599 provider_metadata("azure-openai-responses").expect("azure responses alias metadata");
1600 assert_eq!(azure_responses_alias.canonical_id, "azure-openai");
1601 let vertex_anthropic_alias = provider_metadata("google-vertex-anthropic")
1602 .expect("google-vertex-anthropic alias metadata");
1603 assert_eq!(vertex_anthropic_alias.canonical_id, "google-vertex");
1604 let copilot_enterprise_alias = provider_metadata("github-copilot-enterprise")
1605 .expect("github-copilot-enterprise alias metadata");
1606 assert_eq!(copilot_enterprise_alias.canonical_id, "github-copilot");
1607 let openrouter_alias =
1608 provider_metadata("open-router").expect("open-router alias metadata");
1609 assert_eq!(openrouter_alias.canonical_id, "openrouter");
1610 let vercel_gateway_alias =
1611 provider_metadata("vercel-ai-gateway").expect("vercel alias metadata");
1612 assert_eq!(vercel_gateway_alias.canonical_id, "vercel");
1613 let kimi_coding_alias =
1614 provider_metadata("kimi-coding").expect("kimi-coding alias metadata");
1615 assert_eq!(kimi_coding_alias.canonical_id, "kimi-for-coding");
1616 let kimi_code_alias = provider_metadata("kimi-code").expect("kimi-code alias metadata");
1617 assert_eq!(kimi_code_alias.canonical_id, "kimi-for-coding");
1618 }
1619
1620 #[test]
1621 fn metadata_resolves_ux_discoverability_aliases() {
1622 let cases: &[(&str, &str)] = &[
1624 ("together", "togetherai"),
1625 ("together-ai", "togetherai"),
1626 ("grok", "xai"),
1627 ("x-ai", "xai"),
1628 ("hf", "huggingface"),
1629 ("hugging-face", "huggingface"),
1630 ("nim", "nvidia"),
1631 ("nvidia-nim", "nvidia"),
1632 ("lm-studio", "lmstudio"),
1633 ("deep-seek", "deepseek"),
1634 ("pplx", "perplexity"),
1635 ("deep-infra", "deepinfra"),
1636 ("mistralai", "mistral"),
1637 ("silicon-flow", "siliconflow"),
1638 ("zhipu", "zhipuai"),
1639 ("glm", "zhipuai"),
1640 ("novita", "novita-ai"),
1641 ("nanogpt", "nano-gpt"),
1642 ];
1643 for &(alias, expected_canonical) in cases {
1644 let meta =
1645 provider_metadata(alias).unwrap_or_else(|| panic!("alias '{alias}' not found"));
1646 assert_eq!(
1647 meta.canonical_id, expected_canonical,
1648 "alias '{alias}' should resolve to '{expected_canonical}', got '{}'",
1649 meta.canonical_id
1650 );
1651 }
1652 }
1653
1654 #[test]
1655 fn provider_auth_env_keys_support_aliases() {
1656 assert_eq!(
1657 provider_auth_env_keys("dashscope"),
1658 &["DASHSCOPE_API_KEY", "QWEN_API_KEY"]
1659 );
1660 assert_eq!(
1661 provider_auth_env_keys("qwen"),
1662 &["DASHSCOPE_API_KEY", "QWEN_API_KEY"]
1663 );
1664 assert_eq!(
1665 provider_auth_env_keys("kimi"),
1666 &["MOONSHOT_API_KEY", "KIMI_API_KEY"]
1667 );
1668 assert_eq!(
1669 provider_auth_env_keys("togetherai"),
1670 &["TOGETHER_API_KEY", "TOGETHER_AI_API_KEY"]
1671 );
1672 assert_eq!(
1673 provider_auth_env_keys("fireworks-ai"),
1674 &["FIREWORKS_API_KEY"]
1675 );
1676 assert_eq!(
1677 provider_auth_env_keys("vertexai"),
1678 &["GOOGLE_CLOUD_API_KEY", "VERTEX_API_KEY"]
1679 );
1680 assert_eq!(
1681 provider_auth_env_keys("bedrock"),
1682 &[
1683 "AWS_ACCESS_KEY_ID",
1684 "AWS_SECRET_ACCESS_KEY",
1685 "AWS_SESSION_TOKEN",
1686 "AWS_BEARER_TOKEN_BEDROCK",
1687 "AWS_PROFILE",
1688 "AWS_REGION",
1689 ]
1690 );
1691 assert_eq!(provider_auth_env_keys("azure"), &["AZURE_OPENAI_API_KEY"]);
1692 assert_eq!(
1693 provider_auth_env_keys("azure-cognitive-services"),
1694 &["AZURE_OPENAI_API_KEY"]
1695 );
1696 assert_eq!(
1697 provider_auth_env_keys("azure-openai-responses"),
1698 &["AZURE_OPENAI_API_KEY"]
1699 );
1700 assert_eq!(
1701 provider_auth_env_keys("copilot"),
1702 &["GITHUB_COPILOT_API_KEY", "GITHUB_TOKEN"]
1703 );
1704 assert_eq!(
1705 provider_auth_env_keys("github-copilot-enterprise"),
1706 &["GITHUB_COPILOT_API_KEY", "GITHUB_TOKEN"]
1707 );
1708 assert_eq!(
1709 provider_auth_env_keys("google-vertex-anthropic"),
1710 &["GOOGLE_CLOUD_API_KEY", "VERTEX_API_KEY"]
1711 );
1712 assert_eq!(
1713 provider_auth_env_keys("open-router"),
1714 &["OPENROUTER_API_KEY"]
1715 );
1716 assert_eq!(
1717 provider_auth_env_keys("vercel-ai-gateway"),
1718 &["AI_GATEWAY_API_KEY"]
1719 );
1720 assert_eq!(provider_auth_env_keys("kimi-coding"), &["KIMI_API_KEY"]);
1721 assert_eq!(provider_auth_env_keys("kimi-code"), &["KIMI_API_KEY"]);
1722 assert_eq!(
1724 provider_auth_env_keys("together"),
1725 &["TOGETHER_API_KEY", "TOGETHER_AI_API_KEY"]
1726 );
1727 assert_eq!(provider_auth_env_keys("grok"), &["XAI_API_KEY"]);
1728 assert_eq!(provider_auth_env_keys("hf"), &["HF_TOKEN"]);
1729 assert_eq!(provider_auth_env_keys("nim"), &["NVIDIA_API_KEY"]);
1730 assert_eq!(provider_auth_env_keys("lm-studio"), &["LMSTUDIO_API_KEY"]);
1731 assert_eq!(provider_auth_env_keys("deep-seek"), &["DEEPSEEK_API_KEY"]);
1732 assert_eq!(provider_auth_env_keys("pplx"), &["PERPLEXITY_API_KEY"]);
1733 assert_eq!(provider_auth_env_keys("deep-infra"), &["DEEPINFRA_API_KEY"]);
1734 assert_eq!(provider_auth_env_keys("mistralai"), &["MISTRAL_API_KEY"]);
1735 assert_eq!(
1736 provider_auth_env_keys("silicon-flow"),
1737 &["SILICONFLOW_API_KEY"]
1738 );
1739 }
1740
1741 #[test]
1742 fn provider_auth_env_keys_support_shared_fallbacks() {
1743 assert_eq!(
1744 provider_auth_env_keys("google"),
1745 &["GOOGLE_API_KEY", "GEMINI_API_KEY"]
1746 );
1747 assert_eq!(
1748 provider_auth_env_keys("moonshotai"),
1749 &["MOONSHOT_API_KEY", "KIMI_API_KEY"]
1750 );
1751 assert_eq!(
1752 provider_auth_env_keys("alibaba"),
1753 &["DASHSCOPE_API_KEY", "QWEN_API_KEY"]
1754 );
1755 }
1756
1757 #[test]
1758 fn provider_routing_defaults_available_for_openai_compatible_providers() {
1759 let defaults = provider_routing_defaults("groq").expect("groq defaults");
1760 assert_eq!(defaults.api, "openai-completions");
1761 assert!(defaults.auth_header);
1762 assert!(defaults.base_url.contains("groq"));
1763 }
1764
1765 #[test]
1766 fn provider_routing_defaults_absent_for_native_adapter_only_providers() {
1767 assert!(provider_routing_defaults("azure-openai").is_none());
1768 }
1769
1770 #[test]
1771 fn provider_routing_defaults_present_for_bedrock_native_adapter() {
1772 let defaults = provider_routing_defaults("amazon-bedrock").expect("bedrock defaults");
1773 assert_eq!(defaults.api, "bedrock-converse-stream");
1774 assert_eq!(defaults.base_url, "");
1775 assert!(!defaults.auth_header);
1776 }
1777
1778 #[test]
1779 fn cloudflare_metadata_registered_with_openai_compatible_defaults() {
1780 let gateway =
1781 provider_metadata("cloudflare-ai-gateway").expect("cloudflare-ai-gateway metadata");
1782 assert_eq!(
1783 gateway.onboarding,
1784 ProviderOnboardingMode::OpenAICompatiblePreset
1785 );
1786 let gateway_defaults =
1787 provider_routing_defaults("cloudflare-ai-gateway").expect("gateway defaults");
1788 assert_eq!(gateway_defaults.api, "openai-completions");
1789 assert!(
1790 gateway_defaults
1791 .base_url
1792 .contains("gateway.ai.cloudflare.com")
1793 );
1794
1795 let workers =
1796 provider_metadata("cloudflare-workers-ai").expect("cloudflare-workers-ai metadata");
1797 assert_eq!(
1798 workers.onboarding,
1799 ProviderOnboardingMode::OpenAICompatiblePreset
1800 );
1801 let workers_defaults =
1802 provider_routing_defaults("cloudflare-workers-ai").expect("workers defaults");
1803 assert_eq!(workers_defaults.api, "openai-completions");
1804 assert!(
1805 workers_defaults
1806 .base_url
1807 .contains("api.cloudflare.com/client/v4/accounts")
1808 );
1809
1810 assert_eq!(
1811 provider_auth_env_keys("cloudflare-ai-gateway"),
1812 &["CLOUDFLARE_API_TOKEN"]
1813 );
1814 assert_eq!(
1815 provider_auth_env_keys("cloudflare-workers-ai"),
1816 &["CLOUDFLARE_API_TOKEN"]
1817 );
1818 }
1819
1820 #[test]
1821 fn batch_a1_metadata_resolves_all_eight_providers() {
1822 let ids = [
1823 "302ai",
1824 "abacus",
1825 "aihubmix",
1826 "bailing",
1827 "berget",
1828 "chutes",
1829 "cortecs",
1830 "fastrouter",
1831 ];
1832 for id in &ids {
1833 let meta = provider_metadata(id).unwrap_or_else(|| panic!("{id} metadata missing"));
1834 assert_eq!(meta.canonical_id, *id);
1835 assert_eq!(
1836 meta.onboarding,
1837 ProviderOnboardingMode::OpenAICompatiblePreset
1838 );
1839 }
1840 }
1841
1842 #[test]
1843 fn batch_a1_env_keys_match_upstream_registry() {
1844 assert_eq!(provider_auth_env_keys("302ai"), &["302AI_API_KEY"]);
1845 assert_eq!(provider_auth_env_keys("abacus"), &["ABACUS_API_KEY"]);
1846 assert_eq!(provider_auth_env_keys("aihubmix"), &["AIHUBMIX_API_KEY"]);
1847 assert_eq!(provider_auth_env_keys("bailing"), &["BAILING_API_TOKEN"]);
1848 assert_eq!(provider_auth_env_keys("berget"), &["BERGET_API_KEY"]);
1849 assert_eq!(provider_auth_env_keys("chutes"), &["CHUTES_API_KEY"]);
1850 assert_eq!(provider_auth_env_keys("cortecs"), &["CORTECS_API_KEY"]);
1851 assert_eq!(
1852 provider_auth_env_keys("fastrouter"),
1853 &["FASTROUTER_API_KEY"]
1854 );
1855 }
1856
1857 #[test]
1858 fn batch_a1_routing_defaults_use_openai_completions() {
1859 let ids = [
1860 "302ai",
1861 "abacus",
1862 "aihubmix",
1863 "bailing",
1864 "berget",
1865 "chutes",
1866 "cortecs",
1867 "fastrouter",
1868 ];
1869 for id in &ids {
1870 let defaults =
1871 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
1872 assert_eq!(defaults.api, "openai-completions", "{id} api mismatch");
1873 assert!(defaults.auth_header, "{id} must use auth header");
1874 }
1875 }
1876
1877 #[test]
1878 fn batch_a1_base_urls_are_distinct_and_nonempty() {
1879 let ids = [
1880 "302ai",
1881 "abacus",
1882 "aihubmix",
1883 "bailing",
1884 "berget",
1885 "chutes",
1886 "cortecs",
1887 "fastrouter",
1888 ];
1889 let mut urls: Vec<&str> = Vec::new();
1890 for id in &ids {
1891 let defaults =
1892 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
1893 assert!(
1894 !defaults.base_url.is_empty(),
1895 "{id} base_url must not be empty"
1896 );
1897 assert!(
1898 defaults.base_url.starts_with("https://"),
1899 "{id} base_url must use HTTPS"
1900 );
1901 urls.push(defaults.base_url);
1902 }
1903 urls.sort_unstable();
1905 urls.dedup();
1906 assert_eq!(urls.len(), ids.len(), "duplicate base URLs detected");
1907 }
1908
1909 #[test]
1910 fn batch_a2_metadata_resolves_all_eight_providers() {
1911 let ids = [
1912 "firmware",
1913 "friendli",
1914 "github-models",
1915 "helicone",
1916 "huggingface",
1917 "iflowcn",
1918 "inception",
1919 "inference",
1920 ];
1921 for id in &ids {
1922 let meta = provider_metadata(id).unwrap_or_else(|| panic!("{id} metadata missing"));
1923 assert_eq!(meta.canonical_id, *id);
1924 assert_eq!(
1925 meta.onboarding,
1926 ProviderOnboardingMode::OpenAICompatiblePreset
1927 );
1928 }
1929 }
1930
1931 #[test]
1932 fn batch_a2_env_keys_match_upstream_registry() {
1933 assert_eq!(provider_auth_env_keys("firmware"), &["FIRMWARE_API_KEY"]);
1934 assert_eq!(provider_auth_env_keys("friendli"), &["FRIENDLI_TOKEN"]);
1935 assert_eq!(provider_auth_env_keys("github-models"), &["GITHUB_TOKEN"]);
1936 assert_eq!(provider_auth_env_keys("helicone"), &["HELICONE_API_KEY"]);
1937 assert_eq!(provider_auth_env_keys("huggingface"), &["HF_TOKEN"]);
1938 assert_eq!(provider_auth_env_keys("iflowcn"), &["IFLOW_API_KEY"]);
1939 assert_eq!(provider_auth_env_keys("inception"), &["INCEPTION_API_KEY"]);
1940 assert_eq!(provider_auth_env_keys("inference"), &["INFERENCE_API_KEY"]);
1941 }
1942
1943 #[test]
1944 fn batch_a2_routing_defaults_use_openai_completions() {
1945 let ids = [
1946 "firmware",
1947 "friendli",
1948 "github-models",
1949 "helicone",
1950 "huggingface",
1951 "iflowcn",
1952 "inception",
1953 "inference",
1954 ];
1955 for id in &ids {
1956 let defaults =
1957 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
1958 assert_eq!(defaults.api, "openai-completions", "{id} api mismatch");
1959 assert!(defaults.auth_header, "{id} must use auth header");
1960 }
1961 }
1962
1963 #[test]
1964 fn batch_a2_base_urls_are_distinct_and_nonempty() {
1965 let ids = [
1966 "firmware",
1967 "friendli",
1968 "github-models",
1969 "helicone",
1970 "huggingface",
1971 "iflowcn",
1972 "inception",
1973 "inference",
1974 ];
1975 let mut urls: Vec<&str> = Vec::new();
1976 for id in &ids {
1977 let defaults =
1978 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
1979 assert!(
1980 !defaults.base_url.is_empty(),
1981 "{id} base_url must not be empty"
1982 );
1983 assert!(
1984 defaults.base_url.starts_with("https://"),
1985 "{id} base_url must use HTTPS"
1986 );
1987 urls.push(defaults.base_url);
1988 }
1989 urls.sort_unstable();
1990 urls.dedup();
1991 assert_eq!(urls.len(), ids.len(), "duplicate base URLs detected");
1992 }
1993
1994 #[test]
1997 fn batch_a3_metadata_resolves_all_nine_providers() {
1998 let ids = [
1999 "io-net",
2000 "jiekou",
2001 "lucidquery",
2002 "moark",
2003 "morph",
2004 "nano-gpt",
2005 "nova",
2006 "novita-ai",
2007 "nvidia",
2008 ];
2009 for id in &ids {
2010 let meta = provider_metadata(id).unwrap_or_else(|| panic!("{id} not found"));
2011 assert_eq!(meta.canonical_id, *id);
2012 assert_eq!(
2013 meta.onboarding,
2014 ProviderOnboardingMode::OpenAICompatiblePreset,
2015 "{id} onboarding mode mismatch"
2016 );
2017 }
2018 }
2019
2020 #[test]
2021 fn batch_a3_env_keys_match_upstream_registry() {
2022 assert_eq!(
2023 provider_metadata("io-net").unwrap().auth_env_keys,
2024 &["IOINTELLIGENCE_API_KEY"]
2025 );
2026 assert_eq!(
2027 provider_metadata("jiekou").unwrap().auth_env_keys,
2028 &["JIEKOU_API_KEY"]
2029 );
2030 assert_eq!(
2031 provider_metadata("lucidquery").unwrap().auth_env_keys,
2032 &["LUCIDQUERY_API_KEY"]
2033 );
2034 assert_eq!(
2035 provider_metadata("moark").unwrap().auth_env_keys,
2036 &["MOARK_API_KEY"]
2037 );
2038 assert_eq!(
2039 provider_metadata("morph").unwrap().auth_env_keys,
2040 &["MORPH_API_KEY"]
2041 );
2042 assert_eq!(
2043 provider_metadata("nano-gpt").unwrap().auth_env_keys,
2044 &["NANO_GPT_API_KEY"]
2045 );
2046 assert_eq!(
2047 provider_metadata("nova").unwrap().auth_env_keys,
2048 &["NOVA_API_KEY"]
2049 );
2050 assert_eq!(
2051 provider_metadata("novita-ai").unwrap().auth_env_keys,
2052 &["NOVITA_API_KEY"]
2053 );
2054 assert_eq!(
2055 provider_metadata("nvidia").unwrap().auth_env_keys,
2056 &["NVIDIA_API_KEY"]
2057 );
2058 }
2059
2060 #[test]
2061 fn batch_a3_routing_defaults_use_openai_completions() {
2062 let ids = [
2063 "io-net",
2064 "jiekou",
2065 "lucidquery",
2066 "moark",
2067 "morph",
2068 "nano-gpt",
2069 "nova",
2070 "novita-ai",
2071 "nvidia",
2072 ];
2073 for id in &ids {
2074 let defaults =
2075 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
2076 assert_eq!(
2077 defaults.api, "openai-completions",
2078 "{id} api should be openai-completions"
2079 );
2080 assert!(defaults.auth_header, "{id} auth_header should be true");
2081 }
2082 }
2083
2084 #[test]
2085 fn batch_a3_base_urls_are_distinct_and_nonempty() {
2086 let ids = [
2087 "io-net",
2088 "jiekou",
2089 "lucidquery",
2090 "moark",
2091 "morph",
2092 "nano-gpt",
2093 "nova",
2094 "novita-ai",
2095 "nvidia",
2096 ];
2097 let mut urls: Vec<&str> = Vec::new();
2098 for id in &ids {
2099 let defaults =
2100 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
2101 assert!(
2102 !defaults.base_url.is_empty(),
2103 "{id} base_url must not be empty"
2104 );
2105 assert!(
2106 defaults.base_url.starts_with("https://"),
2107 "{id} base_url must use HTTPS"
2108 );
2109 urls.push(defaults.base_url);
2110 }
2111 urls.sort_unstable();
2112 urls.dedup();
2113 assert_eq!(urls.len(), ids.len(), "duplicate base URLs detected");
2114 }
2115
2116 #[test]
2117 fn fireworks_ai_alias_already_registered() {
2118 let meta = provider_metadata("fireworks-ai").expect("fireworks-ai alias");
2121 assert_eq!(meta.canonical_id, "fireworks");
2122 }
2123
2124 #[test]
2127 fn batch_a4_metadata_resolves_all_nine_providers() {
2128 let ids = [
2129 "poe",
2130 "privatemode-ai",
2131 "requesty",
2132 "submodel",
2133 "synthetic",
2134 "vivgrid",
2135 "vultr",
2136 "wandb",
2137 "xiaomi",
2138 ];
2139 for id in &ids {
2140 let meta = provider_metadata(id).unwrap_or_else(|| panic!("{id} not found"));
2141 assert_eq!(meta.canonical_id, *id);
2142 assert_eq!(
2143 meta.onboarding,
2144 ProviderOnboardingMode::OpenAICompatiblePreset,
2145 "{id} onboarding mode mismatch"
2146 );
2147 }
2148 }
2149
2150 #[test]
2151 fn batch_a4_env_keys_match_upstream_registry() {
2152 assert_eq!(
2153 provider_metadata("poe").unwrap().auth_env_keys,
2154 &["POE_API_KEY"]
2155 );
2156 assert_eq!(
2157 provider_metadata("privatemode-ai").unwrap().auth_env_keys,
2158 &["PRIVATEMODE_API_KEY"]
2159 );
2160 assert_eq!(
2161 provider_metadata("requesty").unwrap().auth_env_keys,
2162 &["REQUESTY_API_KEY"]
2163 );
2164 assert_eq!(
2165 provider_metadata("submodel").unwrap().auth_env_keys,
2166 &["SUBMODEL_INSTAGEN_ACCESS_KEY"]
2167 );
2168 assert_eq!(
2169 provider_metadata("synthetic").unwrap().auth_env_keys,
2170 &["SYNTHETIC_API_KEY"]
2171 );
2172 assert_eq!(
2173 provider_metadata("vivgrid").unwrap().auth_env_keys,
2174 &["VIVGRID_API_KEY"]
2175 );
2176 assert_eq!(
2177 provider_metadata("vultr").unwrap().auth_env_keys,
2178 &["VULTR_API_KEY"]
2179 );
2180 assert_eq!(
2181 provider_metadata("wandb").unwrap().auth_env_keys,
2182 &["WANDB_API_KEY"]
2183 );
2184 assert_eq!(
2185 provider_metadata("xiaomi").unwrap().auth_env_keys,
2186 &["XIAOMI_API_KEY"]
2187 );
2188 }
2189
2190 #[test]
2191 fn batch_a4_routing_defaults_use_openai_completions() {
2192 let ids = [
2193 "poe",
2194 "privatemode-ai",
2195 "requesty",
2196 "submodel",
2197 "synthetic",
2198 "vivgrid",
2199 "vultr",
2200 "wandb",
2201 "xiaomi",
2202 ];
2203 for id in &ids {
2204 let defaults =
2205 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
2206 assert_eq!(
2207 defaults.api, "openai-completions",
2208 "{id} api should be openai-completions"
2209 );
2210 assert!(defaults.auth_header, "{id} auth_header should be true");
2211 }
2212 }
2213
2214 #[test]
2215 fn batch_a4_base_urls_are_distinct_and_nonempty() {
2216 let ids = [
2217 "poe",
2218 "privatemode-ai",
2219 "requesty",
2220 "submodel",
2221 "synthetic",
2222 "vivgrid",
2223 "vultr",
2224 "wandb",
2225 "xiaomi",
2226 ];
2227 let mut urls: Vec<&str> = Vec::new();
2228 for id in &ids {
2229 let defaults =
2230 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
2231 assert!(
2232 !defaults.base_url.is_empty(),
2233 "{id} base_url must not be empty"
2234 );
2235 if *id != "privatemode-ai" {
2237 assert!(
2238 defaults.base_url.starts_with("https://"),
2239 "{id} base_url must use HTTPS"
2240 );
2241 }
2242 urls.push(defaults.base_url);
2243 }
2244 urls.sort_unstable();
2245 urls.dedup();
2246 assert_eq!(urls.len(), ids.len(), "duplicate base URLs detected");
2247 }
2248
2249 #[test]
2250 fn batch_b1_metadata_resolves_all_six_providers() {
2251 let ids = [
2252 "alibaba-cn",
2253 "kimi-for-coding",
2254 "minimax",
2255 "minimax-cn",
2256 "minimax-coding-plan",
2257 "minimax-cn-coding-plan",
2258 ];
2259 for id in &ids {
2260 let meta = provider_metadata(id).unwrap_or_else(|| panic!("{id} metadata missing"));
2261 assert_eq!(meta.canonical_id, *id);
2262 assert_eq!(
2263 meta.onboarding,
2264 ProviderOnboardingMode::OpenAICompatiblePreset
2265 );
2266 }
2267 }
2268
2269 #[test]
2270 fn batch_b1_env_keys_match_expected_families() {
2271 assert_eq!(
2272 provider_metadata("alibaba-cn").unwrap().auth_env_keys,
2273 &["DASHSCOPE_API_KEY"]
2274 );
2275 assert_eq!(
2276 provider_metadata("kimi-for-coding").unwrap().auth_env_keys,
2277 &["KIMI_API_KEY"]
2278 );
2279 assert_eq!(
2280 provider_metadata("minimax").unwrap().auth_env_keys,
2281 &["MINIMAX_API_KEY"]
2282 );
2283 assert_eq!(
2284 provider_metadata("minimax-cn").unwrap().auth_env_keys,
2285 &["MINIMAX_CN_API_KEY"]
2286 );
2287 assert_eq!(
2288 provider_metadata("minimax-coding-plan")
2289 .unwrap()
2290 .auth_env_keys,
2291 &["MINIMAX_API_KEY"]
2292 );
2293 assert_eq!(
2294 provider_metadata("minimax-cn-coding-plan")
2295 .unwrap()
2296 .auth_env_keys,
2297 &["MINIMAX_CN_API_KEY"]
2298 );
2299 }
2300
2301 #[test]
2302 fn batch_b1_routing_defaults_match_expected_api_families() {
2303 let alibaba_cn = provider_routing_defaults("alibaba-cn").expect("alibaba-cn defaults");
2304 assert_eq!(alibaba_cn.api, "openai-completions");
2305 assert!(alibaba_cn.auth_header);
2306 assert!(alibaba_cn.base_url.contains("dashscope.aliyuncs.com"));
2307
2308 let kimi = provider_routing_defaults("kimi-for-coding").expect("kimi-for-coding defaults");
2309 assert_eq!(kimi.api, "anthropic-messages");
2310 assert!(!kimi.auth_header);
2311 assert!(kimi.base_url.contains("api.kimi.com/coding"));
2312
2313 for id in [
2314 "minimax",
2315 "minimax-cn",
2316 "minimax-coding-plan",
2317 "minimax-cn-coding-plan",
2318 ] {
2319 let defaults =
2320 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
2321 assert_eq!(defaults.api, "anthropic-messages");
2322 assert!(!defaults.auth_header);
2323 }
2324 }
2325
2326 #[test]
2327 fn batch_b1_family_coherence_is_explicit() {
2328 let alibaba_global = provider_routing_defaults("alibaba").expect("alibaba defaults");
2329 let alibaba_cn = provider_routing_defaults("alibaba-cn").expect("alibaba-cn defaults");
2330 assert_eq!(alibaba_global.api, "openai-completions");
2331 assert_eq!(alibaba_cn.api, "openai-completions");
2332 assert_ne!(alibaba_global.base_url, alibaba_cn.base_url);
2333
2334 let kimi_alias = canonical_provider_id("kimi").expect("kimi alias");
2335 let kimi_coding = canonical_provider_id("kimi-for-coding").expect("kimi-for-coding");
2336 let kimi_coding_legacy =
2337 canonical_provider_id("kimi-coding").expect("kimi-coding legacy alias");
2338 let kimi_code_alias = canonical_provider_id("kimi-code").expect("kimi-code alias");
2339 assert_eq!(kimi_alias, "moonshotai");
2340 assert_eq!(kimi_coding, "kimi-for-coding");
2341 assert_eq!(kimi_coding_legacy, "kimi-for-coding");
2342 assert_eq!(kimi_code_alias, "kimi-for-coding");
2343
2344 let minimax = provider_routing_defaults("minimax").expect("minimax defaults");
2345 let minimax_cp =
2346 provider_routing_defaults("minimax-coding-plan").expect("minimax-coding-plan");
2347 assert_eq!(minimax.base_url, minimax_cp.base_url);
2348
2349 let minimax_cn = provider_routing_defaults("minimax-cn").expect("minimax-cn defaults");
2350 let minimax_cn_cp = provider_routing_defaults("minimax-cn-coding-plan")
2351 .expect("minimax-cn-coding-plan defaults");
2352 assert_eq!(minimax_cn.base_url, minimax_cn_cp.base_url);
2353 assert_ne!(minimax.base_url, minimax_cn.base_url);
2354 }
2355
2356 #[test]
2357 fn batch_b2_metadata_resolves_all_six_providers() {
2358 let ids = [
2359 "modelscope",
2360 "moonshotai-cn",
2361 "nebius",
2362 "ovhcloud",
2363 "scaleway",
2364 "stackit",
2365 ];
2366 for id in &ids {
2367 let meta = provider_metadata(id).unwrap_or_else(|| panic!("{id} metadata missing"));
2368 assert_eq!(meta.canonical_id, *id);
2369 assert_eq!(
2370 meta.onboarding,
2371 ProviderOnboardingMode::OpenAICompatiblePreset
2372 );
2373 }
2374 }
2375
2376 #[test]
2377 fn batch_b2_env_keys_match_expected() {
2378 assert_eq!(
2379 provider_metadata("modelscope").unwrap().auth_env_keys,
2380 &["MODELSCOPE_API_KEY"]
2381 );
2382 assert_eq!(
2383 provider_metadata("moonshotai-cn").unwrap().auth_env_keys,
2384 &["MOONSHOT_API_KEY"]
2385 );
2386 assert_eq!(
2387 provider_metadata("nebius").unwrap().auth_env_keys,
2388 &["NEBIUS_API_KEY"]
2389 );
2390 assert_eq!(
2391 provider_metadata("ovhcloud").unwrap().auth_env_keys,
2392 &["OVHCLOUD_API_KEY"]
2393 );
2394 assert_eq!(
2395 provider_metadata("scaleway").unwrap().auth_env_keys,
2396 &["SCALEWAY_API_KEY"]
2397 );
2398 assert_eq!(
2399 provider_metadata("stackit").unwrap().auth_env_keys,
2400 &["STACKIT_API_KEY"]
2401 );
2402 }
2403
2404 #[test]
2405 fn batch_b2_routing_defaults_use_openai_completions_and_bearer_auth() {
2406 let ids = [
2407 ("modelscope", "api-inference.modelscope.cn"),
2408 ("moonshotai-cn", "api.moonshot.cn"),
2409 ("nebius", "api.tokenfactory.nebius.com"),
2410 ("ovhcloud", "oai.endpoints.kepler.ai.cloud.ovh.net"),
2411 ("scaleway", "api.scaleway.ai"),
2412 (
2413 "stackit",
2414 "api.openai-compat.model-serving.eu01.onstackit.cloud",
2415 ),
2416 ];
2417 for (id, expected_host) in &ids {
2418 let defaults =
2419 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
2420 assert_eq!(defaults.api, "openai-completions");
2421 assert!(defaults.auth_header);
2422 assert!(defaults.base_url.contains(expected_host));
2423 }
2424 }
2425
2426 #[test]
2427 fn batch_b2_moonshot_cn_and_global_moonshot_stay_distinct() {
2428 let moonshot_global =
2429 provider_routing_defaults("moonshotai").expect("moonshotai defaults missing");
2430 let moonshot_cn =
2431 provider_routing_defaults("moonshotai-cn").expect("moonshotai-cn defaults missing");
2432
2433 assert_eq!(canonical_provider_id("moonshot"), Some("moonshotai"));
2434 assert_eq!(
2435 canonical_provider_id("moonshotai-cn"),
2436 Some("moonshotai-cn")
2437 );
2438 assert_eq!(
2439 provider_auth_env_keys("moonshotai"),
2440 &["MOONSHOT_API_KEY", "KIMI_API_KEY"]
2441 );
2442 assert_eq!(
2443 provider_auth_env_keys("moonshotai-cn"),
2444 &["MOONSHOT_API_KEY"]
2445 );
2446 assert_eq!(moonshot_global.api, "openai-completions");
2447 assert_eq!(moonshot_cn.api, "openai-completions");
2448 assert_ne!(moonshot_global.base_url, moonshot_cn.base_url);
2449 }
2450
2451 #[test]
2452 fn batch_b3_metadata_resolves_all_eight_providers() {
2453 let ids = [
2454 "siliconflow",
2455 "siliconflow-cn",
2456 "upstage",
2457 "venice",
2458 "zai",
2459 "zai-coding-plan",
2460 "zhipuai",
2461 "zhipuai-coding-plan",
2462 ];
2463 for id in &ids {
2464 let meta = provider_metadata(id).unwrap_or_else(|| panic!("{id} metadata missing"));
2465 assert_eq!(meta.canonical_id, *id);
2466 assert_eq!(
2467 meta.onboarding,
2468 ProviderOnboardingMode::OpenAICompatiblePreset
2469 );
2470 }
2471 }
2472
2473 #[test]
2474 fn batch_b3_env_keys_match_expected() {
2475 assert_eq!(
2476 provider_metadata("siliconflow").unwrap().auth_env_keys,
2477 &["SILICONFLOW_API_KEY"]
2478 );
2479 assert_eq!(
2480 provider_metadata("siliconflow-cn").unwrap().auth_env_keys,
2481 &["SILICONFLOW_CN_API_KEY"]
2482 );
2483 assert_eq!(
2484 provider_metadata("upstage").unwrap().auth_env_keys,
2485 &["UPSTAGE_API_KEY"]
2486 );
2487 assert_eq!(
2488 provider_metadata("venice").unwrap().auth_env_keys,
2489 &["VENICE_API_KEY"]
2490 );
2491 assert_eq!(
2492 provider_metadata("zai").unwrap().auth_env_keys,
2493 &["ZHIPU_API_KEY"]
2494 );
2495 assert_eq!(
2496 provider_metadata("zai-coding-plan").unwrap().auth_env_keys,
2497 &["ZHIPU_API_KEY"]
2498 );
2499 assert_eq!(
2500 provider_metadata("zhipuai").unwrap().auth_env_keys,
2501 &["ZHIPU_API_KEY"]
2502 );
2503 assert_eq!(
2504 provider_metadata("zhipuai-coding-plan")
2505 .unwrap()
2506 .auth_env_keys,
2507 &["ZHIPU_API_KEY"]
2508 );
2509 }
2510
2511 #[test]
2512 fn batch_b3_routing_defaults_use_openai_completions_and_bearer_auth() {
2513 let ids = [
2514 ("siliconflow", "api.siliconflow.com"),
2515 ("siliconflow-cn", "api.siliconflow.cn"),
2516 ("upstage", "api.upstage.ai"),
2517 ("venice", "api.venice.ai"),
2518 ("zai", "api.z.ai"),
2519 ("zai-coding-plan", "api.z.ai"),
2520 ("zhipuai", "open.bigmodel.cn"),
2521 ("zhipuai-coding-plan", "open.bigmodel.cn"),
2522 ];
2523 for (id, expected_host) in &ids {
2524 let defaults =
2525 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
2526 assert_eq!(defaults.api, "openai-completions");
2527 assert!(defaults.auth_header);
2528 assert!(defaults.base_url.contains(expected_host));
2529 }
2530 }
2531
2532 #[test]
2533 fn batch_b3_coding_plan_variants_keep_family_auth_but_distinct_base_urls() {
2534 let zai = provider_routing_defaults("zai").expect("zai defaults");
2535 let zai_coding = provider_routing_defaults("zai-coding-plan").expect("zai-coding defaults");
2536 assert_eq!(zai.api, "openai-completions");
2537 assert_eq!(zai_coding.api, "openai-completions");
2538 assert_ne!(zai.base_url, zai_coding.base_url);
2539
2540 let zhipu = provider_routing_defaults("zhipuai").expect("zhipu defaults");
2541 let zhipu_coding =
2542 provider_routing_defaults("zhipuai-coding-plan").expect("zhipu-coding defaults");
2543 assert_eq!(zhipu.api, "openai-completions");
2544 assert_eq!(zhipu_coding.api, "openai-completions");
2545 assert_ne!(zhipu.base_url, zhipu_coding.base_url);
2546
2547 assert_eq!(provider_auth_env_keys("zai"), &["ZHIPU_API_KEY"]);
2548 assert_eq!(provider_auth_env_keys("zhipuai"), &["ZHIPU_API_KEY"]);
2549 }
2550
2551 #[test]
2552 fn batch_c1_metadata_resolves_all_five_providers() {
2553 let ids = ["baseten", "llama", "lmstudio", "ollama", "ollama-cloud"];
2554 for id in &ids {
2555 let meta = provider_metadata(id).unwrap_or_else(|| panic!("{id} metadata missing"));
2556 assert_eq!(meta.canonical_id, *id);
2557 assert_eq!(
2558 meta.onboarding,
2559 ProviderOnboardingMode::OpenAICompatiblePreset
2560 );
2561 }
2562 }
2563
2564 #[test]
2565 fn batch_c1_env_keys_match_expected() {
2566 assert_eq!(
2567 provider_metadata("baseten").unwrap().auth_env_keys,
2568 &["BASETEN_API_KEY"]
2569 );
2570 assert_eq!(
2571 provider_metadata("llama").unwrap().auth_env_keys,
2572 &["LLAMA_API_KEY"]
2573 );
2574 assert_eq!(
2575 provider_metadata("lmstudio").unwrap().auth_env_keys,
2576 &["LMSTUDIO_API_KEY"]
2577 );
2578 assert!(
2579 provider_metadata("ollama")
2580 .unwrap()
2581 .auth_env_keys
2582 .is_empty()
2583 );
2584 assert_eq!(
2585 provider_metadata("ollama-cloud").unwrap().auth_env_keys,
2586 &["OLLAMA_API_KEY"]
2587 );
2588 }
2589
2590 #[test]
2591 fn batch_c1_routing_defaults_use_openai_completions_with_expected_endpoints() {
2592 let ids = [
2593 ("baseten", "https://inference.baseten.co/v1", true),
2594 ("llama", "https://api.llama.com/compat/v1", true),
2595 ("lmstudio", "http://127.0.0.1:1234/v1", true),
2596 ("ollama", "http://127.0.0.1:11434/v1", false),
2597 ("ollama-cloud", "https://ollama.com/v1", true),
2598 ];
2599 for (id, expected_base_url, expected_auth_header) in &ids {
2600 let defaults =
2601 provider_routing_defaults(id).unwrap_or_else(|| panic!("{id} defaults missing"));
2602 assert_eq!(defaults.api, "openai-completions");
2603 assert_eq!(defaults.auth_header, *expected_auth_header);
2604 assert_eq!(defaults.base_url, *expected_base_url);
2605 }
2606 }
2607
2608 #[test]
2609 fn special_routing_metadata_resolves_all_three_providers() {
2610 let ids = ["opencode", "vercel", "zenmux"];
2611 for id in &ids {
2612 let meta = provider_metadata(id).unwrap_or_else(|| panic!("{id} metadata missing"));
2613 assert_eq!(meta.canonical_id, *id);
2614 assert_eq!(
2615 meta.onboarding,
2616 ProviderOnboardingMode::OpenAICompatiblePreset
2617 );
2618 }
2619 }
2620
2621 #[test]
2622 fn special_routing_env_keys_match_expected() {
2623 assert_eq!(
2624 provider_metadata("opencode").unwrap().auth_env_keys,
2625 &["OPENCODE_API_KEY"]
2626 );
2627 assert_eq!(
2628 provider_metadata("vercel").unwrap().auth_env_keys,
2629 &["AI_GATEWAY_API_KEY"]
2630 );
2631 assert_eq!(
2632 provider_metadata("zenmux").unwrap().auth_env_keys,
2633 &["ZENMUX_API_KEY"]
2634 );
2635 }
2636
2637 #[test]
2638 fn special_routing_defaults_match_expected_api_families() {
2639 let opencode = provider_routing_defaults("opencode").expect("opencode defaults");
2640 assert_eq!(opencode.api, "openai-completions");
2641 assert_eq!(opencode.base_url, "https://opencode.ai/zen/v1");
2642 assert!(opencode.auth_header);
2643
2644 let vercel = provider_routing_defaults("vercel").expect("vercel defaults");
2645 assert_eq!(vercel.api, "openai-completions");
2646 assert_eq!(vercel.base_url, "https://ai-gateway.vercel.sh/v1");
2647 assert!(vercel.auth_header);
2648 let vercel_alias =
2649 provider_routing_defaults("vercel-ai-gateway").expect("vercel alias defaults");
2650 assert_eq!(vercel_alias.api, "openai-completions");
2651 assert_eq!(vercel_alias.base_url, "https://ai-gateway.vercel.sh/v1");
2652 assert!(vercel_alias.auth_header);
2653
2654 let zenmux = provider_routing_defaults("zenmux").expect("zenmux defaults");
2655 assert_eq!(zenmux.api, "anthropic-messages");
2656 assert_eq!(
2657 zenmux.base_url,
2658 "https://zenmux.ai/api/anthropic/v1/messages"
2659 );
2660 assert!(!zenmux.auth_header);
2661 }
2662 #[test]
2663 fn v0_registered_as_native_adapter_required_without_routing_defaults() {
2664 let meta = provider_metadata("v0").expect("v0 metadata");
2665 assert_eq!(meta.canonical_id, "v0");
2666 assert_eq!(
2667 meta.onboarding,
2668 ProviderOnboardingMode::NativeAdapterRequired
2669 );
2670 assert_eq!(meta.auth_env_keys, &["V0_API_KEY"]);
2671 assert!(provider_routing_defaults("v0").is_none());
2672 }
2673
2674 #[test]
2675 fn display_name_populated_for_major_providers() {
2676 let cases: &[(&str, &str)] = &[
2677 ("anthropic", "Anthropic"),
2678 ("openai", "OpenAI"),
2679 ("google", "Google Gemini"),
2680 ("xai", "xAI (Grok)"),
2681 ("togetherai", "Together AI"),
2682 ("huggingface", "Hugging Face"),
2683 ("nvidia", "NVIDIA NIM"),
2684 ("amazon-bedrock", "Amazon Bedrock"),
2685 ("azure-openai", "Azure OpenAI"),
2686 ("github-copilot", "GitHub Copilot"),
2687 ("google-vertex", "Google Vertex AI"),
2688 ("deepseek", "DeepSeek"),
2689 ("mistral", "Mistral AI"),
2690 ("lmstudio", "LM Studio"),
2691 ];
2692 for &(id, expected_name) in cases {
2693 let meta = provider_metadata(id).unwrap_or_else(|| panic!("provider '{id}' not found"));
2694 assert_eq!(
2695 meta.display_name,
2696 Some(expected_name),
2697 "display_name for '{id}' should be '{expected_name}'"
2698 );
2699 }
2700 }
2701
2702 #[test]
2703 fn all_providers_have_display_name() {
2704 for meta in PROVIDER_METADATA {
2705 assert!(
2706 meta.display_name.is_some(),
2707 "provider '{}' should have a display_name",
2708 meta.canonical_id
2709 );
2710 }
2711 }
2712
2713 mod proptest_provider_metadata {
2714 use super::*;
2715 use proptest::prelude::*;
2716
2717 proptest! {
2718 #[test]
2720 fn provider_metadata_never_panics(s in ".*") {
2721 let _ = provider_metadata(&s);
2722 }
2723
2724 #[test]
2726 fn provider_metadata_empty_returns_none(_dummy in 0..10u32) {
2727 assert!(provider_metadata("").is_none());
2728 assert!(canonical_provider_id("").is_none());
2729 assert!(provider_auth_env_keys("").is_empty());
2730 assert!(provider_routing_defaults("").is_none());
2731 }
2732
2733 #[test]
2735 fn canonical_ids_resolve_to_self(idx in 0..PROVIDER_METADATA.len()) {
2736 let meta = &PROVIDER_METADATA[idx];
2737 let resolved = canonical_provider_id(meta.canonical_id);
2738 assert_eq!(resolved, Some(meta.canonical_id));
2739 }
2740
2741 #[test]
2743 fn case_insensitive_lookup(idx in 0..PROVIDER_METADATA.len()) {
2744 let meta = &PROVIDER_METADATA[idx];
2745 let upper = provider_metadata(&meta.canonical_id.to_uppercase());
2746 let lower = provider_metadata(&meta.canonical_id.to_lowercase());
2747 assert!(upper.is_some());
2748 assert!(lower.is_some());
2749 assert_eq!(upper.unwrap().canonical_id, lower.unwrap().canonical_id);
2750 }
2751
2752 #[test]
2754 fn aliases_resolve_to_canonical(idx in 0..PROVIDER_METADATA.len()) {
2755 let meta = &PROVIDER_METADATA[idx];
2756 for alias in meta.aliases {
2757 let resolved = canonical_provider_id(alias);
2758 assert_eq!(
2759 resolved,
2760 Some(meta.canonical_id),
2761 "alias '{alias}' should resolve to '{}'",
2762 meta.canonical_id
2763 );
2764 }
2765 }
2766
2767 #[test]
2769 fn canonical_id_is_idempotent(idx in 0..PROVIDER_METADATA.len()) {
2770 let meta = &PROVIDER_METADATA[idx];
2771 let first = canonical_provider_id(meta.canonical_id).unwrap();
2772 let second = canonical_provider_id(first).unwrap();
2773 assert_eq!(first, second);
2774 }
2775
2776 #[test]
2778 fn unknown_provider_returns_none(s in "[a-z]{20,30}") {
2779 assert!(provider_metadata(&s).is_none());
2781 assert!(canonical_provider_id(&s).is_none());
2782 assert!(provider_auth_env_keys(&s).is_empty());
2783 assert!(provider_routing_defaults(&s).is_none());
2784 }
2785
2786 #[test]
2788 fn auth_env_keys_are_valid_env_vars(idx in 0..PROVIDER_METADATA.len()) {
2789 let meta = &PROVIDER_METADATA[idx];
2790 let keys = provider_auth_env_keys(meta.canonical_id);
2791 for &key in keys {
2793 assert!(!key.is_empty());
2794 assert!(
2795 key.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'),
2796 "invalid env var name: {key}"
2797 );
2798 }
2799 }
2800
2801 #[test]
2803 fn auth_keys_consistent_with_metadata(idx in 0..PROVIDER_METADATA.len()) {
2804 let meta = &PROVIDER_METADATA[idx];
2805 let keys = provider_auth_env_keys(meta.canonical_id);
2806 assert_eq!(keys, meta.auth_env_keys);
2807 }
2808
2809 #[test]
2811 fn routing_defaults_consistent_with_metadata(idx in 0..PROVIDER_METADATA.len()) {
2812 let meta = &PROVIDER_METADATA[idx];
2813 let defaults = provider_routing_defaults(meta.canonical_id);
2814 match (defaults, meta.routing_defaults) {
2815 (Some(d), Some(m)) => {
2816 assert_eq!(d.base_url, m.base_url);
2817 assert_eq!(d.api, m.api);
2818 }
2819 (None, None) => {}
2820 _ => panic!(
2821 "mismatch for '{}': fn={:?} meta={:?}",
2822 meta.canonical_id, defaults, meta.routing_defaults
2823 ),
2824 }
2825 }
2826 }
2827 }
2828}