diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index 2734726ddbe1608356bcc34af5c42f479d5a8e8a..1be8cb53872582cc26af65c87189195fa32b643a 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -364,7 +364,7 @@ impl MessageEditor { let task = match mention_uri.clone() { MentionUri::Fetch { url } => self.confirm_mention_for_fetch(url, cx), - MentionUri::Directory { abs_path } => self.confirm_mention_for_directory(abs_path, cx), + MentionUri::Directory { .. } => Task::ready(Ok(Mention::UriOnly)), MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx), MentionUri::TextThread { path, .. } => self.confirm_mention_for_text_thread(path, cx), MentionUri::File { abs_path } => self.confirm_mention_for_file(abs_path, cx), @@ -468,97 +468,6 @@ impl MessageEditor { }) } - fn confirm_mention_for_directory( - &mut self, - abs_path: PathBuf, - cx: &mut Context, - ) -> Task> { - fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc, PathBuf)> { - let mut files = Vec::new(); - - for entry in worktree.child_entries(path) { - if entry.is_dir() { - files.extend(collect_files_in_path(worktree, &entry.path)); - } else if entry.is_file() { - files.push((entry.path.clone(), worktree.full_path(&entry.path))); - } - } - - files - } - - let Some(project_path) = self - .project - .read(cx) - .project_path_for_absolute_path(&abs_path, cx) - else { - return Task::ready(Err(anyhow!("project path not found"))); - }; - let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else { - return Task::ready(Err(anyhow!("project entry not found"))); - }; - let directory_path = entry.path.clone(); - let worktree_id = project_path.worktree_id; - let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) else { - return Task::ready(Err(anyhow!("worktree not found"))); - }; - let project = self.project.clone(); - cx.spawn(async move |_, cx| { - let file_paths = worktree.read_with(cx, |worktree, _cx| { - collect_files_in_path(worktree, &directory_path) - })?; - let descendants_future = cx.update(|cx| { - join_all(file_paths.into_iter().map(|(worktree_path, full_path)| { - let rel_path = worktree_path - .strip_prefix(&directory_path) - .log_err() - .map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into()); - - let open_task = project.update(cx, |project, cx| { - project.buffer_store().update(cx, |buffer_store, cx| { - let project_path = ProjectPath { - worktree_id, - path: worktree_path, - }; - buffer_store.open_buffer(project_path, cx) - }) - }); - - cx.spawn(async move |cx| { - let buffer = open_task.await.log_err()?; - let buffer_content = outline::get_buffer_content_or_outline( - buffer.clone(), - Some(&full_path), - &cx, - ) - .await - .ok()?; - - Some((rel_path, full_path, buffer_content.text, buffer)) - }) - })) - })?; - - let contents = cx - .background_spawn(async move { - let (contents, tracked_buffers) = descendants_future - .await - .into_iter() - .flatten() - .map(|(rel_path, full_path, rope, buffer)| { - ((rel_path, full_path, rope), buffer) - }) - .unzip(); - Mention::Text { - content: render_directory_contents(contents), - tracked_buffers, - } - }) - .await; - anyhow::Ok(contents) - }) - } - fn confirm_mention_for_fetch( &mut self, url: url::Url, @@ -776,6 +685,7 @@ impl MessageEditor { pub fn contents( &self, + full_mention_content: bool, cx: &mut Context, ) -> Task, Vec>)>> { // Check for unsupported slash commands before spawning async task @@ -787,9 +697,12 @@ impl MessageEditor { return Task::ready(Err(err)); } - let contents = self - .mention_set - .contents(&self.prompt_capabilities.borrow(), cx); + let contents = self.mention_set.contents( + &self.prompt_capabilities.borrow(), + full_mention_content, + self.project.clone(), + cx, + ); let editor = self.editor.clone(); cx.spawn(async move |_, cx| { @@ -1263,6 +1176,96 @@ impl MessageEditor { } } +fn full_mention_for_directory( + project: &Entity, + abs_path: &Path, + cx: &mut App, +) -> Task> { + fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc, PathBuf)> { + let mut files = Vec::new(); + + for entry in worktree.child_entries(path) { + if entry.is_dir() { + files.extend(collect_files_in_path(worktree, &entry.path)); + } else if entry.is_file() { + files.push((entry.path.clone(), worktree.full_path(&entry.path))); + } + } + + files + } + + let Some(project_path) = project + .read(cx) + .project_path_for_absolute_path(&abs_path, cx) + else { + return Task::ready(Err(anyhow!("project path not found"))); + }; + let Some(entry) = project.read(cx).entry_for_path(&project_path, cx) else { + return Task::ready(Err(anyhow!("project entry not found"))); + }; + let directory_path = entry.path.clone(); + let worktree_id = project_path.worktree_id; + let Some(worktree) = project.read(cx).worktree_for_id(worktree_id, cx) else { + return Task::ready(Err(anyhow!("worktree not found"))); + }; + let project = project.clone(); + cx.spawn(async move |cx| { + let file_paths = worktree.read_with(cx, |worktree, _cx| { + collect_files_in_path(worktree, &directory_path) + })?; + let descendants_future = cx.update(|cx| { + join_all(file_paths.into_iter().map(|(worktree_path, full_path)| { + let rel_path = worktree_path + .strip_prefix(&directory_path) + .log_err() + .map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into()); + + let open_task = project.update(cx, |project, cx| { + project.buffer_store().update(cx, |buffer_store, cx| { + let project_path = ProjectPath { + worktree_id, + path: worktree_path, + }; + buffer_store.open_buffer(project_path, cx) + }) + }); + + cx.spawn(async move |cx| { + let buffer = open_task.await.log_err()?; + let buffer_content = outline::get_buffer_content_or_outline( + buffer.clone(), + Some(&full_path), + &cx, + ) + .await + .ok()?; + + Some((rel_path, full_path, buffer_content.text, buffer)) + }) + })) + })?; + + let contents = cx + .background_spawn(async move { + let (contents, tracked_buffers) = descendants_future + .await + .into_iter() + .flatten() + .map(|(rel_path, full_path, rope, buffer)| { + ((rel_path, full_path, rope), buffer) + }) + .unzip(); + Mention::Text { + content: render_directory_contents(contents), + tracked_buffers, + } + }) + .await; + anyhow::Ok(contents) + }) +} + fn render_directory_contents(entries: Vec<(Arc, PathBuf, String)>) -> String { let mut output = String::new(); for (_relative_path, full_path, content) in entries { @@ -1514,6 +1517,8 @@ impl MentionSet { fn contents( &self, prompt_capabilities: &acp::PromptCapabilities, + full_mention_content: bool, + project: Entity, cx: &mut App, ) -> Task>> { if !prompt_capabilities.embedded_context { @@ -1527,13 +1532,19 @@ impl MentionSet { } let mentions = self.mentions.clone(); - cx.spawn(async move |_cx| { + cx.spawn(async move |cx| { let mut contents = HashMap::default(); for (crease_id, (mention_uri, task)) in mentions { - contents.insert( - crease_id, - (mention_uri, task.await.map_err(|e| anyhow!("{e}"))?), - ); + let content = if full_mention_content + && let MentionUri::Directory { abs_path } = &mention_uri + { + cx.update(|cx| full_mention_for_directory(&project, abs_path, cx))? + .await? + } else { + task.await.map_err(|e| anyhow!("{e}"))? + }; + + contents.insert(crease_id, (mention_uri, content)); } Ok(contents) }) @@ -1694,7 +1705,7 @@ mod tests { }); let (content, _) = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await .unwrap(); @@ -1757,7 +1768,7 @@ mod tests { }); let contents_result = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await; // Should fail because available_commands is empty (no commands supported) @@ -1780,7 +1791,7 @@ mod tests { }); let contents_result = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await; assert!(contents_result.is_err()); @@ -1795,7 +1806,7 @@ mod tests { }); let contents_result = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await; // Should succeed because /help is in available_commands @@ -1807,7 +1818,7 @@ mod tests { }); let (content, _) = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await .unwrap(); @@ -1825,7 +1836,7 @@ mod tests { // The @ mention functionality should not be affected let (content, _) = message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)) + .update(cx, |message_editor, cx| message_editor.contents(false, cx)) .await .unwrap(); @@ -2271,9 +2282,12 @@ mod tests { let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .unwrap() @@ -2290,9 +2304,12 @@ mod tests { let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&acp::PromptCapabilities::default(), cx) + message_editor.mention_set().contents( + &acp::PromptCapabilities::default(), + false, + project.clone(), + cx, + ) }) .await .unwrap() @@ -2341,9 +2358,12 @@ mod tests { let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .unwrap() @@ -2451,9 +2471,12 @@ mod tests { let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .unwrap() @@ -2501,9 +2524,12 @@ mod tests { // Getting the message contents fails message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .expect_err("Should fail to load x.png"); @@ -2548,9 +2574,12 @@ mod tests { // Now getting the contents succeeds, because the invalid mention was removed let contents = message_editor .update(&mut cx, |message_editor, cx| { - message_editor - .mention_set() - .contents(&all_prompt_capabilities, cx) + message_editor.mention_set().contents( + &all_prompt_capabilities, + false, + project.clone(), + cx, + ) }) .await .unwrap(); diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 391486a68eca87e238f9efb88288bc970e3eb412..cf6f563ecdeb5a60e2b3faa4a5ee36d3282e1bdb 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -1038,10 +1038,7 @@ impl AcpThreadView { return; } - let contents = self - .message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)); - self.send_impl(contents, window, cx) + self.send_impl(self.message_editor.clone(), window, cx) } fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context) { @@ -1051,15 +1048,11 @@ impl AcpThreadView { let cancelled = thread.update(cx, |thread, cx| thread.cancel(cx)); - let contents = self - .message_editor - .update(cx, |message_editor, cx| message_editor.contents(cx)); - cx.spawn_in(window, async move |this, cx| { cancelled.await; this.update_in(cx, |this, window, cx| { - this.send_impl(contents, window, cx); + this.send_impl(this.message_editor.clone(), window, cx); }) .ok(); }) @@ -1068,10 +1061,23 @@ impl AcpThreadView { fn send_impl( &mut self, - contents: Task, Vec>)>>, + message_editor: Entity, window: &mut Window, cx: &mut Context, ) { + let full_mention_content = self.as_native_thread(cx).is_some_and(|thread| { + // Include full contents when using minimal profile + let thread = thread.read(cx); + AgentSettings::get_global(cx) + .profiles + .get(thread.profile()) + .is_some_and(|profile| profile.tools.is_empty()) + }); + + let contents = message_editor.update(cx, |message_editor, cx| { + message_editor.contents(full_mention_content, cx) + }); + let agent_telemetry_id = self.agent.telemetry_id(); self.thread_error.take(); @@ -1200,10 +1206,8 @@ impl AcpThreadView { thread .update(cx, |thread, cx| thread.rewind(user_message_id, cx))? .await?; - let contents = - message_editor.update(cx, |message_editor, cx| message_editor.contents(cx))?; this.update_in(cx, |this, window, cx| { - this.send_impl(contents, window, cx); + this.send_impl(message_editor, window, cx); })?; anyhow::Ok(()) })