1use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use std::fmt;
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum ClaudeModel {
19 Sonnet,
22 Opus,
25 Haiku,
29 Fable,
31 Best,
33 OpusPlan,
35 Sonnet1m,
37 Opus1m,
39 Fable1m,
41 Fable51,
43 Fable5,
45 Mythos51,
47 Mythos5,
49 Opus55,
52 Opus5,
54 Opus48,
56 Opus47,
58 Opus46,
60 Opus45,
62 Opus41,
64 Opus40,
66 Sonnet55,
69 Sonnet5,
71 Sonnet46,
73 Sonnet45,
75 Sonnet40,
77 Sonnet37,
79 Sonnet35,
81 Haiku55,
84 Haiku45,
86 Haiku35,
88 Custom(String),
91}
92
93impl ClaudeModel {
94 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 pub fn as_str(&self) -> &str {
135 self.cli_arg()
136 }
137
138 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 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 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}