mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(compact): trim history on context overflow and retry
This commit is contained in:
@@ -221,33 +221,62 @@ pub(super) async fn perform_compaction(
|
||||
) -> CodexResult<()> {
|
||||
// Convert core InputItem -> ResponseInputItem using the same logic as the main turn flow
|
||||
let initial_input_for_turn: ResponseInputItem = response_input_from_core_items(input);
|
||||
let turn_input = sess.turn_input_with_history(vec![initial_input_for_turn.clone().into()]);
|
||||
let mut turn_input = sess.turn_input_with_history(vec![initial_input_for_turn.clone().into()]);
|
||||
|
||||
let turn_input = sanitize_items_for_compact(turn_input);
|
||||
|
||||
let mut prompt = Prompt::default();
|
||||
prompt.input = turn_input;
|
||||
prompt.store = !sess.disable_response_storage;
|
||||
prompt.user_instructions = turn_context.user_instructions.clone();
|
||||
prompt.environment_context = Some(EnvironmentContext::new(
|
||||
Some(turn_context.cwd.clone()),
|
||||
Some(turn_context.approval_policy),
|
||||
Some(turn_context.sandbox_policy.clone()),
|
||||
Some(sess.user_shell.clone()),
|
||||
));
|
||||
prompt.model_descriptions = sess.model_descriptions.clone();
|
||||
prompt.log_tag = Some("codex/compact".to_string());
|
||||
turn_input = sanitize_items_for_compact(turn_input);
|
||||
|
||||
let max_retries = turn_context.client.get_provider().stream_max_retries();
|
||||
let mut retries = 0;
|
||||
let mut truncated_count = 0usize;
|
||||
|
||||
// Do not persist a TurnContext rollout item here; inline compaction is a
|
||||
// background maintenance task and should not affect rollout reconstruction.
|
||||
|
||||
loop {
|
||||
let mut prompt = Prompt::default();
|
||||
prompt.input = turn_input.clone();
|
||||
prompt.store = !sess.disable_response_storage;
|
||||
prompt.user_instructions = turn_context.user_instructions.clone();
|
||||
prompt.environment_context = Some(EnvironmentContext::new(
|
||||
Some(turn_context.cwd.clone()),
|
||||
Some(turn_context.approval_policy),
|
||||
Some(turn_context.sandbox_policy.clone()),
|
||||
Some(sess.user_shell.clone()),
|
||||
));
|
||||
prompt.model_descriptions = sess.model_descriptions.clone();
|
||||
prompt.log_tag = Some("codex/compact".to_string());
|
||||
|
||||
match drain_to_completed(&sess, turn_context.as_ref(), &prompt).await {
|
||||
Ok(()) => break,
|
||||
Ok(()) => {
|
||||
if truncated_count > 0 {
|
||||
tracing::warn!(
|
||||
"Context window exceeded during compact; trimmed {truncated_count} item(s) from prompt"
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(CodexErr::Interrupted) => return Err(CodexErr::Interrupted),
|
||||
Err(e) if is_context_overflow_error(&e) => {
|
||||
if turn_input.len() > 1 {
|
||||
tracing::warn!(
|
||||
"Context window exceeded while compacting; dropping oldest item ({} remaining)",
|
||||
turn_input.len().saturating_sub(1)
|
||||
);
|
||||
turn_input.remove(0);
|
||||
truncated_count = truncated_count.saturating_add(1);
|
||||
retries = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
let event = sess.make_event(
|
||||
&sub_id,
|
||||
EventMsg::Error(ErrorEvent {
|
||||
message: e.to_string(),
|
||||
}),
|
||||
);
|
||||
sess.send_event(event).await;
|
||||
return Err(e);
|
||||
}
|
||||
Err(e) => {
|
||||
if retries < max_retries {
|
||||
retries += 1;
|
||||
@@ -325,29 +354,58 @@ async fn run_compact_task_inner_inline(
|
||||
) -> Vec<ResponseItem> {
|
||||
// Convert core InputItem -> ResponseInputItem and build prompt
|
||||
let initial_input_for_turn: ResponseInputItem = response_input_from_core_items(input);
|
||||
let turn_input = sess.turn_input_with_history(vec![initial_input_for_turn.clone().into()]);
|
||||
let mut turn_input = sess.turn_input_with_history(vec![initial_input_for_turn.clone().into()]);
|
||||
|
||||
let turn_input = sanitize_items_for_compact(turn_input);
|
||||
|
||||
let mut prompt = Prompt::default();
|
||||
prompt.input = turn_input;
|
||||
prompt.store = !sess.disable_response_storage;
|
||||
prompt.user_instructions = turn_context.user_instructions.clone();
|
||||
prompt.environment_context = Some(EnvironmentContext::new(
|
||||
Some(turn_context.cwd.clone()),
|
||||
Some(turn_context.approval_policy),
|
||||
Some(turn_context.sandbox_policy.clone()),
|
||||
Some(sess.user_shell.clone()),
|
||||
));
|
||||
prompt.model_descriptions = sess.model_descriptions.clone();
|
||||
prompt.log_tag = Some("codex/compact".to_string());
|
||||
turn_input = sanitize_items_for_compact(turn_input);
|
||||
|
||||
let max_retries = turn_context.client.get_provider().stream_max_retries();
|
||||
let mut retries = 0;
|
||||
let mut truncated_count = 0usize;
|
||||
loop {
|
||||
let mut prompt = Prompt::default();
|
||||
prompt.input = turn_input.clone();
|
||||
prompt.store = !sess.disable_response_storage;
|
||||
prompt.user_instructions = turn_context.user_instructions.clone();
|
||||
prompt.environment_context = Some(EnvironmentContext::new(
|
||||
Some(turn_context.cwd.clone()),
|
||||
Some(turn_context.approval_policy),
|
||||
Some(turn_context.sandbox_policy.clone()),
|
||||
Some(sess.user_shell.clone()),
|
||||
));
|
||||
prompt.model_descriptions = sess.model_descriptions.clone();
|
||||
prompt.log_tag = Some("codex/compact".to_string());
|
||||
|
||||
match drain_to_completed(&sess, turn_context.as_ref(), &prompt).await {
|
||||
Ok(()) => break,
|
||||
Ok(()) => {
|
||||
if truncated_count > 0 {
|
||||
tracing::warn!(
|
||||
"Context window exceeded during inline compact; trimmed {truncated_count} item(s) from prompt"
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(CodexErr::Interrupted) => return Vec::new(),
|
||||
Err(e) if is_context_overflow_error(&e) => {
|
||||
if turn_input.len() > 1 {
|
||||
tracing::warn!(
|
||||
"Context window exceeded while compacting; dropping oldest item ({} remaining)",
|
||||
turn_input.len().saturating_sub(1)
|
||||
);
|
||||
turn_input.remove(0);
|
||||
truncated_count = truncated_count.saturating_add(1);
|
||||
retries = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
let event = sess.make_event(
|
||||
&sub_id,
|
||||
EventMsg::Error(ErrorEvent {
|
||||
message: e.to_string(),
|
||||
}),
|
||||
);
|
||||
sess.send_event(event).await;
|
||||
return Vec::new();
|
||||
}
|
||||
Err(e) => {
|
||||
if retries < max_retries {
|
||||
retries += 1;
|
||||
@@ -441,6 +499,27 @@ fn truncate_for_compact(text: String, max_bytes: usize) -> String {
|
||||
truncate_middle(&text, max_bytes).0
|
||||
}
|
||||
|
||||
fn looks_like_context_overflow(message: &str) -> bool {
|
||||
let lower = message.to_ascii_lowercase();
|
||||
lower.contains("context_length_exceeded")
|
||||
|| lower.contains("context length exceeded")
|
||||
|| lower.contains("context window")
|
||||
&& (lower.contains("exceed")
|
||||
|| lower.contains("exceeded")
|
||||
|| lower.contains("full")
|
||||
|| lower.contains("too long"))
|
||||
|| lower.contains("maximum context length")
|
||||
|| lower.contains("exceeds the context window")
|
||||
}
|
||||
|
||||
pub(super) fn is_context_overflow_error(err: &CodexErr) -> bool {
|
||||
match err {
|
||||
CodexErr::UnexpectedStatus(resp) => looks_like_context_overflow(&resp.body),
|
||||
CodexErr::Stream(msg, _) => looks_like_context_overflow(msg),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sanitize_items_for_compact(items: Vec<ResponseItem>) -> Vec<ResponseItem> {
|
||||
items
|
||||
.into_iter()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::compact::{
|
||||
is_context_overflow_error,
|
||||
response_input_from_core_items,
|
||||
sanitize_items_for_compact,
|
||||
send_compaction_checkpoint_warning,
|
||||
@@ -17,6 +18,7 @@ use code_protocol::models::ResponseInputItem;
|
||||
use code_protocol::models::ResponseItem;
|
||||
use code_protocol::protocol::CompactedItem;
|
||||
use code_protocol::protocol::RolloutItem;
|
||||
use crate::util::backoff;
|
||||
|
||||
pub(super) async fn run_inline_remote_auto_compact_task(
|
||||
sess: Arc<Session>,
|
||||
@@ -81,17 +83,63 @@ async fn run_remote_compact_task_inner(
|
||||
});
|
||||
|
||||
turn_items = sanitize_items_for_compact(turn_items);
|
||||
let mut truncated_count = 0usize;
|
||||
let max_retries = turn_context.client.get_provider().stream_max_retries();
|
||||
let mut retries = 0;
|
||||
let new_history = loop {
|
||||
let mut prompt = Prompt::default();
|
||||
prompt.input = turn_items.clone();
|
||||
prompt.base_instructions_override = turn_context.base_instructions.clone();
|
||||
prompt.include_additional_instructions = false;
|
||||
prompt.log_tag = Some("codex/remote-compact".to_string());
|
||||
|
||||
let mut prompt = Prompt::default();
|
||||
prompt.input = turn_items;
|
||||
prompt.base_instructions_override = turn_context.base_instructions.clone();
|
||||
prompt.include_additional_instructions = false;
|
||||
prompt.log_tag = Some("codex/remote-compact".to_string());
|
||||
match turn_context
|
||||
.client
|
||||
.compact_conversation_history(&prompt)
|
||||
.await
|
||||
{
|
||||
Ok(history) => {
|
||||
if truncated_count > 0 {
|
||||
tracing::warn!(
|
||||
"Context window exceeded during remote compact; trimmed {truncated_count} item(s) from prompt"
|
||||
);
|
||||
}
|
||||
break history;
|
||||
}
|
||||
Err(err) if is_context_overflow_error(&err) => {
|
||||
if turn_items.len() > 1 {
|
||||
tracing::warn!(
|
||||
"Context window exceeded while remote compacting; dropping oldest item ({} remaining)",
|
||||
turn_items.len().saturating_sub(1)
|
||||
);
|
||||
turn_items.remove(0);
|
||||
truncated_count = truncated_count.saturating_add(1);
|
||||
retries = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
let new_history = turn_context
|
||||
.client
|
||||
.compact_conversation_history(&prompt)
|
||||
.await?;
|
||||
return Err(err);
|
||||
}
|
||||
Err(err) => {
|
||||
if retries < max_retries {
|
||||
retries += 1;
|
||||
let delay = backoff(retries);
|
||||
sess
|
||||
.notify_stream_error(
|
||||
sub_id,
|
||||
format!(
|
||||
"remote compact error: {err}; retrying {retries}/{max_retries} in {delay:?}…"
|
||||
),
|
||||
)
|
||||
.await;
|
||||
tokio::time::sleep(delay).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sess.replace_history(new_history.clone());
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user