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