Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 51 additions & 27 deletions crates/acp_thread/src/acp_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,12 +298,20 @@ pub enum AgentThreadEntry {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContextCompactionId(pub Arc<str>);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextCompactionStatus {
InProgress,
Completed,
Canceled,
}

/// A point in the thread where the conversation history was compacted to free
/// up room in the model's context window. The summary can be expanded to inspect
/// what the model retained.
#[derive(Debug)]
pub struct ContextCompaction {
pub id: ContextCompactionId,
pub status: ContextCompactionStatus,
/// The compaction summary, streamed in as the model produces it. This is
/// `None` for provider-native compaction, which produces no summary to show.
pub summary: Option<Entity<Markdown>>,
Expand All @@ -313,6 +321,7 @@ pub struct ContextCompaction {
pub struct ContextCompactionUpdate {
pub id: ContextCompactionId,
pub summary_delta: String,
pub status: Option<ContextCompactionStatus>,
}

impl AgentThreadEntry {
Expand Down Expand Up @@ -2063,19 +2072,25 @@ impl AcpThread {
return;
};

if compaction.summary.is_none() {
compaction.summary = Some(cx.new(|cx| {
Markdown::new(
update.summary_delta.into(),
Some(language_registry),
None,
cx,
)
}));
} else if let Some(summary) = compaction.summary.clone() {
summary.update(cx, |markdown, cx| {
markdown.append(&update.summary_delta, cx)
});
if !update.summary_delta.is_empty() {
if compaction.summary.is_none() {
compaction.summary = Some(cx.new(|cx| {
Markdown::new(
update.summary_delta.into(),
Some(language_registry),
None,
cx,
)
}));
} else if let Some(summary) = compaction.summary.clone() {
summary.update(cx, |markdown, cx| {
markdown.append(&update.summary_delta, cx)
});
}
}

if let Some(status) = update.status {
compaction.status = status;
}

cx.emit(AcpThreadEvent::EntryUpdated(ix));
Expand Down Expand Up @@ -2669,7 +2684,7 @@ impl AcpThread {

let canceled = matches!(r.stop_reason, acp::StopReason::Cancelled);
if canceled {
this.mark_pending_tools_as_canceled();
this.mark_pending_entries_as_canceled(cx);
}

if !canceled {
Expand Down Expand Up @@ -2745,25 +2760,34 @@ impl AcpThread {
self.connection.cancel(&self.session_id, cx);

Self::flush_streaming_text(&mut self.streaming_text_buffer, cx);
self.mark_pending_tools_as_canceled();
self.mark_pending_entries_as_canceled(cx);

// Wait for the send task to complete
cx.background_spawn(turn.send_task)
}

fn mark_pending_tools_as_canceled(&mut self) {
for entry in self.entries.iter_mut() {
if let AgentThreadEntry::ToolCall(call) = entry {
let cancel = matches!(
call.status,
ToolCallStatus::Pending
| ToolCallStatus::WaitingForConfirmation { .. }
| ToolCallStatus::InProgress
);

if cancel {
call.status = ToolCallStatus::Canceled;
fn mark_pending_entries_as_canceled(&mut self, cx: &mut Context<Self>) {
for (ix, entry) in self.entries.iter_mut().enumerate() {
match entry {
AgentThreadEntry::ToolCall(call) => {
let cancel = matches!(
call.status,
ToolCallStatus::Pending
| ToolCallStatus::WaitingForConfirmation { .. }
| ToolCallStatus::InProgress
);
if cancel {
call.status = ToolCallStatus::Canceled;
cx.emit(AcpThreadEvent::EntryUpdated(ix));
}
}
AgentThreadEntry::ContextCompaction(compaction) => {
if compaction.status == ContextCompactionStatus::InProgress {
compaction.status = ContextCompactionStatus::Canceled;
cx.emit(AcpThreadEvent::EntryUpdated(ix));
}
}
_ => {}
}
}
}
Expand Down
48 changes: 43 additions & 5 deletions crates/agent/src/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1413,11 +1413,17 @@ impl Thread {
);
match info {
CompactionInfo::Summary(summary) => {
stream.send_context_compaction(compaction_id.clone());
stream.send_context_compaction(
compaction_id.clone(),
acp_thread::ContextCompactionStatus::Completed,
);
stream.send_context_compaction_update(compaction_id.clone(), summary);
}
CompactionInfo::ProviderNative { .. } => {
stream.send_context_compaction(compaction_id);
stream.send_context_compaction(
compaction_id,
acp_thread::ContextCompactionStatus::Completed,
);
}
}
}
Expand Down Expand Up @@ -2691,7 +2697,10 @@ impl Thread {
) -> Result<ControlFlow<()>> {
log::debug!("Running compaction");
let compaction_id = acp_thread::ContextCompactionId(Uuid::new_v4().to_string().into());
event_stream.send_context_compaction(compaction_id.clone());
event_stream.send_context_compaction(
compaction_id.clone(),
acp_thread::ContextCompactionStatus::InProgress,
);
let stream = futures::select! {
result = model.stream_completion(request, cx).fuse() => result,
_ = cancellation_rx.changed().fuse() => {
Expand Down Expand Up @@ -2755,6 +2764,10 @@ impl Thread {
}

log::debug!("Compaction succeeded:\n{summary}");
event_stream.update_context_compaction_status(
compaction_id,
acp_thread::ContextCompactionStatus::Completed,
);

this.update(cx, |this, cx| {
let compaction = Arc::new(Message::Compaction(CompactionInfo::Summary(summary.into())));
Expand Down Expand Up @@ -4626,10 +4639,18 @@ impl ThreadEventStream {
self.0.unbounded_send(Ok(ThreadEvent::Retry(status))).ok();
}

fn send_context_compaction(&self, id: acp_thread::ContextCompactionId) {
fn send_context_compaction(
&self,
id: acp_thread::ContextCompactionId,
status: acp_thread::ContextCompactionStatus,
) {
self.0
.unbounded_send(Ok(ThreadEvent::ContextCompaction(
acp_thread::ContextCompaction { id, summary: None },
acp_thread::ContextCompaction {
id,
status,
summary: None,
},
)))
.ok();
}
Expand All @@ -4644,6 +4665,23 @@ impl ThreadEventStream {
acp_thread::ContextCompactionUpdate {
id,
summary_delta: summary_delta.to_string(),
status: None,
},
)))
.ok();
}

fn update_context_compaction_status(
&self,
id: acp_thread::ContextCompactionId,
status: acp_thread::ContextCompactionStatus,
) {
self.0
.unbounded_send(Ok(ThreadEvent::ContextCompactionUpdate(
acp_thread::ContextCompactionUpdate {
id,
summary_delta: String::new(),
status: Some(status),
},
)))
.ok();
Expand Down
23 changes: 12 additions & 11 deletions crates/agent_ui/src/conversation_view/thread_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3594,16 +3594,13 @@ impl ThreadView {
fn render_context_compaction(
&self,
entry_ix: usize,
total_entries: usize,
compaction: &acp_thread::ContextCompaction,
window: &Window,
cx: &Context<Self>,
) -> AnyElement {
let is_compacting = entry_ix + 1 == total_entries
&& self.thread.read(cx).status() == acp_thread::ThreadStatus::Generating;
let is_compacting = compaction.status == acp_thread::ContextCompactionStatus::InProgress;
let summary = compaction.summary.clone();
let summary_available = summary.is_some();
let is_expanded = summary_available && self.expanded_compactions.contains(&entry_ix);
let is_expanded = self.expanded_compactions.contains(&entry_ix);

let header = h_flex()
.id(("context-compaction", entry_ix))
Expand All @@ -3621,10 +3618,12 @@ impl ThreadView {
.color(Color::Muted),
)
.child(
Label::new(if is_compacting {
"Compacting context…"
} else {
"Context compacted"
Label::new(match compaction.status {
acp_thread::ContextCompactionStatus::InProgress => {
"Compacting context…"
}
acp_thread::ContextCompactionStatus::Completed => "Context compacted",
acp_thread::ContextCompactionStatus::Canceled => "Compaction cancelled",
})
.size(LabelSize::Custom(self.tool_name_font_size()))
.color(Color::Muted),
Expand All @@ -3644,7 +3643,9 @@ impl ThreadView {
this.toggle_compaction_expansion(entry_ix, cx);
}));

if let Some(summary) = summary.filter(|_| is_expanded) {
if let Some(summary) = summary
&& is_expanded
{
v_flex()
.w_full()
.child(header)
Expand Down Expand Up @@ -5653,7 +5654,7 @@ impl ThreadView {
self.render_completed_plan(entries, window, cx)
}
AgentThreadEntry::ContextCompaction(compaction) => {
self.render_context_compaction(entry_ix, total_entries, compaction, window, cx)
self.render_context_compaction(entry_ix, compaction, window, cx)
}
};

Expand Down
Loading