Skip to main content

codex/cli/
features.rs

1use crate::{CliOverridesPatch, ConfigOverride, FlagState};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::{collections::BTreeMap, process::ExitStatus};
5
6/// Stage labels reported by `codex features list`.
7#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
8#[serde(from = "String", into = "String")]
9pub enum CodexFeatureStage {
10    Experimental,
11    Beta,
12    Stable,
13    Deprecated,
14    Removed,
15    Unknown(String),
16}
17
18impl CodexFeatureStage {
19    pub(crate) fn parse(raw: &str) -> Self {
20        let normalized = raw.trim();
21        match normalized.to_ascii_lowercase().as_str() {
22            "experimental" => CodexFeatureStage::Experimental,
23            "beta" => CodexFeatureStage::Beta,
24            "stable" => CodexFeatureStage::Stable,
25            "deprecated" => CodexFeatureStage::Deprecated,
26            "removed" => CodexFeatureStage::Removed,
27            _ => CodexFeatureStage::Unknown(normalized.to_string()),
28        }
29    }
30
31    /// Returns the normalized label for this stage.
32    pub fn as_str(&self) -> &str {
33        match self {
34            CodexFeatureStage::Experimental => "experimental",
35            CodexFeatureStage::Beta => "beta",
36            CodexFeatureStage::Stable => "stable",
37            CodexFeatureStage::Deprecated => "deprecated",
38            CodexFeatureStage::Removed => "removed",
39            CodexFeatureStage::Unknown(label) => label.as_str(),
40        }
41    }
42}
43
44impl From<String> for CodexFeatureStage {
45    fn from(value: String) -> Self {
46        CodexFeatureStage::parse(&value)
47    }
48}
49
50impl From<CodexFeatureStage> for String {
51    fn from(stage: CodexFeatureStage) -> Self {
52        String::from(&stage)
53    }
54}
55
56impl From<&CodexFeatureStage> for String {
57    fn from(stage: &CodexFeatureStage) -> Self {
58        stage.as_str().to_string()
59    }
60}
61
62/// Single feature entry reported by `codex features list`.
63#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
64pub struct CodexFeature {
65    /// Feature name as reported by the CLI.
66    pub name: String,
67    /// Feature stage (experimental/beta/stable/deprecated/removed) when provided.
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub stage: Option<CodexFeatureStage>,
70    /// Whether the feature is enabled for the current config/profile.
71    pub enabled: bool,
72    /// Unrecognized fields from JSON output are preserved here.
73    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
74    pub extra: BTreeMap<String, Value>,
75}
76
77impl CodexFeature {
78    /// Convenience helper mirroring the `enabled` flag.
79    pub const fn is_enabled(&self) -> bool {
80        self.enabled
81    }
82}
83
84/// Format used to parse `codex features list` output.
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub enum FeaturesListFormat {
87    Json,
88    Text,
89}
90
91/// Parsed output from `codex features list`.
92#[derive(Clone, Debug, Eq, PartialEq)]
93pub struct FeaturesListOutput {
94    /// Exit status returned by the subcommand.
95    pub status: ExitStatus,
96    /// Captured stdout (mirrored to the console when `mirror_stdout` is true).
97    pub stdout: String,
98    /// Captured stderr (mirrored unless `quiet` is set).
99    pub stderr: String,
100    /// Parsed feature entries.
101    pub features: Vec<CodexFeature>,
102    /// Indicates whether JSON or text parsing was used.
103    pub format: FeaturesListFormat,
104}
105
106/// Request for `codex features list`.
107#[derive(Clone, Debug, Eq, PartialEq)]
108pub struct FeaturesListRequest {
109    /// Request JSON output via `--json` (falls back to text parsing when JSON is absent).
110    pub json: bool,
111    /// Per-call CLI overrides layered on top of the builder.
112    pub overrides: CliOverridesPatch,
113}
114
115impl FeaturesListRequest {
116    /// Creates a request with JSON disabled by default for compatibility with older binaries.
117    pub fn new() -> Self {
118        Self {
119            json: false,
120            overrides: CliOverridesPatch::default(),
121        }
122    }
123
124    /// Controls whether `--json` is passed to `codex features list`.
125    pub fn json(mut self, enable: bool) -> Self {
126        self.json = enable;
127        self
128    }
129
130    /// Replaces the default CLI overrides for this request.
131    pub fn with_overrides(mut self, overrides: CliOverridesPatch) -> Self {
132        self.overrides = overrides;
133        self
134    }
135
136    /// Adds a `--config key=value` override for this request.
137    pub fn config_override(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
138        self.overrides
139            .config_overrides
140            .push(ConfigOverride::new(key, value));
141        self
142    }
143
144    /// Adds a raw `--config key=value` override without validation.
145    pub fn config_override_raw(mut self, raw: impl Into<String>) -> Self {
146        self.overrides
147            .config_overrides
148            .push(ConfigOverride::from_raw(raw));
149        self
150    }
151
152    /// Sets the config profile (`--profile`) for this request.
153    pub fn profile(mut self, profile: impl Into<String>) -> Self {
154        let profile = profile.into();
155        self.overrides.profile = (!profile.trim().is_empty()).then_some(profile);
156        self
157    }
158
159    /// Requests the CLI `--oss` flag for this call.
160    pub fn oss(mut self, enable: bool) -> Self {
161        self.overrides.oss = if enable {
162            FlagState::Enable
163        } else {
164            FlagState::Disable
165        };
166        self
167    }
168
169    /// Adds a `--enable <feature>` toggle for this call.
170    pub fn enable_feature(mut self, name: impl Into<String>) -> Self {
171        self.overrides.feature_toggles.enable.push(name.into());
172        self
173    }
174
175    /// Adds a `--disable <feature>` toggle for this call.
176    pub fn disable_feature(mut self, name: impl Into<String>) -> Self {
177        self.overrides.feature_toggles.disable.push(name.into());
178        self
179    }
180
181    /// Controls whether `--search` is passed through to Codex.
182    pub fn search(mut self, enable: bool) -> Self {
183        self.overrides.search = if enable {
184            FlagState::Enable
185        } else {
186            FlagState::Disable
187        };
188        self
189    }
190}
191
192impl Default for FeaturesListRequest {
193    fn default() -> Self {
194        Self::new()
195    }
196}
197
198/// Request for `codex features`.
199#[derive(Clone, Debug, Eq, PartialEq)]
200pub struct FeaturesCommandRequest {
201    /// Per-call CLI overrides layered on top of the builder.
202    pub overrides: CliOverridesPatch,
203}
204
205impl FeaturesCommandRequest {
206    pub fn new() -> Self {
207        Self {
208            overrides: CliOverridesPatch::default(),
209        }
210    }
211
212    /// Replaces the default CLI overrides for this request.
213    pub fn with_overrides(mut self, overrides: CliOverridesPatch) -> Self {
214        self.overrides = overrides;
215        self
216    }
217}
218
219impl Default for FeaturesCommandRequest {
220    fn default() -> Self {
221        Self::new()
222    }
223}
224
225/// Request for `codex features enable <FEATURE>`.
226#[derive(Clone, Debug, Eq, PartialEq)]
227pub struct FeaturesEnableRequest {
228    /// Feature key to enable.
229    pub feature: String,
230    /// Per-call CLI overrides layered on top of the builder.
231    pub overrides: CliOverridesPatch,
232}
233
234impl FeaturesEnableRequest {
235    pub fn new(feature: impl Into<String>) -> Self {
236        Self {
237            feature: feature.into(),
238            overrides: CliOverridesPatch::default(),
239        }
240    }
241
242    /// Replaces the default CLI overrides for this request.
243    pub fn with_overrides(mut self, overrides: CliOverridesPatch) -> Self {
244        self.overrides = overrides;
245        self
246    }
247}
248
249/// Request for `codex features disable <FEATURE>`.
250#[derive(Clone, Debug, Eq, PartialEq)]
251pub struct FeaturesDisableRequest {
252    /// Feature key to disable.
253    pub feature: String,
254    /// Per-call CLI overrides layered on top of the builder.
255    pub overrides: CliOverridesPatch,
256}
257
258impl FeaturesDisableRequest {
259    pub fn new(feature: impl Into<String>) -> Self {
260        Self {
261            feature: feature.into(),
262            overrides: CliOverridesPatch::default(),
263        }
264    }
265
266    /// Replaces the default CLI overrides for this request.
267    pub fn with_overrides(mut self, overrides: CliOverridesPatch) -> Self {
268        self.overrides = overrides;
269        self
270    }
271}