Codex Handbook
utils/cli/src/config_override.rs 225 lines
//! Support for `-c key=value` overrides shared across Codex CLI tools.//!//! This module provides a [`CliConfigOverrides`] struct that can be embedded//! into a `clap`-derived CLI struct using `#[clap(flatten)]`. Each occurrence//! of `-c key=value` (or `--config key=value`) will be collected as a raw//! string. Helper methods are provided to convert the raw strings into//! key/value pairs as well as to apply them onto a mutable//! `serde_json::Value` representing the configuration tree.use clap::ArgAction;use clap::Parser;use serde::de::Error as SerdeError;use toml::Value;/// CLI option that captures arbitrary configuration overrides specified as/// `-c key=value`. It intentionally keeps both halves **unparsed** so that the/// calling code can decide how to interpret the right-hand side.#[derive(Parser, Debug, Default, Clone)]pub struct CliConfigOverrides {    /// Override a configuration value that would otherwise be loaded from    /// `~/.codex/config.toml`. Use a dotted path (`foo.bar.baz`) to override    /// nested values. The `value` portion is parsed as TOML. If it fails to    /// parse as TOML, the raw string is used as a literal.    ///    /// Examples:    ///   - `-c model="o3"`    ///   - `-c 'sandbox_permissions=["disk-full-read-access"]'`    ///   - `-c shell_environment_policy.inherit=all`    #[arg(        short = 'c',        long = "config",        value_name = "key=value",        action = ArgAction::Append,        global = true,    )]    pub raw_overrides: Vec<String>,}impl CliConfigOverrides {    /// Prepend root-level config flags so they have lower precedence than    /// command-specific flags parsed after a subcommand.    pub fn prepend_root_overrides(&mut self, root_overrides: Self) {        self.raw_overrides            .splice(0..0, root_overrides.raw_overrides);    }    /// Parse the raw strings captured from the CLI into a list of `(path,    /// value)` tuples where `value` is a `serde_json::Value`.    pub fn parse_overrides(&self) -> Result<Vec<(String, Value)>, String> {        self.raw_overrides            .iter()            .map(|s| {                // Only split on the *first* '=' so values are free to contain                // the character.                let mut parts = s.splitn(2, '=');                let key = match parts.next() {                    Some(k) => k.trim(),                    None => return Err("Override missing key".to_string()),                };                let value_str = parts                    .next()                    .ok_or_else(|| format!("Invalid override (missing '='): {s}"))?                    .trim();                if key.is_empty() {                    return Err(format!("Empty key in override: {s}"));                }                // Attempt to parse as TOML. If that fails, treat it as a raw                // string. This allows convenient usage such as                // `-c model=o3` without the quotes.                let value: Value = match parse_toml_value(value_str) {                    Ok(v) => v,                    Err(_) => {                        // Strip leading/trailing quotes if present                        let trimmed = value_str.trim().trim_matches(|c| c == '"' || c == '\'');                        Value::String(trimmed.to_string())                    }                };                Ok((canonicalize_override_key(key), value))            })            .collect()    }    /// Apply all parsed overrides onto `target`. Intermediate objects will be    /// created as necessary. Values located at the destination path will be    /// replaced.    pub fn apply_on_value(&self, target: &mut Value) -> Result<(), String> {        let overrides = self.parse_overrides()?;        for (path, value) in overrides {            apply_single_override(target, &path, value);        }        Ok(())    }}fn canonicalize_override_key(key: &str) -> String {    if key == "use_legacy_landlock" {        "features.use_legacy_landlock".to_string()    } else {        key.to_string()    }}/// Apply a single override onto `root`, creating intermediate objects as/// necessary.fn apply_single_override(root: &mut Value, path: &str, value: Value) {    use toml::value::Table;    let parts: Vec<&str> = path.split('.').collect();    let mut current = root;    for (i, part) in parts.iter().enumerate() {        let is_last = i == parts.len() - 1;        if is_last {            match current {                Value::Table(tbl) => {                    tbl.insert((*part).to_string(), value);                }                _ => {                    let mut tbl = Table::new();                    tbl.insert((*part).to_string(), value);                    *current = Value::Table(tbl);                }            }            return;        }        // Traverse or create intermediate table.        match current {            Value::Table(tbl) => {                current = tbl                    .entry((*part).to_string())                    .or_insert_with(|| Value::Table(Table::new()));            }            _ => {                *current = Value::Table(Table::new());                if let Value::Table(tbl) = current {                    current = tbl                        .entry((*part).to_string())                        .or_insert_with(|| Value::Table(Table::new()));                }            }        }    }}fn parse_toml_value(raw: &str) -> Result<Value, toml::de::Error> {    let wrapped = format!("_x_ = {raw}");    let table: toml::Table = toml::from_str(&wrapped)?;    table        .get("_x_")        .cloned()        .ok_or_else(|| SerdeError::custom("missing sentinel key"))}#[cfg(test)]mod tests {    use super::*;    #[test]    fn parses_basic_scalar() {        let v = parse_toml_value("42").expect("parse");        assert_eq!(v.as_integer(), Some(42));    }    #[test]    fn parses_bool() {        let true_literal = parse_toml_value("true").expect("parse");        assert_eq!(true_literal.as_bool(), Some(true));        let false_literal = parse_toml_value("false").expect("parse");        assert_eq!(false_literal.as_bool(), Some(false));    }    #[test]    fn fails_on_unquoted_string() {        assert!(parse_toml_value("hello").is_err());    }    #[test]    fn parses_array() {        let v = parse_toml_value("[1, 2, 3]").expect("parse");        let arr = v.as_array().expect("array");        assert_eq!(arr.len(), 3);    }    #[test]    fn canonicalizes_use_legacy_landlock_alias() {        let overrides = CliConfigOverrides {            raw_overrides: vec!["use_legacy_landlock=true".to_string()],        };        let parsed = overrides.parse_overrides().expect("parse_overrides");        assert_eq!(parsed[0].0.as_str(), "features.use_legacy_landlock");        assert_eq!(parsed[0].1.as_bool(), Some(true));    }    #[test]    fn prepends_root_overrides() {        let mut subcommand_overrides = CliConfigOverrides {            raw_overrides: vec![r#"model="gpt-5.2""#.to_string()],        };        subcommand_overrides.prepend_root_overrides(CliConfigOverrides {            raw_overrides: vec![r#"model="gpt-5.1""#.to_string()],        });        assert_eq!(            subcommand_overrides.raw_overrides,            vec![                r#"model="gpt-5.1""#.to_string(),                r#"model="gpt-5.2""#.to_string(),            ]        );    }    #[test]    fn parses_inline_table() {        let v = parse_toml_value("{a = 1, b = 2}").expect("parse");        let tbl = v.as_table().expect("table");        assert_eq!(tbl.get("a").unwrap().as_integer(), Some(1));        assert_eq!(tbl.get("b").unwrap().as_integer(), Some(2));    }}