1use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use std::fmt;
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum CodexModel {
19 Gpt6Astra,
24 DaybreakBlue,
27 DaybreakRed,
30 Gpt56Sol,
32 Gpt56Terra,
34 Gpt56Luna,
36 Gpt55,
38 Gpt54,
40 Gpt54Mini,
42 Gpt52,
44 CodexAutoReview,
47 Custom(String),
50}
51
52impl CodexModel {
53 pub fn cli_arg(&self) -> &str {
55 match self {
56 Self::Gpt6Astra => "gpt-6-astra",
57 Self::DaybreakBlue => "gpt-daybreak-blue-latest",
58 Self::DaybreakRed => "gpt-daybreak-red-latest",
59 Self::Gpt56Sol => "gpt-5.6-sol",
60 Self::Gpt56Terra => "gpt-5.6-terra",
61 Self::Gpt56Luna => "gpt-5.6-luna",
62 Self::Gpt55 => "gpt-5.5",
63 Self::Gpt54 => "gpt-5.4",
64 Self::Gpt54Mini => "gpt-5.4-mini",
65 Self::Gpt52 => "gpt-5.2",
66 Self::CodexAutoReview => "codex-auto-review",
67 Self::Custom(s) => s.as_str(),
68 }
69 }
70
71 pub fn as_str(&self) -> &str {
74 self.cli_arg()
75 }
76
77 pub fn display_name(&self) -> &str {
79 match self {
80 Self::Gpt6Astra => "GPT-6-Astra",
81 Self::DaybreakBlue => "Daybreak Blue",
82 Self::DaybreakRed => "Daybreak Red",
83 Self::Gpt56Sol => "GPT-5.6-Sol",
84 Self::Gpt56Terra => "GPT-5.6-Terra",
85 Self::Gpt56Luna => "GPT-5.6-Luna",
86 Self::Gpt55 => "GPT-5.5",
87 Self::Gpt54 => "GPT-5.4",
88 Self::Gpt54Mini => "GPT-5.4-Mini",
89 Self::Gpt52 => "GPT-5.2",
90 Self::CodexAutoReview => "Codex Auto Review",
91 Self::Custom(s) => s.as_str(),
92 }
93 }
94
95 pub fn known() -> &'static [CodexModel] {
97 &[
98 Self::Gpt6Astra,
99 Self::DaybreakBlue,
100 Self::DaybreakRed,
101 Self::Gpt56Sol,
102 Self::Gpt56Terra,
103 Self::Gpt56Luna,
104 Self::Gpt55,
105 Self::Gpt54,
106 Self::Gpt54Mini,
107 Self::Gpt52,
108 Self::CodexAutoReview,
109 ]
110 }
111}
112
113impl fmt::Display for CodexModel {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 f.write_str(self.cli_arg())
116 }
117}
118
119impl From<&str> for CodexModel {
120 fn from(s: &str) -> Self {
121 match s {
122 "gpt-6-astra" => Self::Gpt6Astra,
123 "gpt-daybreak-blue-latest" => Self::DaybreakBlue,
124 "gpt-daybreak-red-latest" => Self::DaybreakRed,
125 "gpt-5.6-sol" => Self::Gpt56Sol,
126 "gpt-5.6-terra" => Self::Gpt56Terra,
127 "gpt-5.6-luna" => Self::Gpt56Luna,
128 "gpt-5.5" => Self::Gpt55,
129 "gpt-5.4" => Self::Gpt54,
130 "gpt-5.4-mini" => Self::Gpt54Mini,
131 "gpt-5.2" => Self::Gpt52,
132 "codex-auto-review" => Self::CodexAutoReview,
133 other => Self::Custom(other.to_string()),
134 }
135 }
136}
137
138impl From<CodexModel> for String {
139 fn from(model: CodexModel) -> Self {
140 model.cli_arg().to_string()
141 }
142}
143
144impl Serialize for CodexModel {
145 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
146 serializer.serialize_str(self.cli_arg())
147 }
148}
149
150impl<'de> Deserialize<'de> for CodexModel {
151 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
152 let s = String::deserialize(deserializer)?;
153 Ok(Self::from(s.as_str()))
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::CodexModel;
160
161 #[test]
162 fn test_cli_arg_round_trip() {
163 for model in CodexModel::known() {
164 assert_eq!(&CodexModel::from(model.cli_arg()), model);
165 }
166 assert_eq!(
167 CodexModel::from("gpt-9-experimental"),
168 CodexModel::Custom("gpt-9-experimental".to_string())
169 );
170 }
171
172 #[test]
173 fn test_into_string_matches_cli_arg() {
174 let s: String = CodexModel::Gpt56Sol.into();
175 assert_eq!(s, "gpt-5.6-sol");
176 }
177
178 #[test]
179 fn test_converts_into_thread_start_model_field() {
180 let model: Option<String> = Some(CodexModel::Gpt55.into());
182 assert_eq!(model.as_deref(), Some("gpt-5.5"));
183 }
184
185 #[test]
186 fn test_serde_round_trip() {
187 let json = serde_json::to_string(&CodexModel::Gpt54Mini).unwrap();
188 assert_eq!(json, "\"gpt-5.4-mini\"");
189 let back: CodexModel = serde_json::from_str(&json).unwrap();
190 assert_eq!(back, CodexModel::Gpt54Mini);
191 }
192}