Codex Handbook
core/src/config/managed_features.rs 329 lines
use std::collections::BTreeMap;use codex_config::Constrained;use codex_config::ConstrainedWithSource;use codex_config::ConstraintError;use codex_config::ConstraintResult;use codex_config::FeatureRequirementsToml;use codex_config::RequirementSource;use codex_config::Sourced;use codex_config::config_toml::ConfigToml;use codex_features::Feature;use codex_features::FeatureConfigSource;use codex_features::FeatureOverrides;use codex_features::Features;use codex_features::canonical_feature_for_key;use codex_features::feature_for_key;/// Wrapper around [`Features`] which enforces constraints defined in/// `FeatureRequirementsToml` and provides normalization to ensure constraints/// are satisfied. Constraints are enforced on construction and mutation of/// `ManagedFeatures`.#[derive(Debug, Clone, PartialEq)]pub struct ManagedFeatures {    value: ConstrainedWithSource<Features>,    pinned_features: BTreeMap<Feature, bool>,}impl Default for ManagedFeatures {    fn default() -> Self {        Self {            value: ConstrainedWithSource::new(                Constrained::allow_any(Features::default()),                /*source*/ None,            ),            pinned_features: BTreeMap::new(),        }    }}impl ManagedFeatures {    pub(crate) fn from_configured(        configured_features: Features,        feature_requirements: Option<Sourced<FeatureRequirementsToml>>,    ) -> std::io::Result<Self> {        Self::from_configured_with_optional_warnings(            configured_features,            feature_requirements,            /*startup_warnings*/ None,        )    }    pub(crate) fn from_configured_with_warnings(        configured_features: Features,        feature_requirements: Option<Sourced<FeatureRequirementsToml>>,        startup_warnings: &mut Vec<String>,    ) -> std::io::Result<Self> {        Self::from_configured_with_optional_warnings(            configured_features,            feature_requirements,            Some(startup_warnings),        )    }    fn from_configured_with_optional_warnings(        configured_features: Features,        feature_requirements: Option<Sourced<FeatureRequirementsToml>>,        startup_warnings: Option<&mut Vec<String>>,    ) -> std::io::Result<Self> {        let (pinned_features, source) = match feature_requirements {            Some(Sourced {                value: feature_requirements,                source,            }) => (                parse_feature_requirements(feature_requirements, &source, startup_warnings),                Some(source),            ),            None => (BTreeMap::new(), None),        };        let normalized_features = normalize_candidate(configured_features, &pinned_features);        validate_pinned_features(&normalized_features, &pinned_features, source.as_ref())?;        Ok(Self {            value: ConstrainedWithSource::new(Constrained::allow_any(normalized_features), source),            pinned_features,        })    }    pub fn get(&self) -> &Features {        self.value.get()    }    fn normalize_and_validate(&self, candidate: Features) -> ConstraintResult<Features> {        let normalized = normalize_candidate(candidate, &self.pinned_features);        self.value.can_set(&normalized)?;        validate_pinned_features_constraint(            &normalized,            &self.pinned_features,            self.value.source.as_ref(),        )?;        Ok(normalized)    }    pub fn can_set(&self, candidate: &Features) -> ConstraintResult<()> {        self.normalize_and_validate(candidate.clone()).map(|_| ())    }    pub fn set(&mut self, candidate: Features) -> ConstraintResult<()> {        let normalized = self.normalize_and_validate(candidate)?;        self.value.value.set(normalized)    }    pub fn set_enabled(&mut self, feature: Feature, enabled: bool) -> ConstraintResult<()> {        let mut next = self.get().clone();        next.set_enabled(feature, enabled);        self.set(next)    }    pub fn enable(&mut self, feature: Feature) -> ConstraintResult<()> {        self.set_enabled(feature, /*enabled*/ true)    }    pub fn disable(&mut self, feature: Feature) -> ConstraintResult<()> {        self.set_enabled(feature, /*enabled*/ false)    }}/// Only available for tests to ensure `ManagedFeatures` is constructed with/// any required constraints taken into account.#[cfg(test)]impl From<Features> for ManagedFeatures {    fn from(features: Features) -> Self {        Self {            value: ConstrainedWithSource::new(                Constrained::allow_any(features),                /*source*/ None,            ),            pinned_features: BTreeMap::new(),        }    }}impl std::ops::Deref for ManagedFeatures {    type Target = Features;    fn deref(&self) -> &Self::Target {        self.get()    }}fn normalize_candidate(    mut candidate: Features,    pinned_features: &BTreeMap<Feature, bool>,) -> Features {    for (feature, enabled) in pinned_features {        candidate.set_enabled(*feature, *enabled);    }    candidate.normalize_dependencies();    candidate}fn validate_pinned_features_constraint(    normalized_features: &Features,    pinned_features: &BTreeMap<Feature, bool>,    source: Option<&RequirementSource>,) -> ConstraintResult<()> {    let Some(source) = source else {        return Ok(());    };    let allowed = feature_requirements_display(pinned_features);    for (feature, enabled) in pinned_features {        if normalized_features.enabled(*feature) != *enabled {            return Err(ConstraintError::InvalidValue {                field_name: "features",                candidate: format!(                    "{}={}",                    feature.key(),                    normalized_features.enabled(*feature)                ),                allowed,                requirement_source: source.clone(),            });        }    }    Ok(())}fn validate_pinned_features(    normalized_features: &Features,    pinned_features: &BTreeMap<Feature, bool>,    source: Option<&RequirementSource>,) -> std::io::Result<()> {    validate_pinned_features_constraint(normalized_features, pinned_features, source)        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))}fn feature_requirements_display(feature_requirements: &BTreeMap<Feature, bool>) -> String {    let values = feature_requirements        .iter()        .map(|(feature, enabled)| format!("{}={enabled}", feature.key()))        .collect::<Vec<_>>();    format!("[{}]", values.join(", "))}fn parse_feature_requirements(    feature_requirements: FeatureRequirementsToml,    source: &RequirementSource,    mut startup_warnings: Option<&mut Vec<String>>,) -> BTreeMap<Feature, bool> {    let mut pinned_features = BTreeMap::new();    for (key, enabled) in feature_requirements.entries {        if key == "auto_review" {            pinned_features.insert(Feature::GuardianApproval, enabled);            continue;        }        if let Some(feature) = canonical_feature_for_key(&key) {            pinned_features.insert(feature, enabled);            continue;        }        if let Some(feature) = feature_for_key(&key) {            push_feature_requirement_warning(                &mut startup_warnings,                format!(                    "Using legacy `features` requirement `{key}` from {source}; prefer canonical feature key `{}`",                    feature.key()                ),            );            pinned_features.insert(feature, enabled);            continue;        }        push_feature_requirement_warning(            &mut startup_warnings,            format!("Ignoring unknown `features` requirement `{key}` from {source}"),        );    }    pinned_features}fn push_feature_requirement_warning(    startup_warnings: &mut Option<&mut Vec<String>>,    message: String,) {    tracing::warn!("{message}");    if let Some(startup_warnings) = startup_warnings.as_deref_mut() {        startup_warnings.push(message);    }}fn explicit_feature_settings_in_config(cfg: &ConfigToml) -> Vec<(String, Feature, bool)> {    let mut explicit_settings = Vec::new();    if let Some(features) = cfg.features.as_ref() {        for (key, enabled) in features.entries() {            if let Some(feature) = feature_for_key(&key) {                explicit_settings.push((format!("features.{key}"), feature, enabled));            }        }    }    if let Some(enabled) = cfg.experimental_use_unified_exec_tool {        explicit_settings.push((            "experimental_use_unified_exec_tool".to_string(),            Feature::UnifiedExec,            enabled,        ));    }    explicit_settings}pub(crate) fn validate_explicit_feature_settings_in_config_toml(    cfg: &ConfigToml,    feature_requirements: Option<&Sourced<FeatureRequirementsToml>>,) -> std::io::Result<()> {    let Some(Sourced {        value: feature_requirements,        source,    }) = feature_requirements    else {        return Ok(());    };    let pinned_features = parse_feature_requirements(        feature_requirements.clone(),        source,        /*startup_warnings*/ None,    );    if pinned_features.is_empty() {        return Ok(());    }    let allowed = feature_requirements_display(&pinned_features);    for (path, feature, enabled) in explicit_feature_settings_in_config(cfg) {        if pinned_features            .get(&feature)            .is_some_and(|required| *required != enabled)        {            return Err(std::io::Error::new(                std::io::ErrorKind::InvalidData,                ConstraintError::InvalidValue {                    field_name: "features",                    candidate: format!("{path}={enabled}"),                    allowed,                    requirement_source: source.clone(),                },            ));        }    }    Ok(())}pub(crate) fn validate_feature_requirements_in_config_toml(    cfg: &ConfigToml,    feature_requirements: Option<&Sourced<FeatureRequirementsToml>>,) -> std::io::Result<()> {    let configured_features = Features::from_sources(        FeatureConfigSource {            features: cfg.features.as_ref(),            experimental_use_unified_exec_tool: cfg.experimental_use_unified_exec_tool,        },        FeatureConfigSource::default(),        FeatureOverrides::default(),    );    ManagedFeatures::from_configured(configured_features, feature_requirements.cloned()).map(|_| ())}