use crate::app::interactive_runtime::CompactTaskPoll;
use super::{
compaction_display::CompactionUiOutcome,
event_adapter::{compact_finished_event, compact_started_event},
send_confirm::SendSubmission,
App, InteractiveModelSelection, InteractiveRuntime, ViewModelEvent,
};
#[derive(Default)]
pub(super) enum CompactFollowUp {
#[default]
None,
ContextHandoff {
target_selection: Option<InteractiveModelSelection>,
had_source: bool,
},
Send(Box<SendSubmission>),
}
pub(super) enum ReadyFollowUp {
Queued { allow_auto_compact: bool },
Send(Box<SendSubmission>),
}
enum SettledSend {
Ready(ReadyFollowUp),
Cancelled(Box<SendSubmission>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum CompactSettlementIntent {
Poll,
UserCancelled,
}
fn should_start_compact_follow_ups(
intent: CompactSettlementIntent,
outcome_starts_follow_ups: bool,
) -> bool {
matches!(intent, CompactSettlementIntent::Poll) && outcome_starts_follow_ups
}
fn settle_compact_send(submission: Box<SendSubmission>, starts_follow_ups: bool) -> SettledSend {
if starts_follow_ups {
SettledSend::Ready(ReadyFollowUp::Send(submission))
} else {
SettledSend::Cancelled(submission)
}
}
pub(super) fn keep_armed_follow_up_while_busy(ready: &ReadyFollowUp) -> bool {
matches!(ready, ReadyFollowUp::Send(_))
}
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.begin_started_compact(follow_up);
Ok(())
}
pub(super) fn start_compact_send(
&mut self,
agent: &mut InteractiveRuntime,
submission: Box<SendSubmission>,
) -> Result<(), (anyhow::Error, Box<SendSubmission>)> {
if agent.is_compacting() {
return Err((anyhow::anyhow!("already compacting context"), submission));
}
if let Err(error) = agent.begin_compact_task() {
return Err((error, submission));
}
self.begin_started_compact(CompactFollowUp::Send(submission));
Ok(())
}
fn begin_started_compact(&mut self, follow_up: CompactFollowUp) {
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;
}
pub(super) async fn cancel_compact(
&mut self,
agent: &mut InteractiveRuntime,
) -> anyhow::Result<bool> {
let Some(poll) = agent.abort_compact_task().await else {
return Ok(false);
};
self.settle_compact_poll(poll, CompactSettlementIntent::UserCancelled, 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,
agent: &mut InteractiveRuntime,
) -> anyhow::Result<bool> {
let Some(poll) = agent.poll_compact_task().await else {
return Ok(false);
};
self.settle_compact_poll(poll, CompactSettlementIntent::Poll, agent)
.await?;
Ok(true)
}
async fn settle_compact_poll(
&mut self,
poll: CompactTaskPoll,
intent: CompactSettlementIntent,
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(_));
let starts_follow_ups =
should_start_compact_follow_ups(intent, outcome.starts_follow_ups());
if let Some(context) = agent.take_context_usage() {
self.record_agent_event(ViewModelEvent::ContextUsage(context));
}
self.finish_compact_ui(outcome);
let follow_up = std::mem::take(&mut self.compact_follow_up);
self.apply_compact_follow_up(follow_up, succeeded, starts_follow_ups, agent)
.await
}
async fn apply_compact_follow_up(
&mut self,
follow_up: CompactFollowUp,
succeeded: bool,
starts_follow_ups: bool,
agent: &mut InteractiveRuntime,
) -> anyhow::Result<()> {
match follow_up {
CompactFollowUp::None => {
if starts_follow_ups {
self.start_follow_ups = Some(ReadyFollowUp::Queued {
allow_auto_compact: false,
});
}
Ok(())
}
CompactFollowUp::ContextHandoff {
target_selection,
had_source,
} => {
if starts_follow_ups {
self.start_follow_ups = Some(ReadyFollowUp::Queued {
allow_auto_compact: false,
});
}
self.complete_compact_handoff(
succeeded && starts_follow_ups,
target_selection,
had_source,
agent,
)
.await
}
CompactFollowUp::Send(submission) => {
match settle_compact_send(submission, starts_follow_ups) {
SettledSend::Ready(ready) => self.start_follow_ups = Some(ready),
SettledSend::Cancelled(submission) => {
self.cancel_compact_send_submission(*submission, agent);
}
}
Ok(())
}
}
}
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);
}
}
#[cfg(test)]
#[path = "compact_work_tests.rs"]
mod tests;