1use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use std::fmt;
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum ClaudeModel {
19 Sonnet,
21 Opus,
23 Haiku,
25 Fable,
27 Best,
29 OpusPlan,
31 Sonnet1m,
33 Opus1m,
35 Fable1m,
37 Fable5,
39 Mythos5,
41 Opus48,
43 Opus47,
45 Opus46,
47 Opus45,
49 Opus41,
51 Opus40,
53 Sonnet5,
55 Sonnet46,
57 Sonnet45,
59 Sonnet40,
61 Sonnet37,
63 Sonnet35,
65 Haiku45,
67 Haiku35,
69 Custom(String),
72}
73
74impl ClaudeModel {
75 pub fn cli_arg(&self) -> &str {
77 match self {
78 Self::Sonnet => "sonnet",
79 Self::Opus => "opus",
80 Self::Haiku => "haiku",
81 Self::Fable => "fable",
82 Self::Best => "best",
83 Self::OpusPlan => "opusplan",
84 Self::Sonnet1m => "sonnet[1m]",
85 Self::Opus1m => "opus[1m]",
86 Self::Fable1m => "fable[1m]",
87 Self::Fable5 => "claude-fable-5",
88 Self::Mythos5 => "claude-mythos-5",
89 Self::Opus48 => "claude-opus-4-8",
90 Self::Opus47 => "claude-opus-4-7",
91 Self::Opus46 => "claude-opus-4-6",
92 Self::Opus45 => "claude-opus-4-5",
93 Self::Opus41 => "claude-opus-4-1",
94 Self::Opus40 => "claude-opus-4-0",
95 Self::Sonnet5 => "claude-sonnet-5",
96 Self::Sonnet46 => "claude-sonnet-4-6",
97 Self::Sonnet45 => "claude-sonnet-4-5",
98 Self::Sonnet40 => "claude-sonnet-4-0",
99 Self::Sonnet37 => "claude-3-7-sonnet",
100 Self::Sonnet35 => "claude-3-5-sonnet",
101 Self::Haiku45 => "claude-haiku-4-5",
102 Self::Haiku35 => "claude-3-5-haiku",
103 Self::Custom(s) => s.as_str(),
104 }
105 }
106
107 pub fn as_str(&self) -> &str {
110 self.cli_arg()
111 }
112
113 pub fn display_name(&self) -> &str {
115 match self {
116 Self::Sonnet => "Sonnet (latest)",
117 Self::Opus => "Opus (latest)",
118 Self::Haiku => "Haiku (latest)",
119 Self::Fable => "Fable (latest)",
120 Self::Best => "Best available",
121 Self::OpusPlan => "Opus (plan mode) / Sonnet",
122 Self::Sonnet1m => "Sonnet (latest, 1M context)",
123 Self::Opus1m => "Opus (latest, 1M context)",
124 Self::Fable1m => "Fable (latest, 1M context)",
125 Self::Fable5 => "Fable 5",
126 Self::Mythos5 => "Mythos 5",
127 Self::Opus48 => "Opus 4.8",
128 Self::Opus47 => "Opus 4.7",
129 Self::Opus46 => "Opus 4.6",
130 Self::Opus45 => "Opus 4.5",
131 Self::Opus41 => "Opus 4.1",
132 Self::Opus40 => "Opus 4",
133 Self::Sonnet5 => "Sonnet 5",
134 Self::Sonnet46 => "Sonnet 4.6",
135 Self::Sonnet45 => "Sonnet 4.5",
136 Self::Sonnet40 => "Sonnet 4",
137 Self::Sonnet37 => "Sonnet 3.7",
138 Self::Sonnet35 => "Sonnet 3.5",
139 Self::Haiku45 => "Haiku 4.5",
140 Self::Haiku35 => "Haiku 3.5",
141 Self::Custom(s) => s.as_str(),
142 }
143 }
144
145 pub fn is_alias(&self) -> bool {
148 matches!(
149 self,
150 Self::Sonnet
151 | Self::Opus
152 | Self::Haiku
153 | Self::Fable
154 | Self::Best
155 | Self::OpusPlan
156 | Self::Sonnet1m
157 | Self::Opus1m
158 | Self::Fable1m
159 )
160 }
161
162 pub fn known() -> &'static [ClaudeModel] {
164 &[
165 Self::Sonnet,
166 Self::Opus,
167 Self::Haiku,
168 Self::Fable,
169 Self::Best,
170 Self::OpusPlan,
171 Self::Sonnet1m,
172 Self::Opus1m,
173 Self::Fable1m,
174 Self::Fable5,
175 Self::Mythos5,
176 Self::Opus48,
177 Self::Opus47,
178 Self::Opus46,
179 Self::Opus45,
180 Self::Opus41,
181 Self::Opus40,
182 Self::Sonnet5,
183 Self::Sonnet46,
184 Self::Sonnet45,
185 Self::Sonnet40,
186 Self::Sonnet37,
187 Self::Sonnet35,
188 Self::Haiku45,
189 Self::Haiku35,
190 ]
191 }
192}
193
194impl fmt::Display for ClaudeModel {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 f.write_str(self.cli_arg())
197 }
198}
199
200impl From<&str> for ClaudeModel {
201 fn from(s: &str) -> Self {
202 match s {
203 "sonnet" => Self::Sonnet,
204 "opus" => Self::Opus,
205 "haiku" => Self::Haiku,
206 "fable" => Self::Fable,
207 "best" => Self::Best,
208 "opusplan" => Self::OpusPlan,
209 "sonnet[1m]" => Self::Sonnet1m,
210 "opus[1m]" => Self::Opus1m,
211 "fable[1m]" => Self::Fable1m,
212 "claude-fable-5" => Self::Fable5,
213 "claude-mythos-5" => Self::Mythos5,
214 "claude-opus-4-8" => Self::Opus48,
215 "claude-opus-4-7" => Self::Opus47,
216 "claude-opus-4-6" => Self::Opus46,
217 "claude-opus-4-5" => Self::Opus45,
218 "claude-opus-4-1" => Self::Opus41,
219 "claude-opus-4-0" => Self::Opus40,
220 "claude-sonnet-5" => Self::Sonnet5,
221 "claude-sonnet-4-6" => Self::Sonnet46,
222 "claude-sonnet-4-5" => Self::Sonnet45,
223 "claude-sonnet-4-0" => Self::Sonnet40,
224 "claude-3-7-sonnet" => Self::Sonnet37,
225 "claude-3-5-sonnet" => Self::Sonnet35,
226 "claude-haiku-4-5" => Self::Haiku45,
227 "claude-3-5-haiku" => Self::Haiku35,
228 other => Self::Custom(other.to_string()),
229 }
230 }
231}
232
233impl From<ClaudeModel> for String {
234 fn from(model: ClaudeModel) -> Self {
235 model.cli_arg().to_string()
236 }
237}
238
239impl Serialize for ClaudeModel {
240 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
241 serializer.serialize_str(self.cli_arg())
242 }
243}
244
245impl<'de> Deserialize<'de> for ClaudeModel {
246 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
247 let s = String::deserialize(deserializer)?;
248 Ok(Self::from(s.as_str()))
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::ClaudeModel;
255
256 #[test]
257 fn test_cli_arg_round_trip() {
258 for model in ClaudeModel::known() {
259 assert_eq!(&ClaudeModel::from(model.cli_arg()), model);
260 }
261 assert_eq!(
262 ClaudeModel::from("claude-nova-7"),
263 ClaudeModel::Custom("claude-nova-7".to_string())
264 );
265 assert_eq!(
266 ClaudeModel::from("claude-nova-7").cli_arg(),
267 "claude-nova-7"
268 );
269 }
270
271 #[test]
272 fn test_into_string_matches_cli_arg() {
273 let s: String = ClaudeModel::Sonnet5.into();
274 assert_eq!(s, "claude-sonnet-5");
275 let s: String = ClaudeModel::Opus1m.into();
276 assert_eq!(s, "opus[1m]");
277 }
278
279 #[test]
280 fn test_display_names() {
281 assert_eq!(ClaudeModel::Fable5.display_name(), "Fable 5");
282 assert_eq!(ClaudeModel::Opus48.display_name(), "Opus 4.8");
283 assert_eq!(ClaudeModel::Sonnet.display_name(), "Sonnet (latest)");
284 assert!(ClaudeModel::Sonnet.is_alias());
285 assert!(!ClaudeModel::Sonnet5.is_alias());
286 }
287
288 #[test]
289 fn test_serde_round_trip() {
290 let json = serde_json::to_string(&ClaudeModel::Haiku45).unwrap();
291 assert_eq!(json, "\"claude-haiku-4-5\"");
292 let back: ClaudeModel = serde_json::from_str(&json).unwrap();
293 assert_eq!(back, ClaudeModel::Haiku45);
294 }
295}