Skip to main content

claude_codes/
models.rs

1//! Convenience enum for the models the Claude CLI exposes.
2//!
3//! Variants are keyed by human-friendly model names; [`ClaudeModel::cli_arg`]
4//! returns the exact string to pass to `claude --model` (and therefore to
5//! `ClaudeCliBuilder::model`, which accepts the enum directly via
6//! `Into<String>`).
7//!
8//! The model table was extracted from the Claude CLI 2.1.219 binary's model
9//! registry. Aliases (`sonnet`, `opus`, …) float to the newest model of that
10//! family on the CLI side; pinned variants name one concrete model. Unknown
11//! or future model strings round-trip through [`ClaudeModel::Custom`].
12
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use std::fmt;
15
16/// A model selector accepted by `claude --model`.
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum ClaudeModel {
19    /// Newest Sonnet-family model (floating alias `sonnet`).
20    Sonnet,
21    /// Newest Opus-family model (floating alias `opus`). Resolves to
22    /// `claude-opus-5-5` first-party as of CLI 2.1.280.
23    Opus,
24    /// Newest Haiku-family model (floating alias `haiku`).
25    Haiku,
26    /// Newest Fable-family model (floating alias `fable`).
27    Fable,
28    /// The most capable model available to the account (alias `best`).
29    Best,
30    /// Opus for plan mode, Sonnet otherwise (alias `opusplan`).
31    OpusPlan,
32    /// Newest Sonnet with the 1M-token context beta (alias `sonnet[1m]`).
33    Sonnet1m,
34    /// Newest Opus with the 1M-token context beta (alias `opus[1m]`).
35    Opus1m,
36    /// Newest Fable with the 1M-token context beta (alias `fable[1m]`).
37    Fable1m,
38    /// Fable 5.1 (`claude-fable-5-1`).
39    Fable51,
40    /// Fable 5 (`claude-fable-5`).
41    Fable5,
42    /// Mythos 5.1 (`claude-mythos-5-1`).
43    Mythos51,
44    /// Mythos 5 (`claude-mythos-5`).
45    Mythos5,
46    /// Opus 5.5 (`claude-opus-5-5`), released 2026-09-22 — the new
47    /// first-party float target of the `opus` alias.
48    Opus55,
49    /// Opus 5 (`claude-opus-5`).
50    Opus5,
51    /// Opus 4.8 (`claude-opus-4-8`).
52    Opus48,
53    /// Opus 4.7 (`claude-opus-4-7`).
54    Opus47,
55    /// Opus 4.6 (`claude-opus-4-6`).
56    Opus46,
57    /// Opus 4.5 (`claude-opus-4-5`).
58    Opus45,
59    /// Opus 4.1 (`claude-opus-4-1`).
60    Opus41,
61    /// Opus 4 (`claude-opus-4-0`).
62    Opus40,
63    /// Sonnet 5 (`claude-sonnet-5`).
64    Sonnet5,
65    /// Sonnet 4.6 (`claude-sonnet-4-6`).
66    Sonnet46,
67    /// Sonnet 4.5 (`claude-sonnet-4-5`).
68    Sonnet45,
69    /// Sonnet 4 (`claude-sonnet-4-0`).
70    Sonnet40,
71    /// Sonnet 3.7 (`claude-3-7-sonnet`).
72    Sonnet37,
73    /// Sonnet 3.5 (`claude-3-5-sonnet`).
74    Sonnet35,
75    /// Haiku 4.5 (`claude-haiku-4-5`).
76    Haiku45,
77    /// Haiku 3.5 (`claude-3-5-haiku`).
78    Haiku35,
79    /// A model string not yet known to this version of the crate. Passed to
80    /// the CLI verbatim.
81    Custom(String),
82}
83
84impl ClaudeModel {
85    /// The string to pass to `claude --model` for this model.
86    pub fn cli_arg(&self) -> &str {
87        match self {
88            Self::Sonnet => "sonnet",
89            Self::Opus => "opus",
90            Self::Haiku => "haiku",
91            Self::Fable => "fable",
92            Self::Best => "best",
93            Self::OpusPlan => "opusplan",
94            Self::Sonnet1m => "sonnet[1m]",
95            Self::Opus1m => "opus[1m]",
96            Self::Fable1m => "fable[1m]",
97            Self::Fable51 => "claude-fable-5-1",
98            Self::Fable5 => "claude-fable-5",
99            Self::Mythos51 => "claude-mythos-5-1",
100            Self::Mythos5 => "claude-mythos-5",
101            Self::Opus55 => "claude-opus-5-5",
102            Self::Opus5 => "claude-opus-5",
103            Self::Opus48 => "claude-opus-4-8",
104            Self::Opus47 => "claude-opus-4-7",
105            Self::Opus46 => "claude-opus-4-6",
106            Self::Opus45 => "claude-opus-4-5",
107            Self::Opus41 => "claude-opus-4-1",
108            Self::Opus40 => "claude-opus-4-0",
109            Self::Sonnet5 => "claude-sonnet-5",
110            Self::Sonnet46 => "claude-sonnet-4-6",
111            Self::Sonnet45 => "claude-sonnet-4-5",
112            Self::Sonnet40 => "claude-sonnet-4-0",
113            Self::Sonnet37 => "claude-3-7-sonnet",
114            Self::Sonnet35 => "claude-3-5-sonnet",
115            Self::Haiku45 => "claude-haiku-4-5",
116            Self::Haiku35 => "claude-3-5-haiku",
117            Self::Custom(s) => s.as_str(),
118        }
119    }
120
121    /// Alias for [`cli_arg`](Self::cli_arg), matching the crate's string-enum
122    /// convention.
123    pub fn as_str(&self) -> &str {
124        self.cli_arg()
125    }
126
127    /// Human-friendly display name, matching what the CLI's own UI renders.
128    pub fn display_name(&self) -> &str {
129        match self {
130            Self::Sonnet => "Sonnet (latest)",
131            Self::Opus => "Opus (latest)",
132            Self::Haiku => "Haiku (latest)",
133            Self::Fable => "Fable (latest)",
134            Self::Best => "Best available",
135            Self::OpusPlan => "Opus (plan mode) / Sonnet",
136            Self::Sonnet1m => "Sonnet (latest, 1M context)",
137            Self::Opus1m => "Opus (latest, 1M context)",
138            Self::Fable1m => "Fable (latest, 1M context)",
139            Self::Fable51 => "Fable 5.1",
140            Self::Fable5 => "Fable 5",
141            Self::Mythos51 => "Mythos 5.1",
142            Self::Mythos5 => "Mythos 5",
143            Self::Opus55 => "Opus 5.5",
144            Self::Opus5 => "Opus 5",
145            Self::Opus48 => "Opus 4.8",
146            Self::Opus47 => "Opus 4.7",
147            Self::Opus46 => "Opus 4.6",
148            Self::Opus45 => "Opus 4.5",
149            Self::Opus41 => "Opus 4.1",
150            Self::Opus40 => "Opus 4",
151            Self::Sonnet5 => "Sonnet 5",
152            Self::Sonnet46 => "Sonnet 4.6",
153            Self::Sonnet45 => "Sonnet 4.5",
154            Self::Sonnet40 => "Sonnet 4",
155            Self::Sonnet37 => "Sonnet 3.7",
156            Self::Sonnet35 => "Sonnet 3.5",
157            Self::Haiku45 => "Haiku 4.5",
158            Self::Haiku35 => "Haiku 3.5",
159            Self::Custom(s) => s.as_str(),
160        }
161    }
162
163    /// True for the floating aliases (`sonnet`, `opus`, …) that the CLI
164    /// resolves to the newest model of the family at session start.
165    pub fn is_alias(&self) -> bool {
166        matches!(
167            self,
168            Self::Sonnet
169                | Self::Opus
170                | Self::Haiku
171                | Self::Fable
172                | Self::Best
173                | Self::OpusPlan
174                | Self::Sonnet1m
175                | Self::Opus1m
176                | Self::Fable1m
177        )
178    }
179
180    /// Every model known to this version of the crate, aliases first.
181    pub fn known() -> &'static [ClaudeModel] {
182        &[
183            Self::Sonnet,
184            Self::Opus,
185            Self::Haiku,
186            Self::Fable,
187            Self::Best,
188            Self::OpusPlan,
189            Self::Sonnet1m,
190            Self::Opus1m,
191            Self::Fable1m,
192            Self::Fable51,
193            Self::Fable5,
194            Self::Mythos51,
195            Self::Mythos5,
196            Self::Opus55,
197            Self::Opus5,
198            Self::Opus48,
199            Self::Opus47,
200            Self::Opus46,
201            Self::Opus45,
202            Self::Opus41,
203            Self::Opus40,
204            Self::Sonnet5,
205            Self::Sonnet46,
206            Self::Sonnet45,
207            Self::Sonnet40,
208            Self::Sonnet37,
209            Self::Sonnet35,
210            Self::Haiku45,
211            Self::Haiku35,
212        ]
213    }
214}
215
216impl fmt::Display for ClaudeModel {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        f.write_str(self.cli_arg())
219    }
220}
221
222impl From<&str> for ClaudeModel {
223    fn from(s: &str) -> Self {
224        match s {
225            "sonnet" => Self::Sonnet,
226            "opus" => Self::Opus,
227            "haiku" => Self::Haiku,
228            "fable" => Self::Fable,
229            "best" => Self::Best,
230            "opusplan" => Self::OpusPlan,
231            "sonnet[1m]" => Self::Sonnet1m,
232            "opus[1m]" => Self::Opus1m,
233            "fable[1m]" => Self::Fable1m,
234            "claude-fable-5-1" => Self::Fable51,
235            "claude-fable-5" => Self::Fable5,
236            "claude-mythos-5-1" => Self::Mythos51,
237            "claude-mythos-5" => Self::Mythos5,
238            "claude-opus-5-5" => Self::Opus55,
239            "claude-opus-5" => Self::Opus5,
240            "claude-opus-4-8" => Self::Opus48,
241            "claude-opus-4-7" => Self::Opus47,
242            "claude-opus-4-6" => Self::Opus46,
243            "claude-opus-4-5" => Self::Opus45,
244            "claude-opus-4-1" => Self::Opus41,
245            "claude-opus-4-0" => Self::Opus40,
246            "claude-sonnet-5" => Self::Sonnet5,
247            "claude-sonnet-4-6" => Self::Sonnet46,
248            "claude-sonnet-4-5" => Self::Sonnet45,
249            "claude-sonnet-4-0" => Self::Sonnet40,
250            "claude-3-7-sonnet" => Self::Sonnet37,
251            "claude-3-5-sonnet" => Self::Sonnet35,
252            "claude-haiku-4-5" => Self::Haiku45,
253            "claude-3-5-haiku" => Self::Haiku35,
254            other => Self::Custom(other.to_string()),
255        }
256    }
257}
258
259impl From<ClaudeModel> for String {
260    fn from(model: ClaudeModel) -> Self {
261        model.cli_arg().to_string()
262    }
263}
264
265impl Serialize for ClaudeModel {
266    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
267        serializer.serialize_str(self.cli_arg())
268    }
269}
270
271impl<'de> Deserialize<'de> for ClaudeModel {
272    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
273        let s = String::deserialize(deserializer)?;
274        Ok(Self::from(s.as_str()))
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::ClaudeModel;
281
282    #[test]
283    fn test_cli_arg_round_trip() {
284        for model in ClaudeModel::known() {
285            assert_eq!(&ClaudeModel::from(model.cli_arg()), model);
286        }
287        assert_eq!(
288            ClaudeModel::from("claude-nova-7"),
289            ClaudeModel::Custom("claude-nova-7".to_string())
290        );
291        assert_eq!(
292            ClaudeModel::from("claude-nova-7").cli_arg(),
293            "claude-nova-7"
294        );
295    }
296
297    #[test]
298    fn test_into_string_matches_cli_arg() {
299        let s: String = ClaudeModel::Sonnet5.into();
300        assert_eq!(s, "claude-sonnet-5");
301        let s: String = ClaudeModel::Opus1m.into();
302        assert_eq!(s, "opus[1m]");
303    }
304
305    #[test]
306    fn test_display_names() {
307        assert_eq!(ClaudeModel::Opus5.display_name(), "Opus 5");
308        assert_eq!(ClaudeModel::Opus5.cli_arg(), "claude-opus-5");
309        assert_eq!(ClaudeModel::Fable5.display_name(), "Fable 5");
310        assert_eq!(ClaudeModel::Opus48.display_name(), "Opus 4.8");
311        assert_eq!(ClaudeModel::Sonnet.display_name(), "Sonnet (latest)");
312        assert!(ClaudeModel::Sonnet.is_alias());
313        assert!(!ClaudeModel::Sonnet5.is_alias());
314    }
315
316    #[test]
317    fn test_serde_round_trip() {
318        let json = serde_json::to_string(&ClaudeModel::Haiku45).unwrap();
319        assert_eq!(json, "\"claude-haiku-4-5\"");
320        let back: ClaudeModel = serde_json::from_str(&json).unwrap();
321        assert_eq!(back, ClaudeModel::Haiku45);
322    }
323}