Display ACP plans (#34816)

Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
Agus Zubiaga 2025-07-21 11:11:37 -03:00 committed by GitHub
parent 35b4a918c9
commit 405244d422
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 379 additions and 34 deletions

View file

@ -453,9 +453,69 @@ impl Diff {
}
}
#[derive(Debug, Default)]
pub struct Plan {
pub entries: Vec<PlanEntry>,
}
#[derive(Debug)]
pub struct PlanStats<'a> {
pub in_progress_entry: Option<&'a PlanEntry>,
pub pending: u32,
pub completed: u32,
}
impl Plan {
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn stats(&self) -> PlanStats<'_> {
let mut stats = PlanStats {
in_progress_entry: None,
pending: 0,
completed: 0,
};
for entry in &self.entries {
match &entry.status {
acp::PlanEntryStatus::Pending => {
stats.pending += 1;
}
acp::PlanEntryStatus::InProgress => {
stats.in_progress_entry = stats.in_progress_entry.or(Some(entry));
}
acp::PlanEntryStatus::Completed => {
stats.completed += 1;
}
}
}
stats
}
}
#[derive(Debug)]
pub struct PlanEntry {
pub content: Entity<Markdown>,
pub priority: acp::PlanEntryPriority,
pub status: acp::PlanEntryStatus,
}
impl PlanEntry {
pub fn from_acp(entry: acp::PlanEntry, cx: &mut App) -> Self {
Self {
content: cx.new(|cx| Markdown::new_text(entry.content.into(), cx)),
priority: entry.priority,
status: entry.status,
}
}
}
pub struct AcpThread {
entries: Vec<AgentThreadEntry>,
title: SharedString,
entries: Vec<AgentThreadEntry>,
plan: Plan,
project: Entity<Project>,
action_log: Entity<ActionLog>,
shared_buffers: HashMap<Entity<Buffer>, BufferSnapshot>,
@ -515,6 +575,7 @@ impl AcpThread {
action_log,
shared_buffers: Default::default(),
entries: Default::default(),
plan: Default::default(),
title,
project,
send_task: None,
@ -819,6 +880,29 @@ impl AcpThread {
}
}
pub fn plan(&self) -> &Plan {
&self.plan
}
pub fn update_plan(&mut self, request: acp::UpdatePlanParams, cx: &mut Context<Self>) {
self.plan = Plan {
entries: request
.entries
.into_iter()
.map(|entry| PlanEntry::from_acp(entry, cx))
.collect(),
};
cx.notify();
}
pub fn clear_completed_plan_entries(&mut self, cx: &mut Context<Self>) {
self.plan
.entries
.retain(|entry| !matches!(entry.status, acp::PlanEntryStatus::Completed));
cx.notify();
}
pub fn set_project_location(&self, location: ToolCallLocation, cx: &mut Context<Self>) {
self.project.update(cx, |project, cx| {
let Some(path) = project.project_path_for_absolute_path(&location.path, cx) else {
@ -1136,6 +1220,17 @@ impl AcpClientDelegate {
Self { thread, cx }
}
pub async fn clear_completed_plan_entries(&self) -> Result<()> {
let cx = &mut self.cx.clone();
cx.update(|cx| {
self.thread
.update(cx, |thread, cx| thread.clear_completed_plan_entries(cx))
})?
.context("Failed to update thread")?;
Ok(())
}
pub async fn request_existing_tool_call_confirmation(
&self,
tool_call_id: ToolCallId,
@ -1233,6 +1328,18 @@ impl acp::Client for AcpClientDelegate {
Ok(())
}
async fn update_plan(&self, request: acp::UpdatePlanParams) -> Result<(), acp::Error> {
let cx = &mut self.cx.clone();
cx.update(|cx| {
self.thread
.update(cx, |thread, cx| thread.update_plan(request, cx))
})?
.context("Failed to update thread")?;
Ok(())
}
async fn read_text_file(
&self,
request: acp::ReadTextFileParams,