mirror of
https://github.com/openai/codex.git
synced 2026-10-27 00:00:53 +00:00
Support model-owned token budget defaults (#35608)
## What changed - Add token-budget settings to model catalog messages and apply them when the feature is enabled without explicit token-budget configuration. - Keep explicit user settings authoritative, reject invalid catalog defaults, and preserve resolved defaults in exported config locks. - Manage context-window guidance through world state so it updates once when the active model changes while retaining prior conversation history. ## Testing - Cover catalog defaults, explicit overrides, disabled features, invalid values, config-lock replay, and model switching. GitOrigin-RevId: 54544fefaa14b09f5ef5bad9967a13c52b87b0fe
This commit is contained in:
@@ -157,7 +157,9 @@ fn save_config_resolved_fields(
|
||||
resolved_config_to_toml(&config.multi_agent_v2, "features.multi_agent_v2")?;
|
||||
multi_agent_v2.enabled = Some(config.features.enabled(Feature::MultiAgentV2));
|
||||
features.multi_agent_v2 = Some(FeatureToml::Config(multi_agent_v2));
|
||||
if let Some(token_budget) = config.token_budget.as_ref() {
|
||||
if let Some(token_budget) = config.token_budget.as_ref()
|
||||
&& super::token_budget::has_explicit_settings(config)
|
||||
{
|
||||
let mut token_budget: TokenBudgetConfigToml =
|
||||
resolved_config_to_toml(token_budget, "features.token_budget")?;
|
||||
token_budget.enabled = Some(config.features.enabled(Feature::TokenBudget));
|
||||
@@ -387,6 +389,29 @@ mod tests {
|
||||
assert_eq!(lockfile.version, crate::config_lock::CONFIG_LOCK_VERSION);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lock_preserves_model_owned_token_budget_defaults() {
|
||||
let mut sc = crate::session::tests::make_session_configuration_for_tests().await;
|
||||
let mut config = (*sc.original_config_do_not_use).clone();
|
||||
config.token_budget = Some(crate::config::TokenBudgetConfig::default());
|
||||
config
|
||||
.features
|
||||
.enable(Feature::TokenBudget)
|
||||
.expect("token_budget should be enableable in tests");
|
||||
sc.original_config_do_not_use = Arc::new(config);
|
||||
|
||||
let lockfile = sc.to_config_lockfile_toml().expect("lock should serialize");
|
||||
|
||||
assert_eq!(
|
||||
lockfile
|
||||
.config
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.token_budget.as_ref()),
|
||||
Some(&FeatureToml::Enabled(true))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lock_skips_session_values_when_model_catalog_fields_are_not_saved() {
|
||||
let mut sc = crate::session::tests::make_session_configuration_for_tests().await;
|
||||
|
||||
@@ -602,7 +602,7 @@ impl Session {
|
||||
)
|
||||
};
|
||||
|
||||
let config = Arc::new(config);
|
||||
let mut config = Arc::new(config);
|
||||
let refresh_strategy = if session_source.is_non_root_agent() {
|
||||
codex_models_manager::manager::RefreshStrategy::Offline
|
||||
} else {
|
||||
@@ -645,6 +645,11 @@ impl Session {
|
||||
let model_info = models_manager
|
||||
.get_model_info(model.as_str(), &config.to_models_manager_config())
|
||||
.await;
|
||||
if config.config_lock_export_dir.is_some()
|
||||
&& config.config_lock_save_fields_resolved_from_model_catalog
|
||||
{
|
||||
self::token_budget::apply_model_defaults(Arc::make_mut(&mut config), &model_info);
|
||||
}
|
||||
let multi_agent_version = config.multi_agent_version_override().or_else(|| {
|
||||
resolve_multi_agent_version(&conversation_history, inherited_multi_agent_version)
|
||||
});
|
||||
@@ -3489,16 +3494,6 @@ impl Session {
|
||||
)
|
||||
.render(),
|
||||
);
|
||||
if let Some(guidance_message) = turn_context
|
||||
.config
|
||||
.token_budget
|
||||
.as_ref()
|
||||
.and_then(|config| config.guidance_message.as_deref())
|
||||
.filter(|message| !message.trim().is_empty())
|
||||
{
|
||||
developer_sections
|
||||
.push(crate::context::ContextWindowGuidance::new(guidance_message).render());
|
||||
}
|
||||
}
|
||||
// Render the active mode after the usage hint so it can override that hint.
|
||||
let mut initial_multi_agent_mode = None;
|
||||
|
||||
@@ -1,7 +1,59 @@
|
||||
use super::session::Session;
|
||||
use super::turn_context::TurnContext;
|
||||
use crate::config::Config;
|
||||
use crate::config::TokenBudgetConfig;
|
||||
use crate::context::ContextualUserFragment;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
|
||||
pub(super) fn has_explicit_settings(config: &Config) -> bool {
|
||||
config
|
||||
.config_layer_stack
|
||||
.effective_config()
|
||||
.get("features")
|
||||
.and_then(|features| features.get("token_budget"))
|
||||
.and_then(|token_budget| token_budget.as_table())
|
||||
.is_some_and(|settings| settings.keys().any(|key| key != "enabled"))
|
||||
|| config
|
||||
.token_budget
|
||||
.as_ref()
|
||||
.is_some_and(|token_budget| token_budget != &TokenBudgetConfig::default())
|
||||
}
|
||||
|
||||
pub(super) fn apply_model_defaults(config: &mut Config, model_info: &ModelInfo) {
|
||||
if !config.features.enabled(Feature::TokenBudget) || has_explicit_settings(config) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(model_defaults) = model_info
|
||||
.model_messages
|
||||
.as_ref()
|
||||
.and_then(|messages| messages.token_budget.as_ref())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let token_budget = TokenBudgetConfig {
|
||||
reminder_threshold_tokens: Some(model_defaults.reminder_threshold_tokens),
|
||||
reminder_message_template: model_defaults.reminder_message_template.clone(),
|
||||
guidance_message: Some(model_defaults.guidance_message.clone()),
|
||||
auto_compact_fallback_prompt: Some(model_defaults.auto_compact_fallback_prompt.clone()),
|
||||
auto_compact_fallback_buffer_tokens: Some(
|
||||
model_defaults.auto_compact_fallback_buffer_tokens,
|
||||
),
|
||||
};
|
||||
|
||||
if let Err(error) = token_budget.validate() {
|
||||
tracing::warn!(
|
||||
model = %model_info.slug,
|
||||
%error,
|
||||
"ignoring invalid model-owned token-budget defaults"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
config.token_budget = Some(token_budget);
|
||||
}
|
||||
|
||||
pub(super) async fn maybe_record(
|
||||
sess: &Session,
|
||||
|
||||
@@ -536,6 +536,7 @@ impl Session {
|
||||
);
|
||||
|
||||
let mut per_turn_config = per_turn_config;
|
||||
super::token_budget::apply_model_defaults(&mut per_turn_config, &model_info);
|
||||
per_turn_config.service_tier = get_service_tier(
|
||||
per_turn_config.service_tier,
|
||||
per_turn_config.features.enabled(Feature::FastMode),
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::context::ApprovalPromptContext;
|
||||
use crate::context::world_state::AgentsMdState;
|
||||
use crate::context::world_state::AppsInstructionsState;
|
||||
use crate::context::world_state::CollaborationModeState;
|
||||
use crate::context::world_state::ContextWindowGuidanceState;
|
||||
use crate::context::world_state::EnvironmentsInstructionsState;
|
||||
use crate::context::world_state::EnvironmentsState;
|
||||
use crate::context::world_state::ModelInstructionsState;
|
||||
@@ -82,6 +83,17 @@ impl Session {
|
||||
personality_is_baked,
|
||||
));
|
||||
}
|
||||
if turn_context.config.features.enabled(Feature::TokenBudget)
|
||||
&& turn_context.model_context_window().is_some()
|
||||
&& let Some(guidance) = turn_context
|
||||
.config
|
||||
.token_budget
|
||||
.as_ref()
|
||||
.and_then(|config| config.guidance_message.as_deref())
|
||||
.filter(|message| !message.trim().is_empty())
|
||||
{
|
||||
world_state.add_section(ContextWindowGuidanceState::new(guidance));
|
||||
}
|
||||
world_state.add_section(RealtimeState::new(
|
||||
turn_context.realtime_active,
|
||||
turn_context
|
||||
|
||||
Reference in New Issue
Block a user