Skip to main content

pi/
provider_metadata.rs

1//! Canonical provider metadata shared across runtime surfaces.
2//!
3//! This module is intentionally data-first: it centralizes provider identifiers,
4//! aliases, auth env keys, and default routing hints so models/auth/provider
5//! selection paths don't drift independently.
6
7use 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    // ── Batch A1: OAI-compatible preset providers ──────────────────────
358    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    // ── Batch A2: OAI-compatible preset providers ──────────────────────
495    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    // ── Batch A3: OAI-compatible preset providers ──────────────────────
632    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    // ── Batch A4: OAI-compatible preset providers ──────────────────────
786    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            // Default is localhost; users override via PRIVATEMODE_ENDPOINT env var.
812            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    // ── Batch B1: Regional + coding-plan providers ─────────────────────
941    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    // ── Batch B2: Regional + cloud providers ────────────────────────────
1044    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    // ── Batch B3: Regional + coding-plan providers ──────────────────────
1147    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    // ── Batch C1: Local/self-hosted preset providers ──────────────────────
1284    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    // ── Special routing providers (bd-3uqg.3.9) ────────────────────────────
1370    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    // ── Cloudflare provider IDs (gateway + workers-ai) ────────────────────
1422    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    // ── Native adapter required providers ────────────────────────────────
1457    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        // Aliases added to improve user discoverability (bd-tuh3g / bd-3uqg.14.3.4)
1623        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        // New UX aliases resolve to same auth keys as canonical
1723        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        // All URLs must be unique.
1904        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    // ── Batch A3 tests ────────────────────────────────────────────────
1995
1996    #[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        // fireworks-ai is listed in Batch A2 bead but already exists as alias
2119        // for the "fireworks" canonical entry from the initial metadata set.
2120        let meta = provider_metadata("fireworks-ai").expect("fireworks-ai alias");
2121        assert_eq!(meta.canonical_id, "fireworks");
2122    }
2123
2124    // ── Batch A4 tests ────────────────────────────────────────────────
2125
2126    #[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            // privatemode-ai uses localhost (self-hosted); all others use HTTPS.
2236            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            /// `provider_metadata` never panics on arbitrary input.
2719            #[test]
2720            fn provider_metadata_never_panics(s in ".*") {
2721                let _ = provider_metadata(&s);
2722            }
2723
2724            /// Empty string always returns None.
2725            #[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            /// All canonical IDs resolve to themselves.
2734            #[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            /// Lookup is case-insensitive for all canonical IDs.
2742            #[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            /// All aliases resolve to the same canonical ID.
2753            #[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            /// `canonical_provider_id` is idempotent.
2768            #[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            /// Unknown providers return None.
2777            #[test]
2778            fn unknown_provider_returns_none(s in "[a-z]{20,30}") {
2779                // 20+ char lowercase strings won't match any provider
2780                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            /// Auth env keys (when present) are valid environment variable names.
2787            #[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                // Some providers (e.g. ollama) have no auth keys — that's valid.
2792                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            /// `provider_auth_env_keys` matches `provider_metadata().auth_env_keys`.
2802            #[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            /// `provider_routing_defaults` matches `provider_metadata().routing_defaults`.
2810            #[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}