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-5.6-Sol (`gpt-5.6-sol`).
20    Gpt56Sol,
21    /// GPT-5.6-Terra (`gpt-5.6-terra`).
22    Gpt56Terra,
23    /// GPT-5.6-Luna (`gpt-5.6-luna`).
24    Gpt56Luna,
25    /// GPT-5.5 (`gpt-5.5`).
26    Gpt55,
27    /// GPT-5.4 (`gpt-5.4`).
28    Gpt54,
29    /// GPT-5.4-Mini (`gpt-5.4-mini`).
30    Gpt54Mini,
31    /// GPT-5.2 (`gpt-5.2`).
32    Gpt52,
33    /// Codex Auto Review (`codex-auto-review`) — hidden from the picker but
34    /// accepted by the API.
35    CodexAutoReview,
36    /// A model slug not yet known to this version of the crate. Passed
37    /// through verbatim.
38    Custom(String),
39}
40
41impl CodexModel {
42    /// The slug to pass to `codex -m` / `ThreadStartParams.model`.
43    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    /// Alias for [`cli_arg`](Self::cli_arg), matching the crate's string-enum
58    /// convention.
59    pub fn as_str(&self) -> &str {
60        self.cli_arg()
61    }
62
63    /// Human-friendly display name, matching the catalog's `display_name`.
64    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    /// Every model known to this version of the crate.
79    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        // ThreadStartParams.model is Option<String>; the enum feeds it via Into.
158        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}