mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Add auto-compaction fallback token-budget settings (#33243)
## What changed - Add `auto_compact_fallback_prompt` and `auto_compact_fallback_buffer_tokens` to `features.token_budget` and the generated configuration schema. - Trim empty fallback prompts, limit prompts to 2,000 bytes, require a buffer when a prompt is configured, and reject non-positive buffer values. - Preserve the new settings when locking resolved session configuration. ## Testing - Cover config resolution and validation for overlong prompts, missing buffers, and non-positive buffers. - Extend the configuration-lock test to cover both settings. GitOrigin-RevId: 6463f963ba1dbd633d76601b8344360a8c85cf8b
This commit is contained in:
@@ -2923,6 +2923,17 @@
|
||||
"TokenBudgetConfigToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"auto_compact_fallback_buffer_tokens": {
|
||||
"description": "Additional tokens available after the compaction threshold for fallback note-taking.",
|
||||
"format": "int64",
|
||||
"minimum": 1.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"auto_compact_fallback_prompt": {
|
||||
"description": "Developer message sampled before an automatic context-window rollover.",
|
||||
"maxLength": 2000,
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -572,11 +572,15 @@ enabled = true
|
||||
reminder_threshold_tokens = 16000
|
||||
reminder_message_template = "Custom reminder: {n_remaining} tokens."
|
||||
guidance_message = "Preserve important state before compaction."
|
||||
auto_compact_fallback_prompt = " Write notes immediately. "
|
||||
auto_compact_fallback_buffer_tokens = 8000
|
||||
"#,
|
||||
TokenBudgetConfig {
|
||||
reminder_threshold_tokens: Some(16_000),
|
||||
reminder_message_template: "Custom reminder: {n_remaining} tokens.".to_string(),
|
||||
guidance_message: Some("Preserve important state before compaction.".to_string()),
|
||||
auto_compact_fallback_prompt: Some("Write notes immediately.".to_string()),
|
||||
auto_compact_fallback_buffer_tokens: Some(8_000),
|
||||
},
|
||||
),
|
||||
] {
|
||||
@@ -595,6 +599,26 @@ guidance_message = "Preserve important state before compaction."
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_config_rejects_overlong_auto_compact_fallback_prompt() -> std::io::Result<()> {
|
||||
let codex_home = tempdir()?;
|
||||
let prompt = "x".repeat(AUTO_COMPACT_FALLBACK_PROMPT_MAX_BYTES + 1);
|
||||
let config_toml = toml::from_str(&format!(
|
||||
"[features.token_budget]\nenabled = true\nauto_compact_fallback_prompt = {prompt:?}\n"
|
||||
))
|
||||
.expect("TOML should deserialize");
|
||||
let error = Config::load_from_base_config_with_overrides(
|
||||
config_toml,
|
||||
ConfigOverrides::default(),
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await
|
||||
.expect_err("overlong fallback prompt should be rejected");
|
||||
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_config_rejects_invalid_token_budget_reminder_template() -> std::io::Result<()> {
|
||||
for reminder_message_template in [
|
||||
@@ -644,6 +668,55 @@ async fn load_config_rejects_non_positive_token_budget_reminder_threshold() -> s
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_config_rejects_non_positive_auto_compact_fallback_buffer() -> std::io::Result<()> {
|
||||
for auto_compact_fallback_buffer_tokens in [-1, 0] {
|
||||
let codex_home = tempdir()?;
|
||||
let config_toml = toml::from_str(&format!(
|
||||
"[features.token_budget]\nenabled = true\nauto_compact_fallback_prompt = \"Write notes.\"\nauto_compact_fallback_buffer_tokens = {auto_compact_fallback_buffer_tokens}\n"
|
||||
))
|
||||
.expect("TOML should deserialize");
|
||||
let error = Config::load_from_base_config_with_overrides(
|
||||
config_toml,
|
||||
ConfigOverrides::default(),
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await
|
||||
.expect_err("non-positive fallback buffer should be rejected");
|
||||
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"features.token_budget.auto_compact_fallback_buffer_tokens must be positive"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_config_rejects_missing_auto_compact_fallback_buffer() -> std::io::Result<()> {
|
||||
let codex_home = tempdir()?;
|
||||
let config_toml = toml::from_str(
|
||||
"[features.token_budget]\nenabled = true\nauto_compact_fallback_prompt = \"Write notes.\"\n",
|
||||
)
|
||||
.expect("TOML should deserialize");
|
||||
|
||||
let error = Config::load_from_base_config_with_overrides(
|
||||
config_toml,
|
||||
ConfigOverrides::default(),
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await
|
||||
.expect_err("missing fallback buffer should be rejected");
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"features.token_budget.auto_compact_fallback_buffer_tokens is required when auto_compact_fallback_prompt is set"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_config_resolves_rollout_budget() -> std::io::Result<()> {
|
||||
let codex_home = tempdir()?;
|
||||
|
||||
@@ -1143,12 +1143,15 @@ pub(crate) const DEFAULT_TOKEN_BUDGET_REMINDER_MESSAGE_TEMPLATE: &str = concat!(
|
||||
);
|
||||
const TOKEN_BUDGET_REMINDER_MESSAGE_TEMPLATE_MAX_BYTES: usize = 2000;
|
||||
const TOKEN_BUDGET_GUIDANCE_MESSAGE_MAX_BYTES: usize = 2000;
|
||||
const AUTO_COMPACT_FALLBACK_PROMPT_MAX_BYTES: usize = 2000;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct TokenBudgetConfig {
|
||||
pub reminder_threshold_tokens: Option<i64>,
|
||||
pub reminder_message_template: String,
|
||||
pub guidance_message: Option<String>,
|
||||
pub auto_compact_fallback_prompt: Option<String>,
|
||||
pub auto_compact_fallback_buffer_tokens: Option<i64>,
|
||||
}
|
||||
|
||||
impl Default for TokenBudgetConfig {
|
||||
@@ -1157,6 +1160,8 @@ impl Default for TokenBudgetConfig {
|
||||
reminder_threshold_tokens: None,
|
||||
reminder_message_template: DEFAULT_TOKEN_BUDGET_REMINDER_MESSAGE_TEMPLATE.to_string(),
|
||||
guidance_message: None,
|
||||
auto_compact_fallback_prompt: None,
|
||||
auto_compact_fallback_buffer_tokens: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2687,10 +2692,44 @@ fn resolve_token_budget_config(
|
||||
));
|
||||
}
|
||||
|
||||
let auto_compact_fallback_prompt = token_budget_config
|
||||
.and_then(|config| config.auto_compact_fallback_prompt.as_deref())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string);
|
||||
if auto_compact_fallback_prompt
|
||||
.as_ref()
|
||||
.is_some_and(|prompt| prompt.len() > AUTO_COMPACT_FALLBACK_PROMPT_MAX_BYTES)
|
||||
{
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"features.token_budget.auto_compact_fallback_prompt must not exceed {AUTO_COMPACT_FALLBACK_PROMPT_MAX_BYTES} bytes"
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let auto_compact_fallback_buffer_tokens =
|
||||
token_budget_config.and_then(|config| config.auto_compact_fallback_buffer_tokens);
|
||||
if auto_compact_fallback_prompt.is_some() && auto_compact_fallback_buffer_tokens.is_none() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"features.token_budget.auto_compact_fallback_buffer_tokens is required when auto_compact_fallback_prompt is set",
|
||||
));
|
||||
}
|
||||
if auto_compact_fallback_buffer_tokens.is_some_and(|tokens| tokens <= 0) {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"features.token_budget.auto_compact_fallback_buffer_tokens must be positive",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Some(TokenBudgetConfig {
|
||||
reminder_threshold_tokens,
|
||||
reminder_message_template,
|
||||
guidance_message,
|
||||
auto_compact_fallback_prompt,
|
||||
auto_compact_fallback_buffer_tokens,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -249,6 +249,8 @@ mod tests {
|
||||
reminder_threshold_tokens: Some(16_000),
|
||||
reminder_message_template: "Locked reminder: {n_remaining} tokens.".to_string(),
|
||||
guidance_message: Some("Locked context-window guidance.".to_string()),
|
||||
auto_compact_fallback_prompt: Some("Write notes before rollover.".to_string()),
|
||||
auto_compact_fallback_buffer_tokens: Some(8_000),
|
||||
});
|
||||
config
|
||||
.features
|
||||
@@ -342,6 +344,8 @@ mod tests {
|
||||
"Locked reminder: {n_remaining} tokens.".to_string()
|
||||
),
|
||||
guidance_message: Some("Locked context-window guidance.".to_string()),
|
||||
auto_compact_fallback_prompt: Some("Write notes before rollover.".to_string()),
|
||||
auto_compact_fallback_buffer_tokens: Some(8_000),
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
@@ -98,6 +98,14 @@ pub struct TokenBudgetConfigToml {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(length(max = 2000))]
|
||||
pub guidance_message: Option<String>,
|
||||
/// Developer message sampled before an automatic context-window rollover.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(length(max = 2000))]
|
||||
pub auto_compact_fallback_prompt: Option<String>,
|
||||
/// Additional tokens available after the compaction threshold for fallback note-taking.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(range(min = 1))]
|
||||
pub auto_compact_fallback_buffer_tokens: Option<i64>,
|
||||
}
|
||||
|
||||
impl FeatureConfig for TokenBudgetConfigToml {
|
||||
|
||||
Reference in New Issue
Block a user