Threads and rule support

Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
Agus Zubiaga 2025-08-12 15:47:27 -03:00
parent e2973998ad
commit 91e22597a8
5 changed files with 68 additions and 98 deletions

View file

@ -1,4 +1,3 @@
use agent_client_protocol as acp;
use anyhow::{Result, bail};
use std::path::PathBuf;
@ -6,7 +5,8 @@ use std::path::PathBuf;
pub enum MentionUri {
File(PathBuf),
Symbol(PathBuf, String),
Thread(acp::SessionId),
Thread(String),
TextThread(PathBuf),
Rule(String),
}
@ -24,7 +24,7 @@ impl MentionUri {
}
"zed" => {
if let Some(thread) = path.strip_prefix("/agent/thread/") {
Ok(Self::Thread(acp::SessionId(thread.into())))
Ok(Self::Thread(thread.into()))
} else if let Some(rule) = path.strip_prefix("/agent/rule/") {
Ok(Self::Rule(rule.into()))
} else {
@ -40,6 +40,7 @@ impl MentionUri {
MentionUri::File(path) => path.file_name().unwrap().to_string_lossy().into_owned(),
MentionUri::Symbol(_path, name) => name.clone(),
MentionUri::Thread(thread) => thread.to_string(),
MentionUri::TextThread(thread) => thread.display().to_string(),
MentionUri::Rule(rule) => rule.clone(),
}
}
@ -60,7 +61,10 @@ impl MentionUri {
format!("file://{}#{}", path.display(), name)
}
MentionUri::Thread(thread) => {
format!("zed:///agent/thread/{}", thread.0)
format!("zed:///agent/thread/{}", thread)
}
MentionUri::TextThread(path) => {
format!("zed:///agent/text-thread/{}", path.display())
}
MentionUri::Rule(rule) => {
format!("zed:///agent/rule/{}", rule)
@ -100,7 +104,7 @@ mod tests {
let thread_uri = "zed:///agent/thread/session123";
let parsed = MentionUri::parse(thread_uri).unwrap();
match &parsed {
MentionUri::Thread(session_id) => assert_eq!(session_id.0.as_ref(), "session123"),
MentionUri::Thread(thread_id) => assert_eq!(thread_id, "session123"),
_ => panic!("Expected Thread variant"),
}
assert_eq!(parsed.to_uri(), thread_uri);

View file

@ -1196,6 +1196,9 @@ impl AgentMessage {
MentionUri::Thread(_session_id) => {
write!(&mut thread_context, "\n{}\n", content).ok();
}
MentionUri::TextThread(_session_id) => {
write!(&mut thread_context, "\n{}\n", content).ok();
}
MentionUri::Rule(_user_prompt_id) => {
write!(
&mut rules_context,

View file

@ -306,8 +306,8 @@ fn search(
pub struct ContextPickerCompletionProvider {
mention_set: Arc<Mutex<MentionSet>>,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
text_thread_store: Option<WeakEntity<TextThreadStore>>,
thread_store: WeakEntity<ThreadStore>,
text_thread_store: WeakEntity<TextThreadStore>,
editor: WeakEntity<Editor>,
}
@ -315,8 +315,8 @@ impl ContextPickerCompletionProvider {
pub fn new(
mention_set: Arc<Mutex<MentionSet>>,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
text_thread_store: Option<WeakEntity<TextThreadStore>>,
thread_store: WeakEntity<ThreadStore>,
text_thread_store: WeakEntity<TextThreadStore>,
editor: WeakEntity<Editor>,
) -> Self {
Self {
@ -474,77 +474,39 @@ impl ContextPickerCompletionProvider {
recent: bool,
editor: Entity<Editor>,
mention_set: Arc<Mutex<MentionSet>>,
thread_store: Entity<ThreadStore>,
text_thread_store: Entity<TextThreadStore>,
) -> Completion {
todo!();
// let icon_for_completion = if recent {
// IconName::HistoryRerun
// } else {
// IconName::Thread
// };
let icon_for_completion = if recent {
IconName::HistoryRerun
} else {
IconName::Thread
};
// let new_text = format!("{} ", MentionUri::Thread(thread_id));
let uri = match &thread_entry {
ThreadContextEntry::Thread { id, .. } => MentionUri::Thread(id.to_string()),
ThreadContextEntry::Context { path, .. } => MentionUri::TextThread(path.to_path_buf()),
};
let new_text = format!("{} ", uri.to_link());
// let new_text_len = new_text.len();
// Completion {
// replace_range: source_range.clone(),
// new_text,
// label: CodeLabel::plain(thread_entry.title().to_string(), None),
// documentation: None,
// insert_text_mode: None,
// source: project::CompletionSource::Custom,
// icon_path: Some(icon_for_completion.path().into()),
// confirm: Some(confirm_completion_callback(
// IconName::Thread.path().into(),
// thread_entry.title().clone(),
// excerpt_id,
// source_range.start,
// new_text_len - 1,
// editor.clone(),
// context_store.clone(),
// move |window, cx| match &thread_entry {
// ThreadContextEntry::Thread { id, .. } => {
// let thread_id = id.clone();
// let context_store = context_store.clone();
// let thread_store = thread_store.clone();
// window.spawn::<_, Option<_>>(cx, async move |cx| {
// let thread: Entity<Thread> = thread_store
// .update_in(cx, |thread_store, window, cx| {
// thread_store.open_thread(&thread_id, window, cx)
// })
// .ok()?
// .await
// .log_err()?;
// let context = context_store
// .update(cx, |context_store, cx| {
// context_store.add_thread(thread, false, cx)
// })
// .ok()??;
// Some(context)
// })
// }
// ThreadContextEntry::Context { path, .. } => {
// let path = path.clone();
// let context_store = context_store.clone();
// let text_thread_store = text_thread_store.clone();
// cx.spawn::<_, Option<_>>(async move |cx| {
// let thread = text_thread_store
// .update(cx, |store, cx| store.open_local_context(path, cx))
// .ok()?
// .await
// .log_err()?;
// let context = context_store
// .update(cx, |context_store, cx| {
// context_store.add_text_thread(thread, false, cx)
// })
// .ok()??;
// Some(context)
// })
// }
// },
// )),
// }
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
new_text,
label: CodeLabel::plain(thread_entry.title().to_string(), None),
documentation: None,
insert_text_mode: None,
source: project::CompletionSource::Custom,
icon_path: Some(icon_for_completion.path().into()),
confirm: Some(confirm_completion_callback(
IconName::Thread.path().into(),
thread_entry.title().clone(),
excerpt_id,
source_range.start,
new_text_len - 1,
editor.clone(),
mention_set,
uri,
)),
}
}
fn completion_for_rules(
@ -555,7 +517,7 @@ impl ContextPickerCompletionProvider {
mention_set: Arc<Mutex<MentionSet>>,
) -> Completion {
let uri = MentionUri::Rule(rules.prompt_id.0.to_string());
let new_text = uri.to_link();
let new_text = format!("{} ", uri.to_link());
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
@ -769,7 +731,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
excluded_paths.insert(path.clone());
}
MentionUri::Thread(thread) => {
excluded_threads.insert(thread.0.as_ref().into());
excluded_threads.insert(thread.as_str().into());
}
_ => {}
}
@ -850,21 +812,14 @@ impl CompletionProvider for ContextPickerCompletionProvider {
Match::Thread(ThreadMatch {
thread, is_recent, ..
}) => {
let thread_store = thread_store.as_ref().and_then(|t| t.upgrade())?;
let text_thread_store =
text_thread_store.as_ref().and_then(|t| t.upgrade())?;
Some(Self::completion_for_thread(
thread,
excerpt_id,
source_range.clone(),
is_recent,
editor.clone(),
mention_set.clone(),
thread_store,
text_thread_store,
))
}
}) => Some(Self::completion_for_thread(
thread,
excerpt_id,
source_range.clone(),
is_recent,
editor.clone(),
mention_set.clone(),
)),
Match::Rules(user_rules) => Some(Self::completion_for_rules(
user_rules,

View file

@ -4,6 +4,7 @@ use acp_thread::{
};
use acp_thread::{AgentConnection, Plan};
use action_log::ActionLog;
use agent::{TextThreadStore, ThreadStore};
use agent_client_protocol as acp;
use agent_servers::AgentServer;
use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
@ -104,6 +105,8 @@ impl AcpThreadView {
agent: Rc<dyn AgentServer>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
thread_store: WeakEntity<ThreadStore>,
text_thread_store: WeaksEntity<TextThreadStore>,
message_history: Rc<RefCell<MessageHistory<Vec<acp::ContentBlock>>>>,
min_lines: usize,
max_lines: Option<usize>,
@ -141,11 +144,9 @@ impl AcpThreadView {
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
mention_set.clone(),
workspace.clone(),
// todo! provide thread stores
None,
None,
thread_store.clone(),
text_thread_store.clone(),
cx.weak_entity(),
// None,
))));
editor.set_context_menu_options(ContextMenuOptions {
min_entries_visible: 12,
@ -3579,6 +3580,8 @@ mod tests {
Rc::new(agent),
workspace.downgrade(),
project,
WeakEntity::new_invalid(),
WeakEntity::new_invalid(),
Rc::new(RefCell::new(MessageHistory::default())),
1,
None,

View file

@ -924,6 +924,9 @@ impl AgentPanel {
agent: crate::ExternalAgent,
}
let thread_store = self.thread_store.clone();
let text_thread_store = self.context_store.clone();
cx.spawn_in(window, async move |this, cx| {
let server: Rc<dyn AgentServer> = match agent_choice {
Some(agent) => {
@ -962,6 +965,8 @@ impl AgentPanel {
server,
workspace.clone(),
project,
thread_store.clone(),
text_thread_store.clone(),
message_history,
MIN_EDITOR_LINES,
Some(MAX_EDITOR_LINES),