mirror of
https://github.com/openai/codex.git
synced 2026-10-28 16:06:15 +00:00
fix: retry rejected previous-model compaction with selected model (#30319)
## Why Pre-sampling compaction intentionally uses the previous turn's model when the compaction compatibility hash changes or when switching to a model with a smaller context window. This keeps compaction aligned with the settings that produced the history, but it can block the next turn when a resumed ChatGPT thread still references a model slug that has since been retired. The Codex backend rejects that compaction request before the user's currently selected model gets a chance to sample. This PR lets those threads recover without changing previous-model compaction behavior for API-key authentication or custom providers. It is stacked on #31316, which is a behavior-preserving extraction of the individual remote compaction attempts; this PR contains the fallback behavior. ## What changed - For automatic previous-model compaction, capture the selected model's request context when using ChatGPT authentication with the OpenAI provider and the selected model differs from the previous model. - If the previous-model attempt returns an `InvalidRequest`, retry compaction once with the selected model for both `/responses/compact` and Responses Compaction V2. - Complete history processing, lifecycle events, and token accounting with the context of the model that successfully compacted the thread. - If the fallback also fails, return the original previous-model error so the retry does not change the user-visible failure. - Record fallback attempts with reason, implementation, and outcome telemetry. - Leave API-key authentication, custom providers, same-model turns, and non-`InvalidRequest` failures on their existing paths. ## Testing - `just test -p codex-core -E 'test(pre_sampling_compact) | test(model_unavailable_error)'` (10 tests) - Added integration coverage for a resumed thread whose model was renamed, a model downshift using Responses Compaction V2, and API-key authentication with a custom provider.
This commit is contained in:
@@ -349,6 +349,7 @@ pub(crate) async fn run_turn(
|
||||
if let Err(err) = run_auto_compact(
|
||||
&sess,
|
||||
Arc::clone(&step_context),
|
||||
/*fallback_step_context*/ None,
|
||||
&mut client_session,
|
||||
InitialContextInjection::BeforeLastUserMessage(Arc::clone(&world_state)),
|
||||
CompactionReason::ContextLimit,
|
||||
@@ -810,6 +811,7 @@ async fn run_pre_sampling_compact(
|
||||
run_auto_compact(
|
||||
sess,
|
||||
step_context,
|
||||
/*fallback_step_context*/ None,
|
||||
client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
CompactionReason::ContextLimit,
|
||||
@@ -828,6 +830,28 @@ fn comp_hash_changed(previous: Option<&str>, current: Option<&str>) -> bool {
|
||||
.is_some_and(|(previous, current)| previous != current)
|
||||
}
|
||||
|
||||
/// Captures the current model's request-scoped state for retrying previous-model compaction.
|
||||
///
|
||||
/// Returns `None` when the active authentication does not use the Codex backend, the provider is
|
||||
/// not OpenAI, or the previous and current model are the same.
|
||||
async fn capture_current_model_fallback_step_context(
|
||||
sess: &Arc<Session>,
|
||||
turn_context: &Arc<TurnContext>,
|
||||
previous_model: &str,
|
||||
) -> Option<Arc<StepContext>> {
|
||||
let uses_codex_backend = turn_context
|
||||
.auth_manager
|
||||
.as_deref()
|
||||
.is_some_and(codex_login::AuthManager::current_auth_uses_codex_backend);
|
||||
if !uses_codex_backend
|
||||
|| !turn_context.provider.info().is_openai()
|
||||
|| previous_model == turn_context.model_info.slug
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(sess.capture_step_context(Arc::clone(turn_context)).await)
|
||||
}
|
||||
|
||||
/// Runs pre-sampling compaction against the previous model when its compaction compatibility
|
||||
/// hash changed or when switching to a smaller context-window model.
|
||||
///
|
||||
@@ -844,20 +868,27 @@ async fn maybe_run_previous_model_inline_compact(
|
||||
previous_turn_settings.comp_hash.as_deref(),
|
||||
turn_context.model_info.comp_hash.as_deref(),
|
||||
);
|
||||
let previous_model = previous_turn_settings.model;
|
||||
let previous_model_turn_context = Arc::new(
|
||||
turn_context
|
||||
.with_model(previous_turn_settings.model, &sess.services.models_manager)
|
||||
.with_model(previous_model.clone(), &sess.services.models_manager)
|
||||
.await,
|
||||
);
|
||||
|
||||
if should_compact_for_comp_hash_change {
|
||||
// This pre-turn request needs a step built from the previous model's turn context.
|
||||
let step_context = sess
|
||||
.capture_step_context(Arc::clone(&previous_model_turn_context))
|
||||
.await;
|
||||
let fallback_step_context = capture_current_model_fallback_step_context(
|
||||
sess,
|
||||
turn_context,
|
||||
previous_model.as_str(),
|
||||
)
|
||||
.await;
|
||||
run_auto_compact(
|
||||
sess,
|
||||
step_context,
|
||||
fallback_step_context,
|
||||
client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
CompactionReason::CompHashChanged,
|
||||
@@ -892,13 +923,19 @@ async fn maybe_run_previous_model_inline_compact(
|
||||
&& previous_model_turn_context.model_info.slug != turn_context.model_info.slug
|
||||
&& old_context_window > new_context_window;
|
||||
if should_run {
|
||||
// This pre-turn request needs a step built from the previous model's turn context.
|
||||
let step_context = sess
|
||||
.capture_step_context(Arc::clone(&previous_model_turn_context))
|
||||
.await;
|
||||
let fallback_step_context = capture_current_model_fallback_step_context(
|
||||
sess,
|
||||
turn_context,
|
||||
previous_model.as_str(),
|
||||
)
|
||||
.await;
|
||||
run_auto_compact(
|
||||
sess,
|
||||
step_context,
|
||||
fallback_step_context,
|
||||
client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
CompactionReason::ModelDownshift,
|
||||
@@ -917,6 +954,7 @@ async fn maybe_run_previous_model_inline_compact(
|
||||
async fn run_auto_compact(
|
||||
sess: &Arc<Session>,
|
||||
step_context: Arc<StepContext>,
|
||||
fallback_step_context: Option<Arc<StepContext>>,
|
||||
client_session: &mut ModelClientSession,
|
||||
initial_context_injection: InitialContextInjection,
|
||||
reason: CompactionReason,
|
||||
@@ -949,6 +987,7 @@ async fn run_auto_compact(
|
||||
run_inline_remote_auto_compact_task_v2(
|
||||
Arc::clone(sess),
|
||||
step_context,
|
||||
fallback_step_context,
|
||||
client_session,
|
||||
initial_context_injection,
|
||||
reason,
|
||||
@@ -965,6 +1004,7 @@ async fn run_auto_compact(
|
||||
run_inline_remote_auto_compact_task(
|
||||
Arc::clone(sess),
|
||||
step_context,
|
||||
fallback_step_context,
|
||||
client_session.turn_state(),
|
||||
initial_context_injection,
|
||||
reason,
|
||||
|
||||
Reference in New Issue
Block a user