Skip to main content

codex_codes/
models.rs

1//! Convenience enum for the models the Codex CLI exposes.
2//!
3//! Variants are keyed by human-friendly model names; [`CodexModel::cli_arg`]
4//! returns the model slug to pass to `codex -m` / `--model`, to
5//! `ThreadStartParams.model`, or to an `AppServerBuilder::config_override`
6//! of `model`.
7//!
8//! The catalog was taken from `openai/codex@main`'s bundled
9//! `models-manager/models.json` (2026-07-11). The server catalog evolves
10//! faster than this crate; unknown slugs round-trip through
11//! [`CodexModel::Custom`].
12
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use std::fmt;
15
16/// A model slug accepted by the Codex CLI and app-server.
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum CodexModel {
19    /// GPT-6-Astra (`gpt-6-astra`) — first GPT-6-family model in the
20    /// catalog (272k context). Live-probed 2026-09-04: the backend
21    /// recognizes the slug but rejects it for ChatGPT-plan auth
22    /// ("not supported when using Codex with a ChatGPT account").
23    Gpt6Astra,
24    /// Daybreak Blue (`gpt-daybreak-blue-latest`) — the cyber-access
25    /// program models (see `CyberAccessProgram`); server-side gated.
26    DaybreakBlue,
27    /// Daybreak Red (`gpt-daybreak-red-latest`) — cyber-access program,
28    /// 372k context; server-side gated.
29    DaybreakRed,
30    /// GPT-5.6-Sol (`gpt-5.6-sol`).
31    Gpt56Sol,
32    /// GPT-5.6-Terra (`gpt-5.6-terra`).
33    Gpt56Terra,
34    /// GPT-5.6-Luna (`gpt-5.6-luna`).
35    Gpt56Luna,
36    /// GPT-5.5 (`gpt-5.5`).
37    Gpt55,
38    /// GPT-5.4 (`gpt-5.4`).
39    Gpt54,
40    /// GPT-5.4-Mini (`gpt-5.4-mini`).
41    Gpt54Mini,
42    /// GPT-5.2 (`gpt-5.2`).
43    Gpt52,
44    /// Codex Auto Review (`codex-auto-review`) — hidden from the picker but
45    /// accepted by the API.
46    CodexAutoReview,
47    /// A model slug not yet known to this version of the crate. Passed
48    /// through verbatim.
49    Custom(String),
50}
51
52impl CodexModel {
53    /// The slug to pass to `codex -m` / `ThreadStartParams.model`.
54    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    /// Alias for [`cli_arg`](Self::cli_arg), matching the crate's string-enum
72    /// convention.
73    pub fn as_str(&self) -> &str {
74        self.cli_arg()
75    }
76
77    /// Human-friendly display name, matching the catalog's `display_name`.
78    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    /// Every model known to this version of the crate.
96    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        // ThreadStartParams.model is Option<String>; the enum feeds it via Into.
181        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}