mirror of
https://github.com/openai/codex.git
synced 2026-05-02 12:21:26 +03:00
Configure multi_agent_v2 spawn agent hints (#17071)
Allow multi_agent_v2 features to have its own temporary configuration under `[features.multi_agent_v2]` ``` [features.multi_agent_v2] enabled = true usage_hint_enabled = false usage_hint_text = "Custom delegation guidance." hide_spawn_agent_metadata = true ``` Absent `usage_hint_text` means use the default hint. ``` [features] multi_agent_v2 = true ``` still works as the boolean shorthand.
This commit is contained in:
23
codex-rs/features/src/feature_configs.rs
Normal file
23
codex-rs/features/src/feature_configs.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::FeatureConfig;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MultiAgentV2ConfigToml {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub usage_hint_enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub usage_hint_text: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hide_spawn_agent_metadata: Option<bool>,
|
||||
}
|
||||
|
||||
impl FeatureConfig for MultiAgentV2ConfigToml {
|
||||
fn enabled(&self) -> Option<bool> {
|
||||
self.enabled
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,9 @@ use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use toml::Table;
|
||||
|
||||
mod feature_configs;
|
||||
mod legacy;
|
||||
pub use feature_configs::MultiAgentV2ConfigToml;
|
||||
use legacy::LegacyFeatureToggles;
|
||||
pub use legacy::legacy_feature_keys;
|
||||
|
||||
@@ -138,8 +140,6 @@ pub enum Feature {
|
||||
Collab,
|
||||
/// Enable task-path-based multi-agent routing.
|
||||
MultiAgentV2,
|
||||
/// Hide spawn_agent agent/model override fields from the model-visible tool schema.
|
||||
DebugHideSpawnAgentMetadata,
|
||||
/// Enable CSV-backed agent job tools.
|
||||
SpawnCsv,
|
||||
/// Enable apps.
|
||||
@@ -398,7 +398,7 @@ impl Features {
|
||||
.apply(&mut features);
|
||||
|
||||
if let Some(feature_entries) = source.features {
|
||||
features.apply_map(&feature_entries.entries);
|
||||
features.apply_toml(feature_entries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,8 +492,61 @@ pub fn is_known_feature_key(key: &str) -> bool {
|
||||
/// Deserializable features table for TOML.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
|
||||
pub struct FeaturesToml {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub multi_agent_v2: Option<FeatureToml<MultiAgentV2ConfigToml>>,
|
||||
/// Boolean feature toggles keyed by canonical or legacy feature name.
|
||||
#[serde(flatten)]
|
||||
pub entries: BTreeMap<String, bool>,
|
||||
entries: BTreeMap<String, bool>,
|
||||
}
|
||||
|
||||
impl Features {
|
||||
fn apply_toml(&mut self, features: &FeaturesToml) {
|
||||
let entries = features.entries();
|
||||
self.apply_map(&entries);
|
||||
}
|
||||
}
|
||||
|
||||
impl FeaturesToml {
|
||||
pub fn entries(&self) -> BTreeMap<String, bool> {
|
||||
let mut entries = self.entries.clone();
|
||||
if let Some(enabled) = self.multi_agent_v2.as_ref().and_then(FeatureToml::enabled) {
|
||||
entries.insert(Feature::MultiAgentV2.key().to_string(), enabled);
|
||||
}
|
||||
entries
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<String, bool>> for FeaturesToml {
|
||||
fn from(entries: BTreeMap<String, bool>) -> Self {
|
||||
Self {
|
||||
entries,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To be used for features that need more configuration than just enabled/disabled and
|
||||
// require a custom config struct under `[features]`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum FeatureToml<T> {
|
||||
Enabled(bool),
|
||||
Config(T),
|
||||
}
|
||||
|
||||
impl<T: FeatureConfig> FeatureToml<T> {
|
||||
pub fn enabled(&self) -> Option<bool> {
|
||||
match self {
|
||||
Self::Enabled(enabled) => Some(*enabled),
|
||||
Self::Config(config) => config.enabled(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A trait to be implemented by custom feature config structs when defining a feature that needs more configuration than
|
||||
// just enabled/disabled.
|
||||
pub trait FeatureConfig {
|
||||
fn enabled(&self) -> Option<bool>;
|
||||
}
|
||||
|
||||
/// Single, easy-to-read registry of all feature definitions.
|
||||
@@ -708,12 +761,6 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::DebugHideSpawnAgentMetadata,
|
||||
key: "debug_hide_spawn_agent_metadata",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::SpawnCsv,
|
||||
key: "enable_fanout",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::Feature;
|
||||
use crate::FeatureConfigSource;
|
||||
use crate::FeatureOverrides;
|
||||
use crate::FeatureToml;
|
||||
use crate::Features;
|
||||
use crate::FeaturesToml;
|
||||
use crate::Stage;
|
||||
@@ -211,12 +212,14 @@ fn from_sources_applies_base_profile_and_overrides() {
|
||||
base_entries.insert("plugins".to_string(), true);
|
||||
let base_features = FeaturesToml {
|
||||
entries: base_entries,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut profile_entries = BTreeMap::new();
|
||||
profile_entries.insert("code_mode_only".to_string(), true);
|
||||
let profile_features = FeaturesToml {
|
||||
entries: profile_entries,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let features = Features::from_sources(
|
||||
@@ -242,6 +245,81 @@ fn from_sources_applies_base_profile_and_overrides() {
|
||||
assert_eq!(features.enabled(Feature::WebSearchRequest), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_agent_v2_feature_config_deserializes_boolean_toggle() {
|
||||
let features: FeaturesToml = toml::from_str(
|
||||
r#"
|
||||
multi_agent_v2 = true
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
features.entries(),
|
||||
BTreeMap::from([("multi_agent_v2".to_string(), true)])
|
||||
);
|
||||
assert_eq!(features.multi_agent_v2, Some(FeatureToml::Enabled(true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_agent_v2_feature_config_deserializes_table() {
|
||||
let features: FeaturesToml = toml::from_str(
|
||||
r#"
|
||||
[multi_agent_v2]
|
||||
enabled = true
|
||||
usage_hint_enabled = false
|
||||
usage_hint_text = "Custom delegation guidance."
|
||||
hide_spawn_agent_metadata = true
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
features.entries(),
|
||||
BTreeMap::from([("multi_agent_v2".to_string(), true)])
|
||||
);
|
||||
assert_eq!(
|
||||
features.multi_agent_v2,
|
||||
Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
||||
enabled: Some(true),
|
||||
usage_hint_enabled: Some(false),
|
||||
usage_hint_text: Some("Custom delegation guidance.".to_string()),
|
||||
hide_spawn_agent_metadata: Some(true),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_agent_v2_feature_config_usage_hint_enabled_does_not_enable_feature() {
|
||||
let features_toml: FeaturesToml = toml::from_str(
|
||||
r#"
|
||||
[multi_agent_v2]
|
||||
usage_hint_enabled = false
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
let features = Features::from_sources(
|
||||
FeatureConfigSource {
|
||||
features: Some(&features_toml),
|
||||
..Default::default()
|
||||
},
|
||||
FeatureConfigSource::default(),
|
||||
FeatureOverrides::default(),
|
||||
);
|
||||
|
||||
assert_eq!(features.enabled(Feature::MultiAgentV2), false);
|
||||
assert_eq!(features_toml.entries(), BTreeMap::new());
|
||||
assert_eq!(
|
||||
features_toml.multi_agent_v2,
|
||||
Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
||||
enabled: None,
|
||||
usage_hint_enabled: Some(false),
|
||||
usage_hint_text: None,
|
||||
hide_spawn_agent_metadata: None,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unstable_warning_event_only_mentions_enabled_under_development_features() {
|
||||
let mut configured_features = Table::new();
|
||||
|
||||
Reference in New Issue
Block a user