1use std::str::FromStr;
29
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub enum Model {
36 OpenAi(OpenAiModel),
38 Custom(&'static str),
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47pub enum OpenAiModel {
48 Gpt5,
49 Gpt5Nano,
50 Gpt5Mini,
51 Gpt5Pro,
52 Gpt5_1,
53 Gpt5_1Codex,
54 Gpt5_1CodexMini,
55 Gpt5_1CodexMax,
56 Gpt5_2,
57 Gpt5_2Pro,
58 Gpt5_2Codex,
59 Gpt5_3,
60 Gpt5_3Codex,
61 Gpt5_4,
62 Gpt5_4Pro,
63 Gpt5Codex,
64 Gpt4_1,
65 Gpt4_1Mini,
66 Gpt4_1Nano,
67 Gpt4o,
68 Gpt4oMini,
69 O3,
70 O3Mini,
71 O4Mini,
72}
73
74impl From<OpenAiModel> for Model {
75 fn from(val: OpenAiModel) -> Self {
76 Model::OpenAi(val)
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct ModelParseError(pub String);
82
83impl std::fmt::Display for ModelParseError {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 write!(f, "unknown model identifier: {}", self.0)
86 }
87}
88
89impl std::error::Error for ModelParseError {}
90
91impl AsRef<str> for OpenAiModel {
92 fn as_ref(&self) -> &str {
93 match self {
94 OpenAiModel::Gpt5 => "gpt-5",
95 OpenAiModel::Gpt5Nano => "gpt-5-nano",
96 OpenAiModel::Gpt5Mini => "gpt-5-mini",
97 OpenAiModel::Gpt5Pro => "gpt-5-pro",
98 OpenAiModel::Gpt5_1 => "gpt-5.1",
99 OpenAiModel::Gpt5_1Codex => "gpt-5.1-codex",
100 OpenAiModel::Gpt5_1CodexMini => "gpt-5.1-codex-mini",
101 OpenAiModel::Gpt5_1CodexMax => "gpt-5.1-codex-max",
102 OpenAiModel::Gpt5_2 => "gpt-5.2",
103 OpenAiModel::Gpt5_2Pro => "gpt-5.2-pro",
104 OpenAiModel::Gpt5_2Codex => "gpt-5.2-codex",
105 OpenAiModel::Gpt5_3 => "gpt-5.3",
106 OpenAiModel::Gpt5_3Codex => "gpt-5.3-codex",
107 OpenAiModel::Gpt5_4 => "gpt-5.4",
108 OpenAiModel::Gpt5_4Pro => "gpt-5.4-pro",
109 OpenAiModel::Gpt5Codex => "gpt-5-codex",
110 OpenAiModel::Gpt4_1 => "gpt-4.1",
111 OpenAiModel::Gpt4_1Mini => "gpt-4.1-mini",
112 OpenAiModel::Gpt4_1Nano => "gpt-4.1-nano",
113 OpenAiModel::Gpt4o => "gpt-4o",
114 OpenAiModel::Gpt4oMini => "gpt-4o-mini",
115 OpenAiModel::O3 => "o3",
116 OpenAiModel::O3Mini => "o3-mini",
117 OpenAiModel::O4Mini => "o4-mini",
118 }
119 }
120}
121
122impl FromStr for OpenAiModel {
123 type Err = ModelParseError;
124
125 fn from_str(s: &str) -> Result<Self, Self::Err> {
126 match s {
127 "gpt-5" => Ok(OpenAiModel::Gpt5),
128 "gpt-5-nano" => Ok(OpenAiModel::Gpt5Nano),
129 "gpt-5-mini" => Ok(OpenAiModel::Gpt5Mini),
130 "gpt-5-pro" => Ok(OpenAiModel::Gpt5Pro),
131 "gpt-5.1" => Ok(OpenAiModel::Gpt5_1),
132 "gpt-5.1-codex" => Ok(OpenAiModel::Gpt5_1Codex),
133 "gpt-5.1-codex-mini" => Ok(OpenAiModel::Gpt5_1CodexMini),
134 "gpt-5.1-codex-max" => Ok(OpenAiModel::Gpt5_1CodexMax),
135 "gpt-5.2" => Ok(OpenAiModel::Gpt5_2),
136 "gpt-5.2-pro" => Ok(OpenAiModel::Gpt5_2Pro),
137 "gpt-5.2-codex" => Ok(OpenAiModel::Gpt5_2Codex),
138 "gpt-5.3" => Ok(OpenAiModel::Gpt5_3),
139 "gpt-5.3-codex" => Ok(OpenAiModel::Gpt5_3Codex),
140 "gpt-5.4" => Ok(OpenAiModel::Gpt5_4),
141 "gpt-5.4-pro" => Ok(OpenAiModel::Gpt5_4Pro),
142 "gpt-5-codex" => Ok(OpenAiModel::Gpt5Codex),
143 "gpt-4.1" => Ok(OpenAiModel::Gpt4_1),
144 "gpt-4.1-mini" => Ok(OpenAiModel::Gpt4_1Mini),
145 "gpt-4.1-nano" => Ok(OpenAiModel::Gpt4_1Nano),
146 "gpt-4o" => Ok(OpenAiModel::Gpt4o),
147 "gpt-4o-mini" => Ok(OpenAiModel::Gpt4oMini),
148 "o3" => Ok(OpenAiModel::O3),
149 "o3-mini" => Ok(OpenAiModel::O3Mini),
150 "o4-mini" => Ok(OpenAiModel::O4Mini),
151 _ => Err(ModelParseError(s.to_string())),
152 }
153 }
154}
155
156impl AsRef<str> for Model {
157 fn as_ref(&self) -> &str {
158 match self {
159 Model::OpenAi(model) => model.as_ref(),
160 Model::Custom(custom) => custom,
161 }
162 }
163}
164
165impl FromStr for Model {
166 type Err = ModelParseError;
167
168 fn from_str(s: &str) -> Result<Self, Self::Err> {
169 Ok(Model::OpenAi(OpenAiModel::from_str(s)?))
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::{Model, OpenAiModel};
176 use std::str::FromStr;
177
178 #[test]
179 fn openai_model_from_str_roundtrips() {
180 let models = [
181 "gpt-5",
182 "gpt-5-nano",
183 "gpt-5-mini",
184 "gpt-5-pro",
185 "gpt-5.1",
186 "gpt-5.1-codex",
187 "gpt-5.1-codex-mini",
188 "gpt-5.1-codex-max",
189 "gpt-5.2",
190 "gpt-5.2-pro",
191 "gpt-5.2-codex",
192 "gpt-5.3",
193 "gpt-5.3-codex",
194 "gpt-5.4",
195 "gpt-5.4-pro",
196 "gpt-5-codex",
197 "gpt-4.1",
198 "gpt-4.1-mini",
199 "gpt-4.1-nano",
200 "gpt-4o",
201 "gpt-4o-mini",
202 "o3",
203 "o3-mini",
204 "o4-mini",
205 ];
206
207 for model in models {
208 let parsed = OpenAiModel::from_str(model).expect("model should parse");
209 assert_eq!(parsed.as_ref(), model);
210 }
211 }
212
213 #[test]
214 fn model_as_ref_covers_openai_and_custom() {
215 let openai = Model::OpenAi(OpenAiModel::Gpt5Mini);
216 assert_eq!(openai.as_ref(), "gpt-5-mini");
217
218 let custom = Model::Custom("provider:custom-1");
219 assert_eq!(custom.as_ref(), "provider:custom-1");
220 }
221}