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