agent: Use default prompts from prompt library in system prompt (#28915)

Related to #28490.

- Default prompts from the prompt library are now included as "user
rules" in the system prompt.
- Presence of these user rules is shown at the beginning of the thread
in the UI.
_ Now uses an `Entity<PromptStore>` instead of an `Arc<PromptStore>`.
Motivation for this is emitting a `PromptsUpdatedEvent`.
- Now disallows concurrent reloading of the system prompt. Before this
change it was possible for reloads to race.

Release Notes:

- agent: Added support for including default prompts from the Prompt
Library as "user rules" in the system prompt.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
Michael Sloan 2025-04-18 09:32:35 -06:00 committed by GitHub
parent eea6cfb383
commit 502a0f6535
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 433 additions and 165 deletions

View file

@ -136,7 +136,7 @@ pub fn open_prompt_library(
}
pub struct PromptLibrary {
store: Arc<PromptStore>,
store: Entity<PromptStore>,
language_registry: Arc<LanguageRegistry>,
prompt_editors: HashMap<PromptId, PromptEditor>,
active_prompt_id: Option<PromptId>,
@ -158,7 +158,7 @@ struct PromptEditor {
}
struct PromptPickerDelegate {
store: Arc<PromptStore>,
store: Entity<PromptStore>,
selected_index: usize,
matches: Vec<PromptMetadata>,
}
@ -179,8 +179,8 @@ impl PickerDelegate for PromptPickerDelegate {
self.matches.len()
}
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
let text = if self.store.prompt_count() == 0 {
fn no_matches_text(&self, _window: &mut Window, cx: &mut App) -> Option<SharedString> {
let text = if self.store.read(cx).prompt_count() == 0 {
"No prompts.".into()
} else {
"No prompts found matching your search.".into()
@ -211,7 +211,7 @@ impl PickerDelegate for PromptPickerDelegate {
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let search = self.store.search(query);
let search = self.store.read(cx).search(query, cx);
let prev_prompt_id = self.matches.get(self.selected_index).map(|mat| mat.id);
cx.spawn_in(window, async move |this, cx| {
let (matches, selected_index) = cx
@ -339,7 +339,7 @@ impl PickerDelegate for PromptPickerDelegate {
impl PromptLibrary {
fn new(
store: Arc<PromptStore>,
store: Entity<PromptStore>,
language_registry: Arc<LanguageRegistry>,
inline_assist_delegate: Box<dyn InlineAssistDelegate>,
make_completion_provider: Arc<dyn Fn() -> Box<dyn CompletionProvider>>,
@ -398,7 +398,7 @@ impl PromptLibrary {
pub fn new_prompt(&mut self, window: &mut Window, cx: &mut Context<Self>) {
// If we already have an untitled prompt, use that instead
// of creating a new one.
if let Some(metadata) = self.store.first() {
if let Some(metadata) = self.store.read(cx).first() {
if metadata.title.is_none() {
self.load_prompt(metadata.id, true, window, cx);
return;
@ -406,7 +406,9 @@ impl PromptLibrary {
}
let prompt_id = PromptId::new();
let save = self.store.save(prompt_id, None, false, "".into());
let save = self.store.update(cx, |store, cx| {
store.save(prompt_id, None, false, "".into(), cx)
});
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.spawn_in(window, async move |this, cx| {
@ -430,7 +432,7 @@ impl PromptLibrary {
return;
}
let prompt_metadata = self.store.metadata(prompt_id).unwrap();
let prompt_metadata = self.store.read(cx).metadata(prompt_id).unwrap();
let prompt_editor = self.prompt_editors.get_mut(&prompt_id).unwrap();
let title = prompt_editor.title_editor.read(cx).text(cx);
let body = prompt_editor.body_editor.update(cx, |editor, cx| {
@ -465,10 +467,13 @@ impl PromptLibrary {
} else {
Some(SharedString::from(title))
};
store
.save(prompt_id, title, prompt_metadata.default, body)
.await
.log_err();
cx.update(|_window, cx| {
store.update(cx, |store, cx| {
store.save(prompt_id, title, prompt_metadata.default, body, cx)
})
})?
.await
.log_err();
this.update_in(cx, |this, window, cx| {
this.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
@ -521,14 +526,21 @@ impl PromptLibrary {
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
self.store
.save_metadata(prompt_id, prompt_metadata.title, !prompt_metadata.default)
.detach_and_log_err(cx);
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.notify();
}
self.store.update(cx, move |store, cx| {
if let Some(prompt_metadata) = store.metadata(prompt_id) {
store
.save_metadata(
prompt_id,
prompt_metadata.title,
!prompt_metadata.default,
cx,
)
.detach_and_log_err(cx);
}
});
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.notify();
}
pub fn load_prompt(
@ -545,9 +557,9 @@ impl PromptLibrary {
.update(cx, |editor, cx| window.focus(&editor.focus_handle(cx)));
}
self.set_active_prompt(Some(prompt_id), window, cx);
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
} else if let Some(prompt_metadata) = self.store.read(cx).metadata(prompt_id) {
let language_registry = self.language_registry.clone();
let prompt = self.store.load(prompt_id);
let prompt = self.store.read(cx).load(prompt_id, cx);
let make_completion_provider = self.make_completion_provider.clone();
self.pending_load = cx.spawn_in(window, async move |this, cx| {
let prompt = prompt.await;
@ -673,7 +685,7 @@ impl PromptLibrary {
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(metadata) = self.store.metadata(prompt_id) {
if let Some(metadata) = self.store.read(cx).metadata(prompt_id) {
let confirmation = window.prompt(
PromptLevel::Warning,
&format!(
@ -692,7 +704,9 @@ impl PromptLibrary {
this.set_active_prompt(None, window, cx);
}
this.prompt_editors.remove(&prompt_id);
this.store.delete(prompt_id).detach_and_log_err(cx);
this.store
.update(cx, |store, cx| store.delete(prompt_id, cx))
.detach_and_log_err(cx);
this.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.notify();
@ -736,9 +750,9 @@ impl PromptLibrary {
let new_id = PromptId::new();
let body = prompt.body_editor.read(cx).text(cx);
let save = self
.store
.save(new_id, Some(title.into()), false, body.into());
let save = self.store.update(cx, |store, cx| {
store.save(new_id, Some(title.into()), false, body.into(), cx)
});
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.spawn_in(window, async move |this, cx| {
@ -968,7 +982,7 @@ impl PromptLibrary {
.flex_none()
.min_w_64()
.children(self.active_prompt_id.and_then(|prompt_id| {
let prompt_metadata = self.store.metadata(prompt_id)?;
let prompt_metadata = self.store.read(cx).metadata(prompt_id)?;
let prompt_editor = &self.prompt_editors[&prompt_id];
let focus_handle = prompt_editor.body_editor.focus_handle(cx);
let model = LanguageModelRegistry::read_global(cx)
@ -1238,7 +1252,7 @@ impl Render for PromptLibrary {
.text_color(theme.colors().text)
.child(self.render_prompt_list(cx))
.map(|el| {
if self.store.prompt_count() == 0 {
if self.store.read(cx).prompt_count() == 0 {
el.child(
v_flex()
.w_2_3()