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