1use serde::{Deserialize, Serialize};
31
32use crate::error::ProviderKind;
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
52#[serde(tag = "provider", content = "model")]
53pub enum Model {
54 OpenAI(OpenAIModel),
56 Anthropic(AnthropicModel),
58 OpenRouter(OpenRouterModel),
60 OpenAICompatible(OpenAICompatibleModel),
62}
63
64impl Model {
65 pub fn as_str(&self) -> &str {
67 match self {
68 Model::OpenAI(m) => m.as_str(),
69 Model::Anthropic(m) => m.as_str(),
70 Model::OpenRouter(m) => m.as_str(),
71 Model::OpenAICompatible(m) => m.as_str(),
72 }
73 }
74
75 pub fn provider(&self) -> ProviderKind {
77 match self {
78 Model::OpenAI(_) => ProviderKind::OpenAI,
79 Model::Anthropic(_) => ProviderKind::Anthropic,
80 Model::OpenRouter(_) => ProviderKind::OpenRouter,
81 Model::OpenAICompatible(_) => ProviderKind::OpenAICompatible,
82 }
83 }
84
85 pub fn gpt4o() -> Self {
89 Model::OpenAI(OpenAIModel::Gpt4o)
90 }
91
92 pub fn gpt4o_mini() -> Self {
94 Model::OpenAI(OpenAIModel::Gpt4oMini)
95 }
96
97 pub fn gpt4_1() -> Self {
99 Model::OpenAI(OpenAIModel::Gpt4_1)
100 }
101
102 pub fn o3_mini() -> Self {
104 Model::OpenAI(OpenAIModel::O3Mini)
105 }
106
107 pub fn o3() -> Self {
109 Model::OpenAI(OpenAIModel::O3)
110 }
111
112 pub fn o4_mini() -> Self {
114 Model::OpenAI(OpenAIModel::O4Mini)
115 }
116
117 pub fn gpt5() -> Self {
119 Model::OpenAI(OpenAIModel::Gpt5)
120 }
121
122 pub fn gpt5_mini() -> Self {
124 Model::OpenAI(OpenAIModel::Gpt5Mini)
125 }
126
127 pub fn gpt5_nano() -> Self {
129 Model::OpenAI(OpenAIModel::Gpt5Nano)
130 }
131
132 pub fn gpt5_codex() -> Self {
134 Model::OpenAI(OpenAIModel::Gpt5Codex)
135 }
136
137 pub fn gpt_5_1() -> Self {
139 Model::OpenAI(OpenAIModel::Gpt5_1)
140 }
141
142 pub fn gpt_5_2() -> Self {
144 Model::OpenAI(OpenAIModel::Gpt5_2)
145 }
146
147 pub fn gpt_5_2_pro() -> Self {
149 Model::OpenAI(OpenAIModel::Gpt5_2Pro)
150 }
151
152 pub fn gpt_5_3_chat() -> Self {
154 Model::OpenAI(OpenAIModel::Gpt5_3Chat)
155 }
156
157 pub fn gpt_5_3_instant() -> Self {
159 Model::OpenAI(OpenAIModel::Gpt5_3Instant)
160 }
161
162 pub fn gpt_5_4() -> Self {
164 Model::OpenAI(OpenAIModel::Gpt5_4)
165 }
166
167 pub fn gpt_5_4_mini() -> Self {
169 Model::OpenAI(OpenAIModel::Gpt5_4Mini)
170 }
171
172 pub fn gpt_5_4_nano() -> Self {
174 Model::OpenAI(OpenAIModel::Gpt5_4Nano)
175 }
176
177 pub fn gpt_5_5() -> Self {
179 Model::OpenAI(OpenAIModel::Gpt5_5)
180 }
181
182 pub fn claude_fable_5() -> Self {
186 Model::Anthropic(AnthropicModel::ClaudeFable5)
187 }
188
189 pub fn claude_opus_48() -> Self {
191 Model::Anthropic(AnthropicModel::ClaudeOpus48)
192 }
193
194 pub fn claude_opus_47() -> Self {
196 Model::Anthropic(AnthropicModel::ClaudeOpus47)
197 }
198
199 pub fn claude_sonnet_46() -> Self {
201 Model::Anthropic(AnthropicModel::ClaudeSonnet46)
202 }
203
204 pub fn claude_opus_46() -> Self {
206 Model::Anthropic(AnthropicModel::ClaudeOpus46)
207 }
208
209 pub fn claude_sonnet_4() -> Self {
211 Model::Anthropic(AnthropicModel::ClaudeSonnet4)
212 }
213
214 pub fn claude_opus_4() -> Self {
216 Model::Anthropic(AnthropicModel::ClaudeOpus4)
217 }
218
219 pub fn claude_opus_41() -> Self {
221 Model::Anthropic(AnthropicModel::ClaudeOpus41)
222 }
223
224 pub fn claude_sonnet_45() -> Self {
226 Model::Anthropic(AnthropicModel::ClaudeSonnet45)
227 }
228
229 pub fn claude_opus_45() -> Self {
231 Model::Anthropic(AnthropicModel::ClaudeOpus45)
232 }
233
234 pub fn claude_haiku_45() -> Self {
236 Model::Anthropic(AnthropicModel::ClaudeHaiku45)
237 }
238
239 pub fn claude_35_sonnet() -> Self {
241 Model::Anthropic(AnthropicModel::Claude35Sonnet)
242 }
243
244 pub fn claude_35_haiku() -> Self {
246 Model::Anthropic(AnthropicModel::Claude35Haiku)
247 }
248
249 pub fn openai_custom(name: impl Into<String>) -> Self {
251 Model::OpenAI(OpenAIModel::Custom(name.into()))
252 }
253
254 pub fn anthropic_custom(name: impl Into<String>) -> Self {
256 Model::Anthropic(AnthropicModel::Custom(name.into()))
257 }
258
259 pub fn openrouter_custom(name: impl Into<String>) -> Self {
261 Model::OpenRouter(OpenRouterModel::Custom(name.into()))
262 }
263
264 pub fn openai_compatible(name: impl Into<String>) -> Self {
282 Model::OpenAICompatible(OpenAICompatibleModel::new(name))
283 }
284
285 pub fn openrouter_auto() -> Self {
287 Model::OpenRouter(OpenRouterModel::Auto)
288 }
289
290 pub fn openrouter_gpt5() -> Self {
292 Model::OpenRouter(OpenRouterModel::Gpt5)
293 }
294
295 pub fn openrouter_claude_sonnet_4_5() -> Self {
297 Model::OpenRouter(OpenRouterModel::ClaudeSonnet4_5)
298 }
299
300 pub fn openrouter_gemini_25_flash() -> Self {
302 Model::OpenRouter(OpenRouterModel::Gemini25Flash)
303 }
304
305 pub fn openrouter_deepseek_r1() -> Self {
307 Model::OpenRouter(OpenRouterModel::DeepseekR1)
308 }
309
310 pub fn openrouter_qwen3_coder() -> Self {
312 Model::OpenRouter(OpenRouterModel::Qwen3Coder)
313 }
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
320pub enum OpenAIModel {
321 Gpt5_5,
323 Gpt5_4,
325 Gpt5_4Mini,
327 Gpt5_4Nano,
329 Gpt5_3Instant,
331 Gpt5_3Chat,
333 Gpt5_2Pro,
335 Gpt5_2,
337 Gpt5_1,
339 Gpt5,
341 Gpt5Mini,
343 Gpt5Nano,
345 Gpt5Codex,
347 Gpt4_1,
349 Gpt4o,
351 Gpt4oMini,
353 Gpt4Turbo,
355 O1Preview,
357 O1Mini,
359 O3Mini,
361 O3,
363 O4Mini,
365 Custom(String),
367}
368
369impl OpenAIModel {
370 pub fn as_str(&self) -> &str {
372 match self {
373 Self::Gpt5_5 => "gpt-5.5",
375 Self::Gpt5_4 => "gpt-5.4",
376 Self::Gpt5_4Mini => "gpt-5.4-mini",
377 Self::Gpt5_4Nano => "gpt-5.4-nano",
378 Self::Gpt5_3Instant => "gpt-5.3-instant",
379 Self::Gpt5_3Chat => "gpt-5.3-chat",
380 Self::Gpt5_2Pro => "gpt-5.2-pro",
381 Self::Gpt5_2 => "gpt-5.2",
382 Self::Gpt5_1 => "gpt-5.1",
383 Self::Gpt5 => "gpt-5",
384 Self::Gpt5Mini => "gpt-5-mini",
385 Self::Gpt5Nano => "gpt-5-nano",
386 Self::Gpt5Codex => "gpt-5-codex",
387 Self::Gpt4_1 => "gpt-4.1",
389 Self::Gpt4o => "gpt-4o",
390 Self::Gpt4oMini => "gpt-4o-mini",
391 Self::Gpt4Turbo => "gpt-4-turbo",
392 Self::O1Preview => "o1-preview",
394 Self::O1Mini => "o1-mini",
395 Self::O3Mini => "o3-mini",
396 Self::O3 => "o3",
397 Self::O4Mini => "o4-mini",
398 Self::Custom(s) => s,
399 }
400 }
401
402 pub fn is_reasoning_model(&self) -> bool {
404 matches!(
405 self,
406 Self::O1Preview | Self::O1Mini | Self::O3Mini | Self::O3 | Self::O4Mini
407 )
408 }
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
415pub enum AnthropicModel {
416 ClaudeFable5,
418 ClaudeOpus48,
420 ClaudeOpus47,
422 ClaudeOpus46,
424 ClaudeSonnet46,
426 ClaudeSonnet45,
428 ClaudeSonnet4,
430 ClaudeOpus45,
432 ClaudeOpus41,
434 ClaudeOpus4,
436 ClaudeHaiku45,
438 Claude35Sonnet,
440 Claude35Haiku,
442 Claude3Opus,
444 Claude3Sonnet,
446 Claude3Haiku,
448 Custom(String),
450}
451
452impl AnthropicModel {
453 pub fn as_str(&self) -> &str {
455 match self {
456 Self::ClaudeFable5 => "claude-fable-5",
458 Self::ClaudeOpus48 => "claude-opus-4-8",
460 Self::ClaudeOpus47 => "claude-opus-4-7",
462 Self::ClaudeOpus46 => "claude-opus-4-6",
464 Self::ClaudeSonnet46 => "claude-sonnet-4-6",
465 Self::ClaudeSonnet45 => "claude-sonnet-4-5",
467 Self::ClaudeOpus45 => "claude-opus-4-5",
468 Self::ClaudeHaiku45 => "claude-haiku-4-5",
469 Self::ClaudeOpus41 => "claude-opus-4-1",
471 Self::ClaudeSonnet4 => "claude-sonnet-4-0",
472 Self::ClaudeOpus4 => "claude-opus-4-0",
473 Self::Claude35Sonnet => "claude-3-5-sonnet-20241022",
475 Self::Claude35Haiku => "claude-3-5-haiku-20241022",
476 Self::Claude3Opus => "claude-3-opus-20240229",
478 Self::Claude3Sonnet => "claude-3-sonnet-20240229",
479 Self::Claude3Haiku => "claude-3-haiku-20240307",
480 Self::Custom(s) => s,
481 }
482 }
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
492pub enum OpenRouterModel {
493 Auto,
495 Free,
497
498 Gpt5,
501 Gpt5Mini,
503 Gpt5Nano,
505 Gpt5Codex,
507 Gpt5_1,
509 Gpt5_2,
511 Gpt5_2Pro,
513 Gpt5_3Chat,
515 Gpt5_4,
517 Gpt5_4Mini,
519 Gpt5_4Nano,
521 Gpt5_4Pro,
523 Gpt5_5,
525 Gpt5_5Pro,
527 Gpt4_1,
529 Gpt4o,
531 O3,
533 O3Pro,
535 O3DeepResearch,
537 O4Mini,
539 GptOss120b,
541
542 ClaudeFable5,
545 ClaudeSonnet4,
547 ClaudeSonnet4_5,
549 ClaudeOpus4_1,
551 ClaudeOpus4_5,
553 ClaudeOpus4_6,
555 ClaudeOpus4_6Fast,
557 ClaudeOpus4_7,
559 ClaudeOpus4_7Fast,
561 ClaudeOpus4_8,
563 ClaudeOpus4_8Fast,
565 ClaudeSonnet4_6,
567 ClaudeHaiku4_5,
569 Claude3_7Sonnet,
571
572 Gemini35Flash,
575 Gemini31ProPreview,
577 Gemini31ProPreviewCustomTools,
579 Gemini31FlashLite,
581 Gemini31FlashLitePreview,
583 Gemini31FlashImagePreview,
585 Gemini3ProImagePreview,
587 Gemini3FlashPreview,
589 Gemini25Pro,
591 Gemini25Flash,
593 Gemini25FlashImage,
595
596 Grok4_3,
599 Grok4_20,
601 Grok4_20MultiAgent,
603 GrokBuild0_1,
605 Grok4,
607 Grok4Fast,
609 Grok4_1Fast,
611 GrokCodeFast1,
613
614 Llama4Maverick,
617 Llama4Scout,
619 Llama3_3_70bInstruct,
621 Llama3_2_11bVisionInstruct,
623
624 Qwen3Max,
627 Qwen3MaxThinking,
629 Qwen3Coder,
631 Qwen3CoderPlus,
633 Qwen3_235bA22b,
635 Qwen3Vl235bA22bInstruct,
637 Qwen3Vl235bA22bThinking,
639 Qwen3_7Max,
641 Qwen3_7Plus,
643 Qwen3_6MaxPreview,
645 Qwen3_6Plus,
647 Qwen3_6Flash,
649
650 DeepseekChatV3_1,
653 DeepseekR1,
655 DeepseekV3_2,
657 DeepseekV4Flash,
659 DeepseekV4Pro,
661
662 MistralLarge,
665 MistralMedium3_1,
667 Codestral2508,
669 DevstralMedium,
671 PixtralLarge2411,
673 MistralLarge2512,
675 MistralMedium3_5,
677 Devstral2512,
679 Ministral14b2512,
681
682 SonarPro,
685 SonarReasoningPro,
687 SonarDeepResearch,
689
690 CommandA,
693
694 KimiK2_5,
697 KimiK2Thinking,
699 KimiK2_6,
701 KimiK2_7Code,
703
704 Glm5,
707 Glm5_1,
709 Glm5Turbo,
711 Glm4_7,
713
714 MimoV2Omni,
717 MimoV2Flash,
719 MimoV2_5,
721 MimoV2_5Pro,
723
724 Custom(String),
726}
727
728impl OpenRouterModel {
729 pub fn as_str(&self) -> &str {
731 match self {
732 Self::Auto => "openrouter/auto",
733 Self::Free => "openrouter/free",
734 Self::Gpt5 => "openai/gpt-5",
735 Self::Gpt5Mini => "openai/gpt-5-mini",
736 Self::Gpt5Nano => "openai/gpt-5-nano",
737 Self::Gpt5Codex => "openai/gpt-5-codex",
738 Self::Gpt5_1 => "openai/gpt-5.1",
739 Self::Gpt5_2 => "openai/gpt-5.2",
740 Self::Gpt5_2Pro => "openai/gpt-5.2-pro",
741 Self::Gpt5_3Chat => "openai/gpt-5.3-chat",
742 Self::Gpt5_4 => "openai/gpt-5.4",
743 Self::Gpt5_4Mini => "openai/gpt-5.4-mini",
744 Self::Gpt5_4Nano => "openai/gpt-5.4-nano",
745 Self::Gpt5_4Pro => "openai/gpt-5.4-pro",
746 Self::Gpt5_5 => "openai/gpt-5.5",
747 Self::Gpt5_5Pro => "openai/gpt-5.5-pro",
748 Self::Gpt4_1 => "openai/gpt-4.1",
749 Self::Gpt4o => "openai/gpt-4o",
750 Self::O3 => "openai/o3",
751 Self::O3Pro => "openai/o3-pro",
752 Self::O3DeepResearch => "openai/o3-deep-research",
753 Self::O4Mini => "openai/o4-mini",
754 Self::GptOss120b => "openai/gpt-oss-120b",
755 Self::ClaudeFable5 => "anthropic/claude-fable-5",
756 Self::ClaudeSonnet4 => "anthropic/claude-sonnet-4",
757 Self::ClaudeSonnet4_5 => "anthropic/claude-sonnet-4.5",
758 Self::ClaudeOpus4_1 => "anthropic/claude-opus-4.1",
759 Self::ClaudeOpus4_5 => "anthropic/claude-opus-4.5",
760 Self::ClaudeOpus4_6 => "anthropic/claude-opus-4.6",
761 Self::ClaudeOpus4_6Fast => "anthropic/claude-opus-4.6-fast",
762 Self::ClaudeOpus4_7 => "anthropic/claude-opus-4.7",
763 Self::ClaudeOpus4_7Fast => "anthropic/claude-opus-4.7-fast",
764 Self::ClaudeOpus4_8 => "anthropic/claude-opus-4.8",
765 Self::ClaudeOpus4_8Fast => "anthropic/claude-opus-4.8-fast",
766 Self::ClaudeSonnet4_6 => "anthropic/claude-sonnet-4.6",
767 Self::ClaudeHaiku4_5 => "anthropic/claude-haiku-4.5",
768 Self::Claude3_7Sonnet => "anthropic/claude-3.7-sonnet",
769 Self::Gemini35Flash => "google/gemini-3.5-flash",
770 Self::Gemini31ProPreview => "google/gemini-3.1-pro-preview",
771 Self::Gemini31ProPreviewCustomTools => "google/gemini-3.1-pro-preview-customtools",
772 Self::Gemini31FlashLite => "google/gemini-3.1-flash-lite",
773 Self::Gemini31FlashLitePreview => "google/gemini-3.1-flash-lite-preview",
774 Self::Gemini31FlashImagePreview => "google/gemini-3.1-flash-image-preview",
775 Self::Gemini3ProImagePreview => "google/gemini-3-pro-image-preview",
776 Self::Gemini3FlashPreview => "google/gemini-3-flash-preview",
777 Self::Gemini25Pro => "google/gemini-2.5-pro",
778 Self::Gemini25Flash => "google/gemini-2.5-flash",
779 Self::Gemini25FlashImage => "google/gemini-2.5-flash-image",
780 Self::Grok4_3 => "x-ai/grok-4.3",
781 Self::Grok4_20 => "x-ai/grok-4.20",
782 Self::Grok4_20MultiAgent => "x-ai/grok-4.20-multi-agent",
783 Self::GrokBuild0_1 => "x-ai/grok-build-0.1",
784 Self::Grok4 => "x-ai/grok-4",
785 Self::Grok4Fast => "x-ai/grok-4-fast",
786 Self::Grok4_1Fast => "x-ai/grok-4.1-fast",
787 Self::GrokCodeFast1 => "x-ai/grok-code-fast-1",
788 Self::Llama4Maverick => "meta-llama/llama-4-maverick",
789 Self::Llama4Scout => "meta-llama/llama-4-scout",
790 Self::Llama3_3_70bInstruct => "meta-llama/llama-3.3-70b-instruct",
791 Self::Llama3_2_11bVisionInstruct => "meta-llama/llama-3.2-11b-vision-instruct",
792 Self::Qwen3Max => "qwen/qwen3-max",
793 Self::Qwen3MaxThinking => "qwen/qwen3-max-thinking",
794 Self::Qwen3Coder => "qwen/qwen3-coder",
795 Self::Qwen3CoderPlus => "qwen/qwen3-coder-plus",
796 Self::Qwen3_235bA22b => "qwen/qwen3-235b-a22b",
797 Self::Qwen3Vl235bA22bInstruct => "qwen/qwen3-vl-235b-a22b-instruct",
798 Self::Qwen3Vl235bA22bThinking => "qwen/qwen3-vl-235b-a22b-thinking",
799 Self::Qwen3_7Max => "qwen/qwen3.7-max",
800 Self::Qwen3_7Plus => "qwen/qwen3.7-plus",
801 Self::Qwen3_6MaxPreview => "qwen/qwen3.6-max-preview",
802 Self::Qwen3_6Plus => "qwen/qwen3.6-plus",
803 Self::Qwen3_6Flash => "qwen/qwen3.6-flash",
804 Self::DeepseekChatV3_1 => "deepseek/deepseek-chat-v3.1",
805 Self::DeepseekR1 => "deepseek/deepseek-r1",
806 Self::DeepseekV3_2 => "deepseek/deepseek-v3.2",
807 Self::DeepseekV4Flash => "deepseek/deepseek-v4-flash",
808 Self::DeepseekV4Pro => "deepseek/deepseek-v4-pro",
809 Self::MistralLarge => "mistralai/mistral-large",
810 Self::MistralMedium3_1 => "mistralai/mistral-medium-3.1",
811 Self::Codestral2508 => "mistralai/codestral-2508",
812 Self::DevstralMedium => "mistralai/devstral-medium",
813 Self::PixtralLarge2411 => "mistralai/pixtral-large-2411",
814 Self::MistralLarge2512 => "mistralai/mistral-large-2512",
815 Self::MistralMedium3_5 => "mistralai/mistral-medium-3-5",
816 Self::Devstral2512 => "mistralai/devstral-2512",
817 Self::Ministral14b2512 => "mistralai/ministral-14b-2512",
818 Self::SonarPro => "perplexity/sonar-pro",
819 Self::SonarReasoningPro => "perplexity/sonar-reasoning-pro",
820 Self::SonarDeepResearch => "perplexity/sonar-deep-research",
821 Self::CommandA => "cohere/command-a",
822 Self::KimiK2_5 => "moonshotai/kimi-k2.5",
823 Self::KimiK2Thinking => "moonshotai/kimi-k2-thinking",
824 Self::KimiK2_6 => "moonshotai/kimi-k2.6",
825 Self::KimiK2_7Code => "moonshotai/kimi-k2.7-code",
826 Self::Glm5 => "z-ai/glm-5",
827 Self::Glm5_1 => "z-ai/glm-5.1",
828 Self::Glm5Turbo => "z-ai/glm-5-turbo",
829 Self::Glm4_7 => "z-ai/glm-4.7",
830 Self::MimoV2Omni => "xiaomi/mimo-v2-omni",
831 Self::MimoV2Flash => "xiaomi/mimo-v2-flash",
832 Self::MimoV2_5 => "xiaomi/mimo-v2.5",
833 Self::MimoV2_5Pro => "xiaomi/mimo-v2.5-pro",
834 Self::Custom(s) => s,
835 }
836 }
837}
838
839impl std::str::FromStr for OpenRouterModel {
840 type Err = std::convert::Infallible;
841
842 fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
843 let model = match value {
844 "openrouter/auto" => Self::Auto,
845 "openrouter/free" => Self::Free,
846 "openai/gpt-5" => Self::Gpt5,
847 "openai/gpt-5-mini" => Self::Gpt5Mini,
848 "openai/gpt-5-nano" => Self::Gpt5Nano,
849 "openai/gpt-5-codex" => Self::Gpt5Codex,
850 "openai/gpt-5.1" => Self::Gpt5_1,
851 "openai/gpt-5.2" => Self::Gpt5_2,
852 "openai/gpt-5.2-pro" => Self::Gpt5_2Pro,
853 "openai/gpt-5.3-chat" => Self::Gpt5_3Chat,
854 "openai/gpt-5.4" => Self::Gpt5_4,
855 "openai/gpt-5.4-mini" => Self::Gpt5_4Mini,
856 "openai/gpt-5.4-nano" => Self::Gpt5_4Nano,
857 "openai/gpt-5.4-pro" => Self::Gpt5_4Pro,
858 "openai/gpt-5.5" => Self::Gpt5_5,
859 "openai/gpt-5.5-pro" => Self::Gpt5_5Pro,
860 "openai/gpt-4.1" => Self::Gpt4_1,
861 "openai/gpt-4o" => Self::Gpt4o,
862 "openai/o3" => Self::O3,
863 "openai/o3-pro" => Self::O3Pro,
864 "openai/o3-deep-research" => Self::O3DeepResearch,
865 "openai/o4-mini" => Self::O4Mini,
866 "openai/gpt-oss-120b" => Self::GptOss120b,
867 "anthropic/claude-fable-5" => Self::ClaudeFable5,
868 "anthropic/claude-sonnet-4" => Self::ClaudeSonnet4,
869 "anthropic/claude-sonnet-4.5" => Self::ClaudeSonnet4_5,
870 "anthropic/claude-opus-4.1" => Self::ClaudeOpus4_1,
871 "anthropic/claude-opus-4.5" => Self::ClaudeOpus4_5,
872 "anthropic/claude-opus-4.6" => Self::ClaudeOpus4_6,
873 "anthropic/claude-opus-4.6-fast" => Self::ClaudeOpus4_6Fast,
874 "anthropic/claude-opus-4.7" => Self::ClaudeOpus4_7,
875 "anthropic/claude-opus-4.7-fast" => Self::ClaudeOpus4_7Fast,
876 "anthropic/claude-opus-4.8" => Self::ClaudeOpus4_8,
877 "anthropic/claude-opus-4.8-fast" => Self::ClaudeOpus4_8Fast,
878 "anthropic/claude-sonnet-4.6" => Self::ClaudeSonnet4_6,
879 "anthropic/claude-haiku-4.5" => Self::ClaudeHaiku4_5,
880 "anthropic/claude-3.7-sonnet" => Self::Claude3_7Sonnet,
881 "google/gemini-3.5-flash" => Self::Gemini35Flash,
882 "google/gemini-3.1-pro-preview" => Self::Gemini31ProPreview,
883 "google/gemini-3.1-pro-preview-customtools" => Self::Gemini31ProPreviewCustomTools,
884 "google/gemini-3.1-flash-lite" => Self::Gemini31FlashLite,
885 "google/gemini-3.1-flash-lite-preview" => Self::Gemini31FlashLitePreview,
886 "google/gemini-3.1-flash-image-preview" => Self::Gemini31FlashImagePreview,
887 "google/gemini-3-pro-image-preview" => Self::Gemini3ProImagePreview,
888 "google/gemini-3-flash-preview" => Self::Gemini3FlashPreview,
889 "google/gemini-2.5-pro" => Self::Gemini25Pro,
890 "google/gemini-2.5-flash" => Self::Gemini25Flash,
891 "google/gemini-2.5-flash-image" => Self::Gemini25FlashImage,
892 "x-ai/grok-4.3" => Self::Grok4_3,
893 "x-ai/grok-4.20" => Self::Grok4_20,
894 "x-ai/grok-4.20-multi-agent" => Self::Grok4_20MultiAgent,
895 "x-ai/grok-build-0.1" => Self::GrokBuild0_1,
896 "x-ai/grok-4" => Self::Grok4,
897 "x-ai/grok-4-fast" => Self::Grok4Fast,
898 "x-ai/grok-4.1-fast" => Self::Grok4_1Fast,
899 "x-ai/grok-code-fast-1" => Self::GrokCodeFast1,
900 "meta-llama/llama-4-maverick" => Self::Llama4Maverick,
901 "meta-llama/llama-4-scout" => Self::Llama4Scout,
902 "meta-llama/llama-3.3-70b-instruct" => Self::Llama3_3_70bInstruct,
903 "meta-llama/llama-3.2-11b-vision-instruct" => Self::Llama3_2_11bVisionInstruct,
904 "qwen/qwen3-max" => Self::Qwen3Max,
905 "qwen/qwen3-max-thinking" => Self::Qwen3MaxThinking,
906 "qwen/qwen3-coder" => Self::Qwen3Coder,
907 "qwen/qwen3-coder-plus" => Self::Qwen3CoderPlus,
908 "qwen/qwen3-235b-a22b" => Self::Qwen3_235bA22b,
909 "qwen/qwen3-vl-235b-a22b-instruct" => Self::Qwen3Vl235bA22bInstruct,
910 "qwen/qwen3-vl-235b-a22b-thinking" => Self::Qwen3Vl235bA22bThinking,
911 "qwen/qwen3.7-max" => Self::Qwen3_7Max,
912 "qwen/qwen3.7-plus" => Self::Qwen3_7Plus,
913 "qwen/qwen3.6-max-preview" => Self::Qwen3_6MaxPreview,
914 "qwen/qwen3.6-plus" => Self::Qwen3_6Plus,
915 "qwen/qwen3.6-flash" => Self::Qwen3_6Flash,
916 "deepseek/deepseek-chat-v3.1" => Self::DeepseekChatV3_1,
917 "deepseek/deepseek-r1" => Self::DeepseekR1,
918 "deepseek/deepseek-v3.2" => Self::DeepseekV3_2,
919 "deepseek/deepseek-v4-flash" => Self::DeepseekV4Flash,
920 "deepseek/deepseek-v4-pro" => Self::DeepseekV4Pro,
921 "mistralai/mistral-large" => Self::MistralLarge,
922 "mistralai/mistral-medium-3.1" => Self::MistralMedium3_1,
923 "mistralai/codestral-2508" => Self::Codestral2508,
924 "mistralai/devstral-medium" => Self::DevstralMedium,
925 "mistralai/pixtral-large-2411" => Self::PixtralLarge2411,
926 "mistralai/mistral-large-2512" => Self::MistralLarge2512,
927 "mistralai/mistral-medium-3-5" => Self::MistralMedium3_5,
928 "mistralai/devstral-2512" => Self::Devstral2512,
929 "mistralai/ministral-14b-2512" => Self::Ministral14b2512,
930 "perplexity/sonar-pro" => Self::SonarPro,
931 "perplexity/sonar-reasoning-pro" => Self::SonarReasoningPro,
932 "perplexity/sonar-deep-research" => Self::SonarDeepResearch,
933 "cohere/command-a" => Self::CommandA,
934 "moonshotai/kimi-k2.5" => Self::KimiK2_5,
935 "moonshotai/kimi-k2-thinking" => Self::KimiK2Thinking,
936 "moonshotai/kimi-k2.6" => Self::KimiK2_6,
937 "moonshotai/kimi-k2.7-code" => Self::KimiK2_7Code,
938 "z-ai/glm-5" => Self::Glm5,
939 "z-ai/glm-5.1" => Self::Glm5_1,
940 "z-ai/glm-5-turbo" => Self::Glm5Turbo,
941 "z-ai/glm-4.7" => Self::Glm4_7,
942 "xiaomi/mimo-v2-omni" => Self::MimoV2Omni,
943 "xiaomi/mimo-v2-flash" => Self::MimoV2Flash,
944 "xiaomi/mimo-v2.5" => Self::MimoV2_5,
945 "xiaomi/mimo-v2.5-pro" => Self::MimoV2_5Pro,
946 other => Self::Custom(other.to_string()),
947 };
948
949 Ok(model)
950 }
951}
952
953#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
971#[serde(transparent)]
972pub struct OpenAICompatibleModel(String);
973
974impl OpenAICompatibleModel {
975 pub fn new(name: impl Into<String>) -> Self {
977 Self(name.into())
978 }
979
980 pub fn as_str(&self) -> &str {
982 &self.0
983 }
984}
985
986impl std::fmt::Display for OpenAICompatibleModel {
987 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
988 f.write_str(&self.0)
989 }
990}
991
992impl std::str::FromStr for OpenAICompatibleModel {
993 type Err = std::convert::Infallible;
994
995 fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
996 Ok(Self::new(value))
997 }
998}
999
1000impl From<String> for OpenAICompatibleModel {
1001 fn from(value: String) -> Self {
1002 Self::new(value)
1003 }
1004}
1005
1006impl From<&str> for OpenAICompatibleModel {
1007 fn from(value: &str) -> Self {
1008 Self::new(value)
1009 }
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014 use super::*;
1015
1016 #[test]
1017 fn model_provider_mapping() {
1018 assert_eq!(Model::gpt4o_mini().provider(), ProviderKind::OpenAI);
1019 assert_eq!(
1020 Model::claude_sonnet_45().provider(),
1021 ProviderKind::Anthropic
1022 );
1023 assert_eq!(Model::gpt_5_3_instant().provider(), ProviderKind::OpenAI);
1024 assert_eq!(
1025 Model::claude_sonnet_46().provider(),
1026 ProviderKind::Anthropic
1027 );
1028 assert_eq!(Model::claude_opus_46().provider(), ProviderKind::Anthropic);
1029 assert_eq!(
1030 Model::openrouter_auto().provider(),
1031 ProviderKind::OpenRouter
1032 );
1033 }
1034
1035 #[test]
1036 fn model_string_identifiers() {
1037 assert_eq!(Model::gpt4o_mini().as_str(), "gpt-4o-mini");
1038 assert_eq!(Model::claude_sonnet_45().as_str(), "claude-sonnet-4-5");
1039 assert_eq!(Model::o3_mini().as_str(), "o3-mini");
1040 assert_eq!(Model::gpt_5_3_instant().as_str(), "gpt-5.3-instant");
1041 assert_eq!(Model::claude_sonnet_46().as_str(), "claude-sonnet-4-6");
1042 assert_eq!(Model::claude_opus_46().as_str(), "claude-opus-4-6");
1043 assert_eq!(Model::openrouter_auto().as_str(), "openrouter/auto");
1044 assert_eq!(Model::openrouter_gpt5().as_str(), "openai/gpt-5");
1045 assert_eq!(Model::openrouter_qwen3_coder().as_str(), "qwen/qwen3-coder");
1046 }
1047
1048 #[test]
1049 fn openrouter_model_from_str_handles_known_and_custom_models() {
1050 assert_eq!(
1051 "openrouter/auto".parse::<OpenRouterModel>(),
1052 Ok(OpenRouterModel::Auto)
1053 );
1054 assert_eq!(
1055 "acme/custom-model".parse::<OpenRouterModel>(),
1056 Ok(OpenRouterModel::Custom("acme/custom-model".to_string()))
1057 );
1058 }
1059
1060 #[test]
1061 fn reasoning_model_detection() {
1062 assert!(OpenAIModel::O3.is_reasoning_model());
1063 assert!(OpenAIModel::O4Mini.is_reasoning_model());
1064 assert!(!OpenAIModel::Gpt4oMini.is_reasoning_model());
1065 }
1066
1067 #[test]
1068 fn openai_gpt5_family_string_identifiers() {
1069 let cases = [
1070 (OpenAIModel::Gpt5_5, "gpt-5.5"),
1071 (OpenAIModel::Gpt5_4, "gpt-5.4"),
1072 (OpenAIModel::Gpt5_4Mini, "gpt-5.4-mini"),
1073 (OpenAIModel::Gpt5_4Nano, "gpt-5.4-nano"),
1074 (OpenAIModel::Gpt5, "gpt-5"),
1075 (OpenAIModel::Gpt5Mini, "gpt-5-mini"),
1076 (OpenAIModel::Gpt5Nano, "gpt-5-nano"),
1077 (OpenAIModel::Gpt5Codex, "gpt-5-codex"),
1078 (OpenAIModel::Gpt5_1, "gpt-5.1"),
1079 (OpenAIModel::Gpt5_2, "gpt-5.2"),
1080 (OpenAIModel::Gpt5_2Pro, "gpt-5.2-pro"),
1081 (OpenAIModel::Gpt5_3Instant, "gpt-5.3-instant"),
1082 (OpenAIModel::Gpt5_3Chat, "gpt-5.3-chat"),
1083 (OpenAIModel::Gpt4_1, "gpt-4.1"),
1084 ];
1085
1086 for (model, expected) in cases {
1087 assert_eq!(model.as_str(), expected);
1088 assert!(!model.is_reasoning_model());
1090 }
1091 }
1092
1093 #[test]
1094 fn model_gpt5_constructors_map_to_provider() {
1095 assert_eq!(Model::gpt5().as_str(), "gpt-5");
1096 assert_eq!(Model::gpt5().provider(), ProviderKind::OpenAI);
1097 assert_eq!(Model::gpt_5_5().as_str(), "gpt-5.5");
1098 assert_eq!(Model::gpt_5_4().as_str(), "gpt-5.4");
1099 assert_eq!(Model::gpt_5_4_mini().as_str(), "gpt-5.4-mini");
1100 assert_eq!(Model::gpt_5_4_nano().as_str(), "gpt-5.4-nano");
1101 assert_eq!(Model::gpt4_1().as_str(), "gpt-4.1");
1102 }
1103
1104 #[test]
1105 fn anthropic_latest_models_string_identifiers() {
1106 let cases = [
1107 (AnthropicModel::ClaudeFable5, "claude-fable-5"),
1108 (AnthropicModel::ClaudeOpus48, "claude-opus-4-8"),
1109 (AnthropicModel::ClaudeOpus47, "claude-opus-4-7"),
1110 (AnthropicModel::ClaudeOpus41, "claude-opus-4-1"),
1111 ];
1112
1113 for (model, expected) in cases {
1114 assert_eq!(model.as_str(), expected);
1115 }
1116
1117 assert_eq!(Model::claude_fable_5().as_str(), "claude-fable-5");
1118 assert_eq!(Model::claude_opus_48().as_str(), "claude-opus-4-8");
1119 assert_eq!(Model::claude_opus_47().as_str(), "claude-opus-4-7");
1120 assert_eq!(Model::claude_opus_41().provider(), ProviderKind::Anthropic);
1121 }
1122
1123 #[test]
1124 fn openrouter_latest_catalog_models_round_trip() {
1125 let cases = [
1126 (OpenRouterModel::Gpt5_5, "openai/gpt-5.5"),
1127 (OpenRouterModel::ClaudeFable5, "anthropic/claude-fable-5"),
1128 (OpenRouterModel::ClaudeOpus4_8, "anthropic/claude-opus-4.8"),
1129 (OpenRouterModel::Gemini35Flash, "google/gemini-3.5-flash"),
1130 (OpenRouterModel::Grok4_3, "x-ai/grok-4.3"),
1131 (OpenRouterModel::Qwen3_7Max, "qwen/qwen3.7-max"),
1132 (OpenRouterModel::DeepseekV4Pro, "deepseek/deepseek-v4-pro"),
1133 (OpenRouterModel::KimiK2_7Code, "moonshotai/kimi-k2.7-code"),
1134 ];
1135
1136 for (model, expected) in cases {
1137 assert_eq!(model.as_str(), expected);
1138 assert_eq!(expected.parse::<OpenRouterModel>(), Ok(model));
1139 }
1140 }
1141}