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