mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
core: nest auto-compaction fallback config
This commit is contained in:
@@ -115,7 +115,7 @@ struct AutoCompactFallbackPrompt(Option<String>);
|
||||
impl AutoCompactFallbackPromptExtension {
|
||||
fn store(thread_store: &ExtensionData, config: &Config) {
|
||||
thread_store.insert(AutoCompactFallbackPrompt(
|
||||
config.auto_compact_fallback_prompt.clone(),
|
||||
config.auto_compact_fallback.prompt.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,10 +243,6 @@ pub struct ConfigToml {
|
||||
/// Compact prompt used for history compaction.
|
||||
pub compact_prompt: Option<String>,
|
||||
|
||||
/// Developer message contributed by the app-server extension for the single
|
||||
/// fallback turn before an automatic compaction rollover.
|
||||
pub auto_compact_fallback_prompt: Option<String>,
|
||||
|
||||
/// When set, restricts ChatGPT login to one or more workspace identifiers.
|
||||
#[serde(default)]
|
||||
pub forced_chatgpt_workspace_id: Option<ForcedChatgptWorkspaceIds>,
|
||||
|
||||
@@ -53,6 +53,15 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if feature.id == codex_features::Feature::AutoCompactFallback {
|
||||
validation.properties.insert(
|
||||
feature.key.to_string(),
|
||||
schema_gen.subschema_for::<codex_features::FeatureToml<
|
||||
codex_features::AutoCompactFallbackConfigToml,
|
||||
>>(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if feature.id == codex_features::Feature::RolloutBudget {
|
||||
validation.properties.insert(
|
||||
feature.key.to_string(),
|
||||
|
||||
@@ -315,6 +315,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"AutoCompactFallbackConfigToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"prompt": {
|
||||
"description": "Developer message contributed for the single fallback turn before an automatic compaction rollover.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AutoCompactTokenLimitScope": {
|
||||
"description": "Selects which part of the active context is charged against `model_auto_compact_token_limit`.",
|
||||
"oneOf": [
|
||||
@@ -435,7 +448,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"auto_compact_fallback": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_AutoCompactFallbackConfigToml"
|
||||
},
|
||||
"browser_use": {
|
||||
"type": "boolean"
|
||||
@@ -946,6 +959,16 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FeatureToml_for_AutoCompactFallbackConfigToml": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/AutoCompactFallbackConfigToml"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FeatureToml_for_CodeModeConfigToml": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -4668,10 +4691,6 @@
|
||||
"default": null,
|
||||
"description": "Machine-local realtime audio device preferences used by realtime voice."
|
||||
},
|
||||
"auto_compact_fallback_prompt": {
|
||||
"description": "Developer message contributed by the app-server extension for the single fallback turn before an automatic compaction rollover.",
|
||||
"type": "string"
|
||||
},
|
||||
"auto_review": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -4814,7 +4833,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"auto_compact_fallback": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_AutoCompactFallbackConfigToml"
|
||||
},
|
||||
"browser_use": {
|
||||
"type": "boolean"
|
||||
@@ -5545,4 +5564,4 @@
|
||||
},
|
||||
"title": "ConfigToml",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -7095,10 +7095,18 @@ async fn loads_compact_prompt_from_file() -> std::io::Result<()> {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn loads_auto_compact_fallback_prompt() -> std::io::Result<()> {
|
||||
async fn loads_auto_compact_fallback_settings() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let features = toml::from_str(
|
||||
r#"
|
||||
[auto_compact_fallback]
|
||||
enabled = true
|
||||
prompt = " write notes immediately "
|
||||
"#,
|
||||
)
|
||||
.expect("auto compact fallback feature config should deserialize");
|
||||
let cfg = ConfigToml {
|
||||
auto_compact_fallback_prompt: Some(" write notes immediately ".to_string()),
|
||||
features: Some(features),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -7110,9 +7118,42 @@ async fn loads_auto_compact_fallback_prompt() -> std::io::Result<()> {
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.auto_compact_fallback_prompt.as_deref(),
|
||||
config.auto_compact_fallback.prompt.as_deref(),
|
||||
Some("write notes immediately")
|
||||
);
|
||||
assert!(config.features.enabled(Feature::AutoCompactFallback));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prompt_only_auto_compact_fallback_settings_do_not_enable_feature() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let features = toml::from_str(
|
||||
r#"
|
||||
[auto_compact_fallback]
|
||||
prompt = " write notes immediately "
|
||||
"#,
|
||||
)
|
||||
.expect("auto compact fallback feature config should deserialize");
|
||||
let cfg = ConfigToml {
|
||||
features: Some(features),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
cfg,
|
||||
ConfigOverrides::default(),
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.auto_compact_fallback.prompt.as_deref(),
|
||||
Some("write notes immediately")
|
||||
);
|
||||
assert!(!config.features.enabled(Feature::AutoCompactFallback));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ use codex_core_plugins::PluginLoadOutcome;
|
||||
use codex_core_plugins::PluginsConfigInput;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
use codex_features::AutoCompactFallbackConfigToml;
|
||||
use codex_features::CodeModeConfigToml;
|
||||
use codex_features::CurrentTimeReminderConfigToml;
|
||||
use codex_features::CurrentTimeReminderDeliveryMode;
|
||||
@@ -711,8 +712,8 @@ pub struct Config {
|
||||
/// Compact prompt override.
|
||||
pub compact_prompt: Option<String>,
|
||||
|
||||
/// Developer message contributed for the optional pre-rollover fallback turn.
|
||||
pub auto_compact_fallback_prompt: Option<String>,
|
||||
/// Settings for the optional pre-rollover fallback turn.
|
||||
pub auto_compact_fallback: AutoCompactFallbackConfig,
|
||||
|
||||
/// Optional external notifier command. When set, Codex will spawn this
|
||||
/// program after each completed *turn* (i.e. when the agent finishes
|
||||
@@ -1103,6 +1104,11 @@ pub struct TokenBudgetConfig {
|
||||
pub guidance_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
|
||||
pub struct AutoCompactFallbackConfig {
|
||||
pub prompt: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for TokenBudgetConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -2615,6 +2621,17 @@ fn resolve_token_budget_config(
|
||||
}))
|
||||
}
|
||||
|
||||
fn resolve_auto_compact_fallback_config(config_toml: &ConfigToml) -> AutoCompactFallbackConfig {
|
||||
let configured = auto_compact_fallback_toml_config(config_toml.features.as_ref());
|
||||
let prompt = configured
|
||||
.and_then(|config| config.prompt.as_deref())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string);
|
||||
|
||||
AutoCompactFallbackConfig { prompt }
|
||||
}
|
||||
|
||||
fn resolve_rollout_budget_config(
|
||||
config_toml: &ConfigToml,
|
||||
features: &ManagedFeatures,
|
||||
@@ -2758,6 +2775,15 @@ fn token_budget_toml_config(features: Option<&FeaturesToml>) -> Option<&TokenBud
|
||||
}
|
||||
}
|
||||
|
||||
fn auto_compact_fallback_toml_config(
|
||||
features: Option<&FeaturesToml>,
|
||||
) -> Option<&AutoCompactFallbackConfigToml> {
|
||||
match features?.auto_compact_fallback.as_ref()? {
|
||||
FeatureToml::Enabled(_) => None,
|
||||
FeatureToml::Config(config) => Some(config),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_time_reminder_toml_config(
|
||||
features: Option<&FeaturesToml>,
|
||||
) -> Option<&CurrentTimeReminderConfigToml> {
|
||||
@@ -3414,6 +3440,7 @@ impl Config {
|
||||
let code_mode = resolve_code_mode_config(&cfg);
|
||||
let multi_agent_v2 = resolve_multi_agent_v2_config(&cfg);
|
||||
let token_budget = resolve_token_budget_config(&cfg, &features)?;
|
||||
let auto_compact_fallback = resolve_auto_compact_fallback_config(&cfg);
|
||||
let rollout_budget = resolve_rollout_budget_config(&cfg, &features)?;
|
||||
let current_time_reminder = resolve_current_time_reminder_config(&cfg, &features)?;
|
||||
let terminal_resize_reflow = resolve_terminal_resize_reflow_config(&cfg);
|
||||
@@ -3602,14 +3629,6 @@ impl Config {
|
||||
Some(trimmed.to_string())
|
||||
}
|
||||
});
|
||||
let auto_compact_fallback_prompt = cfg.auto_compact_fallback_prompt.and_then(|value| {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(trimmed.to_string())
|
||||
}
|
||||
});
|
||||
// Load base instructions override from a file if specified. If the
|
||||
// path is relative, resolve it against the effective cwd so the
|
||||
// behaviour matches other path-like config values.
|
||||
@@ -3825,7 +3844,7 @@ impl Config {
|
||||
personality,
|
||||
developer_instructions,
|
||||
compact_prompt,
|
||||
auto_compact_fallback_prompt,
|
||||
auto_compact_fallback,
|
||||
include_permissions_instructions,
|
||||
include_apps_instructions,
|
||||
include_collaboration_mode_instructions,
|
||||
|
||||
@@ -106,6 +106,27 @@ impl FeatureConfig for TokenBudgetConfigToml {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AutoCompactFallbackConfigToml {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
/// Developer message contributed for the single fallback turn before an
|
||||
/// automatic compaction rollover.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prompt: Option<String>,
|
||||
}
|
||||
|
||||
impl FeatureConfig for AutoCompactFallbackConfigToml {
|
||||
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, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RolloutBudgetConfigToml {
|
||||
|
||||
@@ -16,6 +16,7 @@ use toml::Table;
|
||||
|
||||
mod feature_configs;
|
||||
mod legacy;
|
||||
pub use feature_configs::AutoCompactFallbackConfigToml;
|
||||
pub use feature_configs::CodeModeConfigToml;
|
||||
pub use feature_configs::CurrentTimeReminderConfigToml;
|
||||
pub use feature_configs::CurrentTimeReminderDeliveryMode;
|
||||
@@ -644,6 +645,8 @@ pub struct FeaturesToml {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub token_budget: Option<FeatureToml<TokenBudgetConfigToml>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub auto_compact_fallback: Option<FeatureToml<AutoCompactFallbackConfigToml>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub rollout_budget: Option<FeatureToml<RolloutBudgetConfigToml>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub current_time_reminder: Option<FeatureToml<CurrentTimeReminderConfigToml>>,
|
||||
@@ -682,6 +685,13 @@ impl FeaturesToml {
|
||||
if let Some(enabled) = self.token_budget.as_ref().and_then(FeatureToml::enabled) {
|
||||
entries.insert(Feature::TokenBudget.key().to_string(), enabled);
|
||||
}
|
||||
if let Some(enabled) = self
|
||||
.auto_compact_fallback
|
||||
.as_ref()
|
||||
.and_then(FeatureToml::enabled)
|
||||
{
|
||||
entries.insert(Feature::AutoCompactFallback.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);
|
||||
}
|
||||
@@ -704,6 +714,7 @@ impl FeaturesToml {
|
||||
code_mode,
|
||||
multi_agent_v2,
|
||||
token_budget,
|
||||
auto_compact_fallback,
|
||||
rollout_budget,
|
||||
current_time_reminder,
|
||||
removed_apps_mcp_path_override: _,
|
||||
@@ -721,6 +732,8 @@ impl FeaturesToml {
|
||||
materialize_resolved_feature_enabled(multi_agent_v2, enabled);
|
||||
} else if spec.id == Feature::TokenBudget {
|
||||
materialize_resolved_feature_enabled(token_budget, enabled);
|
||||
} else if spec.id == Feature::AutoCompactFallback {
|
||||
materialize_resolved_feature_enabled(auto_compact_fallback, enabled);
|
||||
} else if spec.id == Feature::RolloutBudget {
|
||||
materialize_resolved_feature_enabled(rollout_budget, enabled);
|
||||
} else if spec.id == Feature::CurrentTimeReminder {
|
||||
|
||||
@@ -642,11 +642,51 @@ non_code_mode_only = true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_compact_fallback_feature_config_deserializes_boolean_toggle() {
|
||||
let features: FeaturesToml =
|
||||
toml::from_str("auto_compact_fallback = true").expect("features table should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
features.entries(),
|
||||
BTreeMap::from([("auto_compact_fallback".to_string(), true)])
|
||||
);
|
||||
assert_eq!(
|
||||
features.auto_compact_fallback,
|
||||
Some(FeatureToml::Enabled(true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_compact_fallback_feature_config_deserializes_table() {
|
||||
let features: FeaturesToml = toml::from_str(
|
||||
r#"
|
||||
[auto_compact_fallback]
|
||||
enabled = true
|
||||
prompt = "Persist the important state."
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
features.entries(),
|
||||
BTreeMap::from([("auto_compact_fallback".to_string(), true)])
|
||||
);
|
||||
assert_eq!(
|
||||
features.auto_compact_fallback,
|
||||
Some(FeatureToml::Config(crate::AutoCompactFallbackConfigToml {
|
||||
enabled: Some(true),
|
||||
prompt: Some("Persist the important state.".to_string()),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
features.enable(Feature::MultiAgentV2);
|
||||
features.enable(Feature::AutoCompactFallback);
|
||||
features.enable(Feature::NetworkProxy);
|
||||
features.enable(Feature::RespectSystemProxy);
|
||||
|
||||
@@ -656,6 +696,10 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config(
|
||||
min_wait_timeout_ms: Some(2500),
|
||||
..Default::default()
|
||||
})),
|
||||
auto_compact_fallback: Some(FeatureToml::Config(crate::AutoCompactFallbackConfigToml {
|
||||
enabled: Some(false),
|
||||
prompt: Some("Persist the important state.".to_string()),
|
||||
})),
|
||||
network_proxy: Some(FeatureToml::Config(crate::NetworkProxyConfigToml {
|
||||
enabled: Some(false),
|
||||
proxy_url: Some("http://127.0.0.1:43128".to_string()),
|
||||
@@ -684,6 +728,13 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config(
|
||||
..Default::default()
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
features_toml.auto_compact_fallback,
|
||||
Some(FeatureToml::Config(crate::AutoCompactFallbackConfigToml {
|
||||
enabled: Some(true),
|
||||
prompt: Some("Persist the important state.".to_string()),
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
features_toml.network_proxy,
|
||||
Some(FeatureToml::Config(crate::NetworkProxyConfigToml {
|
||||
|
||||
Reference in New Issue
Block a user