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::OpenAI,
111 Provider::Anthropic,
112 Provider::Gemini,
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 MoonshotKimiK2TurboPreview,
255 MoonshotKimiK20905Preview,
257 MoonshotKimiK20711Preview,
259 MoonshotKimiLatest,
261 MoonshotKimiLatest8k,
263 MoonshotKimiLatest32k,
265 MoonshotKimiLatest128k,
267
268 OpenRouterGrokCodeFast1,
271 OpenRouterGrok4Fast,
273 OpenRouterGrok4,
275 OpenRouterZaiGlm45AirFree,
277 OpenRouterZaiGlm46,
279 OpenRouterMoonshotaiKimiK20905,
281 OpenRouterQwen3Max,
283 OpenRouterQwen3235bA22b,
285 OpenRouterQwen3235bA22bFree,
287 OpenRouterQwen3235bA22b2507,
289 OpenRouterQwen3235bA22bThinking2507,
291 OpenRouterQwen332b,
293 OpenRouterQwen330bA3b,
295 OpenRouterQwen330bA3bFree,
297 OpenRouterQwen330bA3bInstruct2507,
299 OpenRouterQwen330bA3bThinking2507,
301 OpenRouterQwen314b,
303 OpenRouterQwen314bFree,
305 OpenRouterQwen38b,
307 OpenRouterQwen38bFree,
309 OpenRouterQwen34bFree,
311 OpenRouterQwen3Next80bA3bInstruct,
313 OpenRouterQwen3Next80bA3bThinking,
315 OpenRouterQwen3Coder,
317 OpenRouterQwen3CoderFree,
319 OpenRouterQwen3CoderPlus,
321 OpenRouterQwen3CoderFlash,
323 OpenRouterQwen3Coder30bA3bInstruct,
325 OpenRouterDeepSeekV32Exp,
327 OpenRouterDeepSeekChatV31,
329 OpenRouterDeepSeekR1,
331 OpenRouterOpenAIGptOss120b,
333 OpenRouterOpenAIGptOss20b,
335 OpenRouterOpenAIGptOss20bFree,
337 OpenRouterOpenAIGpt5,
339 OpenRouterOpenAIGpt5Codex,
341 OpenRouterOpenAIGpt5Chat,
343 OpenRouterOpenAIGpt4oSearchPreview,
345 OpenRouterOpenAIGpt4oMiniSearchPreview,
347 OpenRouterOpenAIChatgpt4oLatest,
349 OpenRouterAnthropicClaudeSonnet45,
351 OpenRouterAnthropicClaudeOpus41,
353}
354impl ModelId {
355 fn openrouter_metadata(&self) -> Option<OpenRouterMetadata> {
356 use crate::config::constants::models;
357
358 macro_rules! metadata_match {
359 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
360 match self {
361 $(ModelId::$variant => Some(OpenRouterMetadata {
362 id: models::$const,
363 display: $display,
364 description: $description,
365 efficient: $efficient,
366 top_tier: $top,
367 generation: $generation,
368 }),)*
369 _ => None,
370 }
371 };
372 }
373
374 each_openrouter_variant!(metadata_match)
375 }
376
377 fn parse_openrouter_model(value: &str) -> Option<Self> {
378 use crate::config::constants::models;
379
380 macro_rules! parse_match {
381 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
382 match value {
383 $(models::$const => Some(ModelId::$variant),)*
384 _ => None,
385 }
386 };
387 }
388
389 each_openrouter_variant!(parse_match)
390 }
391
392 fn openrouter_models() -> Vec<Self> {
393 macro_rules! to_vec {
394 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
395 vec![$(ModelId::$variant,)*]
396 };
397 }
398
399 each_openrouter_variant!(to_vec)
400 }
401
402 pub fn as_str(&self) -> &'static str {
405 use crate::config::constants::models;
406 if let Some(meta) = self.openrouter_metadata() {
407 return meta.id;
408 }
409 match self {
410 ModelId::Gemini25FlashPreview => models::GEMINI_2_5_FLASH_PREVIEW,
412 ModelId::Gemini25Flash => models::GEMINI_2_5_FLASH,
413 ModelId::Gemini25FlashLite => models::GEMINI_2_5_FLASH_LITE,
414 ModelId::Gemini25Pro => models::GEMINI_2_5_PRO,
415 ModelId::GPT5 => models::GPT_5,
417 ModelId::GPT5Codex => models::GPT_5_CODEX,
418 ModelId::GPT5Mini => models::GPT_5_MINI,
419 ModelId::GPT5Nano => models::GPT_5_NANO,
420 ModelId::CodexMiniLatest => models::CODEX_MINI_LATEST,
421 ModelId::ClaudeOpus41 => models::CLAUDE_OPUS_4_1_20250805,
423 ModelId::ClaudeSonnet45 => models::CLAUDE_SONNET_4_5,
424 ModelId::ClaudeSonnet4 => models::CLAUDE_SONNET_4_20250514,
425 ModelId::DeepSeekChat => models::DEEPSEEK_CHAT,
427 ModelId::DeepSeekReasoner => models::DEEPSEEK_REASONER,
428 ModelId::XaiGrok4 => models::xai::GROK_4,
430 ModelId::XaiGrok4Mini => models::xai::GROK_4_MINI,
431 ModelId::XaiGrok4Code => models::xai::GROK_4_CODE,
432 ModelId::XaiGrok4CodeLatest => models::xai::GROK_4_CODE_LATEST,
433 ModelId::XaiGrok4Vision => models::xai::GROK_4_VISION,
434 ModelId::ZaiGlm46 => models::zai::GLM_4_6,
436 ModelId::ZaiGlm45 => models::zai::GLM_4_5,
437 ModelId::ZaiGlm45Air => models::zai::GLM_4_5_AIR,
438 ModelId::ZaiGlm45X => models::zai::GLM_4_5_X,
439 ModelId::ZaiGlm45Airx => models::zai::GLM_4_5_AIRX,
440 ModelId::ZaiGlm45Flash => models::zai::GLM_4_5_FLASH,
441 ModelId::ZaiGlm432b0414128k => models::zai::GLM_4_32B_0414_128K,
442 ModelId::MoonshotKimiK2TurboPreview => models::MOONSHOT_KIMI_K2_TURBO_PREVIEW,
444 ModelId::MoonshotKimiK20905Preview => models::MOONSHOT_KIMI_K2_0905_PREVIEW,
445 ModelId::MoonshotKimiK20711Preview => models::MOONSHOT_KIMI_K2_0711_PREVIEW,
446 ModelId::MoonshotKimiLatest => models::MOONSHOT_KIMI_LATEST,
447 ModelId::MoonshotKimiLatest8k => models::MOONSHOT_KIMI_LATEST_8K,
448 ModelId::MoonshotKimiLatest32k => models::MOONSHOT_KIMI_LATEST_32K,
449 ModelId::MoonshotKimiLatest128k => models::MOONSHOT_KIMI_LATEST_128K,
450 _ => unreachable!(),
452 }
453 }
454
455 pub fn provider(&self) -> Provider {
457 if self.openrouter_metadata().is_some() {
458 return Provider::OpenRouter;
459 }
460 match self {
461 ModelId::Gemini25FlashPreview
462 | ModelId::Gemini25Flash
463 | ModelId::Gemini25FlashLite
464 | ModelId::Gemini25Pro => Provider::Gemini,
465 ModelId::GPT5
466 | ModelId::GPT5Codex
467 | ModelId::GPT5Mini
468 | ModelId::GPT5Nano
469 | ModelId::CodexMiniLatest => Provider::OpenAI,
470 ModelId::ClaudeOpus41 | ModelId::ClaudeSonnet45 | ModelId::ClaudeSonnet4 => {
471 Provider::Anthropic
472 }
473 ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => Provider::DeepSeek,
474 ModelId::XaiGrok4
475 | ModelId::XaiGrok4Mini
476 | ModelId::XaiGrok4Code
477 | ModelId::XaiGrok4CodeLatest
478 | ModelId::XaiGrok4Vision => Provider::XAI,
479 ModelId::ZaiGlm46
480 | ModelId::ZaiGlm45
481 | ModelId::ZaiGlm45Air
482 | ModelId::ZaiGlm45X
483 | ModelId::ZaiGlm45Airx
484 | ModelId::ZaiGlm45Flash
485 | ModelId::ZaiGlm432b0414128k => Provider::ZAI,
486 ModelId::MoonshotKimiK2TurboPreview
487 | ModelId::MoonshotKimiK20905Preview
488 | ModelId::MoonshotKimiK20711Preview
489 | ModelId::MoonshotKimiLatest
490 | ModelId::MoonshotKimiLatest8k
491 | ModelId::MoonshotKimiLatest32k
492 | ModelId::MoonshotKimiLatest128k => Provider::Moonshot,
493 _ => unreachable!(),
494 }
495 }
496
497 pub fn supports_reasoning_effort(&self) -> bool {
499 self.provider().supports_reasoning_effort(self.as_str())
500 }
501
502 pub fn display_name(&self) -> &'static str {
504 if let Some(meta) = self.openrouter_metadata() {
505 return meta.display;
506 }
507 match self {
508 ModelId::Gemini25FlashPreview => "Gemini 2.5 Flash Preview",
510 ModelId::Gemini25Flash => "Gemini 2.5 Flash",
511 ModelId::Gemini25FlashLite => "Gemini 2.5 Flash Lite",
512 ModelId::Gemini25Pro => "Gemini 2.5 Pro",
513 ModelId::GPT5 => "GPT-5",
515 ModelId::GPT5Codex => "GPT-5 Codex",
516 ModelId::GPT5Mini => "GPT-5 Mini",
517 ModelId::GPT5Nano => "GPT-5 Nano",
518 ModelId::CodexMiniLatest => "Codex Mini Latest",
519 ModelId::ClaudeOpus41 => "Claude Opus 4.1",
521 ModelId::ClaudeSonnet45 => "Claude Sonnet 4.5",
522 ModelId::ClaudeSonnet4 => "Claude Sonnet 4",
523 ModelId::DeepSeekChat => "DeepSeek V3.2-Exp (Chat)",
525 ModelId::DeepSeekReasoner => "DeepSeek V3.2-Exp (Reasoner)",
526 ModelId::XaiGrok4 => "Grok-4",
528 ModelId::XaiGrok4Mini => "Grok-4 Mini",
529 ModelId::XaiGrok4Code => "Grok-4 Code",
530 ModelId::XaiGrok4CodeLatest => "Grok-4 Code Latest",
531 ModelId::XaiGrok4Vision => "Grok-4 Vision",
532 ModelId::ZaiGlm46 => "GLM 4.6",
534 ModelId::ZaiGlm45 => "GLM 4.5",
535 ModelId::ZaiGlm45Air => "GLM 4.5 Air",
536 ModelId::ZaiGlm45X => "GLM 4.5 X",
537 ModelId::ZaiGlm45Airx => "GLM 4.5 AirX",
538 ModelId::ZaiGlm45Flash => "GLM 4.5 Flash",
539 ModelId::ZaiGlm432b0414128k => "GLM 4 32B 0414 128K",
540 ModelId::MoonshotKimiK2TurboPreview => "Kimi K2 Turbo Preview",
542 ModelId::MoonshotKimiK20905Preview => "Kimi K2 0905 Preview",
543 ModelId::MoonshotKimiK20711Preview => "Kimi K2 0711 Preview",
544 ModelId::MoonshotKimiLatest => "Kimi Latest (auto-tier)",
545 ModelId::MoonshotKimiLatest8k => "Kimi Latest 8K",
546 ModelId::MoonshotKimiLatest32k => "Kimi Latest 32K",
547 ModelId::MoonshotKimiLatest128k => "Kimi Latest 128K",
548 _ => unreachable!(),
550 }
551 }
552
553 pub fn description(&self) -> &'static str {
555 if let Some(meta) = self.openrouter_metadata() {
556 return meta.description;
557 }
558 match self {
559 ModelId::Gemini25FlashPreview => {
561 "Latest fast Gemini model with advanced multimodal capabilities"
562 }
563 ModelId::Gemini25Flash => {
564 "Legacy alias for Gemini 2.5 Flash Preview (same capabilities)"
565 }
566 ModelId::Gemini25FlashLite => {
567 "Legacy alias for Gemini 2.5 Flash Preview optimized for efficiency"
568 }
569 ModelId::Gemini25Pro => "Latest most capable Gemini model with reasoning",
570 ModelId::GPT5 => "Latest most capable OpenAI model with advanced reasoning",
572 ModelId::GPT5Codex => {
573 "Code-focused GPT-5 variant optimized for tool calling and structured outputs"
574 }
575 ModelId::GPT5Mini => "Latest efficient OpenAI model, great for most tasks",
576 ModelId::GPT5Nano => "Latest most cost-effective OpenAI model",
577 ModelId::CodexMiniLatest => "Latest Codex model optimized for code generation",
578 ModelId::ClaudeOpus41 => "Latest most capable Anthropic model with advanced reasoning",
580 ModelId::ClaudeSonnet45 => "Latest balanced Anthropic model for general tasks",
581 ModelId::ClaudeSonnet4 => {
582 "Previous balanced Anthropic model maintained for compatibility"
583 }
584 ModelId::DeepSeekChat => {
586 "DeepSeek V3.2-Exp non-thinking mode optimized for fast coding responses"
587 }
588 ModelId::DeepSeekReasoner => {
589 "DeepSeek V3.2-Exp thinking mode with structured reasoning output"
590 }
591 ModelId::XaiGrok4 => "Flagship Grok 4 model with long context and tool use",
593 ModelId::XaiGrok4Mini => "Efficient Grok 4 Mini tuned for low latency",
594 ModelId::XaiGrok4Code => "Code-specialized Grok 4 deployment with tool support",
595 ModelId::XaiGrok4CodeLatest => {
596 "Latest Grok 4 code model offering enhanced reasoning traces"
597 }
598 ModelId::XaiGrok4Vision => "Multimodal Grok 4 model with image understanding",
599 ModelId::ZaiGlm46 => {
601 "Latest Z.AI GLM flagship with long-context reasoning and coding strengths"
602 }
603 ModelId::ZaiGlm45 => "Balanced GLM 4.5 release for general assistant tasks",
604 ModelId::ZaiGlm45Air => "Efficient GLM 4.5 Air variant tuned for lower latency",
605 ModelId::ZaiGlm45X => "Enhanced GLM 4.5 X variant with improved reasoning",
606 ModelId::ZaiGlm45Airx => "Hybrid GLM 4.5 AirX variant blending efficiency with quality",
607 ModelId::ZaiGlm45Flash => "Low-latency GLM 4.5 Flash optimized for responsiveness",
608 ModelId::ZaiGlm432b0414128k => {
609 "Legacy GLM 4 32B deployment offering extended 128K context window"
610 }
611 ModelId::MoonshotKimiK2TurboPreview => {
613 "Recommended high-speed Kimi K2 turbo variant with 256K context and 60+ tok/s output"
614 }
615 ModelId::MoonshotKimiK20905Preview => {
616 "Latest Kimi K2 0905 flagship with enhanced agentic coding, 256K context, and richer tool support"
617 }
618 ModelId::MoonshotKimiK20711Preview => {
619 "Kimi K2 0711 preview tuned for balanced cost and capability with 131K context"
620 }
621 ModelId::MoonshotKimiLatest => {
622 "Auto-tier alias that selects the right Kimi Latest vision tier (8K/32K/128K) with context caching"
623 }
624 ModelId::MoonshotKimiLatest8k => {
625 "Kimi Latest 8K vision tier for short tasks with automatic context caching"
626 }
627 ModelId::MoonshotKimiLatest32k => {
628 "Kimi Latest 32K vision tier blending longer context with latest assistant features"
629 }
630 ModelId::MoonshotKimiLatest128k => {
631 "Kimi Latest 128K flagship vision tier delivering maximum context and newest capabilities"
632 }
633 _ => unreachable!(),
634 }
635 }
636
637 pub fn all_models() -> Vec<ModelId> {
639 let mut models = vec![
640 ModelId::Gemini25FlashPreview,
642 ModelId::Gemini25Flash,
643 ModelId::Gemini25FlashLite,
644 ModelId::Gemini25Pro,
645 ModelId::GPT5,
647 ModelId::GPT5Codex,
648 ModelId::GPT5Mini,
649 ModelId::GPT5Nano,
650 ModelId::CodexMiniLatest,
651 ModelId::ClaudeOpus41,
653 ModelId::ClaudeSonnet45,
654 ModelId::ClaudeSonnet4,
655 ModelId::DeepSeekChat,
657 ModelId::DeepSeekReasoner,
658 ModelId::XaiGrok4,
660 ModelId::XaiGrok4Mini,
661 ModelId::XaiGrok4Code,
662 ModelId::XaiGrok4CodeLatest,
663 ModelId::XaiGrok4Vision,
664 ModelId::ZaiGlm46,
666 ModelId::ZaiGlm45,
667 ModelId::ZaiGlm45Air,
668 ModelId::ZaiGlm45X,
669 ModelId::ZaiGlm45Airx,
670 ModelId::ZaiGlm45Flash,
671 ModelId::ZaiGlm432b0414128k,
672 ModelId::MoonshotKimiK2TurboPreview,
674 ModelId::MoonshotKimiK20905Preview,
675 ModelId::MoonshotKimiK20711Preview,
676 ModelId::MoonshotKimiLatest,
677 ModelId::MoonshotKimiLatest8k,
678 ModelId::MoonshotKimiLatest32k,
679 ModelId::MoonshotKimiLatest128k,
680 ];
681 models.extend(Self::openrouter_models());
682 models
683 }
684
685 pub fn models_for_provider(provider: Provider) -> Vec<ModelId> {
687 Self::all_models()
688 .into_iter()
689 .filter(|model| model.provider() == provider)
690 .collect()
691 }
692
693 pub fn fallback_models() -> Vec<ModelId> {
695 vec![
696 ModelId::Gemini25FlashPreview,
697 ModelId::Gemini25Pro,
698 ModelId::GPT5,
699 ModelId::ClaudeOpus41,
700 ModelId::ClaudeSonnet45,
701 ModelId::DeepSeekReasoner,
702 ModelId::MoonshotKimiK20905Preview,
703 ModelId::XaiGrok4,
704 ModelId::ZaiGlm46,
705 ModelId::OpenRouterGrokCodeFast1,
706 ]
707 }
708
709 pub fn default() -> Self {
711 ModelId::Gemini25FlashPreview
712 }
713
714 pub fn default_orchestrator() -> Self {
716 ModelId::Gemini25Pro
717 }
718
719 pub fn default_subagent() -> Self {
721 ModelId::Gemini25FlashPreview
722 }
723
724 pub fn default_orchestrator_for_provider(provider: Provider) -> Self {
726 match provider {
727 Provider::Gemini => ModelId::Gemini25Pro,
728 Provider::OpenAI => ModelId::GPT5,
729 Provider::Anthropic => ModelId::ClaudeOpus41,
730 Provider::DeepSeek => ModelId::DeepSeekReasoner,
731 Provider::Moonshot => ModelId::MoonshotKimiK20905Preview,
732 Provider::XAI => ModelId::XaiGrok4,
733 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
734 Provider::ZAI => ModelId::ZaiGlm46,
735 }
736 }
737
738 pub fn default_subagent_for_provider(provider: Provider) -> Self {
740 match provider {
741 Provider::Gemini => ModelId::Gemini25FlashPreview,
742 Provider::OpenAI => ModelId::GPT5Mini,
743 Provider::Anthropic => ModelId::ClaudeSonnet45,
744 Provider::DeepSeek => ModelId::DeepSeekChat,
745 Provider::Moonshot => ModelId::MoonshotKimiK2TurboPreview,
746 Provider::XAI => ModelId::XaiGrok4Code,
747 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
748 Provider::ZAI => ModelId::ZaiGlm45Flash,
749 }
750 }
751
752 pub fn default_single_for_provider(provider: Provider) -> Self {
754 match provider {
755 Provider::Gemini => ModelId::Gemini25FlashPreview,
756 Provider::OpenAI => ModelId::GPT5,
757 Provider::Anthropic => ModelId::ClaudeOpus41,
758 Provider::DeepSeek => ModelId::DeepSeekReasoner,
759 Provider::Moonshot => ModelId::MoonshotKimiK2TurboPreview,
760 Provider::XAI => ModelId::XaiGrok4,
761 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
762 Provider::ZAI => ModelId::ZaiGlm46,
763 }
764 }
765
766 pub fn is_flash_variant(&self) -> bool {
768 matches!(
769 self,
770 ModelId::Gemini25FlashPreview
771 | ModelId::Gemini25Flash
772 | ModelId::Gemini25FlashLite
773 | ModelId::ZaiGlm45Flash
774 | ModelId::MoonshotKimiK2TurboPreview
775 | ModelId::MoonshotKimiLatest8k
776 )
777 }
778
779 pub fn is_pro_variant(&self) -> bool {
781 matches!(
782 self,
783 ModelId::Gemini25Pro
784 | ModelId::GPT5
785 | ModelId::GPT5Codex
786 | ModelId::ClaudeOpus41
787 | ModelId::DeepSeekReasoner
788 | ModelId::XaiGrok4
789 | ModelId::ZaiGlm46
790 | ModelId::MoonshotKimiK20905Preview
791 | ModelId::MoonshotKimiLatest128k
792 )
793 }
794
795 pub fn is_efficient_variant(&self) -> bool {
797 if let Some(meta) = self.openrouter_metadata() {
798 return meta.efficient;
799 }
800 matches!(
801 self,
802 ModelId::Gemini25FlashPreview
803 | ModelId::Gemini25Flash
804 | ModelId::Gemini25FlashLite
805 | ModelId::GPT5Mini
806 | ModelId::GPT5Nano
807 | ModelId::DeepSeekChat
808 | ModelId::XaiGrok4Code
809 | ModelId::ZaiGlm45Air
810 | ModelId::ZaiGlm45Airx
811 | ModelId::ZaiGlm45Flash
812 | ModelId::MoonshotKimiK2TurboPreview
813 | ModelId::MoonshotKimiLatest8k
814 )
815 }
816
817 pub fn is_top_tier(&self) -> bool {
819 if let Some(meta) = self.openrouter_metadata() {
820 return meta.top_tier;
821 }
822 matches!(
823 self,
824 ModelId::Gemini25Pro
825 | ModelId::GPT5
826 | ModelId::GPT5Codex
827 | ModelId::ClaudeOpus41
828 | ModelId::ClaudeSonnet45
829 | ModelId::ClaudeSonnet4
830 | ModelId::DeepSeekReasoner
831 | ModelId::XaiGrok4
832 | ModelId::XaiGrok4CodeLatest
833 | ModelId::ZaiGlm46
834 | ModelId::MoonshotKimiK20905Preview
835 | ModelId::MoonshotKimiLatest128k
836 )
837 }
838
839 pub fn generation(&self) -> &'static str {
841 if let Some(meta) = self.openrouter_metadata() {
842 return meta.generation;
843 }
844 match self {
845 ModelId::Gemini25FlashPreview
847 | ModelId::Gemini25Flash
848 | ModelId::Gemini25FlashLite
849 | ModelId::Gemini25Pro => "2.5",
850 ModelId::GPT5
852 | ModelId::GPT5Codex
853 | ModelId::GPT5Mini
854 | ModelId::GPT5Nano
855 | ModelId::CodexMiniLatest => "5",
856 ModelId::ClaudeSonnet45 => "4.5",
858 ModelId::ClaudeSonnet4 => "4",
859 ModelId::ClaudeOpus41 => "4.1",
860 ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
862 ModelId::XaiGrok4
864 | ModelId::XaiGrok4Mini
865 | ModelId::XaiGrok4Code
866 | ModelId::XaiGrok4CodeLatest
867 | ModelId::XaiGrok4Vision => "4",
868 ModelId::ZaiGlm46 => "4.6",
870 ModelId::ZaiGlm45
871 | ModelId::ZaiGlm45Air
872 | ModelId::ZaiGlm45X
873 | ModelId::ZaiGlm45Airx
874 | ModelId::ZaiGlm45Flash => "4.5",
875 ModelId::ZaiGlm432b0414128k => "4-32B",
876 ModelId::MoonshotKimiK2TurboPreview
878 | ModelId::MoonshotKimiK20905Preview
879 | ModelId::MoonshotKimiK20711Preview => "k2",
880 ModelId::MoonshotKimiLatest
881 | ModelId::MoonshotKimiLatest8k
882 | ModelId::MoonshotKimiLatest32k
883 | ModelId::MoonshotKimiLatest128k => "latest",
884 _ => unreachable!(),
885 }
886 }
887}
888
889impl fmt::Display for ModelId {
890 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
891 write!(f, "{}", self.as_str())
892 }
893}
894
895impl FromStr for ModelId {
896 type Err = ModelParseError;
897
898 fn from_str(s: &str) -> Result<Self, Self::Err> {
899 use crate::config::constants::models;
900 match s {
901 s if s == models::GEMINI_2_5_FLASH_PREVIEW => Ok(ModelId::Gemini25FlashPreview),
903 s if s == models::GEMINI_2_5_FLASH => Ok(ModelId::Gemini25Flash),
904 s if s == models::GEMINI_2_5_FLASH_LITE => Ok(ModelId::Gemini25FlashLite),
905 s if s == models::GEMINI_2_5_PRO => Ok(ModelId::Gemini25Pro),
906 s if s == models::GPT_5 => Ok(ModelId::GPT5),
908 s if s == models::GPT_5_CODEX => Ok(ModelId::GPT5Codex),
909 s if s == models::GPT_5_MINI => Ok(ModelId::GPT5Mini),
910 s if s == models::GPT_5_NANO => Ok(ModelId::GPT5Nano),
911 s if s == models::CODEX_MINI_LATEST => Ok(ModelId::CodexMiniLatest),
912 s if s == models::CLAUDE_OPUS_4_1_20250805 => Ok(ModelId::ClaudeOpus41),
914 s if s == models::CLAUDE_SONNET_4_5 => Ok(ModelId::ClaudeSonnet45),
915 s if s == models::CLAUDE_SONNET_4_20250514 => Ok(ModelId::ClaudeSonnet4),
916 s if s == models::DEEPSEEK_CHAT => Ok(ModelId::DeepSeekChat),
918 s if s == models::DEEPSEEK_REASONER => Ok(ModelId::DeepSeekReasoner),
919 s if s == models::xai::GROK_4 => Ok(ModelId::XaiGrok4),
921 s if s == models::xai::GROK_4_MINI => Ok(ModelId::XaiGrok4Mini),
922 s if s == models::xai::GROK_4_CODE => Ok(ModelId::XaiGrok4Code),
923 s if s == models::xai::GROK_4_CODE_LATEST => Ok(ModelId::XaiGrok4CodeLatest),
924 s if s == models::xai::GROK_4_VISION => Ok(ModelId::XaiGrok4Vision),
925 s if s == models::zai::GLM_4_6 => Ok(ModelId::ZaiGlm46),
927 s if s == models::zai::GLM_4_5 => Ok(ModelId::ZaiGlm45),
928 s if s == models::zai::GLM_4_5_AIR => Ok(ModelId::ZaiGlm45Air),
929 s if s == models::zai::GLM_4_5_X => Ok(ModelId::ZaiGlm45X),
930 s if s == models::zai::GLM_4_5_AIRX => Ok(ModelId::ZaiGlm45Airx),
931 s if s == models::zai::GLM_4_5_FLASH => Ok(ModelId::ZaiGlm45Flash),
932 s if s == models::zai::GLM_4_32B_0414_128K => Ok(ModelId::ZaiGlm432b0414128k),
933 s if s == models::MOONSHOT_KIMI_K2_TURBO_PREVIEW => {
935 Ok(ModelId::MoonshotKimiK2TurboPreview)
936 }
937 s if s == models::MOONSHOT_KIMI_K2_0905_PREVIEW => {
938 Ok(ModelId::MoonshotKimiK20905Preview)
939 }
940 s if s == models::MOONSHOT_KIMI_K2_0711_PREVIEW => {
941 Ok(ModelId::MoonshotKimiK20711Preview)
942 }
943 s if s == models::MOONSHOT_KIMI_LATEST => Ok(ModelId::MoonshotKimiLatest),
944 s if s == models::MOONSHOT_KIMI_LATEST_8K => Ok(ModelId::MoonshotKimiLatest8k),
945 s if s == models::MOONSHOT_KIMI_LATEST_32K => Ok(ModelId::MoonshotKimiLatest32k),
946 s if s == models::MOONSHOT_KIMI_LATEST_128K => Ok(ModelId::MoonshotKimiLatest128k),
947 _ => {
948 if let Some(model) = Self::parse_openrouter_model(s) {
949 Ok(model)
950 } else {
951 Err(ModelParseError::InvalidModel(s.to_string()))
952 }
953 }
954 }
955 }
956}
957
958#[derive(Debug, Clone, PartialEq)]
960pub enum ModelParseError {
961 InvalidModel(String),
962 InvalidProvider(String),
963}
964
965impl fmt::Display for ModelParseError {
966 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
967 match self {
968 ModelParseError::InvalidModel(model) => {
969 write!(
970 f,
971 "Invalid model identifier: '{}'. Supported models: {}",
972 model,
973 ModelId::all_models()
974 .iter()
975 .map(|m| m.as_str())
976 .collect::<Vec<_>>()
977 .join(", ")
978 )
979 }
980 ModelParseError::InvalidProvider(provider) => {
981 write!(
982 f,
983 "Invalid provider: '{}'. Supported providers: {}",
984 provider,
985 Provider::all_providers()
986 .iter()
987 .map(|p| p.to_string())
988 .collect::<Vec<_>>()
989 .join(", ")
990 )
991 }
992 }
993 }
994}
995
996impl std::error::Error for ModelParseError {}
997
998#[cfg(test)]
999mod tests {
1000 use super::*;
1001 use crate::config::constants::models;
1002
1003 #[test]
1004 fn test_model_string_conversion() {
1005 assert_eq!(
1007 ModelId::Gemini25FlashPreview.as_str(),
1008 models::GEMINI_2_5_FLASH_PREVIEW
1009 );
1010 assert_eq!(ModelId::Gemini25Flash.as_str(), models::GEMINI_2_5_FLASH);
1011 assert_eq!(
1012 ModelId::Gemini25FlashLite.as_str(),
1013 models::GEMINI_2_5_FLASH_LITE
1014 );
1015 assert_eq!(ModelId::Gemini25Pro.as_str(), models::GEMINI_2_5_PRO);
1016 assert_eq!(ModelId::GPT5.as_str(), models::GPT_5);
1018 assert_eq!(ModelId::GPT5Codex.as_str(), models::GPT_5_CODEX);
1019 assert_eq!(ModelId::GPT5Mini.as_str(), models::GPT_5_MINI);
1020 assert_eq!(ModelId::GPT5Nano.as_str(), models::GPT_5_NANO);
1021 assert_eq!(ModelId::CodexMiniLatest.as_str(), models::CODEX_MINI_LATEST);
1022 assert_eq!(ModelId::ClaudeSonnet45.as_str(), models::CLAUDE_SONNET_4_5);
1024 assert_eq!(
1025 ModelId::ClaudeSonnet4.as_str(),
1026 models::CLAUDE_SONNET_4_20250514
1027 );
1028 assert_eq!(
1029 ModelId::ClaudeOpus41.as_str(),
1030 models::CLAUDE_OPUS_4_1_20250805
1031 );
1032 assert_eq!(ModelId::DeepSeekChat.as_str(), models::DEEPSEEK_CHAT);
1034 assert_eq!(
1035 ModelId::DeepSeekReasoner.as_str(),
1036 models::DEEPSEEK_REASONER
1037 );
1038 assert_eq!(ModelId::XaiGrok4.as_str(), models::xai::GROK_4);
1040 assert_eq!(ModelId::XaiGrok4Mini.as_str(), models::xai::GROK_4_MINI);
1041 assert_eq!(ModelId::XaiGrok4Code.as_str(), models::xai::GROK_4_CODE);
1042 assert_eq!(
1043 ModelId::XaiGrok4CodeLatest.as_str(),
1044 models::xai::GROK_4_CODE_LATEST
1045 );
1046 assert_eq!(ModelId::XaiGrok4Vision.as_str(), models::xai::GROK_4_VISION);
1047 assert_eq!(ModelId::ZaiGlm46.as_str(), models::zai::GLM_4_6);
1049 assert_eq!(ModelId::ZaiGlm45.as_str(), models::zai::GLM_4_5);
1050 assert_eq!(ModelId::ZaiGlm45Air.as_str(), models::zai::GLM_4_5_AIR);
1051 assert_eq!(ModelId::ZaiGlm45X.as_str(), models::zai::GLM_4_5_X);
1052 assert_eq!(ModelId::ZaiGlm45Airx.as_str(), models::zai::GLM_4_5_AIRX);
1053 assert_eq!(ModelId::ZaiGlm45Flash.as_str(), models::zai::GLM_4_5_FLASH);
1054 assert_eq!(
1055 ModelId::ZaiGlm432b0414128k.as_str(),
1056 models::zai::GLM_4_32B_0414_128K
1057 );
1058 macro_rules! assert_openrouter_to_string {
1059 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1060 $(assert_eq!(ModelId::$variant.as_str(), models::$const);)*
1061 };
1062 }
1063 each_openrouter_variant!(assert_openrouter_to_string);
1064 }
1065
1066 #[test]
1067 fn test_model_from_string() {
1068 assert_eq!(
1070 models::GEMINI_2_5_FLASH_PREVIEW.parse::<ModelId>().unwrap(),
1071 ModelId::Gemini25FlashPreview
1072 );
1073 assert_eq!(
1074 models::GEMINI_2_5_FLASH.parse::<ModelId>().unwrap(),
1075 ModelId::Gemini25Flash
1076 );
1077 assert_eq!(
1078 models::GEMINI_2_5_FLASH_LITE.parse::<ModelId>().unwrap(),
1079 ModelId::Gemini25FlashLite
1080 );
1081 assert_eq!(
1082 models::GEMINI_2_5_PRO.parse::<ModelId>().unwrap(),
1083 ModelId::Gemini25Pro
1084 );
1085 assert_eq!(models::GPT_5.parse::<ModelId>().unwrap(), ModelId::GPT5);
1087 assert_eq!(
1088 models::GPT_5_CODEX.parse::<ModelId>().unwrap(),
1089 ModelId::GPT5Codex
1090 );
1091 assert_eq!(
1092 models::GPT_5_MINI.parse::<ModelId>().unwrap(),
1093 ModelId::GPT5Mini
1094 );
1095 assert_eq!(
1096 models::GPT_5_NANO.parse::<ModelId>().unwrap(),
1097 ModelId::GPT5Nano
1098 );
1099 assert_eq!(
1100 models::CODEX_MINI_LATEST.parse::<ModelId>().unwrap(),
1101 ModelId::CodexMiniLatest
1102 );
1103 assert_eq!(
1105 models::CLAUDE_SONNET_4_5.parse::<ModelId>().unwrap(),
1106 ModelId::ClaudeSonnet45
1107 );
1108 assert_eq!(
1109 models::CLAUDE_SONNET_4_20250514.parse::<ModelId>().unwrap(),
1110 ModelId::ClaudeSonnet4
1111 );
1112 assert_eq!(
1113 models::CLAUDE_OPUS_4_1_20250805.parse::<ModelId>().unwrap(),
1114 ModelId::ClaudeOpus41
1115 );
1116 assert_eq!(
1118 models::DEEPSEEK_CHAT.parse::<ModelId>().unwrap(),
1119 ModelId::DeepSeekChat
1120 );
1121 assert_eq!(
1122 models::DEEPSEEK_REASONER.parse::<ModelId>().unwrap(),
1123 ModelId::DeepSeekReasoner
1124 );
1125 assert_eq!(
1127 models::xai::GROK_4.parse::<ModelId>().unwrap(),
1128 ModelId::XaiGrok4
1129 );
1130 assert_eq!(
1131 models::xai::GROK_4_MINI.parse::<ModelId>().unwrap(),
1132 ModelId::XaiGrok4Mini
1133 );
1134 assert_eq!(
1135 models::xai::GROK_4_CODE.parse::<ModelId>().unwrap(),
1136 ModelId::XaiGrok4Code
1137 );
1138 assert_eq!(
1139 models::xai::GROK_4_CODE_LATEST.parse::<ModelId>().unwrap(),
1140 ModelId::XaiGrok4CodeLatest
1141 );
1142 assert_eq!(
1143 models::xai::GROK_4_VISION.parse::<ModelId>().unwrap(),
1144 ModelId::XaiGrok4Vision
1145 );
1146 assert_eq!(
1148 models::zai::GLM_4_6.parse::<ModelId>().unwrap(),
1149 ModelId::ZaiGlm46
1150 );
1151 assert_eq!(
1152 models::zai::GLM_4_5.parse::<ModelId>().unwrap(),
1153 ModelId::ZaiGlm45
1154 );
1155 assert_eq!(
1156 models::zai::GLM_4_5_AIR.parse::<ModelId>().unwrap(),
1157 ModelId::ZaiGlm45Air
1158 );
1159 assert_eq!(
1160 models::zai::GLM_4_5_X.parse::<ModelId>().unwrap(),
1161 ModelId::ZaiGlm45X
1162 );
1163 assert_eq!(
1164 models::zai::GLM_4_5_AIRX.parse::<ModelId>().unwrap(),
1165 ModelId::ZaiGlm45Airx
1166 );
1167 assert_eq!(
1168 models::zai::GLM_4_5_FLASH.parse::<ModelId>().unwrap(),
1169 ModelId::ZaiGlm45Flash
1170 );
1171 assert_eq!(
1172 models::zai::GLM_4_32B_0414_128K.parse::<ModelId>().unwrap(),
1173 ModelId::ZaiGlm432b0414128k
1174 );
1175 assert_eq!(
1176 models::MOONSHOT_KIMI_K2_TURBO_PREVIEW
1177 .parse::<ModelId>()
1178 .unwrap(),
1179 ModelId::MoonshotKimiK2TurboPreview
1180 );
1181 assert_eq!(
1182 models::MOONSHOT_KIMI_K2_0905_PREVIEW
1183 .parse::<ModelId>()
1184 .unwrap(),
1185 ModelId::MoonshotKimiK20905Preview
1186 );
1187 assert_eq!(
1188 models::MOONSHOT_KIMI_K2_0711_PREVIEW
1189 .parse::<ModelId>()
1190 .unwrap(),
1191 ModelId::MoonshotKimiK20711Preview
1192 );
1193 assert_eq!(
1194 models::MOONSHOT_KIMI_LATEST.parse::<ModelId>().unwrap(),
1195 ModelId::MoonshotKimiLatest
1196 );
1197 assert_eq!(
1198 models::MOONSHOT_KIMI_LATEST_8K.parse::<ModelId>().unwrap(),
1199 ModelId::MoonshotKimiLatest8k
1200 );
1201 assert_eq!(
1202 models::MOONSHOT_KIMI_LATEST_32K.parse::<ModelId>().unwrap(),
1203 ModelId::MoonshotKimiLatest32k
1204 );
1205 assert_eq!(
1206 models::MOONSHOT_KIMI_LATEST_128K
1207 .parse::<ModelId>()
1208 .unwrap(),
1209 ModelId::MoonshotKimiLatest128k
1210 );
1211 macro_rules! assert_openrouter_parse {
1212 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1213 $(assert_eq!(models::$const.parse::<ModelId>().unwrap(), ModelId::$variant);)*
1214 };
1215 }
1216 each_openrouter_variant!(assert_openrouter_parse);
1217 assert!("invalid-model".parse::<ModelId>().is_err());
1219 }
1220
1221 #[test]
1222 fn test_provider_parsing() {
1223 assert_eq!("gemini".parse::<Provider>().unwrap(), Provider::Gemini);
1224 assert_eq!("openai".parse::<Provider>().unwrap(), Provider::OpenAI);
1225 assert_eq!(
1226 "anthropic".parse::<Provider>().unwrap(),
1227 Provider::Anthropic
1228 );
1229 assert_eq!("deepseek".parse::<Provider>().unwrap(), Provider::DeepSeek);
1230 assert_eq!(
1231 "openrouter".parse::<Provider>().unwrap(),
1232 Provider::OpenRouter
1233 );
1234 assert_eq!("xai".parse::<Provider>().unwrap(), Provider::XAI);
1235 assert_eq!("zai".parse::<Provider>().unwrap(), Provider::ZAI);
1236 assert_eq!("moonshot".parse::<Provider>().unwrap(), Provider::Moonshot);
1237 assert!("invalid-provider".parse::<Provider>().is_err());
1238 }
1239
1240 #[test]
1241 fn test_model_providers() {
1242 assert_eq!(ModelId::Gemini25FlashPreview.provider(), Provider::Gemini);
1243 assert_eq!(ModelId::GPT5.provider(), Provider::OpenAI);
1244 assert_eq!(ModelId::GPT5Codex.provider(), Provider::OpenAI);
1245 assert_eq!(ModelId::ClaudeSonnet45.provider(), Provider::Anthropic);
1246 assert_eq!(ModelId::ClaudeSonnet4.provider(), Provider::Anthropic);
1247 assert_eq!(ModelId::DeepSeekChat.provider(), Provider::DeepSeek);
1248 assert_eq!(ModelId::XaiGrok4.provider(), Provider::XAI);
1249 assert_eq!(ModelId::ZaiGlm46.provider(), Provider::ZAI);
1250 assert_eq!(
1251 ModelId::MoonshotKimiK20905Preview.provider(),
1252 Provider::Moonshot
1253 );
1254 assert_eq!(
1255 ModelId::OpenRouterGrokCodeFast1.provider(),
1256 Provider::OpenRouter
1257 );
1258 assert_eq!(
1259 ModelId::OpenRouterAnthropicClaudeSonnet45.provider(),
1260 Provider::OpenRouter
1261 );
1262
1263 macro_rules! assert_openrouter_provider_all {
1264 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1265 $(assert_eq!(ModelId::$variant.provider(), Provider::OpenRouter);)*
1266 };
1267 }
1268 each_openrouter_variant!(assert_openrouter_provider_all);
1269 }
1270
1271 #[test]
1272 fn test_provider_defaults() {
1273 assert_eq!(
1274 ModelId::default_orchestrator_for_provider(Provider::Gemini),
1275 ModelId::Gemini25Pro
1276 );
1277 assert_eq!(
1278 ModelId::default_orchestrator_for_provider(Provider::OpenAI),
1279 ModelId::GPT5
1280 );
1281 assert_eq!(
1282 ModelId::default_orchestrator_for_provider(Provider::Anthropic),
1283 ModelId::ClaudeSonnet4
1284 );
1285 assert_eq!(
1286 ModelId::default_orchestrator_for_provider(Provider::DeepSeek),
1287 ModelId::DeepSeekReasoner
1288 );
1289 assert_eq!(
1290 ModelId::default_orchestrator_for_provider(Provider::OpenRouter),
1291 ModelId::OpenRouterGrokCodeFast1
1292 );
1293 assert_eq!(
1294 ModelId::default_orchestrator_for_provider(Provider::XAI),
1295 ModelId::XaiGrok4
1296 );
1297 assert_eq!(
1298 ModelId::default_orchestrator_for_provider(Provider::ZAI),
1299 ModelId::ZaiGlm46
1300 );
1301 assert_eq!(
1302 ModelId::default_orchestrator_for_provider(Provider::Moonshot),
1303 ModelId::MoonshotKimiK20905Preview
1304 );
1305
1306 assert_eq!(
1307 ModelId::default_subagent_for_provider(Provider::Gemini),
1308 ModelId::Gemini25FlashPreview
1309 );
1310 assert_eq!(
1311 ModelId::default_subagent_for_provider(Provider::OpenAI),
1312 ModelId::GPT5Mini
1313 );
1314 assert_eq!(
1315 ModelId::default_subagent_for_provider(Provider::Anthropic),
1316 ModelId::ClaudeSonnet45
1317 );
1318 assert_eq!(
1319 ModelId::default_subagent_for_provider(Provider::DeepSeek),
1320 ModelId::DeepSeekChat
1321 );
1322 assert_eq!(
1323 ModelId::default_subagent_for_provider(Provider::OpenRouter),
1324 ModelId::OpenRouterGrokCodeFast1
1325 );
1326 assert_eq!(
1327 ModelId::default_subagent_for_provider(Provider::XAI),
1328 ModelId::XaiGrok4Code
1329 );
1330 assert_eq!(
1331 ModelId::default_subagent_for_provider(Provider::ZAI),
1332 ModelId::ZaiGlm45Flash
1333 );
1334 assert_eq!(
1335 ModelId::default_subagent_for_provider(Provider::Moonshot),
1336 ModelId::MoonshotKimiK2TurboPreview
1337 );
1338
1339 assert_eq!(
1340 ModelId::default_single_for_provider(Provider::DeepSeek),
1341 ModelId::DeepSeekReasoner
1342 );
1343 assert_eq!(
1344 ModelId::default_single_for_provider(Provider::Moonshot),
1345 ModelId::MoonshotKimiK2TurboPreview
1346 );
1347 }
1348
1349 #[test]
1350 fn test_model_defaults() {
1351 assert_eq!(ModelId::default(), ModelId::Gemini25FlashPreview);
1352 assert_eq!(ModelId::default_orchestrator(), ModelId::Gemini25Pro);
1353 assert_eq!(ModelId::default_subagent(), ModelId::Gemini25FlashPreview);
1354 }
1355
1356 #[test]
1357 fn test_model_variants() {
1358 assert!(ModelId::Gemini25FlashPreview.is_flash_variant());
1360 assert!(ModelId::Gemini25Flash.is_flash_variant());
1361 assert!(ModelId::Gemini25FlashLite.is_flash_variant());
1362 assert!(!ModelId::GPT5.is_flash_variant());
1363 assert!(ModelId::ZaiGlm45Flash.is_flash_variant());
1364 assert!(ModelId::MoonshotKimiK2TurboPreview.is_flash_variant());
1365 assert!(ModelId::MoonshotKimiLatest8k.is_flash_variant());
1366
1367 assert!(ModelId::Gemini25Pro.is_pro_variant());
1369 assert!(ModelId::GPT5.is_pro_variant());
1370 assert!(ModelId::GPT5Codex.is_pro_variant());
1371 assert!(ModelId::DeepSeekReasoner.is_pro_variant());
1372 assert!(ModelId::ZaiGlm46.is_pro_variant());
1373 assert!(ModelId::MoonshotKimiK20905Preview.is_pro_variant());
1374 assert!(ModelId::MoonshotKimiLatest128k.is_pro_variant());
1375 assert!(!ModelId::Gemini25FlashPreview.is_pro_variant());
1376
1377 assert!(ModelId::Gemini25FlashPreview.is_efficient_variant());
1379 assert!(ModelId::Gemini25Flash.is_efficient_variant());
1380 assert!(ModelId::Gemini25FlashLite.is_efficient_variant());
1381 assert!(ModelId::GPT5Mini.is_efficient_variant());
1382 assert!(ModelId::XaiGrok4Code.is_efficient_variant());
1383 assert!(ModelId::DeepSeekChat.is_efficient_variant());
1384 assert!(ModelId::ZaiGlm45Air.is_efficient_variant());
1385 assert!(ModelId::ZaiGlm45Airx.is_efficient_variant());
1386 assert!(ModelId::ZaiGlm45Flash.is_efficient_variant());
1387 assert!(ModelId::MoonshotKimiK2TurboPreview.is_efficient_variant());
1388 assert!(ModelId::MoonshotKimiLatest8k.is_efficient_variant());
1389 assert!(!ModelId::GPT5.is_efficient_variant());
1390
1391 macro_rules! assert_openrouter_efficiency {
1392 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1393 $(assert_eq!(ModelId::$variant.is_efficient_variant(), $efficient);)*
1394 };
1395 }
1396 each_openrouter_variant!(assert_openrouter_efficiency);
1397
1398 assert!(ModelId::Gemini25Pro.is_top_tier());
1400 assert!(ModelId::GPT5.is_top_tier());
1401 assert!(ModelId::GPT5Codex.is_top_tier());
1402 assert!(ModelId::ClaudeSonnet45.is_top_tier());
1403 assert!(ModelId::ClaudeSonnet4.is_top_tier());
1404 assert!(ModelId::XaiGrok4.is_top_tier());
1405 assert!(ModelId::XaiGrok4CodeLatest.is_top_tier());
1406 assert!(ModelId::DeepSeekReasoner.is_top_tier());
1407 assert!(ModelId::ZaiGlm46.is_top_tier());
1408 assert!(ModelId::MoonshotKimiK20905Preview.is_top_tier());
1409 assert!(ModelId::MoonshotKimiLatest128k.is_top_tier());
1410 assert!(!ModelId::Gemini25FlashPreview.is_top_tier());
1411
1412 macro_rules! assert_openrouter_top_tier {
1413 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1414 $(assert_eq!(ModelId::$variant.is_top_tier(), $top);)*
1415 };
1416 }
1417 each_openrouter_variant!(assert_openrouter_top_tier);
1418 }
1419
1420 #[test]
1421 fn test_model_generation() {
1422 assert_eq!(ModelId::Gemini25FlashPreview.generation(), "2.5");
1424 assert_eq!(ModelId::Gemini25Flash.generation(), "2.5");
1425 assert_eq!(ModelId::Gemini25FlashLite.generation(), "2.5");
1426 assert_eq!(ModelId::Gemini25Pro.generation(), "2.5");
1427
1428 assert_eq!(ModelId::GPT5.generation(), "5");
1430 assert_eq!(ModelId::GPT5Codex.generation(), "5");
1431 assert_eq!(ModelId::GPT5Mini.generation(), "5");
1432 assert_eq!(ModelId::GPT5Nano.generation(), "5");
1433 assert_eq!(ModelId::CodexMiniLatest.generation(), "5");
1434
1435 assert_eq!(ModelId::ClaudeSonnet45.generation(), "4.5");
1437 assert_eq!(ModelId::ClaudeSonnet4.generation(), "4");
1438 assert_eq!(ModelId::ClaudeOpus41.generation(), "4.1");
1439
1440 assert_eq!(ModelId::DeepSeekChat.generation(), "V3.2-Exp");
1442 assert_eq!(ModelId::DeepSeekReasoner.generation(), "V3.2-Exp");
1443
1444 assert_eq!(ModelId::XaiGrok4.generation(), "4");
1446 assert_eq!(ModelId::XaiGrok4Mini.generation(), "4");
1447 assert_eq!(ModelId::XaiGrok4Code.generation(), "4");
1448 assert_eq!(ModelId::XaiGrok4CodeLatest.generation(), "4");
1449 assert_eq!(ModelId::XaiGrok4Vision.generation(), "4");
1450 assert_eq!(ModelId::ZaiGlm46.generation(), "4.6");
1452 assert_eq!(ModelId::ZaiGlm45.generation(), "4.5");
1453 assert_eq!(ModelId::ZaiGlm45Air.generation(), "4.5");
1454 assert_eq!(ModelId::ZaiGlm45X.generation(), "4.5");
1455 assert_eq!(ModelId::ZaiGlm45Airx.generation(), "4.5");
1456 assert_eq!(ModelId::ZaiGlm45Flash.generation(), "4.5");
1457 assert_eq!(ModelId::ZaiGlm432b0414128k.generation(), "4-32B");
1458 assert_eq!(ModelId::MoonshotKimiK2TurboPreview.generation(), "k2");
1459 assert_eq!(ModelId::MoonshotKimiK20905Preview.generation(), "k2");
1460 assert_eq!(ModelId::MoonshotKimiK20711Preview.generation(), "k2");
1461 assert_eq!(ModelId::MoonshotKimiLatest.generation(), "latest");
1462 assert_eq!(ModelId::MoonshotKimiLatest8k.generation(), "latest");
1463 assert_eq!(ModelId::MoonshotKimiLatest32k.generation(), "latest");
1464 assert_eq!(ModelId::MoonshotKimiLatest128k.generation(), "latest");
1465
1466 macro_rules! assert_openrouter_generation {
1467 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1468 $(assert_eq!(ModelId::$variant.generation(), $generation);)*
1469 };
1470 }
1471 each_openrouter_variant!(assert_openrouter_generation);
1472 }
1473
1474 #[test]
1475 fn test_models_for_provider() {
1476 let gemini_models = ModelId::models_for_provider(Provider::Gemini);
1477 assert!(gemini_models.contains(&ModelId::Gemini25Pro));
1478 assert!(!gemini_models.contains(&ModelId::GPT5));
1479
1480 let openai_models = ModelId::models_for_provider(Provider::OpenAI);
1481 assert!(openai_models.contains(&ModelId::GPT5));
1482 assert!(openai_models.contains(&ModelId::GPT5Codex));
1483 assert!(!openai_models.contains(&ModelId::Gemini25Pro));
1484
1485 let anthropic_models = ModelId::models_for_provider(Provider::Anthropic);
1486 assert!(anthropic_models.contains(&ModelId::ClaudeSonnet45));
1487 assert!(anthropic_models.contains(&ModelId::ClaudeSonnet4));
1488 assert!(!anthropic_models.contains(&ModelId::GPT5));
1489
1490 let deepseek_models = ModelId::models_for_provider(Provider::DeepSeek);
1491 assert!(deepseek_models.contains(&ModelId::DeepSeekChat));
1492 assert!(deepseek_models.contains(&ModelId::DeepSeekReasoner));
1493
1494 let openrouter_models = ModelId::models_for_provider(Provider::OpenRouter);
1495 macro_rules! assert_openrouter_models_present {
1496 ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1497 $(assert!(openrouter_models.contains(&ModelId::$variant));)*
1498 };
1499 }
1500 each_openrouter_variant!(assert_openrouter_models_present);
1501
1502 let xai_models = ModelId::models_for_provider(Provider::XAI);
1503 assert!(xai_models.contains(&ModelId::XaiGrok4));
1504 assert!(xai_models.contains(&ModelId::XaiGrok4Mini));
1505 assert!(xai_models.contains(&ModelId::XaiGrok4Code));
1506 assert!(xai_models.contains(&ModelId::XaiGrok4CodeLatest));
1507 assert!(xai_models.contains(&ModelId::XaiGrok4Vision));
1508
1509 let zai_models = ModelId::models_for_provider(Provider::ZAI);
1510 assert!(zai_models.contains(&ModelId::ZaiGlm46));
1511 assert!(zai_models.contains(&ModelId::ZaiGlm45));
1512 assert!(zai_models.contains(&ModelId::ZaiGlm45Air));
1513 assert!(zai_models.contains(&ModelId::ZaiGlm45X));
1514 assert!(zai_models.contains(&ModelId::ZaiGlm45Airx));
1515 assert!(zai_models.contains(&ModelId::ZaiGlm45Flash));
1516 assert!(zai_models.contains(&ModelId::ZaiGlm432b0414128k));
1517
1518 let moonshot_models = ModelId::models_for_provider(Provider::Moonshot);
1519 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK2TurboPreview));
1520 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK20905Preview));
1521 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK20711Preview));
1522 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest));
1523 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest8k));
1524 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest32k));
1525 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest128k));
1526 assert_eq!(moonshot_models.len(), 7);
1527 }
1528
1529 #[test]
1530 fn test_fallback_models() {
1531 let fallbacks = ModelId::fallback_models();
1532 assert!(!fallbacks.is_empty());
1533 assert!(fallbacks.contains(&ModelId::Gemini25Pro));
1534 assert!(fallbacks.contains(&ModelId::GPT5));
1535 assert!(fallbacks.contains(&ModelId::ClaudeOpus41));
1536 assert!(fallbacks.contains(&ModelId::ClaudeSonnet45));
1537 assert!(fallbacks.contains(&ModelId::DeepSeekReasoner));
1538 assert!(fallbacks.contains(&ModelId::MoonshotKimiK20905Preview));
1539 assert!(fallbacks.contains(&ModelId::XaiGrok4));
1540 assert!(fallbacks.contains(&ModelId::ZaiGlm46));
1541 assert!(fallbacks.contains(&ModelId::OpenRouterGrokCodeFast1));
1542 }
1543}