From 0beb919bbb8c662ee7ff3302bfb5e49bec1e3fba Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Thu, 21 Aug 2025 17:29:53 -0300 Subject: [PATCH] acp: Fix `MessageEditor::set_message` for sent messages (#36715) The `PromptCapabilities` introduced in previous PRs were only getting set on the main message editor and not for the editors in user messages. This caused a bug where mentions would disappear after resending the message, and for the completion provider to be limited to files. Release Notes: - N/A --- crates/agent_ui/src/acp/entry_view_state.rs | 9 ++++-- crates/agent_ui/src/acp/message_editor.rs | 34 +++++++++++---------- crates/agent_ui/src/acp/thread_view.rs | 16 ++++++---- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/crates/agent_ui/src/acp/entry_view_state.rs b/crates/agent_ui/src/acp/entry_view_state.rs index c310473259..0e4080d689 100644 --- a/crates/agent_ui/src/acp/entry_view_state.rs +++ b/crates/agent_ui/src/acp/entry_view_state.rs @@ -1,7 +1,7 @@ -use std::ops::Range; +use std::{cell::Cell, ops::Range, rc::Rc}; use acp_thread::{AcpThread, AgentThreadEntry}; -use agent_client_protocol::ToolCallId; +use agent_client_protocol::{PromptCapabilities, ToolCallId}; use agent2::HistoryStore; use collections::HashMap; use editor::{Editor, EditorMode, MinimapVisibility}; @@ -27,6 +27,7 @@ pub struct EntryViewState { prompt_store: Option>, entries: Vec, prevent_slash_commands: bool, + prompt_capabilities: Rc>, } impl EntryViewState { @@ -35,6 +36,7 @@ impl EntryViewState { project: Entity, history_store: Entity, prompt_store: Option>, + prompt_capabilities: Rc>, prevent_slash_commands: bool, ) -> Self { Self { @@ -44,6 +46,7 @@ impl EntryViewState { prompt_store, entries: Vec::new(), prevent_slash_commands, + prompt_capabilities, } } @@ -81,6 +84,7 @@ impl EntryViewState { self.project.clone(), self.history_store.clone(), self.prompt_store.clone(), + self.prompt_capabilities.clone(), "Edit message - @ to include context", self.prevent_slash_commands, editor::EditorMode::AutoHeight { @@ -403,6 +407,7 @@ mod tests { project.clone(), history_store, None, + Default::default(), false, ) }); diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index 8f5044cb21..7d73ebeb19 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -87,6 +87,7 @@ impl MessageEditor { project: Entity, history_store: Entity, prompt_store: Option>, + prompt_capabilities: Rc>, placeholder: impl Into>, prevent_slash_commands: bool, mode: EditorMode, @@ -100,7 +101,6 @@ impl MessageEditor { }, None, ); - let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default())); let completion_provider = ContextPickerCompletionProvider::new( cx.weak_entity(), workspace.clone(), @@ -203,10 +203,6 @@ impl MessageEditor { .detach(); } - pub fn set_prompt_capabilities(&mut self, capabilities: acp::PromptCapabilities) { - self.prompt_capabilities.set(capabilities); - } - #[cfg(test)] pub(crate) fn editor(&self) -> &Entity { &self.editor @@ -1095,15 +1091,21 @@ impl MessageEditor { mentions.push((start..end, mention_uri, resource.text)); } } + acp::ContentBlock::ResourceLink(resource) => { + if let Some(mention_uri) = MentionUri::parse(&resource.uri).log_err() { + let start = text.len(); + write!(&mut text, "{}", mention_uri.as_link()).ok(); + let end = text.len(); + mentions.push((start..end, mention_uri, resource.uri)); + } + } acp::ContentBlock::Image(content) => { let start = text.len(); text.push_str("image"); let end = text.len(); images.push((start..end, content)); } - acp::ContentBlock::Audio(_) - | acp::ContentBlock::Resource(_) - | acp::ContentBlock::ResourceLink(_) => {} + acp::ContentBlock::Audio(_) | acp::ContentBlock::Resource(_) => {} } } @@ -1850,7 +1852,7 @@ impl Addon for MessageEditorAddon { #[cfg(test)] mod tests { - use std::{ops::Range, path::Path, sync::Arc}; + use std::{cell::Cell, ops::Range, path::Path, rc::Rc, sync::Arc}; use acp_thread::MentionUri; use agent_client_protocol as acp; @@ -1896,6 +1898,7 @@ mod tests { project.clone(), history_store.clone(), None, + Default::default(), "Test", false, EditorMode::AutoHeight { @@ -2086,6 +2089,7 @@ mod tests { let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx)); let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); + let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default())); let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| { let workspace_handle = cx.weak_entity(); @@ -2095,6 +2099,7 @@ mod tests { project.clone(), history_store.clone(), None, + prompt_capabilities.clone(), "Test", false, EditorMode::AutoHeight { @@ -2139,13 +2144,10 @@ mod tests { editor.set_text("", window, cx); }); - message_editor.update(&mut cx, |editor, _cx| { - // Enable all prompt capabilities - editor.set_prompt_capabilities(acp::PromptCapabilities { - image: true, - audio: true, - embedded_context: true, - }); + prompt_capabilities.set(acp::PromptCapabilities { + image: true, + audio: true, + embedded_context: true, }); cx.simulate_input("Lorem "); diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 936f987864..c7d6bb439f 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -5,7 +5,7 @@ use acp_thread::{ }; use acp_thread::{AgentConnection, Plan}; use action_log::ActionLog; -use agent_client_protocol::{self as acp}; +use agent_client_protocol::{self as acp, PromptCapabilities}; use agent_servers::{AgentServer, ClaudeCode}; use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting}; use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore}; @@ -34,6 +34,7 @@ use project::{Project, ProjectEntryId}; use prompt_store::{PromptId, PromptStore}; use rope::Point; use settings::{Settings as _, SettingsStore}; +use std::cell::Cell; use std::sync::Arc; use std::time::Instant; use std::{collections::BTreeMap, rc::Rc, time::Duration}; @@ -271,6 +272,7 @@ pub struct AcpThreadView { plan_expanded: bool, editor_expanded: bool, editing_message: Option, + prompt_capabilities: Rc>, _cancel_task: Option>, _subscriptions: [Subscription; 3], } @@ -306,6 +308,7 @@ impl AcpThreadView { window: &mut Window, cx: &mut Context, ) -> Self { + let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default())); let prevent_slash_commands = agent.clone().downcast::().is_some(); let message_editor = cx.new(|cx| { let mut editor = MessageEditor::new( @@ -313,6 +316,7 @@ impl AcpThreadView { project.clone(), history_store.clone(), prompt_store.clone(), + prompt_capabilities.clone(), "Message the agent — @ to include context", prevent_slash_commands, editor::EditorMode::AutoHeight { @@ -336,6 +340,7 @@ impl AcpThreadView { project.clone(), history_store.clone(), prompt_store.clone(), + prompt_capabilities.clone(), prevent_slash_commands, ) }); @@ -371,6 +376,7 @@ impl AcpThreadView { editor_expanded: false, history_store, hovered_recent_history_item: None, + prompt_capabilities, _subscriptions: subscriptions, _cancel_task: None, } @@ -448,6 +454,9 @@ impl AcpThreadView { Ok(thread) => { let action_log = thread.read(cx).action_log().clone(); + this.prompt_capabilities + .set(connection.prompt_capabilities()); + let count = thread.read(cx).entries().len(); this.list_state.splice(0..0, count); this.entry_view_state.update(cx, |view_state, cx| { @@ -523,11 +532,6 @@ impl AcpThreadView { }) }); - this.message_editor.update(cx, |message_editor, _cx| { - message_editor - .set_prompt_capabilities(connection.prompt_capabilities()); - }); - cx.notify(); } Err(err) => {