mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
[codex] add rollout token budget configuration (varlength 1/N) (#28746)
## What This PR defines the structured configuration contract for shared rollout token budgets (across ALL agent threads under 1 rollout). ```toml [features.rollout_budget] enabled = true limit_tokens = 100000 reminder_interval_tokens = 10000 sampling_token_weight = 1.0 prefill_token_weight = 0.1 ``` The reminder interval defaults to 10% of the rollout limit. Sampling and prefill weights default to `1.0`. ## Scope This PR only defines and validates configuration. It does not track usage, inject reminders, or stop a rollout. Accounting and reminders are implemented in the stacked follow-up #28494. The existing `token_budget` feature remains unchanged. `rollout_budget` has its own feature key and configuration type. ## Tests The config test verifies that the structured fields resolve into `RolloutBudgetConfig` and do not enable the existing `token_budget` feature. Local checks: - `just write-config-schema` - `just test -p codex-core load_config_resolves_rollout_budget` - `cargo check -p codex-thread-manager-sample` - `git diff --check` The full workspace test suite was not run locally.
This commit is contained in:
1 parent
c73296a0f0
commit
ecc4c30e28
8 files changed
+264
No files matched your search
@@ -44,6 +44,15 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if feature.id == codex_features::Feature::RolloutBudget {
|
||||||
|
validation.properties.insert(
|
||||||
|
feature.key.to_string(),
|
||||||
|
schema_gen.subschema_for::<codex_features::FeatureToml<
|
||||||
|
codex_features::RolloutBudgetConfigToml,
|
||||||
|
>>(),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if feature.id == codex_features::Feature::AppsMcpPathOverride {
|
if feature.id == codex_features::Feature::AppsMcpPathOverride {
|
||||||
validation.properties.insert(
|
validation.properties.insert(
|
||||||
feature.key.to_string(),
|
feature.key.to_string(),
|
||||||
|
|||||||
@@ -611,6 +611,9 @@
|
|||||||
"responses_websockets_v2": {
|
"responses_websockets_v2": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"rollout_budget": {
|
||||||
|
"$ref": "#/definitions/FeatureToml_for_RolloutBudgetConfigToml"
|
||||||
|
},
|
||||||
"runtime_metrics": {
|
"runtime_metrics": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@@ -908,6 +911,16 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"FeatureToml_for_RolloutBudgetConfigToml": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/RolloutBudgetConfigToml"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"FeedbackConfigToml": {
|
"FeedbackConfigToml": {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2572,6 +2585,35 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"RolloutBudgetConfigToml": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"limit_tokens": {
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1.0,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"prefill_token_weight": {
|
||||||
|
"format": "double",
|
||||||
|
"minimum": 0.0,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"reminder_interval_tokens": {
|
||||||
|
"format": "int64",
|
||||||
|
"minimum": 1.0,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"sampling_token_weight": {
|
||||||
|
"format": "double",
|
||||||
|
"minimum": 0.0,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SandboxMode": {
|
"SandboxMode": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"read-only",
|
"read-only",
|
||||||
@@ -4772,6 +4814,9 @@
|
|||||||
"responses_websockets_v2": {
|
"responses_websockets_v2": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"rollout_budget": {
|
||||||
|
"$ref": "#/definitions/FeatureToml_for_RolloutBudgetConfigToml"
|
||||||
|
},
|
||||||
"runtime_metrics": {
|
"runtime_metrics": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -461,6 +461,67 @@ direct_only_tool_namespaces = ["mcp__history", "mcp__notes"]
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn load_config_resolves_rollout_budget() -> std::io::Result<()> {
|
||||||
|
let codex_home = tempdir()?;
|
||||||
|
let config_toml: ConfigToml = toml::from_str(
|
||||||
|
r#"
|
||||||
|
[features.rollout_budget]
|
||||||
|
enabled = true
|
||||||
|
limit_tokens = 100000
|
||||||
|
reminder_interval_tokens = 10000
|
||||||
|
sampling_token_weight = 1.0
|
||||||
|
prefill_token_weight = 0.1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("TOML deserialization should succeed");
|
||||||
|
let config = Config::load_from_base_config_with_overrides(
|
||||||
|
config_toml,
|
||||||
|
ConfigOverrides::default(),
|
||||||
|
codex_home.abs(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert!(config.features.enabled(Feature::RolloutBudget));
|
||||||
|
assert!(!config.features.enabled(Feature::TokenBudget));
|
||||||
|
assert_eq!(
|
||||||
|
config.rollout_budget,
|
||||||
|
Some(RolloutBudgetConfig {
|
||||||
|
limit_tokens: 100_000,
|
||||||
|
reminder_interval_tokens: 10_000,
|
||||||
|
sampling_token_weight: 1.0,
|
||||||
|
prefill_token_weight: 0.1,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn load_config_rejects_enabled_rollout_budget_without_limit() -> std::io::Result<()> {
|
||||||
|
for config_toml in [
|
||||||
|
"[features]\nrollout_budget = true\n",
|
||||||
|
"[features.rollout_budget]\nenabled = true\n",
|
||||||
|
] {
|
||||||
|
let codex_home = tempdir()?;
|
||||||
|
let config_toml: ConfigToml =
|
||||||
|
toml::from_str(config_toml).expect("TOML deserialization should succeed");
|
||||||
|
let err = Config::load_from_base_config_with_overrides(
|
||||||
|
config_toml,
|
||||||
|
ConfigOverrides::default(),
|
||||||
|
codex_home.abs(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect_err("enabled rollout budget without limit_tokens should be rejected");
|
||||||
|
|
||||||
|
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"features.rollout_budget.limit_tokens is required when rollout_budget is enabled"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_provider_auth_with_env_key() {
|
fn rejects_provider_auth_with_env_key() {
|
||||||
let err = toml::from_str::<ConfigToml>(
|
let err = toml::from_str::<ConfigToml>(
|
||||||
|
|||||||
@@ -1020,6 +1020,9 @@ pub struct Config {
|
|||||||
/// Settings specific to the task-path-based multi-agent tool surface.
|
/// Settings specific to the task-path-based multi-agent tool surface.
|
||||||
pub multi_agent_v2: MultiAgentV2Config,
|
pub multi_agent_v2: MultiAgentV2Config,
|
||||||
|
|
||||||
|
/// Shared token budget for the root thread and its sub-agents.
|
||||||
|
pub rollout_budget: Option<RolloutBudgetConfig>,
|
||||||
|
|
||||||
/// Centralized feature flags; source of truth for feature gating.
|
/// Centralized feature flags; source of truth for feature gating.
|
||||||
pub features: ManagedFeatures,
|
pub features: ManagedFeatures,
|
||||||
|
|
||||||
@@ -1064,6 +1067,14 @@ pub struct CodeModeConfig {
|
|||||||
pub direct_only_tool_namespaces: Vec<String>,
|
pub direct_only_tool_namespaces: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
|
||||||
|
pub struct RolloutBudgetConfig {
|
||||||
|
pub limit_tokens: i64,
|
||||||
|
pub reminder_interval_tokens: i64,
|
||||||
|
pub sampling_token_weight: f64,
|
||||||
|
pub prefill_token_weight: f64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
pub struct MultiAgentV2Config {
|
pub struct MultiAgentV2Config {
|
||||||
pub max_concurrent_threads_per_session: usize,
|
pub max_concurrent_threads_per_session: usize,
|
||||||
@@ -2469,6 +2480,65 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_rollout_budget_config(
|
||||||
|
config_toml: &ConfigToml,
|
||||||
|
features: &ManagedFeatures,
|
||||||
|
) -> std::io::Result<Option<RolloutBudgetConfig>> {
|
||||||
|
if !features.enabled(Feature::RolloutBudget) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let missing_limit_error = || {
|
||||||
|
std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidInput,
|
||||||
|
"features.rollout_budget.limit_tokens is required when rollout_budget is enabled",
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let Some(FeatureToml::Config(config)) = config_toml
|
||||||
|
.features
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|features| features.rollout_budget.as_ref())
|
||||||
|
else {
|
||||||
|
return Err(missing_limit_error());
|
||||||
|
};
|
||||||
|
let Some(limit_tokens) = config.limit_tokens else {
|
||||||
|
return Err(missing_limit_error());
|
||||||
|
};
|
||||||
|
if limit_tokens <= 0 {
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidInput,
|
||||||
|
"features.rollout_budget.limit_tokens must be positive",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let reminder_interval_tokens = config
|
||||||
|
.reminder_interval_tokens
|
||||||
|
.unwrap_or_else(|| (limit_tokens / 10).max(1));
|
||||||
|
if reminder_interval_tokens <= 0 {
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidInput,
|
||||||
|
"features.rollout_budget.reminder_interval_tokens must be positive",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let sampling_token_weight = config.sampling_token_weight.unwrap_or(1.0);
|
||||||
|
let prefill_token_weight = config.prefill_token_weight.unwrap_or(1.0);
|
||||||
|
for (field, weight) in [
|
||||||
|
("sampling_token_weight", sampling_token_weight),
|
||||||
|
("prefill_token_weight", prefill_token_weight),
|
||||||
|
] {
|
||||||
|
if !weight.is_finite() || weight < 0.0 {
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidInput,
|
||||||
|
format!("features.rollout_budget.{field} must be finite and non-negative"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(RolloutBudgetConfig {
|
||||||
|
limit_tokens,
|
||||||
|
reminder_interval_tokens,
|
||||||
|
sampling_token_weight,
|
||||||
|
prefill_token_weight,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_terminal_resize_reflow_config(config_toml: &ConfigToml) -> TerminalResizeReflowConfig {
|
fn resolve_terminal_resize_reflow_config(config_toml: &ConfigToml) -> TerminalResizeReflowConfig {
|
||||||
let Some(tui) = config_toml.tui.as_ref() else {
|
let Some(tui) = config_toml.tui.as_ref() else {
|
||||||
return TerminalResizeReflowConfig::default();
|
return TerminalResizeReflowConfig::default();
|
||||||
@@ -3146,6 +3216,7 @@ impl Config {
|
|||||||
resolve_experimental_request_user_input_enabled(&cfg);
|
resolve_experimental_request_user_input_enabled(&cfg);
|
||||||
let code_mode = resolve_code_mode_config(&cfg);
|
let code_mode = resolve_code_mode_config(&cfg);
|
||||||
let multi_agent_v2 = resolve_multi_agent_v2_config(&cfg);
|
let multi_agent_v2 = resolve_multi_agent_v2_config(&cfg);
|
||||||
|
let rollout_budget = resolve_rollout_budget_config(&cfg, &features)?;
|
||||||
let terminal_resize_reflow = resolve_terminal_resize_reflow_config(&cfg);
|
let terminal_resize_reflow = resolve_terminal_resize_reflow_config(&cfg);
|
||||||
|
|
||||||
let agent_roles =
|
let agent_roles =
|
||||||
@@ -3686,6 +3757,7 @@ impl Config {
|
|||||||
background_terminal_max_timeout,
|
background_terminal_max_timeout,
|
||||||
ghost_snapshot,
|
ghost_snapshot,
|
||||||
multi_agent_v2,
|
multi_agent_v2,
|
||||||
|
rollout_budget,
|
||||||
features,
|
features,
|
||||||
suppress_unstable_features_warning: cfg
|
suppress_unstable_features_warning: cfg
|
||||||
.suppress_unstable_features_warning
|
.suppress_unstable_features_warning
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use codex_features::Feature;
|
|||||||
use codex_features::FeatureToml;
|
use codex_features::FeatureToml;
|
||||||
use codex_features::FeaturesToml;
|
use codex_features::FeaturesToml;
|
||||||
use codex_features::MultiAgentV2ConfigToml;
|
use codex_features::MultiAgentV2ConfigToml;
|
||||||
|
use codex_features::RolloutBudgetConfigToml;
|
||||||
use codex_protocol::ThreadId;
|
use codex_protocol::ThreadId;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@@ -148,6 +149,12 @@ fn save_config_resolved_fields(
|
|||||||
resolved_config_to_toml(&config.multi_agent_v2, "features.multi_agent_v2")?;
|
resolved_config_to_toml(&config.multi_agent_v2, "features.multi_agent_v2")?;
|
||||||
multi_agent_v2.enabled = Some(config.features.enabled(Feature::MultiAgentV2));
|
multi_agent_v2.enabled = Some(config.features.enabled(Feature::MultiAgentV2));
|
||||||
features.multi_agent_v2 = Some(FeatureToml::Config(multi_agent_v2));
|
features.multi_agent_v2 = Some(FeatureToml::Config(multi_agent_v2));
|
||||||
|
if let Some(rollout_budget) = config.rollout_budget.as_ref() {
|
||||||
|
let mut rollout_budget: RolloutBudgetConfigToml =
|
||||||
|
resolved_config_to_toml(rollout_budget, "features.rollout_budget")?;
|
||||||
|
rollout_budget.enabled = Some(config.features.enabled(Feature::RolloutBudget));
|
||||||
|
features.rollout_budget = Some(FeatureToml::Config(rollout_budget));
|
||||||
|
}
|
||||||
lock_config.memories = Some(resolved_config_to_toml::<MemoriesToml>(
|
lock_config.memories = Some(resolved_config_to_toml::<MemoriesToml>(
|
||||||
&config.memories,
|
&config.memories,
|
||||||
"memories",
|
"memories",
|
||||||
@@ -209,6 +216,18 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn lock_contains_prompts_and_materializes_features() {
|
async fn lock_contains_prompts_and_materializes_features() {
|
||||||
let mut sc = crate::session::tests::make_session_configuration_for_tests().await;
|
let mut sc = crate::session::tests::make_session_configuration_for_tests().await;
|
||||||
|
let mut config = (*sc.original_config_do_not_use).clone();
|
||||||
|
config.rollout_budget = Some(crate::config::RolloutBudgetConfig {
|
||||||
|
limit_tokens: 100_000,
|
||||||
|
reminder_interval_tokens: 10_000,
|
||||||
|
sampling_token_weight: 1.0,
|
||||||
|
prefill_token_weight: 0.25,
|
||||||
|
});
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::RolloutBudget)
|
||||||
|
.expect("rollout_budget should be enableable in tests");
|
||||||
|
sc.original_config_do_not_use = Arc::new(config);
|
||||||
sc.base_instructions = "resolved instructions".to_string();
|
sc.base_instructions = "resolved instructions".to_string();
|
||||||
sc.developer_instructions = Some("resolved developer instructions".to_string());
|
sc.developer_instructions = Some("resolved developer instructions".to_string());
|
||||||
sc.compact_prompt = Some("resolved compact prompt".to_string());
|
sc.compact_prompt = Some("resolved compact prompt".to_string());
|
||||||
@@ -273,6 +292,17 @@ mod tests {
|
|||||||
})
|
})
|
||||||
));
|
));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
features.rollout_budget,
|
||||||
|
Some(FeatureToml::Config(RolloutBudgetConfigToml {
|
||||||
|
enabled: Some(true),
|
||||||
|
limit_tokens: Some(100_000),
|
||||||
|
reminder_interval_tokens: Some(10_000),
|
||||||
|
sampling_token_weight: Some(1.0),
|
||||||
|
prefill_token_weight: Some(0.25),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(lockfile.version, crate::config_lock::CONFIG_LOCK_VERSION);
|
assert_eq!(lockfile.version, crate::config_lock::CONFIG_LOCK_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,35 @@ impl FeatureConfig for MultiAgentV2ConfigToml {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct RolloutBudgetConfigToml {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub enabled: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schemars(range(min = 1))]
|
||||||
|
pub limit_tokens: Option<i64>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schemars(range(min = 1))]
|
||||||
|
pub reminder_interval_tokens: Option<i64>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schemars(range(min = 0.0))]
|
||||||
|
pub sampling_token_weight: Option<f64>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schemars(range(min = 0.0))]
|
||||||
|
pub prefill_token_weight: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FeatureConfig for RolloutBudgetConfigToml {
|
||||||
|
fn enabled(&self) -> Option<bool> {
|
||||||
|
self.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_enabled(&mut self, enabled: bool) {
|
||||||
|
self.enabled = Some(enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub(crate) struct RemovedAppsMcpPathOverrideConfigToml {
|
pub(crate) struct RemovedAppsMcpPathOverrideConfigToml {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ pub use feature_configs::NetworkProxyDomainPermissionToml;
|
|||||||
pub use feature_configs::NetworkProxyModeToml;
|
pub use feature_configs::NetworkProxyModeToml;
|
||||||
pub use feature_configs::NetworkProxyUnixSocketPermissionToml;
|
pub use feature_configs::NetworkProxyUnixSocketPermissionToml;
|
||||||
use feature_configs::RemovedAppsMcpPathOverrideConfigToml;
|
use feature_configs::RemovedAppsMcpPathOverrideConfigToml;
|
||||||
|
pub use feature_configs::RolloutBudgetConfigToml;
|
||||||
use legacy::LegacyFeatureToggles;
|
use legacy::LegacyFeatureToggles;
|
||||||
pub use legacy::legacy_feature_keys;
|
pub use legacy::legacy_feature_keys;
|
||||||
|
|
||||||
@@ -203,6 +204,8 @@ pub enum Feature {
|
|||||||
Goals,
|
Goals,
|
||||||
/// Add current context-window metadata to model-visible context.
|
/// Add current context-window metadata to model-visible context.
|
||||||
TokenBudget,
|
TokenBudget,
|
||||||
|
/// Track and report a shared token budget across a session's agent threads.
|
||||||
|
RolloutBudget,
|
||||||
/// Expose an input-interruptible sleep tool.
|
/// Expose an input-interruptible sleep tool.
|
||||||
SleepTool,
|
SleepTool,
|
||||||
/// Route MCP tool approval prompts through the MCP elicitation request path.
|
/// Route MCP tool approval prompts through the MCP elicitation request path.
|
||||||
@@ -618,6 +621,8 @@ pub struct FeaturesToml {
|
|||||||
pub code_mode: Option<FeatureToml<CodeModeConfigToml>>,
|
pub code_mode: Option<FeatureToml<CodeModeConfigToml>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub multi_agent_v2: Option<FeatureToml<MultiAgentV2ConfigToml>>,
|
pub multi_agent_v2: Option<FeatureToml<MultiAgentV2ConfigToml>>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub rollout_budget: Option<FeatureToml<RolloutBudgetConfigToml>>,
|
||||||
#[serde(default, rename = "apps_mcp_path_override", skip_serializing)]
|
#[serde(default, rename = "apps_mcp_path_override", skip_serializing)]
|
||||||
#[schemars(skip)]
|
#[schemars(skip)]
|
||||||
removed_apps_mcp_path_override: Option<FeatureToml<RemovedAppsMcpPathOverrideConfigToml>>,
|
removed_apps_mcp_path_override: Option<FeatureToml<RemovedAppsMcpPathOverrideConfigToml>>,
|
||||||
@@ -650,6 +655,9 @@ impl FeaturesToml {
|
|||||||
if let Some(enabled) = self.multi_agent_v2.as_ref().and_then(FeatureToml::enabled) {
|
if let Some(enabled) = self.multi_agent_v2.as_ref().and_then(FeatureToml::enabled) {
|
||||||
entries.insert(Feature::MultiAgentV2.key().to_string(), enabled);
|
entries.insert(Feature::MultiAgentV2.key().to_string(), enabled);
|
||||||
}
|
}
|
||||||
|
if let Some(enabled) = self.rollout_budget.as_ref().and_then(FeatureToml::enabled) {
|
||||||
|
entries.insert(Feature::RolloutBudget.key().to_string(), enabled);
|
||||||
|
}
|
||||||
if let Some(enabled) = self.network_proxy.as_ref().and_then(FeatureToml::enabled) {
|
if let Some(enabled) = self.network_proxy.as_ref().and_then(FeatureToml::enabled) {
|
||||||
entries.insert(Feature::NetworkProxy.key().to_string(), enabled);
|
entries.insert(Feature::NetworkProxy.key().to_string(), enabled);
|
||||||
}
|
}
|
||||||
@@ -661,6 +669,7 @@ impl FeaturesToml {
|
|||||||
let Self {
|
let Self {
|
||||||
code_mode,
|
code_mode,
|
||||||
multi_agent_v2,
|
multi_agent_v2,
|
||||||
|
rollout_budget,
|
||||||
removed_apps_mcp_path_override: _,
|
removed_apps_mcp_path_override: _,
|
||||||
network_proxy,
|
network_proxy,
|
||||||
entries,
|
entries,
|
||||||
@@ -674,6 +683,8 @@ impl FeaturesToml {
|
|||||||
materialize_resolved_feature_enabled(code_mode, enabled);
|
materialize_resolved_feature_enabled(code_mode, enabled);
|
||||||
} else if spec.id == Feature::MultiAgentV2 {
|
} else if spec.id == Feature::MultiAgentV2 {
|
||||||
materialize_resolved_feature_enabled(multi_agent_v2, enabled);
|
materialize_resolved_feature_enabled(multi_agent_v2, enabled);
|
||||||
|
} else if spec.id == Feature::RolloutBudget {
|
||||||
|
materialize_resolved_feature_enabled(rollout_budget, enabled);
|
||||||
} else if spec.id == Feature::NetworkProxy {
|
} else if spec.id == Feature::NetworkProxy {
|
||||||
materialize_resolved_feature_enabled(network_proxy, enabled);
|
materialize_resolved_feature_enabled(network_proxy, enabled);
|
||||||
} else {
|
} else {
|
||||||
@@ -1167,6 +1178,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
|||||||
stage: Stage::UnderDevelopment,
|
stage: Stage::UnderDevelopment,
|
||||||
default_enabled: false,
|
default_enabled: false,
|
||||||
},
|
},
|
||||||
|
FeatureSpec {
|
||||||
|
id: Feature::RolloutBudget,
|
||||||
|
key: "rollout_budget",
|
||||||
|
stage: Stage::UnderDevelopment,
|
||||||
|
default_enabled: false,
|
||||||
|
},
|
||||||
FeatureSpec {
|
FeatureSpec {
|
||||||
id: Feature::SleepTool,
|
id: Feature::SleepTool,
|
||||||
key: "sleep_tool",
|
key: "sleep_tool",
|
||||||
|
|||||||
@@ -277,6 +277,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
|
|||||||
background_terminal_max_timeout: 300_000,
|
background_terminal_max_timeout: 300_000,
|
||||||
ghost_snapshot: GhostSnapshotConfig::default(),
|
ghost_snapshot: GhostSnapshotConfig::default(),
|
||||||
multi_agent_v2: MultiAgentV2Config::default(),
|
multi_agent_v2: MultiAgentV2Config::default(),
|
||||||
|
rollout_budget: None,
|
||||||
features: Default::default(),
|
features: Default::default(),
|
||||||
suppress_unstable_features_warning: false,
|
suppress_unstable_features_warning: false,
|
||||||
active_project: ProjectConfig { trust_level: None },
|
active_project: ProjectConfig { trust_level: None },
|
||||||
|
|||||||
Reference in new issue
Block a user