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