1use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::str::FromStr;
10
11#[derive(Clone, Copy)]
12struct OpenRouterMetadata {
13 id: &'static str,
14 display: &'static str,
15 description: &'static str,
16 efficient: bool,
17 top_tier: bool,
18 generation: &'static str,
19}
20
21macro_rules! each_openrouter_variant {
22 ($macro:ident) => {
23 $macro! {
24 (OpenRouterGrokCodeFast1, OPENROUTER_X_AI_GROK_CODE_FAST_1, "Grok Code Fast 1", "Fast OpenRouter coding model powered by xAI Grok", true, false, "marketplace"),
25 (OpenRouterGrok4Fast, OPENROUTER_X_AI_GROK_4_FAST, "Grok 4 Fast", "Reasoning-focused Grok endpoint with transparent traces", false, true, "marketplace"),
26 (OpenRouterGrok4, OPENROUTER_X_AI_GROK_4, "Grok 4", "Flagship Grok 4 endpoint exposed through OpenRouter", false, true, "marketplace"),
27 (OpenRouterZaiGlm45AirFree, OPENROUTER_Z_AI_GLM_4_5_AIR_FREE, "GLM 4.5 Air (free)", "Community tier for Z.AI GLM 4.5 Air", false, false, "GLM-4.5"),
28 (OpenRouterZaiGlm46, OPENROUTER_Z_AI_GLM_4_6, "GLM 4.6", "Z.AI GLM 4.6 long-context reasoning model", false, true, "GLM-4.6"),
29 (OpenRouterMoonshotaiKimiK20905, OPENROUTER_MOONSHOTAI_KIMI_K2_0905, "Kimi K2 0905", "MoonshotAI Kimi K2 0905 MoE release optimised for coding agents", false, true, "K2-0905"),
30 (OpenRouterQwen3Max, OPENROUTER_QWEN3_MAX, "Qwen3 Max", "Flagship Qwen3 mixture for general reasoning", false, true, "Qwen3"),
31 (OpenRouterQwen3235bA22b, OPENROUTER_QWEN3_235B_A22B, "Qwen3 235B A22B", "Mixture-of-experts Qwen3 235B general model", false, true, "Qwen3"),
32 (OpenRouterQwen3235bA22bFree, OPENROUTER_QWEN3_235B_A22B_FREE, "Qwen3 235B A22B (free)", "Community tier for Qwen3 235B A22B", false, true, "Qwen3"),
33 (OpenRouterQwen3235bA22b2507, OPENROUTER_QWEN3_235B_A22B_2507, "Qwen3 235B A22B Instruct 2507", "Instruction-tuned Qwen3 235B A22B", false, true, "Qwen3-2507"),
34 (OpenRouterQwen3235bA22bThinking2507, OPENROUTER_QWEN3_235B_A22B_THINKING_2507, "Qwen3 235B A22B Thinking 2507", "Deliberative Qwen3 235B A22B reasoning release", false, true, "Qwen3-2507"),
35 (OpenRouterQwen332b, OPENROUTER_QWEN3_32B, "Qwen3 32B", "Dense 32B Qwen3 deployment", false, false, "Qwen3-32B"),
36 (OpenRouterQwen330bA3b, OPENROUTER_QWEN3_30B_A3B, "Qwen3 30B A3B", "Active-parameter 30B Qwen3 model", false, false, "Qwen3-30B"),
37 (OpenRouterQwen330bA3bFree, OPENROUTER_QWEN3_30B_A3B_FREE, "Qwen3 30B A3B (free)", "Community tier for Qwen3 30B A3B", false, false, "Qwen3-30B"),
38 (OpenRouterQwen330bA3bInstruct2507, OPENROUTER_QWEN3_30B_A3B_INSTRUCT_2507, "Qwen3 30B A3B Instruct 2507", "Instruction-tuned Qwen3 30B A3B", false, false, "Qwen3-30B"),
39 (OpenRouterQwen330bA3bThinking2507, OPENROUTER_QWEN3_30B_A3B_THINKING_2507, "Qwen3 30B A3B Thinking 2507", "Deliberative Qwen3 30B A3B release", false, true, "Qwen3-30B"),
40 (OpenRouterQwen314b, OPENROUTER_QWEN3_14B, "Qwen3 14B", "Lightweight Qwen3 14B model", true, false, "Qwen3-14B"),
41 (OpenRouterQwen314bFree, OPENROUTER_QWEN3_14B_FREE, "Qwen3 14B (free)", "Community tier for Qwen3 14B", true, false, "Qwen3-14B"),
42 (OpenRouterQwen38b, OPENROUTER_QWEN3_8B, "Qwen3 8B", "Compact Qwen3 8B deployment", true, false, "Qwen3-8B"),
43 (OpenRouterQwen38bFree, OPENROUTER_QWEN3_8B_FREE, "Qwen3 8B (free)", "Community tier for Qwen3 8B", true, false, "Qwen3-8B"),
44 (OpenRouterQwen34bFree, OPENROUTER_QWEN3_4B_FREE, "Qwen3 4B (free)", "Entry level Qwen3 4B deployment", true, false, "Qwen3-4B"),
45 (OpenRouterQwen3Next80bA3bInstruct, OPENROUTER_QWEN3_NEXT_80B_A3B_INSTRUCT, "Qwen3 Next 80B A3B Instruct", "Next-generation Qwen3 instruction model", false, false, "Qwen3-Next"),
46 (OpenRouterQwen3Next80bA3bThinking, OPENROUTER_QWEN3_NEXT_80B_A3B_THINKING, "Qwen3 Next 80B A3B Thinking", "Next-generation Qwen3 reasoning release", false, true, "Qwen3-Next"),
47 (OpenRouterQwen3Coder, OPENROUTER_QWEN3_CODER, "Qwen3 Coder", "Qwen3-based coding model tuned for IDE workflows", false, true, "Qwen3-Coder"),
48 (OpenRouterQwen3CoderFree, OPENROUTER_QWEN3_CODER_FREE, "Qwen3 Coder (free)", "Community tier for Qwen3 Coder", false, false, "Qwen3-Coder"),
49 (OpenRouterQwen3CoderPlus, OPENROUTER_QWEN3_CODER_PLUS, "Qwen3 Coder Plus", "Premium Qwen3 coding model with long context", false, true, "Qwen3-Coder"),
50 (OpenRouterQwen3CoderFlash, OPENROUTER_QWEN3_CODER_FLASH, "Qwen3 Coder Flash", "Latency optimised Qwen3 coding model", true, false, "Qwen3-Coder"),
51 (OpenRouterQwen3Coder30bA3bInstruct, OPENROUTER_QWEN3_CODER_30B_A3B_INSTRUCT, "Qwen3 Coder 30B A3B Instruct", "Large Mixture-of-Experts coding deployment", false, true, "Qwen3-Coder"),
52 (OpenRouterDeepSeekV32Exp, OPENROUTER_DEEPSEEK_V3_2_EXP, "DeepSeek V3.2 Exp", "Experimental DeepSeek V3.2 listing", false, true, "V3.2-Exp"),
53 (OpenRouterDeepSeekChatV31, OPENROUTER_DEEPSEEK_CHAT_V3_1, "DeepSeek Chat v3.1", "Advanced DeepSeek model via OpenRouter", false, true, "2025-08-21"),
54 (OpenRouterDeepSeekR1, OPENROUTER_DEEPSEEK_R1, "DeepSeek R1", "DeepSeek R1 reasoning model with chain-of-thought", false, true, "2025-01-20"),
55 (OpenRouterOpenAIGptOss120b, OPENROUTER_OPENAI_GPT_OSS_120B, "OpenAI gpt-oss-120b", "Open-weight 120B reasoning model via OpenRouter", false, true, "OSS-120B"),
56 (OpenRouterOpenAIGptOss20b, OPENROUTER_OPENAI_GPT_OSS_20B, "OpenAI gpt-oss-20b", "Open-weight 20B deployment via OpenRouter", false, false, "OSS-20B"),
57 (OpenRouterOpenAIGptOss20bFree, OPENROUTER_OPENAI_GPT_OSS_20B_FREE, "OpenAI gpt-oss-20b (free)", "Community tier for OpenAI gpt-oss-20b", false, false, "OSS-20B"),
58 (OpenRouterOpenAIGpt5, OPENROUTER_OPENAI_GPT_5, "OpenAI GPT-5", "OpenAI GPT-5 model accessed through OpenRouter", false, true, "2025-09-20"),
59 (OpenRouterOpenAIGpt5Codex, OPENROUTER_OPENAI_GPT_5_CODEX, "OpenAI GPT-5 Codex", "OpenRouter listing for GPT-5 Codex", false, true, "2025-09-20"),
60 (OpenRouterOpenAIGpt5Chat, OPENROUTER_OPENAI_GPT_5_CHAT, "OpenAI GPT-5 Chat", "Chat optimised GPT-5 endpoint without tool use", false, false, "2025-09-20"),
61 (OpenRouterOpenAIGpt4oSearchPreview, OPENROUTER_OPENAI_GPT_4O_SEARCH_PREVIEW, "OpenAI GPT-4o Search Preview", "GPT-4o search preview endpoint via OpenRouter", false, false, "4o-Search"),
62 (OpenRouterOpenAIGpt4oMiniSearchPreview, OPENROUTER_OPENAI_GPT_4O_MINI_SEARCH_PREVIEW, "OpenAI GPT-4o Mini Search Preview", "GPT-4o mini search preview endpoint", false, false, "4o-Search"),
63 (OpenRouterOpenAIChatgpt4oLatest, OPENROUTER_OPENAI_CHATGPT_4O_LATEST, "OpenAI ChatGPT-4o Latest", "ChatGPT 4o latest listing via OpenRouter", false, false, "4o"),
64 (OpenRouterAnthropicClaudeSonnet45, OPENROUTER_ANTHROPIC_CLAUDE_SONNET_4_5, "Claude Sonnet 4.5", "Anthropic Claude Sonnet 4.5 listing", false, true, "2025-09-29"),
65 (OpenRouterAnthropicClaudeOpus41, OPENROUTER_ANTHROPIC_CLAUDE_OPUS_4_1, "Claude Opus 4.1", "Anthropic Claude Opus 4.1 listing", false, true, "2025-08-05"),
66 }
67 };
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
72pub enum Provider {
73 #[default]
75 Gemini,
76 OpenAI,
78 Anthropic,
80 DeepSeek,
82 OpenRouter,
84 Moonshot,
86 XAI,
88 ZAI,
90}
91
92impl Provider {
93 pub fn default_api_key_env(&self) -> &'static str {
95 match self {
96 Provider::Gemini => "GEMINI_API_KEY",
97 Provider::OpenAI => "OPENAI_API_KEY",
98 Provider::Anthropic => "ANTHROPIC_API_KEY",
99 Provider::DeepSeek => "DEEPSEEK_API_KEY",
100 Provider::OpenRouter => "OPENROUTER_API_KEY",
101 Provider::Moonshot => "MOONSHOT_API_KEY",
102 Provider::XAI => "XAI_API_KEY",
103 Provider::ZAI => "ZAI_API_KEY",
104 }
105 }
106
107 pub fn all_providers() -> Vec<Provider> {
109 vec![
110 Provider::Gemini,
111 Provider::OpenAI,
112 Provider::Anthropic,
113 Provider::DeepSeek,
114 Provider::OpenRouter,
115 Provider::Moonshot,
116 Provider::XAI,
117 Provider::ZAI,
118 ]
119 }
120
121 pub fn label(&self) -> &'static str {
123 match self {
124 Provider::Gemini => "Gemini",
125 Provider::OpenAI => "OpenAI",
126 Provider::Anthropic => "Anthropic",
127 Provider::DeepSeek => "DeepSeek",
128 Provider::OpenRouter => "OpenRouter",
129 Provider::Moonshot => "Moonshot",
130 Provider::XAI => "xAI",
131 Provider::ZAI => "Z.AI",
132 }
133 }
134
135 pub fn supports_reasoning_effort(&self, model: &str) -> bool {
137 use crate::config::constants::models;
138
139 match self {
140 Provider::Gemini => model == models::google::GEMINI_2_5_PRO,
141 Provider::OpenAI => models::openai::REASONING_MODELS.contains(&model),
142 Provider::Anthropic => models::anthropic::SUPPORTED_MODELS.contains(&model),
143 Provider::DeepSeek => model == models::deepseek::DEEPSEEK_REASONER,
144 Provider::OpenRouter => models::openrouter::REASONING_MODELS.contains(&model),
145 Provider::Moonshot => false,
146 Provider::XAI => model == models::xai::GROK_4 || model == models::xai::GROK_4_CODE,
147 Provider::ZAI => model == models::zai::GLM_4_6,
148 }
149 }
150}
151
152impl fmt::Display for Provider {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 Provider::Gemini => write!(f, "gemini"),
156 Provider::OpenAI => write!(f, "openai"),
157 Provider::Anthropic => write!(f, "anthropic"),
158 Provider::DeepSeek => write!(f, "deepseek"),
159 Provider::OpenRouter => write!(f, "openrouter"),
160 Provider::Moonshot => write!(f, "moonshot"),
161 Provider::XAI => write!(f, "xai"),
162 Provider::ZAI => write!(f, "zai"),
163 }
164 }
165}
166
167impl FromStr for Provider {
168 type Err = ModelParseError;
169
170 fn from_str(s: &str) -> Result<Self, Self::Err> {
171 match s.to_lowercase().as_str() {
172 "gemini" => Ok(Provider::Gemini),
173 "openai" => Ok(Provider::OpenAI),
174 "anthropic" => Ok(Provider::Anthropic),
175 "deepseek" => Ok(Provider::DeepSeek),
176 "openrouter" => Ok(Provider::OpenRouter),
177 "moonshot" => Ok(Provider::Moonshot),
178 "xai" => Ok(Provider::XAI),
179 "zai" => Ok(Provider::ZAI),
180 _ => Err(ModelParseError::InvalidProvider(s.to_string())),
181 }
182 }
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
187pub enum ModelId {
188 Gemini25FlashPreview,
191 Gemini25Flash,
193 Gemini25FlashLite,
195 Gemini25Pro,
197
198 GPT5,
201 GPT5Codex,
203 GPT5Mini,
205 GPT5Nano,
207 CodexMiniLatest,
209
210 ClaudeOpus41,
213 ClaudeSonnet45,
215 ClaudeSonnet4,
217
218 DeepSeekChat,
221 DeepSeekReasoner,
223
224 XaiGrok4,
227 XaiGrok4Mini,
229 XaiGrok4Code,
231 XaiGrok4CodeLatest,
233 XaiGrok4Vision,
235
236 ZaiGlm46,
239 ZaiGlm45,
241 ZaiGlm45Air,
243 ZaiGlm45X,
245 ZaiGlm45Airx,
247 ZaiGlm45Flash,
249 ZaiGlm432b0414128k,
251
252 MoonshotV18k,
255 MoonshotV132k,
257 MoonshotV1128k,
259
260 OpenRouterGrokCodeFast1,
263 OpenRouterGrok4Fast,
265 OpenRouterGrok4,
267 OpenRouterZaiGlm45AirFree,
269 OpenRouterZaiGlm46,
271 OpenRouterMoonshotaiKimiK20905,
273 OpenRouterQwen3Max,
275 OpenRouterQwen3235bA22b,
277 OpenRouterQwen3235bA22bFree,
279 OpenRouterQwen3235bA22b2507,
281 OpenRouterQwen3235bA22bThinking2507,
283 OpenRouterQwen332b,
285 OpenRouterQwen330bA3b,
287 OpenRouterQwen330bA3bFree,
289 OpenRouterQwen330bA3bInstruct2507,
291 OpenRouterQwen330bA3bThinking2507,
293 OpenRouterQwen314b,
295 OpenRouterQwen314bFree,
297 OpenRouterQwen38b,
299 OpenRouterQwen38bFree,
301 OpenRouterQwen34bFree,
303 OpenRouterQwen3Next80bA3bInstruct,
305 OpenRouterQwen3Next80bA3bThinking,
307 OpenRouterQwen3Coder,
309 OpenRouterQwen3CoderFree,
311 OpenRouterQwen3CoderPlus,
313 OpenRouterQwen3CoderFlash,
315 OpenRouterQwen3Coder30bA3bInstruct,
317 OpenRouterDeepSeekV32Exp,
319 OpenRouterDeepSeekChatV31,
321 OpenRouterDeepSeekR1,
323 OpenRouterOpenAIGptOss120b,
325 OpenRouterOpenAIGptOss20b,
327 OpenRouterOpenAIGptOss20bFree,
329 OpenRouterOpenAIGpt5,
331 OpenRouterOpenAIGpt5Codex,
333 OpenRouterOpenAIGpt5Chat,
335 OpenRouterOpenAIGpt4oSearchPreview,
337 OpenRouterOpenAIGpt4oMiniSearchPreview,
339 OpenRouterOpenAIChatgpt4oLatest,
341 OpenRouterAnthropicClaudeSonnet45,
343 OpenRouterAnthropicClaudeOpus41,
345}
346impl ModelId {
347 fn openrouter_metadata(&self) -> Option<OpenRouterMetadata> {
348 use crate::config::constants::models;
349
350 macro_rules! metadata_match {
351 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
352 match self {
353 $(ModelId::$variant => Some(OpenRouterMetadata {
354 id: models::$const,
355 display: $display,
356 description: $description,
357 efficient: $efficient,
358 top_tier: $top,
359 generation: $generation,
360 }),)*
361 _ => None,
362 }
363 };
364 }
365
366 each_openrouter_variant!(metadata_match)
367 }
368
369 fn parse_openrouter_model(value: &str) -> Option<Self> {
370 use crate::config::constants::models;
371
372 macro_rules! parse_match {
373 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
374 match value {
375 $(models::$const => Some(ModelId::$variant),)*
376 _ => None,
377 }
378 };
379 }
380
381 each_openrouter_variant!(parse_match)
382 }
383
384 fn openrouter_models() -> Vec<Self> {
385 macro_rules! to_vec {
386 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
387 vec![$(ModelId::$variant,)*]
388 };
389 }
390
391 each_openrouter_variant!(to_vec)
392 }
393
394 pub fn as_str(&self) -> &'static str {
397 use crate::config::constants::models;
398 if let Some(meta) = self.openrouter_metadata() {
399 return meta.id;
400 }
401 match self {
402 ModelId::Gemini25FlashPreview => models::GEMINI_2_5_FLASH_PREVIEW,
404 ModelId::Gemini25Flash => models::GEMINI_2_5_FLASH,
405 ModelId::Gemini25FlashLite => models::GEMINI_2_5_FLASH_LITE,
406 ModelId::Gemini25Pro => models::GEMINI_2_5_PRO,
407 ModelId::GPT5 => models::GPT_5,
409 ModelId::GPT5Codex => models::GPT_5_CODEX,
410 ModelId::GPT5Mini => models::GPT_5_MINI,
411 ModelId::GPT5Nano => models::GPT_5_NANO,
412 ModelId::CodexMiniLatest => models::CODEX_MINI_LATEST,
413 ModelId::ClaudeOpus41 => models::CLAUDE_OPUS_4_1_20250805,
415 ModelId::ClaudeSonnet45 => models::CLAUDE_SONNET_4_5,
416 ModelId::ClaudeSonnet4 => models::CLAUDE_SONNET_4_20250514,
417 ModelId::DeepSeekChat => models::DEEPSEEK_CHAT,
419 ModelId::DeepSeekReasoner => models::DEEPSEEK_REASONER,
420 ModelId::XaiGrok4 => models::xai::GROK_4,
422 ModelId::XaiGrok4Mini => models::xai::GROK_4_MINI,
423 ModelId::XaiGrok4Code => models::xai::GROK_4_CODE,
424 ModelId::XaiGrok4CodeLatest => models::xai::GROK_4_CODE_LATEST,
425 ModelId::XaiGrok4Vision => models::xai::GROK_4_VISION,
426 ModelId::ZaiGlm46 => models::zai::GLM_4_6,
428 ModelId::ZaiGlm45 => models::zai::GLM_4_5,
429 ModelId::ZaiGlm45Air => models::zai::GLM_4_5_AIR,
430 ModelId::ZaiGlm45X => models::zai::GLM_4_5_X,
431 ModelId::ZaiGlm45Airx => models::zai::GLM_4_5_AIRX,
432 ModelId::ZaiGlm45Flash => models::zai::GLM_4_5_FLASH,
433 ModelId::ZaiGlm432b0414128k => models::zai::GLM_4_32B_0414_128K,
434 ModelId::MoonshotV18k => models::MOONSHOT_V1_8K,
436 ModelId::MoonshotV132k => models::MOONSHOT_V1_32K,
437 ModelId::MoonshotV1128k => models::MOONSHOT_V1_128K,
438 _ => unreachable!(),
440 }
441 }
442
443 pub fn provider(&self) -> Provider {
445 if self.openrouter_metadata().is_some() {
446 return Provider::OpenRouter;
447 }
448 match self {
449 ModelId::Gemini25FlashPreview
450 | ModelId::Gemini25Flash
451 | ModelId::Gemini25FlashLite
452 | ModelId::Gemini25Pro => Provider::Gemini,
453 ModelId::GPT5
454 | ModelId::GPT5Codex
455 | ModelId::GPT5Mini
456 | ModelId::GPT5Nano
457 | ModelId::CodexMiniLatest => Provider::OpenAI,
458 ModelId::ClaudeOpus41 | ModelId::ClaudeSonnet45 | ModelId::ClaudeSonnet4 => {
459 Provider::Anthropic
460 }
461 ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => Provider::DeepSeek,
462 ModelId::XaiGrok4
463 | ModelId::XaiGrok4Mini
464 | ModelId::XaiGrok4Code
465 | ModelId::XaiGrok4CodeLatest
466 | ModelId::XaiGrok4Vision => Provider::XAI,
467 ModelId::ZaiGlm46
468 | ModelId::ZaiGlm45
469 | ModelId::ZaiGlm45Air
470 | ModelId::ZaiGlm45X
471 | ModelId::ZaiGlm45Airx
472 | ModelId::ZaiGlm45Flash
473 | ModelId::ZaiGlm432b0414128k => Provider::ZAI,
474 ModelId::MoonshotV18k | ModelId::MoonshotV132k | ModelId::MoonshotV1128k => {
475 Provider::Moonshot
476 }
477 _ => unreachable!(),
478 }
479 }
480
481 pub fn supports_reasoning_effort(&self) -> bool {
483 self.provider().supports_reasoning_effort(self.as_str())
484 }
485
486 pub fn display_name(&self) -> &'static str {
488 if let Some(meta) = self.openrouter_metadata() {
489 return meta.display;
490 }
491 match self {
492 ModelId::Gemini25FlashPreview => "Gemini 2.5 Flash Preview",
494 ModelId::Gemini25Flash => "Gemini 2.5 Flash",
495 ModelId::Gemini25FlashLite => "Gemini 2.5 Flash Lite",
496 ModelId::Gemini25Pro => "Gemini 2.5 Pro",
497 ModelId::GPT5 => "GPT-5",
499 ModelId::GPT5Codex => "GPT-5 Codex",
500 ModelId::GPT5Mini => "GPT-5 Mini",
501 ModelId::GPT5Nano => "GPT-5 Nano",
502 ModelId::CodexMiniLatest => "Codex Mini Latest",
503 ModelId::ClaudeOpus41 => "Claude Opus 4.1",
505 ModelId::ClaudeSonnet45 => "Claude Sonnet 4.5",
506 ModelId::ClaudeSonnet4 => "Claude Sonnet 4",
507 ModelId::DeepSeekChat => "DeepSeek V3.2-Exp (Chat)",
509 ModelId::DeepSeekReasoner => "DeepSeek V3.2-Exp (Reasoner)",
510 ModelId::XaiGrok4 => "Grok-4",
512 ModelId::XaiGrok4Mini => "Grok-4 Mini",
513 ModelId::XaiGrok4Code => "Grok-4 Code",
514 ModelId::XaiGrok4CodeLatest => "Grok-4 Code Latest",
515 ModelId::XaiGrok4Vision => "Grok-4 Vision",
516 ModelId::ZaiGlm46 => "GLM 4.6",
518 ModelId::ZaiGlm45 => "GLM 4.5",
519 ModelId::ZaiGlm45Air => "GLM 4.5 Air",
520 ModelId::ZaiGlm45X => "GLM 4.5 X",
521 ModelId::ZaiGlm45Airx => "GLM 4.5 AirX",
522 ModelId::ZaiGlm45Flash => "GLM 4.5 Flash",
523 ModelId::ZaiGlm432b0414128k => "GLM 4 32B 0414 128K",
524 ModelId::MoonshotV18k => "Moonshot v1 8K",
526 ModelId::MoonshotV132k => "Moonshot v1 32K",
527 ModelId::MoonshotV1128k => "Moonshot v1 128K",
528 _ => unreachable!(),
530 }
531 }
532
533 pub fn description(&self) -> &'static str {
535 if let Some(meta) = self.openrouter_metadata() {
536 return meta.description;
537 }
538 match self {
539 ModelId::Gemini25FlashPreview => {
541 "Latest fast Gemini model with advanced multimodal capabilities"
542 }
543 ModelId::Gemini25Flash => {
544 "Legacy alias for Gemini 2.5 Flash Preview (same capabilities)"
545 }
546 ModelId::Gemini25FlashLite => {
547 "Legacy alias for Gemini 2.5 Flash Preview optimized for efficiency"
548 }
549 ModelId::Gemini25Pro => "Latest most capable Gemini model with reasoning",
550 ModelId::GPT5 => "Latest most capable OpenAI model with advanced reasoning",
552 ModelId::GPT5Codex => {
553 "Code-focused GPT-5 variant optimized for tool calling and structured outputs"
554 }
555 ModelId::GPT5Mini => "Latest efficient OpenAI model, great for most tasks",
556 ModelId::GPT5Nano => "Latest most cost-effective OpenAI model",
557 ModelId::CodexMiniLatest => "Latest Codex model optimized for code generation",
558 ModelId::ClaudeOpus41 => "Latest most capable Anthropic model with advanced reasoning",
560 ModelId::ClaudeSonnet45 => "Latest balanced Anthropic model for general tasks",
561 ModelId::ClaudeSonnet4 => {
562 "Previous balanced Anthropic model maintained for compatibility"
563 }
564 ModelId::DeepSeekChat => {
566 "DeepSeek V3.2-Exp non-thinking mode optimized for fast coding responses"
567 }
568 ModelId::DeepSeekReasoner => {
569 "DeepSeek V3.2-Exp thinking mode with structured reasoning output"
570 }
571 ModelId::XaiGrok4 => "Flagship Grok 4 model with long context and tool use",
573 ModelId::XaiGrok4Mini => "Efficient Grok 4 Mini tuned for low latency",
574 ModelId::XaiGrok4Code => "Code-specialized Grok 4 deployment with tool support",
575 ModelId::XaiGrok4CodeLatest => {
576 "Latest Grok 4 code model offering enhanced reasoning traces"
577 }
578 ModelId::XaiGrok4Vision => "Multimodal Grok 4 model with image understanding",
579 ModelId::ZaiGlm46 => {
581 "Latest Z.AI GLM flagship with long-context reasoning and coding strengths"
582 }
583 ModelId::ZaiGlm45 => "Balanced GLM 4.5 release for general assistant tasks",
584 ModelId::ZaiGlm45Air => "Efficient GLM 4.5 Air variant tuned for lower latency",
585 ModelId::ZaiGlm45X => "Enhanced GLM 4.5 X variant with improved reasoning",
586 ModelId::ZaiGlm45Airx => "Hybrid GLM 4.5 AirX variant blending efficiency with quality",
587 ModelId::ZaiGlm45Flash => "Low-latency GLM 4.5 Flash optimized for responsiveness",
588 ModelId::ZaiGlm432b0414128k => {
589 "Legacy GLM 4 32B deployment offering extended 128K context window"
590 }
591 ModelId::MoonshotV18k => {
593 "Fast Moonshot v1 deployment tuned for quick iterations with 8K context"
594 }
595 ModelId::MoonshotV132k => {
596 "Balanced Moonshot v1 configuration blending speed and 32K context support"
597 }
598 ModelId::MoonshotV1128k => {
599 "Flagship Moonshot v1 model delivering maximum 128K context window"
600 }
601 _ => unreachable!(),
602 }
603 }
604
605 pub fn all_models() -> Vec<ModelId> {
607 let mut models = vec![
608 ModelId::Gemini25FlashPreview,
610 ModelId::Gemini25Flash,
611 ModelId::Gemini25FlashLite,
612 ModelId::Gemini25Pro,
613 ModelId::GPT5,
615 ModelId::GPT5Codex,
616 ModelId::GPT5Mini,
617 ModelId::GPT5Nano,
618 ModelId::CodexMiniLatest,
619 ModelId::ClaudeOpus41,
621 ModelId::ClaudeSonnet45,
622 ModelId::ClaudeSonnet4,
623 ModelId::DeepSeekChat,
625 ModelId::DeepSeekReasoner,
626 ModelId::XaiGrok4,
628 ModelId::XaiGrok4Mini,
629 ModelId::XaiGrok4Code,
630 ModelId::XaiGrok4CodeLatest,
631 ModelId::XaiGrok4Vision,
632 ModelId::ZaiGlm46,
634 ModelId::ZaiGlm45,
635 ModelId::ZaiGlm45Air,
636 ModelId::ZaiGlm45X,
637 ModelId::ZaiGlm45Airx,
638 ModelId::ZaiGlm45Flash,
639 ModelId::ZaiGlm432b0414128k,
640 ModelId::MoonshotV18k,
642 ModelId::MoonshotV132k,
643 ModelId::MoonshotV1128k,
644 ];
645 models.extend(Self::openrouter_models());
646 models
647 }
648
649 pub fn models_for_provider(provider: Provider) -> Vec<ModelId> {
651 Self::all_models()
652 .into_iter()
653 .filter(|model| model.provider() == provider)
654 .collect()
655 }
656
657 pub fn fallback_models() -> Vec<ModelId> {
659 vec![
660 ModelId::Gemini25FlashPreview,
661 ModelId::Gemini25Pro,
662 ModelId::GPT5,
663 ModelId::ClaudeOpus41,
664 ModelId::ClaudeSonnet45,
665 ModelId::DeepSeekReasoner,
666 ModelId::MoonshotV132k,
667 ModelId::XaiGrok4,
668 ModelId::ZaiGlm46,
669 ModelId::OpenRouterGrokCodeFast1,
670 ]
671 }
672
673 pub fn default() -> Self {
675 ModelId::Gemini25FlashPreview
676 }
677
678 pub fn default_orchestrator() -> Self {
680 ModelId::Gemini25Pro
681 }
682
683 pub fn default_subagent() -> Self {
685 ModelId::Gemini25FlashPreview
686 }
687
688 pub fn default_orchestrator_for_provider(provider: Provider) -> Self {
690 match provider {
691 Provider::Gemini => ModelId::Gemini25Pro,
692 Provider::OpenAI => ModelId::GPT5,
693 Provider::Anthropic => ModelId::ClaudeOpus41,
694 Provider::DeepSeek => ModelId::DeepSeekReasoner,
695 Provider::Moonshot => ModelId::MoonshotV132k,
696 Provider::XAI => ModelId::XaiGrok4,
697 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
698 Provider::ZAI => ModelId::ZaiGlm46,
699 }
700 }
701
702 pub fn default_subagent_for_provider(provider: Provider) -> Self {
704 match provider {
705 Provider::Gemini => ModelId::Gemini25FlashPreview,
706 Provider::OpenAI => ModelId::GPT5Mini,
707 Provider::Anthropic => ModelId::ClaudeSonnet45,
708 Provider::DeepSeek => ModelId::DeepSeekChat,
709 Provider::Moonshot => ModelId::MoonshotV18k,
710 Provider::XAI => ModelId::XaiGrok4Code,
711 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
712 Provider::ZAI => ModelId::ZaiGlm45Flash,
713 }
714 }
715
716 pub fn default_single_for_provider(provider: Provider) -> Self {
718 match provider {
719 Provider::Gemini => ModelId::Gemini25FlashPreview,
720 Provider::OpenAI => ModelId::GPT5,
721 Provider::Anthropic => ModelId::ClaudeOpus41,
722 Provider::DeepSeek => ModelId::DeepSeekReasoner,
723 Provider::Moonshot => ModelId::MoonshotV132k,
724 Provider::XAI => ModelId::XaiGrok4,
725 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
726 Provider::ZAI => ModelId::ZaiGlm46,
727 }
728 }
729
730 pub fn is_flash_variant(&self) -> bool {
732 matches!(
733 self,
734 ModelId::Gemini25FlashPreview
735 | ModelId::Gemini25Flash
736 | ModelId::Gemini25FlashLite
737 | ModelId::ZaiGlm45Flash
738 | ModelId::MoonshotV18k
739 )
740 }
741
742 pub fn is_pro_variant(&self) -> bool {
744 matches!(
745 self,
746 ModelId::Gemini25Pro
747 | ModelId::GPT5
748 | ModelId::GPT5Codex
749 | ModelId::ClaudeOpus41
750 | ModelId::DeepSeekReasoner
751 | ModelId::XaiGrok4
752 | ModelId::ZaiGlm46
753 | ModelId::MoonshotV1128k
754 )
755 }
756
757 pub fn is_efficient_variant(&self) -> bool {
759 if let Some(meta) = self.openrouter_metadata() {
760 return meta.efficient;
761 }
762 matches!(
763 self,
764 ModelId::Gemini25FlashPreview
765 | ModelId::Gemini25Flash
766 | ModelId::Gemini25FlashLite
767 | ModelId::GPT5Mini
768 | ModelId::GPT5Nano
769 | ModelId::DeepSeekChat
770 | ModelId::XaiGrok4Code
771 | ModelId::ZaiGlm45Air
772 | ModelId::ZaiGlm45Airx
773 | ModelId::ZaiGlm45Flash
774 | ModelId::MoonshotV18k
775 )
776 }
777
778 pub fn is_top_tier(&self) -> bool {
780 if let Some(meta) = self.openrouter_metadata() {
781 return meta.top_tier;
782 }
783 matches!(
784 self,
785 ModelId::Gemini25Pro
786 | ModelId::GPT5
787 | ModelId::GPT5Codex
788 | ModelId::ClaudeOpus41
789 | ModelId::ClaudeSonnet45
790 | ModelId::ClaudeSonnet4
791 | ModelId::DeepSeekReasoner
792 | ModelId::XaiGrok4
793 | ModelId::XaiGrok4CodeLatest
794 | ModelId::ZaiGlm46
795 | ModelId::MoonshotV1128k
796 )
797 }
798
799 pub fn generation(&self) -> &'static str {
801 if let Some(meta) = self.openrouter_metadata() {
802 return meta.generation;
803 }
804 match self {
805 ModelId::Gemini25FlashPreview
807 | ModelId::Gemini25Flash
808 | ModelId::Gemini25FlashLite
809 | ModelId::Gemini25Pro => "2.5",
810 ModelId::GPT5
812 | ModelId::GPT5Codex
813 | ModelId::GPT5Mini
814 | ModelId::GPT5Nano
815 | ModelId::CodexMiniLatest => "5",
816 ModelId::ClaudeSonnet45 => "4.5",
818 ModelId::ClaudeSonnet4 => "4",
819 ModelId::ClaudeOpus41 => "4.1",
820 ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
822 ModelId::XaiGrok4
824 | ModelId::XaiGrok4Mini
825 | ModelId::XaiGrok4Code
826 | ModelId::XaiGrok4CodeLatest
827 | ModelId::XaiGrok4Vision => "4",
828 ModelId::ZaiGlm46 => "4.6",
830 ModelId::ZaiGlm45
831 | ModelId::ZaiGlm45Air
832 | ModelId::ZaiGlm45X
833 | ModelId::ZaiGlm45Airx
834 | ModelId::ZaiGlm45Flash => "4.5",
835 ModelId::ZaiGlm432b0414128k => "4-32B",
836 ModelId::MoonshotV18k | ModelId::MoonshotV132k | ModelId::MoonshotV1128k => "v1",
838 _ => unreachable!(),
839 }
840 }
841}
842
843impl fmt::Display for ModelId {
844 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
845 write!(f, "{}", self.as_str())
846 }
847}
848
849impl FromStr for ModelId {
850 type Err = ModelParseError;
851
852 fn from_str(s: &str) -> Result<Self, Self::Err> {
853 use crate::config::constants::models;
854 match s {
855 s if s == models::GEMINI_2_5_FLASH_PREVIEW => Ok(ModelId::Gemini25FlashPreview),
857 s if s == models::GEMINI_2_5_FLASH => Ok(ModelId::Gemini25Flash),
858 s if s == models::GEMINI_2_5_FLASH_LITE => Ok(ModelId::Gemini25FlashLite),
859 s if s == models::GEMINI_2_5_PRO => Ok(ModelId::Gemini25Pro),
860 s if s == models::GPT_5 => Ok(ModelId::GPT5),
862 s if s == models::GPT_5_CODEX => Ok(ModelId::GPT5Codex),
863 s if s == models::GPT_5_MINI => Ok(ModelId::GPT5Mini),
864 s if s == models::GPT_5_NANO => Ok(ModelId::GPT5Nano),
865 s if s == models::CODEX_MINI_LATEST => Ok(ModelId::CodexMiniLatest),
866 s if s == models::CLAUDE_OPUS_4_1_20250805 => Ok(ModelId::ClaudeOpus41),
868 s if s == models::CLAUDE_SONNET_4_5 => Ok(ModelId::ClaudeSonnet45),
869 s if s == models::CLAUDE_SONNET_4_20250514 => Ok(ModelId::ClaudeSonnet4),
870 s if s == models::DEEPSEEK_CHAT => Ok(ModelId::DeepSeekChat),
872 s if s == models::DEEPSEEK_REASONER => Ok(ModelId::DeepSeekReasoner),
873 s if s == models::xai::GROK_4 => Ok(ModelId::XaiGrok4),
875 s if s == models::xai::GROK_4_MINI => Ok(ModelId::XaiGrok4Mini),
876 s if s == models::xai::GROK_4_CODE => Ok(ModelId::XaiGrok4Code),
877 s if s == models::xai::GROK_4_CODE_LATEST => Ok(ModelId::XaiGrok4CodeLatest),
878 s if s == models::xai::GROK_4_VISION => Ok(ModelId::XaiGrok4Vision),
879 s if s == models::zai::GLM_4_6 => Ok(ModelId::ZaiGlm46),
881 s if s == models::zai::GLM_4_5 => Ok(ModelId::ZaiGlm45),
882 s if s == models::zai::GLM_4_5_AIR => Ok(ModelId::ZaiGlm45Air),
883 s if s == models::zai::GLM_4_5_X => Ok(ModelId::ZaiGlm45X),
884 s if s == models::zai::GLM_4_5_AIRX => Ok(ModelId::ZaiGlm45Airx),
885 s if s == models::zai::GLM_4_5_FLASH => Ok(ModelId::ZaiGlm45Flash),
886 s if s == models::zai::GLM_4_32B_0414_128K => Ok(ModelId::ZaiGlm432b0414128k),
887 s if s == models::MOONSHOT_V1_8K => Ok(ModelId::MoonshotV18k),
889 s if s == models::MOONSHOT_V1_32K => Ok(ModelId::MoonshotV132k),
890 s if s == models::MOONSHOT_V1_128K => Ok(ModelId::MoonshotV1128k),
891 _ => {
892 if let Some(model) = Self::parse_openrouter_model(s) {
893 Ok(model)
894 } else {
895 Err(ModelParseError::InvalidModel(s.to_string()))
896 }
897 }
898 }
899 }
900}
901
902#[derive(Debug, Clone, PartialEq)]
904pub enum ModelParseError {
905 InvalidModel(String),
906 InvalidProvider(String),
907}
908
909impl fmt::Display for ModelParseError {
910 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911 match self {
912 ModelParseError::InvalidModel(model) => {
913 write!(
914 f,
915 "Invalid model identifier: '{}'. Supported models: {}",
916 model,
917 ModelId::all_models()
918 .iter()
919 .map(|m| m.as_str())
920 .collect::<Vec<_>>()
921 .join(", ")
922 )
923 }
924 ModelParseError::InvalidProvider(provider) => {
925 write!(
926 f,
927 "Invalid provider: '{}'. Supported providers: {}",
928 provider,
929 Provider::all_providers()
930 .iter()
931 .map(|p| p.to_string())
932 .collect::<Vec<_>>()
933 .join(", ")
934 )
935 }
936 }
937 }
938}
939
940impl std::error::Error for ModelParseError {}
941
942#[cfg(test)]
943mod tests {
944 use super::*;
945 use crate::config::constants::models;
946
947 #[test]
948 fn test_model_string_conversion() {
949 assert_eq!(
951 ModelId::Gemini25FlashPreview.as_str(),
952 models::GEMINI_2_5_FLASH_PREVIEW
953 );
954 assert_eq!(ModelId::Gemini25Flash.as_str(), models::GEMINI_2_5_FLASH);
955 assert_eq!(
956 ModelId::Gemini25FlashLite.as_str(),
957 models::GEMINI_2_5_FLASH_LITE
958 );
959 assert_eq!(ModelId::Gemini25Pro.as_str(), models::GEMINI_2_5_PRO);
960 assert_eq!(ModelId::GPT5.as_str(), models::GPT_5);
962 assert_eq!(ModelId::GPT5Codex.as_str(), models::GPT_5_CODEX);
963 assert_eq!(ModelId::GPT5Mini.as_str(), models::GPT_5_MINI);
964 assert_eq!(ModelId::GPT5Nano.as_str(), models::GPT_5_NANO);
965 assert_eq!(ModelId::CodexMiniLatest.as_str(), models::CODEX_MINI_LATEST);
966 assert_eq!(ModelId::ClaudeSonnet45.as_str(), models::CLAUDE_SONNET_4_5);
968 assert_eq!(
969 ModelId::ClaudeSonnet4.as_str(),
970 models::CLAUDE_SONNET_4_20250514
971 );
972 assert_eq!(
973 ModelId::ClaudeOpus41.as_str(),
974 models::CLAUDE_OPUS_4_1_20250805
975 );
976 assert_eq!(ModelId::DeepSeekChat.as_str(), models::DEEPSEEK_CHAT);
978 assert_eq!(
979 ModelId::DeepSeekReasoner.as_str(),
980 models::DEEPSEEK_REASONER
981 );
982 assert_eq!(ModelId::XaiGrok4.as_str(), models::xai::GROK_4);
984 assert_eq!(ModelId::XaiGrok4Mini.as_str(), models::xai::GROK_4_MINI);
985 assert_eq!(ModelId::XaiGrok4Code.as_str(), models::xai::GROK_4_CODE);
986 assert_eq!(
987 ModelId::XaiGrok4CodeLatest.as_str(),
988 models::xai::GROK_4_CODE_LATEST
989 );
990 assert_eq!(ModelId::XaiGrok4Vision.as_str(), models::xai::GROK_4_VISION);
991 assert_eq!(ModelId::ZaiGlm46.as_str(), models::zai::GLM_4_6);
993 assert_eq!(ModelId::ZaiGlm45.as_str(), models::zai::GLM_4_5);
994 assert_eq!(ModelId::ZaiGlm45Air.as_str(), models::zai::GLM_4_5_AIR);
995 assert_eq!(ModelId::ZaiGlm45X.as_str(), models::zai::GLM_4_5_X);
996 assert_eq!(ModelId::ZaiGlm45Airx.as_str(), models::zai::GLM_4_5_AIRX);
997 assert_eq!(ModelId::ZaiGlm45Flash.as_str(), models::zai::GLM_4_5_FLASH);
998 assert_eq!(
999 ModelId::ZaiGlm432b0414128k.as_str(),
1000 models::zai::GLM_4_32B_0414_128K
1001 );
1002 macro_rules! assert_openrouter_to_string {
1003 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1004 $(assert_eq!(ModelId::$variant.as_str(), models::$const);)*
1005 };
1006 }
1007 each_openrouter_variant!(assert_openrouter_to_string);
1008 }
1009
1010 #[test]
1011 fn test_model_from_string() {
1012 assert_eq!(
1014 models::GEMINI_2_5_FLASH_PREVIEW.parse::<ModelId>().unwrap(),
1015 ModelId::Gemini25FlashPreview
1016 );
1017 assert_eq!(
1018 models::GEMINI_2_5_FLASH.parse::<ModelId>().unwrap(),
1019 ModelId::Gemini25Flash
1020 );
1021 assert_eq!(
1022 models::GEMINI_2_5_FLASH_LITE.parse::<ModelId>().unwrap(),
1023 ModelId::Gemini25FlashLite
1024 );
1025 assert_eq!(
1026 models::GEMINI_2_5_PRO.parse::<ModelId>().unwrap(),
1027 ModelId::Gemini25Pro
1028 );
1029 assert_eq!(models::GPT_5.parse::<ModelId>().unwrap(), ModelId::GPT5);
1031 assert_eq!(
1032 models::GPT_5_CODEX.parse::<ModelId>().unwrap(),
1033 ModelId::GPT5Codex
1034 );
1035 assert_eq!(
1036 models::GPT_5_MINI.parse::<ModelId>().unwrap(),
1037 ModelId::GPT5Mini
1038 );
1039 assert_eq!(
1040 models::GPT_5_NANO.parse::<ModelId>().unwrap(),
1041 ModelId::GPT5Nano
1042 );
1043 assert_eq!(
1044 models::CODEX_MINI_LATEST.parse::<ModelId>().unwrap(),
1045 ModelId::CodexMiniLatest
1046 );
1047 assert_eq!(
1049 models::CLAUDE_SONNET_4_5.parse::<ModelId>().unwrap(),
1050 ModelId::ClaudeSonnet45
1051 );
1052 assert_eq!(
1053 models::CLAUDE_SONNET_4_20250514.parse::<ModelId>().unwrap(),
1054 ModelId::ClaudeSonnet4
1055 );
1056 assert_eq!(
1057 models::CLAUDE_OPUS_4_1_20250805.parse::<ModelId>().unwrap(),
1058 ModelId::ClaudeOpus41
1059 );
1060 assert_eq!(
1062 models::DEEPSEEK_CHAT.parse::<ModelId>().unwrap(),
1063 ModelId::DeepSeekChat
1064 );
1065 assert_eq!(
1066 models::DEEPSEEK_REASONER.parse::<ModelId>().unwrap(),
1067 ModelId::DeepSeekReasoner
1068 );
1069 assert_eq!(
1071 models::xai::GROK_4.parse::<ModelId>().unwrap(),
1072 ModelId::XaiGrok4
1073 );
1074 assert_eq!(
1075 models::xai::GROK_4_MINI.parse::<ModelId>().unwrap(),
1076 ModelId::XaiGrok4Mini
1077 );
1078 assert_eq!(
1079 models::xai::GROK_4_CODE.parse::<ModelId>().unwrap(),
1080 ModelId::XaiGrok4Code
1081 );
1082 assert_eq!(
1083 models::xai::GROK_4_CODE_LATEST.parse::<ModelId>().unwrap(),
1084 ModelId::XaiGrok4CodeLatest
1085 );
1086 assert_eq!(
1087 models::xai::GROK_4_VISION.parse::<ModelId>().unwrap(),
1088 ModelId::XaiGrok4Vision
1089 );
1090 assert_eq!(
1092 models::zai::GLM_4_6.parse::<ModelId>().unwrap(),
1093 ModelId::ZaiGlm46
1094 );
1095 assert_eq!(
1096 models::zai::GLM_4_5.parse::<ModelId>().unwrap(),
1097 ModelId::ZaiGlm45
1098 );
1099 assert_eq!(
1100 models::zai::GLM_4_5_AIR.parse::<ModelId>().unwrap(),
1101 ModelId::ZaiGlm45Air
1102 );
1103 assert_eq!(
1104 models::zai::GLM_4_5_X.parse::<ModelId>().unwrap(),
1105 ModelId::ZaiGlm45X
1106 );
1107 assert_eq!(
1108 models::zai::GLM_4_5_AIRX.parse::<ModelId>().unwrap(),
1109 ModelId::ZaiGlm45Airx
1110 );
1111 assert_eq!(
1112 models::zai::GLM_4_5_FLASH.parse::<ModelId>().unwrap(),
1113 ModelId::ZaiGlm45Flash
1114 );
1115 assert_eq!(
1116 models::zai::GLM_4_32B_0414_128K.parse::<ModelId>().unwrap(),
1117 ModelId::ZaiGlm432b0414128k
1118 );
1119 assert_eq!(
1120 models::MOONSHOT_V1_8K.parse::<ModelId>().unwrap(),
1121 ModelId::MoonshotV18k
1122 );
1123 assert_eq!(
1124 models::MOONSHOT_V1_32K.parse::<ModelId>().unwrap(),
1125 ModelId::MoonshotV132k
1126 );
1127 assert_eq!(
1128 models::MOONSHOT_V1_128K.parse::<ModelId>().unwrap(),
1129 ModelId::MoonshotV1128k
1130 );
1131 macro_rules! assert_openrouter_parse {
1132 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1133 $(assert_eq!(models::$const.parse::<ModelId>().unwrap(), ModelId::$variant);)*
1134 };
1135 }
1136 each_openrouter_variant!(assert_openrouter_parse);
1137 assert!("invalid-model".parse::<ModelId>().is_err());
1139 }
1140
1141 #[test]
1142 fn test_provider_parsing() {
1143 assert_eq!("gemini".parse::<Provider>().unwrap(), Provider::Gemini);
1144 assert_eq!("openai".parse::<Provider>().unwrap(), Provider::OpenAI);
1145 assert_eq!(
1146 "anthropic".parse::<Provider>().unwrap(),
1147 Provider::Anthropic
1148 );
1149 assert_eq!("deepseek".parse::<Provider>().unwrap(), Provider::DeepSeek);
1150 assert_eq!(
1151 "openrouter".parse::<Provider>().unwrap(),
1152 Provider::OpenRouter
1153 );
1154 assert_eq!("xai".parse::<Provider>().unwrap(), Provider::XAI);
1155 assert_eq!("zai".parse::<Provider>().unwrap(), Provider::ZAI);
1156 assert_eq!("moonshot".parse::<Provider>().unwrap(), Provider::Moonshot);
1157 assert!("invalid-provider".parse::<Provider>().is_err());
1158 }
1159
1160 #[test]
1161 fn test_model_providers() {
1162 assert_eq!(ModelId::Gemini25FlashPreview.provider(), Provider::Gemini);
1163 assert_eq!(ModelId::GPT5.provider(), Provider::OpenAI);
1164 assert_eq!(ModelId::GPT5Codex.provider(), Provider::OpenAI);
1165 assert_eq!(ModelId::ClaudeSonnet45.provider(), Provider::Anthropic);
1166 assert_eq!(ModelId::ClaudeSonnet4.provider(), Provider::Anthropic);
1167 assert_eq!(ModelId::DeepSeekChat.provider(), Provider::DeepSeek);
1168 assert_eq!(ModelId::XaiGrok4.provider(), Provider::XAI);
1169 assert_eq!(ModelId::ZaiGlm46.provider(), Provider::ZAI);
1170 assert_eq!(ModelId::MoonshotV132k.provider(), Provider::Moonshot);
1171 assert_eq!(
1172 ModelId::OpenRouterGrokCodeFast1.provider(),
1173 Provider::OpenRouter
1174 );
1175 assert_eq!(
1176 ModelId::OpenRouterAnthropicClaudeSonnet45.provider(),
1177 Provider::OpenRouter
1178 );
1179
1180 macro_rules! assert_openrouter_provider_all {
1181 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1182 $(assert_eq!(ModelId::$variant.provider(), Provider::OpenRouter);)*
1183 };
1184 }
1185 each_openrouter_variant!(assert_openrouter_provider_all);
1186 }
1187
1188 #[test]
1189 fn test_provider_defaults() {
1190 assert_eq!(
1191 ModelId::default_orchestrator_for_provider(Provider::Gemini),
1192 ModelId::Gemini25Pro
1193 );
1194 assert_eq!(
1195 ModelId::default_orchestrator_for_provider(Provider::OpenAI),
1196 ModelId::GPT5
1197 );
1198 assert_eq!(
1199 ModelId::default_orchestrator_for_provider(Provider::Anthropic),
1200 ModelId::ClaudeSonnet4
1201 );
1202 assert_eq!(
1203 ModelId::default_orchestrator_for_provider(Provider::DeepSeek),
1204 ModelId::DeepSeekReasoner
1205 );
1206 assert_eq!(
1207 ModelId::default_orchestrator_for_provider(Provider::OpenRouter),
1208 ModelId::OpenRouterGrokCodeFast1
1209 );
1210 assert_eq!(
1211 ModelId::default_orchestrator_for_provider(Provider::XAI),
1212 ModelId::XaiGrok4
1213 );
1214 assert_eq!(
1215 ModelId::default_orchestrator_for_provider(Provider::ZAI),
1216 ModelId::ZaiGlm46
1217 );
1218 assert_eq!(
1219 ModelId::default_orchestrator_for_provider(Provider::Moonshot),
1220 ModelId::MoonshotV132k
1221 );
1222
1223 assert_eq!(
1224 ModelId::default_subagent_for_provider(Provider::Gemini),
1225 ModelId::Gemini25FlashPreview
1226 );
1227 assert_eq!(
1228 ModelId::default_subagent_for_provider(Provider::OpenAI),
1229 ModelId::GPT5Mini
1230 );
1231 assert_eq!(
1232 ModelId::default_subagent_for_provider(Provider::Anthropic),
1233 ModelId::ClaudeSonnet45
1234 );
1235 assert_eq!(
1236 ModelId::default_subagent_for_provider(Provider::DeepSeek),
1237 ModelId::DeepSeekChat
1238 );
1239 assert_eq!(
1240 ModelId::default_subagent_for_provider(Provider::OpenRouter),
1241 ModelId::OpenRouterGrokCodeFast1
1242 );
1243 assert_eq!(
1244 ModelId::default_subagent_for_provider(Provider::XAI),
1245 ModelId::XaiGrok4Code
1246 );
1247 assert_eq!(
1248 ModelId::default_subagent_for_provider(Provider::ZAI),
1249 ModelId::ZaiGlm45Flash
1250 );
1251 assert_eq!(
1252 ModelId::default_subagent_for_provider(Provider::Moonshot),
1253 ModelId::MoonshotV18k
1254 );
1255
1256 assert_eq!(
1257 ModelId::default_single_for_provider(Provider::DeepSeek),
1258 ModelId::DeepSeekReasoner
1259 );
1260 assert_eq!(
1261 ModelId::default_single_for_provider(Provider::Moonshot),
1262 ModelId::MoonshotV132k
1263 );
1264 }
1265
1266 #[test]
1267 fn test_model_defaults() {
1268 assert_eq!(ModelId::default(), ModelId::Gemini25FlashPreview);
1269 assert_eq!(ModelId::default_orchestrator(), ModelId::Gemini25Pro);
1270 assert_eq!(ModelId::default_subagent(), ModelId::Gemini25FlashPreview);
1271 }
1272
1273 #[test]
1274 fn test_model_variants() {
1275 assert!(ModelId::Gemini25FlashPreview.is_flash_variant());
1277 assert!(ModelId::Gemini25Flash.is_flash_variant());
1278 assert!(ModelId::Gemini25FlashLite.is_flash_variant());
1279 assert!(!ModelId::GPT5.is_flash_variant());
1280 assert!(ModelId::ZaiGlm45Flash.is_flash_variant());
1281 assert!(ModelId::MoonshotV18k.is_flash_variant());
1282
1283 assert!(ModelId::Gemini25Pro.is_pro_variant());
1285 assert!(ModelId::GPT5.is_pro_variant());
1286 assert!(ModelId::GPT5Codex.is_pro_variant());
1287 assert!(ModelId::DeepSeekReasoner.is_pro_variant());
1288 assert!(ModelId::ZaiGlm46.is_pro_variant());
1289 assert!(ModelId::MoonshotV1128k.is_pro_variant());
1290 assert!(!ModelId::Gemini25FlashPreview.is_pro_variant());
1291
1292 assert!(ModelId::Gemini25FlashPreview.is_efficient_variant());
1294 assert!(ModelId::Gemini25Flash.is_efficient_variant());
1295 assert!(ModelId::Gemini25FlashLite.is_efficient_variant());
1296 assert!(ModelId::GPT5Mini.is_efficient_variant());
1297 assert!(ModelId::XaiGrok4Code.is_efficient_variant());
1298 assert!(ModelId::DeepSeekChat.is_efficient_variant());
1299 assert!(ModelId::ZaiGlm45Air.is_efficient_variant());
1300 assert!(ModelId::ZaiGlm45Airx.is_efficient_variant());
1301 assert!(ModelId::ZaiGlm45Flash.is_efficient_variant());
1302 assert!(ModelId::MoonshotV18k.is_efficient_variant());
1303 assert!(!ModelId::GPT5.is_efficient_variant());
1304
1305 macro_rules! assert_openrouter_efficiency {
1306 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1307 $(assert_eq!(ModelId::$variant.is_efficient_variant(), $efficient);)*
1308 };
1309 }
1310 each_openrouter_variant!(assert_openrouter_efficiency);
1311
1312 assert!(ModelId::Gemini25Pro.is_top_tier());
1314 assert!(ModelId::GPT5.is_top_tier());
1315 assert!(ModelId::GPT5Codex.is_top_tier());
1316 assert!(ModelId::ClaudeSonnet45.is_top_tier());
1317 assert!(ModelId::ClaudeSonnet4.is_top_tier());
1318 assert!(ModelId::XaiGrok4.is_top_tier());
1319 assert!(ModelId::XaiGrok4CodeLatest.is_top_tier());
1320 assert!(ModelId::DeepSeekReasoner.is_top_tier());
1321 assert!(ModelId::ZaiGlm46.is_top_tier());
1322 assert!(ModelId::MoonshotV1128k.is_top_tier());
1323 assert!(!ModelId::Gemini25FlashPreview.is_top_tier());
1324
1325 macro_rules! assert_openrouter_top_tier {
1326 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1327 $(assert_eq!(ModelId::$variant.is_top_tier(), $top);)*
1328 };
1329 }
1330 each_openrouter_variant!(assert_openrouter_top_tier);
1331 }
1332
1333 #[test]
1334 fn test_model_generation() {
1335 assert_eq!(ModelId::Gemini25FlashPreview.generation(), "2.5");
1337 assert_eq!(ModelId::Gemini25Flash.generation(), "2.5");
1338 assert_eq!(ModelId::Gemini25FlashLite.generation(), "2.5");
1339 assert_eq!(ModelId::Gemini25Pro.generation(), "2.5");
1340
1341 assert_eq!(ModelId::GPT5.generation(), "5");
1343 assert_eq!(ModelId::GPT5Codex.generation(), "5");
1344 assert_eq!(ModelId::GPT5Mini.generation(), "5");
1345 assert_eq!(ModelId::GPT5Nano.generation(), "5");
1346 assert_eq!(ModelId::CodexMiniLatest.generation(), "5");
1347
1348 assert_eq!(ModelId::ClaudeSonnet45.generation(), "4.5");
1350 assert_eq!(ModelId::ClaudeSonnet4.generation(), "4");
1351 assert_eq!(ModelId::ClaudeOpus41.generation(), "4.1");
1352
1353 assert_eq!(ModelId::DeepSeekChat.generation(), "V3.2-Exp");
1355 assert_eq!(ModelId::DeepSeekReasoner.generation(), "V3.2-Exp");
1356
1357 assert_eq!(ModelId::XaiGrok4.generation(), "4");
1359 assert_eq!(ModelId::XaiGrok4Mini.generation(), "4");
1360 assert_eq!(ModelId::XaiGrok4Code.generation(), "4");
1361 assert_eq!(ModelId::XaiGrok4CodeLatest.generation(), "4");
1362 assert_eq!(ModelId::XaiGrok4Vision.generation(), "4");
1363 assert_eq!(ModelId::ZaiGlm46.generation(), "4.6");
1365 assert_eq!(ModelId::ZaiGlm45.generation(), "4.5");
1366 assert_eq!(ModelId::ZaiGlm45Air.generation(), "4.5");
1367 assert_eq!(ModelId::ZaiGlm45X.generation(), "4.5");
1368 assert_eq!(ModelId::ZaiGlm45Airx.generation(), "4.5");
1369 assert_eq!(ModelId::ZaiGlm45Flash.generation(), "4.5");
1370 assert_eq!(ModelId::ZaiGlm432b0414128k.generation(), "4-32B");
1371 assert_eq!(ModelId::MoonshotV18k.generation(), "v1");
1372 assert_eq!(ModelId::MoonshotV132k.generation(), "v1");
1373 assert_eq!(ModelId::MoonshotV1128k.generation(), "v1");
1374
1375 macro_rules! assert_openrouter_generation {
1376 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1377 $(assert_eq!(ModelId::$variant.generation(), $generation);)*
1378 };
1379 }
1380 each_openrouter_variant!(assert_openrouter_generation);
1381 }
1382
1383 #[test]
1384 fn test_models_for_provider() {
1385 let gemini_models = ModelId::models_for_provider(Provider::Gemini);
1386 assert!(gemini_models.contains(&ModelId::Gemini25Pro));
1387 assert!(!gemini_models.contains(&ModelId::GPT5));
1388
1389 let openai_models = ModelId::models_for_provider(Provider::OpenAI);
1390 assert!(openai_models.contains(&ModelId::GPT5));
1391 assert!(openai_models.contains(&ModelId::GPT5Codex));
1392 assert!(!openai_models.contains(&ModelId::Gemini25Pro));
1393
1394 let anthropic_models = ModelId::models_for_provider(Provider::Anthropic);
1395 assert!(anthropic_models.contains(&ModelId::ClaudeSonnet45));
1396 assert!(anthropic_models.contains(&ModelId::ClaudeSonnet4));
1397 assert!(!anthropic_models.contains(&ModelId::GPT5));
1398
1399 let deepseek_models = ModelId::models_for_provider(Provider::DeepSeek);
1400 assert!(deepseek_models.contains(&ModelId::DeepSeekChat));
1401 assert!(deepseek_models.contains(&ModelId::DeepSeekReasoner));
1402
1403 let openrouter_models = ModelId::models_for_provider(Provider::OpenRouter);
1404 macro_rules! assert_openrouter_models_present {
1405 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1406 $(assert!(openrouter_models.contains(&ModelId::$variant));)*
1407 };
1408 }
1409 each_openrouter_variant!(assert_openrouter_models_present);
1410
1411 let xai_models = ModelId::models_for_provider(Provider::XAI);
1412 assert!(xai_models.contains(&ModelId::XaiGrok4));
1413 assert!(xai_models.contains(&ModelId::XaiGrok4Mini));
1414 assert!(xai_models.contains(&ModelId::XaiGrok4Code));
1415 assert!(xai_models.contains(&ModelId::XaiGrok4CodeLatest));
1416 assert!(xai_models.contains(&ModelId::XaiGrok4Vision));
1417
1418 let zai_models = ModelId::models_for_provider(Provider::ZAI);
1419 assert!(zai_models.contains(&ModelId::ZaiGlm46));
1420 assert!(zai_models.contains(&ModelId::ZaiGlm45));
1421 assert!(zai_models.contains(&ModelId::ZaiGlm45Air));
1422 assert!(zai_models.contains(&ModelId::ZaiGlm45X));
1423 assert!(zai_models.contains(&ModelId::ZaiGlm45Airx));
1424 assert!(zai_models.contains(&ModelId::ZaiGlm45Flash));
1425 assert!(zai_models.contains(&ModelId::ZaiGlm432b0414128k));
1426
1427 let moonshot_models = ModelId::models_for_provider(Provider::Moonshot);
1428 assert!(moonshot_models.contains(&ModelId::MoonshotV18k));
1429 assert!(moonshot_models.contains(&ModelId::MoonshotV132k));
1430 assert!(moonshot_models.contains(&ModelId::MoonshotV1128k));
1431 assert_eq!(moonshot_models.len(), 3);
1432 }
1433
1434 #[test]
1435 fn test_fallback_models() {
1436 let fallbacks = ModelId::fallback_models();
1437 assert!(!fallbacks.is_empty());
1438 assert!(fallbacks.contains(&ModelId::Gemini25Pro));
1439 assert!(fallbacks.contains(&ModelId::GPT5));
1440 assert!(fallbacks.contains(&ModelId::ClaudeOpus41));
1441 assert!(fallbacks.contains(&ModelId::ClaudeSonnet45));
1442 assert!(fallbacks.contains(&ModelId::DeepSeekReasoner));
1443 assert!(fallbacks.contains(&ModelId::MoonshotV132k));
1444 assert!(fallbacks.contains(&ModelId::XaiGrok4));
1445 assert!(fallbacks.contains(&ModelId::ZaiGlm46));
1446 assert!(fallbacks.contains(&ModelId::OpenRouterGrokCodeFast1));
1447 }
1448}