use compact_str::CompactString;
use tokio::sync::mpsc;
use crate::context::ContextFiles;
use crate::event::AgentEvent;
use crate::provider::AnyAgent;
use crate::ui::agent_io::{persist_turn_to_db, render_agent_stream};
use crate::ui::colors::{c_agent, c_error};
use crate::ui::events::sanitize_output;
use crate::ui::run_handlers::{AgentBuildDeps, RunCtx};
use crate::ui::theme;
use crate::ui::tool_display::close_tool_chamber_if_open;
#[allow(clippy::too_many_arguments)]
pub(crate) async fn handle_context_overflow(
ctx: &mut RunCtx<'_>,
prompt: CompactString,
error: CompactString,
was_reasoning: &mut bool,
is_running: &mut bool,
agent: &mut AnyAgent,
_context: &mut ContextFiles,
deps: &AgentBuildDeps<'_>,
agent_rx: &mut Option<mpsc::Receiver<AgentEvent>>,
agent_abort: &mut Option<tokio::task::JoinHandle<()>>,
agent_interject: &mut Option<mpsc::Sender<()>>,
agent_cancel: &mut Option<mpsc::Sender<()>>,
interjection_queue: &std::sync::Arc<std::sync::Mutex<std::collections::VecDeque<String>>>,
compaction_phase: &mut Option<crate::ui::compaction::CompactionPhaseHandle>,
) -> anyhow::Result<()> {
let client = deps.client;
*was_reasoning = false;
close_tool_chamber_if_open(ctx.renderer, ctx.last_tool_name, ctx.tool_chamber_open)?;
if !ctx.response_buf.is_empty() {
render_agent_stream(
ctx.response_buf,
ctx.response_start_line,
c_agent(),
ctx.renderer,
)?;
}
let safe = sanitize_output(&error);
ctx.renderer
.write_line(&format!("context overflow: {}", safe), c_error())?;
persist_turn_to_db(
ctx.session,
ctx.last_user_prompt,
ctx.response_buf,
ctx.tool_calls_buf,
);
let made_progress = !ctx.response_buf.is_empty() || !ctx.tool_calls_buf.is_empty();
if made_progress {
let partial = std::mem::take(ctx.response_buf);
ctx.session.add_message_with_tool_calls(
crate::session::MessageRole::Assistant,
&partial,
std::mem::take(ctx.tool_calls_buf),
);
}
if let Some(h) = agent_abort.take() {
h.abort();
}
*agent_rx = None;
*agent_interject = None;
*agent_cancel = None;
*ctx.agent_line_started = false;
ctx.response_buf.clear();
*ctx.response_start_line = None;
ctx.reasoning_buf.clear();
*ctx.reasoning_start_line = None;
*ctx.tool_calls_this_run = 0;
ctx.renderer
.write_line("▒░ auto-compacting then retrying ░▒", theme::accent())?;
match crate::ui::slash::prepare_compaction(
None,
false, agent,
client,
ctx.renderer,
ctx.session,
ctx.cfg,
) {
Ok(crate::ui::slash::CompactionDecision::Ready(req)) => {
*compaction_phase = Some(crate::ui::compaction::spawn(
*req,
crate::ui::compaction::CompactionThen::RetryAfterOverflow {
prompt: prompt.to_string(),
made_progress,
},
));
}
Ok(crate::ui::slash::CompactionDecision::NoOp) => {
ctx.renderer.write_line(
"auto-compact made no progress; leaving session as-is. Lower keep_recent_tokens, configure summarization_provider, or /clear.",
c_error(),
)?;
*is_running = false;
drop_queued(ctx, interjection_queue, "compact no-op")?;
}
Err(e) if crate::provider::is_anthropic_oauth_compaction_disabled_error(&e) => {
match crate::ui::slash::prepare_prune_only_compaction(
ctx.renderer,
ctx.session,
ctx.cfg,
crate::provider::ANTHROPIC_OAUTH_COMPACTION_DISABLED,
)? {
Some(req) => {
ctx.renderer.write_line(
"LLM compaction requires a non-Anthropic-OAuth summarization_provider; using prune-only emergency compaction for this retry.",
c_error(),
)?;
*compaction_phase = Some(crate::ui::compaction::spawn_local(
req.summary,
req.cut_idx,
req.tokens_before,
crate::ui::compaction::CompactionThen::RetryAfterOverflow {
prompt: prompt.to_string(),
made_progress,
},
));
}
None => {
ctx.renderer.write_line(
"auto-compact could not prune enough context. Configure summarization_provider or use /clear.",
c_error(),
)?;
*is_running = false;
drop_queued(ctx, interjection_queue, "compact failure")?;
}
}
}
Err(e) => {
ctx.renderer.write_line(
&format!("auto-compact failed ({e}); leaving session as-is. Configure summarization_provider or use /clear."),
c_error(),
)?;
*is_running = false;
drop_queued(ctx, interjection_queue, "compact failure")?;
}
}
Ok(())
}
fn drop_queued(
ctx: &mut RunCtx<'_>,
interjection_queue: &std::sync::Arc<std::sync::Mutex<std::collections::VecDeque<String>>>,
reason: &str,
) -> anyhow::Result<()> {
let dropped = interjection_queue.lock().unwrap().len();
interjection_queue.lock().unwrap().clear();
if dropped > 0 {
ctx.renderer.write_line(
&format!(
"{} queued message{} dropped due to {reason}",
dropped,
if dropped == 1 { "" } else { "s" }
),
c_error(),
)?;
}
Ok(())
}