use ratatui::DefaultTerminal;
use crate::app::interactive_runtime::CompactTaskPoll;
use super::{
compaction_display::CompactionUiOutcome,
context_handoff::AfterHandoff,
event_adapter::{compact_finished_event, compact_started_event},
App, InteractiveModelSelection, InteractiveRuntime, ViewModelEvent,
};
#[derive(Default)]
pub(super) enum CompactFollowUp {
#[default]
None,
ContextHandoff {
target_selection: Option<InteractiveModelSelection>,
had_source: bool,
after: AfterHandoff,
},
}
impl App {
pub(super) fn start_compact(
&mut self,
agent: &mut InteractiveRuntime,
follow_up: CompactFollowUp,
) -> anyhow::Result<()> {
if agent.is_compacting() {
if !matches!(follow_up, CompactFollowUp::None) {
anyhow::bail!("already compacting context");
}
self.notify_status("already compacting context");
return Ok(());
}
agent.begin_compact_task()?;
self.pending.steering_prompts_mut().clear();
self.pending_input_changed();
self.set_status("compacting context");
self.begin_compact_ui();
self.turn.start_loading();
self.apply_compact_view_event(compact_started_event());
self.compact_follow_up = follow_up;
Ok(())
}
pub(super) async fn cancel_compact(
&mut self,
terminal: &mut DefaultTerminal,
agent: &mut InteractiveRuntime,
) -> anyhow::Result<bool> {
let Some(poll) = agent.abort_compact_task().await else {
return Ok(false);
};
self.settle_compact_poll(poll, terminal, agent).await?;
Ok(true)
}
pub(super) async fn abort_compact(&mut self, agent: &mut InteractiveRuntime) -> bool {
let started =
agent.is_compacting() || !matches!(self.compact_follow_up, CompactFollowUp::None);
if let Some(CompactTaskPoll::Finished(result)) = agent.abort_compact_task().await {
let _ = std::mem::take(&mut self.compact_follow_up);
self.finish_compact_ui(CompactionUiOutcome::from_task_result(result));
return true;
}
self.compact_follow_up = CompactFollowUp::None;
if started {
self.finish_compact_ui(CompactionUiOutcome::Cancelled);
}
started
}
pub(super) async fn poll_compact(
&mut self,
terminal: &mut DefaultTerminal,
agent: &mut InteractiveRuntime,
) -> anyhow::Result<bool> {
let Some(poll) = agent.poll_compact_task().await else {
return Ok(false);
};
self.settle_compact_poll(poll, terminal, agent).await?;
Ok(true)
}
async fn settle_compact_poll(
&mut self,
poll: CompactTaskPoll,
terminal: &mut DefaultTerminal,
agent: &mut InteractiveRuntime,
) -> anyhow::Result<()> {
let outcome = match poll {
CompactTaskPoll::Cancelled => CompactionUiOutcome::Cancelled,
CompactTaskPoll::Finished(result) => CompactionUiOutcome::from_task_result(result),
};
let succeeded = matches!(outcome, CompactionUiOutcome::Completed(_));
if let Some(context) = agent.take_context_usage() {
self.record_agent_event(ViewModelEvent::ContextUsage(context));
}
if outcome.starts_follow_ups() {
self.start_follow_ups = Some(false);
}
self.finish_compact_ui(outcome);
let follow_up = std::mem::take(&mut self.compact_follow_up);
self.apply_compact_follow_up(follow_up, succeeded, terminal, agent)
.await
}
async fn apply_compact_follow_up(
&mut self,
follow_up: CompactFollowUp,
succeeded: bool,
terminal: &mut DefaultTerminal,
agent: &mut InteractiveRuntime,
) -> anyhow::Result<()> {
match follow_up {
CompactFollowUp::None => Ok(()),
CompactFollowUp::ContextHandoff {
target_selection,
had_source,
after,
} => {
self.complete_compact_handoff(
succeeded,
target_selection,
had_source,
after,
terminal,
agent,
)
.await
}
}
}
fn apply_compact_view_event(&mut self, event: super::ViewModelEvent) {
if let Some(phase) = event.activity_phase() {
self.turn.set_activity_phase(phase);
}
if let Some(entry) = self.record_agent_event(event) {
self.insert_entry(&entry);
}
}
fn finish_compact_ui(&mut self, outcome: CompactionUiOutcome) {
let status = outcome.status_label();
self.apply_compact_view_event(compact_finished_event(outcome));
self.end_busy_ui();
self.turn.stop_loading();
self.set_status(status);
}
}