ACP history mentions (#36551)
- **TEMP** - **Update @-mentions to use new history** Closes #ISSUE Release Notes: - N/A
This commit is contained in:
parent
159b5e9fb5
commit
5d2bb2466e
17 changed files with 581 additions and 392 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -7,7 +7,6 @@ name = "acp_thread"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"action_log",
|
"action_log",
|
||||||
"agent",
|
|
||||||
"agent-client-protocol",
|
"agent-client-protocol",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"buffer_diff",
|
"buffer_diff",
|
||||||
|
|
|
@ -18,7 +18,6 @@ test-support = ["gpui/test-support", "project/test-support", "dep:parking_lot"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
action_log.workspace = true
|
action_log.workspace = true
|
||||||
agent-client-protocol.workspace = true
|
agent-client-protocol.workspace = true
|
||||||
agent.workspace = true
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
buffer_diff.workspace = true
|
buffer_diff.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use agent::ThreadId;
|
use agent_client_protocol as acp;
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use prompt_store::{PromptId, UserPromptId};
|
use prompt_store::{PromptId, UserPromptId};
|
||||||
|
@ -12,7 +12,7 @@ use std::{
|
||||||
use ui::{App, IconName, SharedString};
|
use ui::{App, IconName, SharedString};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||||
pub enum MentionUri {
|
pub enum MentionUri {
|
||||||
File {
|
File {
|
||||||
abs_path: PathBuf,
|
abs_path: PathBuf,
|
||||||
|
@ -26,7 +26,7 @@ pub enum MentionUri {
|
||||||
line_range: Range<u32>,
|
line_range: Range<u32>,
|
||||||
},
|
},
|
||||||
Thread {
|
Thread {
|
||||||
id: ThreadId,
|
id: acp::SessionId,
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
TextThread {
|
TextThread {
|
||||||
|
@ -89,7 +89,7 @@ impl MentionUri {
|
||||||
if let Some(thread_id) = path.strip_prefix("/agent/thread/") {
|
if let Some(thread_id) = path.strip_prefix("/agent/thread/") {
|
||||||
let name = single_query_param(&url, "name")?.context("Missing thread name")?;
|
let name = single_query_param(&url, "name")?.context("Missing thread name")?;
|
||||||
Ok(Self::Thread {
|
Ok(Self::Thread {
|
||||||
id: thread_id.into(),
|
id: acp::SessionId(thread_id.into()),
|
||||||
name,
|
name,
|
||||||
})
|
})
|
||||||
} else if let Some(path) = path.strip_prefix("/agent/text-thread/") {
|
} else if let Some(path) = path.strip_prefix("/agent/text-thread/") {
|
||||||
|
|
|
@ -9,7 +9,10 @@ use crate::{
|
||||||
tool_use::{PendingToolUse, ToolUse, ToolUseMetadata, ToolUseState},
|
tool_use::{PendingToolUse, ToolUse, ToolUseMetadata, ToolUseState},
|
||||||
};
|
};
|
||||||
use action_log::ActionLog;
|
use action_log::ActionLog;
|
||||||
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, SUMMARIZE_THREAD_PROMPT};
|
use agent_settings::{
|
||||||
|
AgentProfileId, AgentSettings, CompletionMode, SUMMARIZE_THREAD_DETAILED_PROMPT,
|
||||||
|
SUMMARIZE_THREAD_PROMPT,
|
||||||
|
};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use assistant_tool::{AnyToolCard, Tool, ToolWorkingSet};
|
use assistant_tool::{AnyToolCard, Tool, ToolWorkingSet};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
@ -107,7 +110,7 @@ impl std::fmt::Display for PromptId {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub struct MessageId(pub(crate) usize);
|
pub struct MessageId(pub usize);
|
||||||
|
|
||||||
impl MessageId {
|
impl MessageId {
|
||||||
fn post_inc(&mut self) -> Self {
|
fn post_inc(&mut self) -> Self {
|
||||||
|
@ -2425,12 +2428,10 @@ impl Thread {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let added_user_message = include_str!("./prompts/summarize_thread_detailed_prompt.txt");
|
|
||||||
|
|
||||||
let request = self.to_summarize_request(
|
let request = self.to_summarize_request(
|
||||||
&model,
|
&model,
|
||||||
CompletionIntent::ThreadContextSummarization,
|
CompletionIntent::ThreadContextSummarization,
|
||||||
added_user_message.into(),
|
SUMMARIZE_THREAD_DETAILED_PROMPT.into(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@ license = "GPL-3.0-or-later"
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/agent2.rs"
|
path = "src/agent2.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-support = ["db/test-support"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
@ -72,6 +75,7 @@ ctor.workspace = true
|
||||||
client = { workspace = true, "features" = ["test-support"] }
|
client = { workspace = true, "features" = ["test-support"] }
|
||||||
clock = { workspace = true, "features" = ["test-support"] }
|
clock = { workspace = true, "features" = ["test-support"] }
|
||||||
context_server = { workspace = true, "features" = ["test-support"] }
|
context_server = { workspace = true, "features" = ["test-support"] }
|
||||||
|
db = { workspace = true, "features" = ["test-support"] }
|
||||||
editor = { workspace = true, "features" = ["test-support"] }
|
editor = { workspace = true, "features" = ["test-support"] }
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
fs = { workspace = true, "features" = ["test-support"] }
|
fs = { workspace = true, "features" = ["test-support"] }
|
||||||
|
|
|
@ -536,6 +536,28 @@ impl NativeAgent {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn thread_summary(
|
||||||
|
&mut self,
|
||||||
|
id: acp::SessionId,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Result<SharedString>> {
|
||||||
|
let thread = self.open_thread(id.clone(), cx);
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
let acp_thread = thread.await?;
|
||||||
|
let result = this
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.sessions
|
||||||
|
.get(&id)
|
||||||
|
.unwrap()
|
||||||
|
.thread
|
||||||
|
.update(cx, |thread, cx| thread.summary(cx))
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
drop(acp_thread);
|
||||||
|
Ok(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn save_thread(&mut self, thread: Entity<Thread>, cx: &mut Context<Self>) {
|
fn save_thread(&mut self, thread: Entity<Thread>, cx: &mut Context<Self>) {
|
||||||
let database_future = ThreadsDatabase::connect(cx);
|
let database_future = ThreadsDatabase::connect(cx);
|
||||||
let (id, db_thread) =
|
let (id, db_thread) =
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{AgentMessage, AgentMessageContent, UserMessage, UserMessageContent};
|
use crate::{AgentMessage, AgentMessageContent, UserMessage, UserMessageContent};
|
||||||
use acp_thread::UserMessageId;
|
use acp_thread::UserMessageId;
|
||||||
use agent::thread_store;
|
use agent::{thread::DetailedSummaryState, thread_store};
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use agent_settings::{AgentProfileId, CompletionMode};
|
use agent_settings::{AgentProfileId, CompletionMode};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
|
@ -20,7 +20,7 @@ use std::sync::Arc;
|
||||||
use ui::{App, SharedString};
|
use ui::{App, SharedString};
|
||||||
|
|
||||||
pub type DbMessage = crate::Message;
|
pub type DbMessage = crate::Message;
|
||||||
pub type DbSummary = agent::thread::DetailedSummaryState;
|
pub type DbSummary = DetailedSummaryState;
|
||||||
pub type DbLanguageModel = thread_store::SerializedLanguageModel;
|
pub type DbLanguageModel = thread_store::SerializedLanguageModel;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -37,7 +37,7 @@ pub struct DbThread {
|
||||||
pub messages: Vec<DbMessage>,
|
pub messages: Vec<DbMessage>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub summary: DbSummary,
|
pub detailed_summary: Option<SharedString>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub initial_project_snapshot: Option<Arc<agent::thread::ProjectSnapshot>>,
|
pub initial_project_snapshot: Option<Arc<agent::thread::ProjectSnapshot>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -185,7 +185,12 @@ impl DbThread {
|
||||||
title: thread.summary,
|
title: thread.summary,
|
||||||
messages,
|
messages,
|
||||||
updated_at: thread.updated_at,
|
updated_at: thread.updated_at,
|
||||||
summary: thread.detailed_summary_state,
|
detailed_summary: match thread.detailed_summary_state {
|
||||||
|
DetailedSummaryState::NotGenerated | DetailedSummaryState::Generating { .. } => {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
DetailedSummaryState::Generated { text, .. } => Some(text),
|
||||||
|
},
|
||||||
initial_project_snapshot: thread.initial_project_snapshot,
|
initial_project_snapshot: thread.initial_project_snapshot,
|
||||||
cumulative_token_usage: thread.cumulative_token_usage,
|
cumulative_token_usage: thread.cumulative_token_usage,
|
||||||
request_token_usage,
|
request_token_usage,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{DbThreadMetadata, ThreadsDatabase};
|
use crate::{DbThreadMetadata, ThreadsDatabase};
|
||||||
|
use acp_thread::MentionUri;
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_context::SavedContextMetadata;
|
use assistant_context::{AssistantContext, SavedContextMetadata};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
|
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
|
||||||
|
@ -38,6 +39,19 @@ impl HistoryEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mention_uri(&self) -> MentionUri {
|
||||||
|
match self {
|
||||||
|
HistoryEntry::AcpThread(thread) => MentionUri::Thread {
|
||||||
|
id: thread.id.clone(),
|
||||||
|
name: thread.title.to_string(),
|
||||||
|
},
|
||||||
|
HistoryEntry::TextThread(context) => MentionUri::TextThread {
|
||||||
|
path: context.path.as_ref().to_owned(),
|
||||||
|
name: context.title.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn title(&self) -> &SharedString {
|
pub fn title(&self) -> &SharedString {
|
||||||
match self {
|
match self {
|
||||||
HistoryEntry::AcpThread(thread) if thread.title.is_empty() => DEFAULT_TITLE,
|
HistoryEntry::AcpThread(thread) if thread.title.is_empty() => DEFAULT_TITLE,
|
||||||
|
@ -48,7 +62,7 @@ impl HistoryEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generic identifier for a history entry.
|
/// Generic identifier for a history entry.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||||
pub enum HistoryEntryId {
|
pub enum HistoryEntryId {
|
||||||
AcpThread(acp::SessionId),
|
AcpThread(acp::SessionId),
|
||||||
TextThread(Arc<Path>),
|
TextThread(Arc<Path>),
|
||||||
|
@ -120,6 +134,16 @@ impl HistoryStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_text_thread(
|
||||||
|
&self,
|
||||||
|
path: Arc<Path>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Result<Entity<AssistantContext>>> {
|
||||||
|
self.context_store.update(cx, |context_store, cx| {
|
||||||
|
context_store.open_local_context(path, cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reload(&self, cx: &mut Context<Self>) {
|
pub fn reload(&self, cx: &mut Context<Self>) {
|
||||||
let database_future = ThreadsDatabase::connect(cx);
|
let database_future = ThreadsDatabase::connect(cx);
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
|
@ -149,7 +173,7 @@ impl HistoryStore {
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
pub fn entries(&self, cx: &App) -> Vec<HistoryEntry> {
|
||||||
let mut history_entries = Vec::new();
|
let mut history_entries = Vec::new();
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -180,10 +204,6 @@ impl HistoryStore {
|
||||||
.is_none()
|
.is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recent_entries(&self, limit: usize, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
|
||||||
self.entries(cx).into_iter().take(limit).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recently_opened_entries(&self, cx: &App) -> Vec<HistoryEntry> {
|
pub fn recently_opened_entries(&self, cx: &App) -> Vec<HistoryEntry> {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
|
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
|
||||||
|
@ -246,6 +266,10 @@ impl HistoryStore {
|
||||||
cx.background_executor()
|
cx.background_executor()
|
||||||
.timer(SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE)
|
.timer(SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
if cfg!(any(feature = "test-support", test)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
KEY_VALUE_STORE
|
KEY_VALUE_STORE
|
||||||
.write_kvp(RECENTLY_OPENED_THREADS_KEY.to_owned(), content)
|
.write_kvp(RECENTLY_OPENED_THREADS_KEY.to_owned(), content)
|
||||||
.await
|
.await
|
||||||
|
@ -255,6 +279,9 @@ impl HistoryStore {
|
||||||
|
|
||||||
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<VecDeque<HistoryEntryId>>> {
|
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<VecDeque<HistoryEntryId>>> {
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
|
if cfg!(any(feature = "test-support", test)) {
|
||||||
|
anyhow::bail!("history store does not persist in tests");
|
||||||
|
}
|
||||||
let json = KEY_VALUE_STORE
|
let json = KEY_VALUE_STORE
|
||||||
.read_kvp(RECENTLY_OPENED_THREADS_KEY)?
|
.read_kvp(RECENTLY_OPENED_THREADS_KEY)?
|
||||||
.unwrap_or("[]".to_string());
|
.unwrap_or("[]".to_string());
|
||||||
|
|
|
@ -6,9 +6,12 @@ use crate::{
|
||||||
};
|
};
|
||||||
use acp_thread::{MentionUri, UserMessageId};
|
use acp_thread::{MentionUri, UserMessageId};
|
||||||
use action_log::ActionLog;
|
use action_log::ActionLog;
|
||||||
use agent::thread::{DetailedSummaryState, GitState, ProjectSnapshot, WorktreeSnapshot};
|
use agent::thread::{GitState, ProjectSnapshot, WorktreeSnapshot};
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, SUMMARIZE_THREAD_PROMPT};
|
use agent_settings::{
|
||||||
|
AgentProfileId, AgentSettings, CompletionMode, SUMMARIZE_THREAD_DETAILED_PROMPT,
|
||||||
|
SUMMARIZE_THREAD_PROMPT,
|
||||||
|
};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_tool::adapt_schema_to_format;
|
use assistant_tool::adapt_schema_to_format;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
@ -499,8 +502,7 @@ pub struct Thread {
|
||||||
prompt_id: PromptId,
|
prompt_id: PromptId,
|
||||||
updated_at: DateTime<Utc>,
|
updated_at: DateTime<Utc>,
|
||||||
title: Option<SharedString>,
|
title: Option<SharedString>,
|
||||||
#[allow(unused)]
|
summary: Option<SharedString>,
|
||||||
summary: DetailedSummaryState,
|
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
completion_mode: CompletionMode,
|
completion_mode: CompletionMode,
|
||||||
/// Holds the task that handles agent interaction until the end of the turn.
|
/// Holds the task that handles agent interaction until the end of the turn.
|
||||||
|
@ -541,7 +543,7 @@ impl Thread {
|
||||||
prompt_id: PromptId::new(),
|
prompt_id: PromptId::new(),
|
||||||
updated_at: Utc::now(),
|
updated_at: Utc::now(),
|
||||||
title: None,
|
title: None,
|
||||||
summary: DetailedSummaryState::default(),
|
summary: None,
|
||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
|
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
|
||||||
running_turn: None,
|
running_turn: None,
|
||||||
|
@ -691,7 +693,7 @@ impl Thread {
|
||||||
} else {
|
} else {
|
||||||
Some(db_thread.title.clone())
|
Some(db_thread.title.clone())
|
||||||
},
|
},
|
||||||
summary: db_thread.summary,
|
summary: db_thread.detailed_summary,
|
||||||
messages: db_thread.messages,
|
messages: db_thread.messages,
|
||||||
completion_mode: db_thread.completion_mode.unwrap_or_default(),
|
completion_mode: db_thread.completion_mode.unwrap_or_default(),
|
||||||
running_turn: None,
|
running_turn: None,
|
||||||
|
@ -719,7 +721,7 @@ impl Thread {
|
||||||
title: self.title.clone().unwrap_or_default(),
|
title: self.title.clone().unwrap_or_default(),
|
||||||
messages: self.messages.clone(),
|
messages: self.messages.clone(),
|
||||||
updated_at: self.updated_at,
|
updated_at: self.updated_at,
|
||||||
summary: self.summary.clone(),
|
detailed_summary: self.summary.clone(),
|
||||||
initial_project_snapshot: None,
|
initial_project_snapshot: None,
|
||||||
cumulative_token_usage: self.cumulative_token_usage,
|
cumulative_token_usage: self.cumulative_token_usage,
|
||||||
request_token_usage: self.request_token_usage.clone(),
|
request_token_usage: self.request_token_usage.clone(),
|
||||||
|
@ -976,7 +978,7 @@ impl Thread {
|
||||||
Message::Agent(_) | Message::Resume => {}
|
Message::Agent(_) | Message::Resume => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.summary = None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1047,6 +1049,7 @@ impl Thread {
|
||||||
let event_stream = ThreadEventStream(events_tx);
|
let event_stream = ThreadEventStream(events_tx);
|
||||||
let message_ix = self.messages.len().saturating_sub(1);
|
let message_ix = self.messages.len().saturating_sub(1);
|
||||||
self.tool_use_limit_reached = false;
|
self.tool_use_limit_reached = false;
|
||||||
|
self.summary = None;
|
||||||
self.running_turn = Some(RunningTurn {
|
self.running_turn = Some(RunningTurn {
|
||||||
event_stream: event_stream.clone(),
|
event_stream: event_stream.clone(),
|
||||||
_task: cx.spawn(async move |this, cx| {
|
_task: cx.spawn(async move |this, cx| {
|
||||||
|
@ -1507,6 +1510,63 @@ impl Thread {
|
||||||
self.title.clone().unwrap_or("New Thread".into())
|
self.title.clone().unwrap_or("New Thread".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn summary(&mut self, cx: &mut Context<Self>) -> Task<Result<SharedString>> {
|
||||||
|
if let Some(summary) = self.summary.as_ref() {
|
||||||
|
return Task::ready(Ok(summary.clone()));
|
||||||
|
}
|
||||||
|
let Some(model) = self.summarization_model.clone() else {
|
||||||
|
return Task::ready(Err(anyhow!("No summarization model available")));
|
||||||
|
};
|
||||||
|
let mut request = LanguageModelRequest {
|
||||||
|
intent: Some(CompletionIntent::ThreadSummarization),
|
||||||
|
temperature: AgentSettings::temperature_for_model(&model, cx),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
for message in &self.messages {
|
||||||
|
request.messages.extend(message.to_request());
|
||||||
|
}
|
||||||
|
|
||||||
|
request.messages.push(LanguageModelRequestMessage {
|
||||||
|
role: Role::User,
|
||||||
|
content: vec![SUMMARIZE_THREAD_DETAILED_PROMPT.into()],
|
||||||
|
cache: false,
|
||||||
|
});
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
let mut summary = String::new();
|
||||||
|
let mut messages = model.stream_completion(request, cx).await?;
|
||||||
|
while let Some(event) = messages.next().await {
|
||||||
|
let event = event?;
|
||||||
|
let text = match event {
|
||||||
|
LanguageModelCompletionEvent::Text(text) => text,
|
||||||
|
LanguageModelCompletionEvent::StatusUpdate(
|
||||||
|
CompletionRequestStatus::UsageUpdated { .. },
|
||||||
|
) => {
|
||||||
|
// this.update(cx, |thread, cx| {
|
||||||
|
// thread.update_model_request_usage(amount as u32, limit, cx);
|
||||||
|
// })?;
|
||||||
|
// TODO: handle usage update
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut lines = text.lines();
|
||||||
|
summary.extend(lines.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Setting summary: {}", summary);
|
||||||
|
let summary = SharedString::from(summary);
|
||||||
|
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.summary = Some(summary.clone());
|
||||||
|
cx.notify()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(summary)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn update_title(
|
fn update_title(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_stream: &ThreadEventStream,
|
event_stream: &ThreadEventStream,
|
||||||
|
@ -1617,6 +1677,7 @@ impl Thread {
|
||||||
|
|
||||||
self.messages.push(Message::Agent(message));
|
self.messages.push(Message::Agent(message));
|
||||||
self.updated_at = Utc::now();
|
self.updated_at = Utc::now();
|
||||||
|
self.summary = None;
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ pub use crate::agent_profile::*;
|
||||||
|
|
||||||
pub const SUMMARIZE_THREAD_PROMPT: &str =
|
pub const SUMMARIZE_THREAD_PROMPT: &str =
|
||||||
include_str!("../../agent/src/prompts/summarize_thread_prompt.txt");
|
include_str!("../../agent/src/prompts/summarize_thread_prompt.txt");
|
||||||
|
pub const SUMMARIZE_THREAD_DETAILED_PROMPT: &str =
|
||||||
|
include_str!("../../agent/src/prompts/summarize_thread_detailed_prompt.txt");
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
AgentSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
|
|
|
@ -104,9 +104,11 @@ zed_actions.workspace = true
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
acp_thread = { workspace = true, features = ["test-support"] }
|
acp_thread = { workspace = true, features = ["test-support"] }
|
||||||
agent = { workspace = true, features = ["test-support"] }
|
agent = { workspace = true, features = ["test-support"] }
|
||||||
|
agent2 = { workspace = true, features = ["test-support"] }
|
||||||
assistant_context = { workspace = true, features = ["test-support"] }
|
assistant_context = { workspace = true, features = ["test-support"] }
|
||||||
assistant_tools.workspace = true
|
assistant_tools.workspace = true
|
||||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||||
|
db = { workspace = true, features = ["test-support"] }
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
gpui = { workspace = true, "features" = ["test-support"] }
|
gpui = { workspace = true, "features" = ["test-support"] }
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use acp_thread::MentionUri;
|
use acp_thread::MentionUri;
|
||||||
|
use agent2::{HistoryEntry, HistoryStore};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use editor::{CompletionProvider, Editor, ExcerptId};
|
use editor::{CompletionProvider, Editor, ExcerptId};
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
|
@ -18,25 +19,21 @@ use text::{Anchor, ToPoint as _};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use agent::thread_store::{TextThreadStore, ThreadStore};
|
use crate::AgentPanel;
|
||||||
|
|
||||||
use crate::acp::message_editor::MessageEditor;
|
use crate::acp::message_editor::MessageEditor;
|
||||||
use crate::context_picker::file_context_picker::{FileMatch, search_files};
|
use crate::context_picker::file_context_picker::{FileMatch, search_files};
|
||||||
use crate::context_picker::rules_context_picker::{RulesContextEntry, search_rules};
|
use crate::context_picker::rules_context_picker::{RulesContextEntry, search_rules};
|
||||||
use crate::context_picker::symbol_context_picker::SymbolMatch;
|
use crate::context_picker::symbol_context_picker::SymbolMatch;
|
||||||
use crate::context_picker::symbol_context_picker::search_symbols;
|
use crate::context_picker::symbol_context_picker::search_symbols;
|
||||||
use crate::context_picker::thread_context_picker::{
|
|
||||||
ThreadContextEntry, ThreadMatch, search_threads,
|
|
||||||
};
|
|
||||||
use crate::context_picker::{
|
use crate::context_picker::{
|
||||||
ContextPickerAction, ContextPickerEntry, ContextPickerMode, RecentEntry,
|
ContextPickerAction, ContextPickerEntry, ContextPickerMode, selection_ranges,
|
||||||
available_context_picker_entries, recent_context_picker_entries, selection_ranges,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) enum Match {
|
pub(crate) enum Match {
|
||||||
File(FileMatch),
|
File(FileMatch),
|
||||||
Symbol(SymbolMatch),
|
Symbol(SymbolMatch),
|
||||||
Thread(ThreadMatch),
|
Thread(HistoryEntry),
|
||||||
|
RecentThread(HistoryEntry),
|
||||||
Fetch(SharedString),
|
Fetch(SharedString),
|
||||||
Rules(RulesContextEntry),
|
Rules(RulesContextEntry),
|
||||||
Entry(EntryMatch),
|
Entry(EntryMatch),
|
||||||
|
@ -53,6 +50,7 @@ impl Match {
|
||||||
Match::File(file) => file.mat.score,
|
Match::File(file) => file.mat.score,
|
||||||
Match::Entry(mode) => mode.mat.as_ref().map(|mat| mat.score).unwrap_or(1.),
|
Match::Entry(mode) => mode.mat.as_ref().map(|mat| mat.score).unwrap_or(1.),
|
||||||
Match::Thread(_) => 1.,
|
Match::Thread(_) => 1.,
|
||||||
|
Match::RecentThread(_) => 1.,
|
||||||
Match::Symbol(_) => 1.,
|
Match::Symbol(_) => 1.,
|
||||||
Match::Rules(_) => 1.,
|
Match::Rules(_) => 1.,
|
||||||
Match::Fetch(_) => 1.,
|
Match::Fetch(_) => 1.,
|
||||||
|
@ -60,209 +58,25 @@ impl Match {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search(
|
|
||||||
mode: Option<ContextPickerMode>,
|
|
||||||
query: String,
|
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
|
||||||
recent_entries: Vec<RecentEntry>,
|
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
|
||||||
text_thread_context_store: WeakEntity<assistant_context::ContextStore>,
|
|
||||||
workspace: Entity<Workspace>,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Vec<Match>> {
|
|
||||||
match mode {
|
|
||||||
Some(ContextPickerMode::File) => {
|
|
||||||
let search_files_task =
|
|
||||||
search_files(query.clone(), cancellation_flag.clone(), &workspace, cx);
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
search_files_task
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(Match::File)
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(ContextPickerMode::Symbol) => {
|
|
||||||
let search_symbols_task =
|
|
||||||
search_symbols(query.clone(), cancellation_flag.clone(), &workspace, cx);
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
search_symbols_task
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(Match::Symbol)
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(ContextPickerMode::Thread) => {
|
|
||||||
if let Some((thread_store, context_store)) = thread_store
|
|
||||||
.upgrade()
|
|
||||||
.zip(text_thread_context_store.upgrade())
|
|
||||||
{
|
|
||||||
let search_threads_task = search_threads(
|
|
||||||
query.clone(),
|
|
||||||
cancellation_flag.clone(),
|
|
||||||
thread_store,
|
|
||||||
context_store,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
search_threads_task
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(Match::Thread)
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Task::ready(Vec::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(ContextPickerMode::Fetch) => {
|
|
||||||
if !query.is_empty() {
|
|
||||||
Task::ready(vec![Match::Fetch(query.into())])
|
|
||||||
} else {
|
|
||||||
Task::ready(Vec::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(ContextPickerMode::Rules) => {
|
|
||||||
if let Some(prompt_store) = prompt_store.as_ref() {
|
|
||||||
let search_rules_task =
|
|
||||||
search_rules(query.clone(), cancellation_flag.clone(), prompt_store, cx);
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
search_rules_task
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(Match::Rules)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Task::ready(Vec::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None => {
|
|
||||||
if query.is_empty() {
|
|
||||||
let mut matches = recent_entries
|
|
||||||
.into_iter()
|
|
||||||
.map(|entry| match entry {
|
|
||||||
RecentEntry::File {
|
|
||||||
project_path,
|
|
||||||
path_prefix,
|
|
||||||
} => Match::File(FileMatch {
|
|
||||||
mat: fuzzy::PathMatch {
|
|
||||||
score: 1.,
|
|
||||||
positions: Vec::new(),
|
|
||||||
worktree_id: project_path.worktree_id.to_usize(),
|
|
||||||
path: project_path.path,
|
|
||||||
path_prefix,
|
|
||||||
is_dir: false,
|
|
||||||
distance_to_relative_ancestor: 0,
|
|
||||||
},
|
|
||||||
is_recent: true,
|
|
||||||
}),
|
|
||||||
RecentEntry::Thread(thread_context_entry) => Match::Thread(ThreadMatch {
|
|
||||||
thread: thread_context_entry,
|
|
||||||
is_recent: true,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
matches.extend(
|
|
||||||
available_context_picker_entries(
|
|
||||||
&prompt_store,
|
|
||||||
&Some(thread_store.clone()),
|
|
||||||
&workspace,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.into_iter()
|
|
||||||
.map(|mode| {
|
|
||||||
Match::Entry(EntryMatch {
|
|
||||||
entry: mode,
|
|
||||||
mat: None,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Task::ready(matches)
|
|
||||||
} else {
|
|
||||||
let executor = cx.background_executor().clone();
|
|
||||||
|
|
||||||
let search_files_task =
|
|
||||||
search_files(query.clone(), cancellation_flag.clone(), &workspace, cx);
|
|
||||||
|
|
||||||
let entries = available_context_picker_entries(
|
|
||||||
&prompt_store,
|
|
||||||
&Some(thread_store.clone()),
|
|
||||||
&workspace,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
let entry_candidates = entries
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(ix, entry)| StringMatchCandidate::new(ix, entry.keyword()))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
let mut matches = search_files_task
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(Match::File)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let entry_matches = fuzzy::match_strings(
|
|
||||||
&entry_candidates,
|
|
||||||
&query,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
100,
|
|
||||||
&Arc::new(AtomicBool::default()),
|
|
||||||
executor,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
matches.extend(entry_matches.into_iter().map(|mat| {
|
|
||||||
Match::Entry(EntryMatch {
|
|
||||||
entry: entries[mat.candidate_id],
|
|
||||||
mat: Some(mat),
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
|
|
||||||
matches.sort_by(|a, b| {
|
|
||||||
b.score()
|
|
||||||
.partial_cmp(&a.score())
|
|
||||||
.unwrap_or(std::cmp::Ordering::Equal)
|
|
||||||
});
|
|
||||||
|
|
||||||
matches
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ContextPickerCompletionProvider {
|
pub struct ContextPickerCompletionProvider {
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
|
||||||
text_thread_store: WeakEntity<TextThreadStore>,
|
|
||||||
message_editor: WeakEntity<MessageEditor>,
|
message_editor: WeakEntity<MessageEditor>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
history_store: Entity<HistoryStore>,
|
||||||
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextPickerCompletionProvider {
|
impl ContextPickerCompletionProvider {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
|
||||||
text_thread_store: WeakEntity<TextThreadStore>,
|
|
||||||
message_editor: WeakEntity<MessageEditor>,
|
message_editor: WeakEntity<MessageEditor>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
history_store: Entity<HistoryStore>,
|
||||||
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
workspace,
|
|
||||||
thread_store,
|
|
||||||
text_thread_store,
|
|
||||||
message_editor,
|
message_editor,
|
||||||
|
workspace,
|
||||||
|
history_store,
|
||||||
|
prompt_store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,22 +163,13 @@ impl ContextPickerCompletionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completion_for_thread(
|
fn completion_for_thread(
|
||||||
thread_entry: ThreadContextEntry,
|
thread_entry: HistoryEntry,
|
||||||
source_range: Range<Anchor>,
|
source_range: Range<Anchor>,
|
||||||
recent: bool,
|
recent: bool,
|
||||||
editor: WeakEntity<MessageEditor>,
|
editor: WeakEntity<MessageEditor>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Completion {
|
) -> Completion {
|
||||||
let uri = match &thread_entry {
|
let uri = thread_entry.mention_uri();
|
||||||
ThreadContextEntry::Thread { id, title } => MentionUri::Thread {
|
|
||||||
id: id.clone(),
|
|
||||||
name: title.to_string(),
|
|
||||||
},
|
|
||||||
ThreadContextEntry::Context { path, title } => MentionUri::TextThread {
|
|
||||||
path: path.to_path_buf(),
|
|
||||||
name: title.to_string(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let icon_for_completion = if recent {
|
let icon_for_completion = if recent {
|
||||||
IconName::HistoryRerun.path().into()
|
IconName::HistoryRerun.path().into()
|
||||||
|
@ -547,6 +352,251 @@ impl ContextPickerCompletionProvider {
|
||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search(
|
||||||
|
&self,
|
||||||
|
mode: Option<ContextPickerMode>,
|
||||||
|
query: String,
|
||||||
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Vec<Match>> {
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return Task::ready(Vec::default());
|
||||||
|
};
|
||||||
|
match mode {
|
||||||
|
Some(ContextPickerMode::File) => {
|
||||||
|
let search_files_task =
|
||||||
|
search_files(query.clone(), cancellation_flag.clone(), &workspace, cx);
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
search_files_task
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(Match::File)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ContextPickerMode::Symbol) => {
|
||||||
|
let search_symbols_task =
|
||||||
|
search_symbols(query.clone(), cancellation_flag.clone(), &workspace, cx);
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
search_symbols_task
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(Match::Symbol)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ContextPickerMode::Thread) => {
|
||||||
|
let search_threads_task = search_threads(
|
||||||
|
query.clone(),
|
||||||
|
cancellation_flag.clone(),
|
||||||
|
&self.history_store,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
search_threads_task
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(Match::Thread)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ContextPickerMode::Fetch) => {
|
||||||
|
if !query.is_empty() {
|
||||||
|
Task::ready(vec![Match::Fetch(query.into())])
|
||||||
|
} else {
|
||||||
|
Task::ready(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ContextPickerMode::Rules) => {
|
||||||
|
if let Some(prompt_store) = self.prompt_store.as_ref() {
|
||||||
|
let search_rules_task =
|
||||||
|
search_rules(query.clone(), cancellation_flag.clone(), prompt_store, cx);
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
search_rules_task
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(Match::Rules)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Task::ready(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None if query.is_empty() => {
|
||||||
|
let mut matches = self.recent_context_picker_entries(&workspace, cx);
|
||||||
|
|
||||||
|
matches.extend(
|
||||||
|
self.available_context_picker_entries(&workspace, cx)
|
||||||
|
.into_iter()
|
||||||
|
.map(|mode| {
|
||||||
|
Match::Entry(EntryMatch {
|
||||||
|
entry: mode,
|
||||||
|
mat: None,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
Task::ready(matches)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let executor = cx.background_executor().clone();
|
||||||
|
|
||||||
|
let search_files_task =
|
||||||
|
search_files(query.clone(), cancellation_flag.clone(), &workspace, cx);
|
||||||
|
|
||||||
|
let entries = self.available_context_picker_entries(&workspace, cx);
|
||||||
|
let entry_candidates = entries
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(ix, entry)| StringMatchCandidate::new(ix, entry.keyword()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let mut matches = search_files_task
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(Match::File)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let entry_matches = fuzzy::match_strings(
|
||||||
|
&entry_candidates,
|
||||||
|
&query,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
100,
|
||||||
|
&Arc::new(AtomicBool::default()),
|
||||||
|
executor,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
matches.extend(entry_matches.into_iter().map(|mat| {
|
||||||
|
Match::Entry(EntryMatch {
|
||||||
|
entry: entries[mat.candidate_id],
|
||||||
|
mat: Some(mat),
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
matches.sort_by(|a, b| {
|
||||||
|
b.score()
|
||||||
|
.partial_cmp(&a.score())
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
});
|
||||||
|
|
||||||
|
matches
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recent_context_picker_entries(
|
||||||
|
&self,
|
||||||
|
workspace: &Entity<Workspace>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Vec<Match> {
|
||||||
|
let mut recent = Vec::with_capacity(6);
|
||||||
|
|
||||||
|
let mut mentions = self
|
||||||
|
.message_editor
|
||||||
|
.read_with(cx, |message_editor, _cx| message_editor.mentions())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let workspace = workspace.read(cx);
|
||||||
|
let project = workspace.project().read(cx);
|
||||||
|
|
||||||
|
if let Some(agent_panel) = workspace.panel::<AgentPanel>(cx)
|
||||||
|
&& let Some(thread) = agent_panel.read(cx).active_agent_thread(cx)
|
||||||
|
{
|
||||||
|
let thread = thread.read(cx);
|
||||||
|
mentions.insert(MentionUri::Thread {
|
||||||
|
id: thread.session_id().clone(),
|
||||||
|
name: thread.title().into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
recent.extend(
|
||||||
|
workspace
|
||||||
|
.recent_navigation_history_iter(cx)
|
||||||
|
.filter(|(_, abs_path)| {
|
||||||
|
abs_path.as_ref().is_none_or(|path| {
|
||||||
|
!mentions.contains(&MentionUri::File {
|
||||||
|
abs_path: path.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.take(4)
|
||||||
|
.filter_map(|(project_path, _)| {
|
||||||
|
project
|
||||||
|
.worktree_for_id(project_path.worktree_id, cx)
|
||||||
|
.map(|worktree| {
|
||||||
|
let path_prefix = worktree.read(cx).root_name().into();
|
||||||
|
Match::File(FileMatch {
|
||||||
|
mat: fuzzy::PathMatch {
|
||||||
|
score: 1.,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: project_path.worktree_id.to_usize(),
|
||||||
|
path: project_path.path,
|
||||||
|
path_prefix,
|
||||||
|
is_dir: false,
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
},
|
||||||
|
is_recent: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const RECENT_COUNT: usize = 2;
|
||||||
|
let threads = self
|
||||||
|
.history_store
|
||||||
|
.read(cx)
|
||||||
|
.recently_opened_entries(cx)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|thread| !mentions.contains(&thread.mention_uri()))
|
||||||
|
.take(RECENT_COUNT)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
recent.extend(threads.into_iter().map(Match::RecentThread));
|
||||||
|
|
||||||
|
recent
|
||||||
|
}
|
||||||
|
|
||||||
|
fn available_context_picker_entries(
|
||||||
|
&self,
|
||||||
|
workspace: &Entity<Workspace>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Vec<ContextPickerEntry> {
|
||||||
|
let mut entries = vec![
|
||||||
|
ContextPickerEntry::Mode(ContextPickerMode::File),
|
||||||
|
ContextPickerEntry::Mode(ContextPickerMode::Symbol),
|
||||||
|
ContextPickerEntry::Mode(ContextPickerMode::Thread),
|
||||||
|
];
|
||||||
|
|
||||||
|
let has_selection = workspace
|
||||||
|
.read(cx)
|
||||||
|
.active_item(cx)
|
||||||
|
.and_then(|item| item.downcast::<Editor>())
|
||||||
|
.is_some_and(|editor| {
|
||||||
|
editor.update(cx, |editor, cx| editor.has_non_empty_selection(cx))
|
||||||
|
});
|
||||||
|
if has_selection {
|
||||||
|
entries.push(ContextPickerEntry::Action(
|
||||||
|
ContextPickerAction::AddSelections,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.prompt_store.is_some() {
|
||||||
|
entries.push(ContextPickerEntry::Mode(ContextPickerMode::Rules));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.push(ContextPickerEntry::Mode(ContextPickerMode::Fetch));
|
||||||
|
|
||||||
|
entries
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
||||||
|
@ -596,45 +646,12 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
let source_range = snapshot.anchor_before(state.source_range.start)
|
let source_range = snapshot.anchor_before(state.source_range.start)
|
||||||
..snapshot.anchor_after(state.source_range.end);
|
..snapshot.anchor_after(state.source_range.end);
|
||||||
|
|
||||||
let thread_store = self.thread_store.clone();
|
|
||||||
let text_thread_store = self.text_thread_store.clone();
|
|
||||||
let editor = self.message_editor.clone();
|
let editor = self.message_editor.clone();
|
||||||
let Ok((exclude_paths, exclude_threads)) =
|
|
||||||
self.message_editor.update(cx, |message_editor, _cx| {
|
|
||||||
message_editor.mentioned_path_and_threads()
|
|
||||||
})
|
|
||||||
else {
|
|
||||||
return Task::ready(Ok(Vec::new()));
|
|
||||||
};
|
|
||||||
|
|
||||||
let MentionCompletion { mode, argument, .. } = state;
|
let MentionCompletion { mode, argument, .. } = state;
|
||||||
let query = argument.unwrap_or_else(|| "".to_string());
|
let query = argument.unwrap_or_else(|| "".to_string());
|
||||||
|
|
||||||
let recent_entries = recent_context_picker_entries(
|
let search_task = self.search(mode, query, Arc::<AtomicBool>::default(), cx);
|
||||||
Some(thread_store.clone()),
|
|
||||||
Some(text_thread_store.clone()),
|
|
||||||
workspace.clone(),
|
|
||||||
&exclude_paths,
|
|
||||||
&exclude_threads,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_store = thread_store
|
|
||||||
.read_with(cx, |thread_store, _cx| thread_store.prompt_store().clone())
|
|
||||||
.ok()
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
let search_task = search(
|
|
||||||
mode,
|
|
||||||
query,
|
|
||||||
Arc::<AtomicBool>::default(),
|
|
||||||
recent_entries,
|
|
||||||
prompt_store,
|
|
||||||
thread_store.clone(),
|
|
||||||
text_thread_store.clone(),
|
|
||||||
workspace.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
cx.spawn(async move |_, cx| {
|
cx.spawn(async move |_, cx| {
|
||||||
let matches = search_task.await;
|
let matches = search_task.await;
|
||||||
|
@ -669,12 +686,18 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
|
|
||||||
Match::Thread(ThreadMatch {
|
Match::Thread(thread) => Some(Self::completion_for_thread(
|
||||||
thread, is_recent, ..
|
|
||||||
}) => Some(Self::completion_for_thread(
|
|
||||||
thread,
|
thread,
|
||||||
source_range.clone(),
|
source_range.clone(),
|
||||||
is_recent,
|
false,
|
||||||
|
editor.clone(),
|
||||||
|
cx,
|
||||||
|
)),
|
||||||
|
|
||||||
|
Match::RecentThread(thread) => Some(Self::completion_for_thread(
|
||||||
|
thread,
|
||||||
|
source_range.clone(),
|
||||||
|
true,
|
||||||
editor.clone(),
|
editor.clone(),
|
||||||
cx,
|
cx,
|
||||||
)),
|
)),
|
||||||
|
@ -748,6 +771,42 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn search_threads(
|
||||||
|
query: String,
|
||||||
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
|
history_store: &Entity<HistoryStore>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Vec<HistoryEntry>> {
|
||||||
|
let threads = history_store.read(cx).entries(cx);
|
||||||
|
if query.is_empty() {
|
||||||
|
return Task::ready(threads);
|
||||||
|
}
|
||||||
|
|
||||||
|
let executor = cx.background_executor().clone();
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let candidates = threads
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, thread)| StringMatchCandidate::new(id, thread.title()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let matches = fuzzy::match_strings(
|
||||||
|
&candidates,
|
||||||
|
&query,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
100,
|
||||||
|
&cancellation_flag,
|
||||||
|
executor,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
matches
|
||||||
|
.into_iter()
|
||||||
|
.map(|mat| threads[mat.candidate_id].clone())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn confirm_completion_callback(
|
fn confirm_completion_callback(
|
||||||
crease_text: SharedString,
|
crease_text: SharedString,
|
||||||
start: Anchor,
|
start: Anchor,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use acp_thread::{AcpThread, AgentThreadEntry};
|
use acp_thread::{AcpThread, AgentThreadEntry};
|
||||||
use agent::{TextThreadStore, ThreadStore};
|
use agent2::HistoryStore;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{Editor, EditorMode, MinimapVisibility};
|
use editor::{Editor, EditorMode, MinimapVisibility};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -10,6 +10,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::language_settings::SoftWrap;
|
use language::language_settings::SoftWrap;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
use prompt_store::PromptStore;
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use terminal_view::TerminalView;
|
use terminal_view::TerminalView;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
|
@ -21,8 +22,8 @@ use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};
|
||||||
pub struct EntryViewState {
|
pub struct EntryViewState {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
thread_store: Entity<ThreadStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
text_thread_store: Entity<TextThreadStore>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
entries: Vec<Entry>,
|
entries: Vec<Entry>,
|
||||||
prevent_slash_commands: bool,
|
prevent_slash_commands: bool,
|
||||||
}
|
}
|
||||||
|
@ -31,15 +32,15 @@ impl EntryViewState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
thread_store: Entity<ThreadStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
text_thread_store: Entity<TextThreadStore>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
prevent_slash_commands: bool,
|
prevent_slash_commands: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
workspace,
|
workspace,
|
||||||
project,
|
project,
|
||||||
thread_store,
|
history_store,
|
||||||
text_thread_store,
|
prompt_store,
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
prevent_slash_commands,
|
prevent_slash_commands,
|
||||||
}
|
}
|
||||||
|
@ -77,8 +78,8 @@ impl EntryViewState {
|
||||||
let mut editor = MessageEditor::new(
|
let mut editor = MessageEditor::new(
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
self.project.clone(),
|
self.project.clone(),
|
||||||
self.thread_store.clone(),
|
self.history_store.clone(),
|
||||||
self.text_thread_store.clone(),
|
self.prompt_store.clone(),
|
||||||
"Edit message - @ to include context",
|
"Edit message - @ to include context",
|
||||||
self.prevent_slash_commands,
|
self.prevent_slash_commands,
|
||||||
editor::EditorMode::AutoHeight {
|
editor::EditorMode::AutoHeight {
|
||||||
|
@ -313,9 +314,10 @@ mod tests {
|
||||||
use std::{path::Path, rc::Rc};
|
use std::{path::Path, rc::Rc};
|
||||||
|
|
||||||
use acp_thread::{AgentConnection, StubAgentConnection};
|
use acp_thread::{AgentConnection, StubAgentConnection};
|
||||||
use agent::{TextThreadStore, ThreadStore};
|
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use agent_settings::AgentSettings;
|
use agent_settings::AgentSettings;
|
||||||
|
use agent2::HistoryStore;
|
||||||
|
use assistant_context::ContextStore;
|
||||||
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
|
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
|
||||||
use editor::{EditorSettings, RowInfo};
|
use editor::{EditorSettings, RowInfo};
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
|
@ -378,15 +380,15 @@ mod tests {
|
||||||
connection.send_update(session_id, acp::SessionUpdate::ToolCall(tool_call), cx)
|
connection.send_update(session_id, acp::SessionUpdate::ToolCall(tool_call), cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let thread_store = cx.new(|cx| ThreadStore::fake(project.clone(), cx));
|
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||||
|
|
||||||
let view_state = cx.new(|_cx| {
|
let view_state = cx.new(|_cx| {
|
||||||
EntryViewState::new(
|
EntryViewState::new(
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
project.clone(),
|
project.clone(),
|
||||||
thread_store,
|
history_store,
|
||||||
text_thread_store,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,8 +3,9 @@ use crate::{
|
||||||
context_picker::fetch_context_picker::fetch_url_content,
|
context_picker::fetch_context_picker::fetch_url_content,
|
||||||
};
|
};
|
||||||
use acp_thread::{MentionUri, selection_name};
|
use acp_thread::{MentionUri, selection_name};
|
||||||
use agent::{TextThreadStore, ThreadId, ThreadStore};
|
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
|
use agent_servers::AgentServer;
|
||||||
|
use agent2::HistoryStore;
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_slash_commands::codeblock_fence_for_path;
|
use assistant_slash_commands::codeblock_fence_for_path;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
|
@ -27,6 +28,7 @@ use gpui::{
|
||||||
use language::{Buffer, Language};
|
use language::{Buffer, Language};
|
||||||
use language_model::LanguageModelImage;
|
use language_model::LanguageModelImage;
|
||||||
use project::{Project, ProjectPath, Worktree};
|
use project::{Project, ProjectPath, Worktree};
|
||||||
|
use prompt_store::PromptStore;
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -59,8 +61,8 @@ pub struct MessageEditor {
|
||||||
editor: Entity<Editor>,
|
editor: Entity<Editor>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Entity<ThreadStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
text_thread_store: Entity<TextThreadStore>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
prevent_slash_commands: bool,
|
prevent_slash_commands: bool,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
_parse_slash_command_task: Task<()>,
|
_parse_slash_command_task: Task<()>,
|
||||||
|
@ -79,8 +81,8 @@ impl MessageEditor {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
thread_store: Entity<ThreadStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
text_thread_store: Entity<TextThreadStore>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
placeholder: impl Into<Arc<str>>,
|
placeholder: impl Into<Arc<str>>,
|
||||||
prevent_slash_commands: bool,
|
prevent_slash_commands: bool,
|
||||||
mode: EditorMode,
|
mode: EditorMode,
|
||||||
|
@ -95,10 +97,10 @@ impl MessageEditor {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let completion_provider = ContextPickerCompletionProvider::new(
|
let completion_provider = ContextPickerCompletionProvider::new(
|
||||||
workspace.clone(),
|
|
||||||
thread_store.downgrade(),
|
|
||||||
text_thread_store.downgrade(),
|
|
||||||
cx.weak_entity(),
|
cx.weak_entity(),
|
||||||
|
workspace.clone(),
|
||||||
|
history_store.clone(),
|
||||||
|
prompt_store.clone(),
|
||||||
);
|
);
|
||||||
let semantics_provider = Rc::new(SlashCommandSemanticsProvider {
|
let semantics_provider = Rc::new(SlashCommandSemanticsProvider {
|
||||||
range: Cell::new(None),
|
range: Cell::new(None),
|
||||||
|
@ -152,9 +154,9 @@ impl MessageEditor {
|
||||||
editor,
|
editor,
|
||||||
project,
|
project,
|
||||||
mention_set,
|
mention_set,
|
||||||
thread_store,
|
|
||||||
text_thread_store,
|
|
||||||
workspace,
|
workspace,
|
||||||
|
history_store,
|
||||||
|
prompt_store,
|
||||||
prevent_slash_commands,
|
prevent_slash_commands,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
_parse_slash_command_task: Task::ready(()),
|
_parse_slash_command_task: Task::ready(()),
|
||||||
|
@ -175,23 +177,12 @@ impl MessageEditor {
|
||||||
self.editor.read(cx).is_empty(cx)
|
self.editor.read(cx).is_empty(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mentioned_path_and_threads(&self) -> (HashSet<PathBuf>, HashSet<ThreadId>) {
|
pub fn mentions(&self) -> HashSet<MentionUri> {
|
||||||
let mut excluded_paths = HashSet::default();
|
self.mention_set
|
||||||
let mut excluded_threads = HashSet::default();
|
.uri_by_crease_id
|
||||||
|
.values()
|
||||||
for uri in self.mention_set.uri_by_crease_id.values() {
|
.cloned()
|
||||||
match uri {
|
.collect()
|
||||||
MentionUri::File { abs_path, .. } => {
|
|
||||||
excluded_paths.insert(abs_path.clone());
|
|
||||||
}
|
|
||||||
MentionUri::Thread { id, .. } => {
|
|
||||||
excluded_threads.insert(id.clone());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(excluded_paths, excluded_threads)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn confirm_completion(
|
pub fn confirm_completion(
|
||||||
|
@ -529,7 +520,7 @@ impl MessageEditor {
|
||||||
&mut self,
|
&mut self,
|
||||||
crease_id: CreaseId,
|
crease_id: CreaseId,
|
||||||
anchor: Anchor,
|
anchor: Anchor,
|
||||||
id: ThreadId,
|
id: acp::SessionId,
|
||||||
name: String,
|
name: String,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
@ -538,17 +529,25 @@ impl MessageEditor {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
name,
|
name,
|
||||||
};
|
};
|
||||||
let open_task = self.thread_store.update(cx, |thread_store, cx| {
|
let server = Rc::new(agent2::NativeAgentServer::new(
|
||||||
thread_store.open_thread(&id, window, cx)
|
self.project.read(cx).fs().clone(),
|
||||||
|
self.history_store.clone(),
|
||||||
|
));
|
||||||
|
let connection = server.connect(Path::new(""), &self.project, cx);
|
||||||
|
let load_summary = cx.spawn({
|
||||||
|
let id = id.clone();
|
||||||
|
async move |_, cx| {
|
||||||
|
let agent = connection.await?;
|
||||||
|
let agent = agent.downcast::<agent2::NativeAgentConnection>().unwrap();
|
||||||
|
let summary = agent
|
||||||
|
.0
|
||||||
|
.update(cx, |agent, cx| agent.thread_summary(id, cx))?
|
||||||
|
.await?;
|
||||||
|
anyhow::Ok(summary)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
let task = cx
|
let task = cx
|
||||||
.spawn(async move |_, cx| {
|
.spawn(async move |_, _| load_summary.await.map_err(|e| format!("{e}")))
|
||||||
let thread = open_task.await.map_err(|e| e.to_string())?;
|
|
||||||
let content = thread
|
|
||||||
.read_with(cx, |thread, _cx| thread.latest_detailed_summary_or_text())
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
Ok(content)
|
|
||||||
})
|
|
||||||
.shared();
|
.shared();
|
||||||
|
|
||||||
self.mention_set.insert_thread(id.clone(), task.clone());
|
self.mention_set.insert_thread(id.clone(), task.clone());
|
||||||
|
@ -590,8 +589,8 @@ impl MessageEditor {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
name,
|
name,
|
||||||
};
|
};
|
||||||
let context = self.text_thread_store.update(cx, |text_thread_store, cx| {
|
let context = self.history_store.update(cx, |text_thread_store, cx| {
|
||||||
text_thread_store.open_local_context(path.as_path().into(), cx)
|
text_thread_store.load_text_thread(path.as_path().into(), cx)
|
||||||
});
|
});
|
||||||
let task = cx
|
let task = cx
|
||||||
.spawn(async move |_, cx| {
|
.spawn(async move |_, cx| {
|
||||||
|
@ -637,7 +636,7 @@ impl MessageEditor {
|
||||||
) -> Task<Result<Vec<acp::ContentBlock>>> {
|
) -> Task<Result<Vec<acp::ContentBlock>>> {
|
||||||
let contents =
|
let contents =
|
||||||
self.mention_set
|
self.mention_set
|
||||||
.contents(self.project.clone(), self.thread_store.clone(), window, cx);
|
.contents(&self.project, self.prompt_store.as_ref(), window, cx);
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let prevent_slash_commands = self.prevent_slash_commands;
|
let prevent_slash_commands = self.prevent_slash_commands;
|
||||||
|
|
||||||
|
@ -1316,7 +1315,7 @@ pub struct MentionSet {
|
||||||
uri_by_crease_id: HashMap<CreaseId, MentionUri>,
|
uri_by_crease_id: HashMap<CreaseId, MentionUri>,
|
||||||
fetch_results: HashMap<Url, Shared<Task<Result<String, String>>>>,
|
fetch_results: HashMap<Url, Shared<Task<Result<String, String>>>>,
|
||||||
images: HashMap<CreaseId, Shared<Task<Result<MentionImage, String>>>>,
|
images: HashMap<CreaseId, Shared<Task<Result<MentionImage, String>>>>,
|
||||||
thread_summaries: HashMap<ThreadId, Shared<Task<Result<SharedString, String>>>>,
|
thread_summaries: HashMap<acp::SessionId, Shared<Task<Result<SharedString, String>>>>,
|
||||||
text_thread_summaries: HashMap<PathBuf, Shared<Task<Result<String, String>>>>,
|
text_thread_summaries: HashMap<PathBuf, Shared<Task<Result<String, String>>>>,
|
||||||
directories: HashMap<PathBuf, Shared<Task<Result<String, String>>>>,
|
directories: HashMap<PathBuf, Shared<Task<Result<String, String>>>>,
|
||||||
}
|
}
|
||||||
|
@ -1338,7 +1337,11 @@ impl MentionSet {
|
||||||
self.images.insert(crease_id, task);
|
self.images.insert(crease_id, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_thread(&mut self, id: ThreadId, task: Shared<Task<Result<SharedString, String>>>) {
|
fn insert_thread(
|
||||||
|
&mut self,
|
||||||
|
id: acp::SessionId,
|
||||||
|
task: Shared<Task<Result<SharedString, String>>>,
|
||||||
|
) {
|
||||||
self.thread_summaries.insert(id, task);
|
self.thread_summaries.insert(id, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1358,8 +1361,8 @@ impl MentionSet {
|
||||||
|
|
||||||
pub fn contents(
|
pub fn contents(
|
||||||
&self,
|
&self,
|
||||||
project: Entity<Project>,
|
project: &Entity<Project>,
|
||||||
thread_store: Entity<ThreadStore>,
|
prompt_store: Option<&Entity<PromptStore>>,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<HashMap<CreaseId, Mention>>> {
|
) -> Task<Result<HashMap<CreaseId, Mention>>> {
|
||||||
|
@ -1484,8 +1487,7 @@ impl MentionSet {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
MentionUri::Rule { id: prompt_id, .. } => {
|
MentionUri::Rule { id: prompt_id, .. } => {
|
||||||
let Some(prompt_store) = thread_store.read(cx).prompt_store().clone()
|
let Some(prompt_store) = prompt_store else {
|
||||||
else {
|
|
||||||
return Task::ready(Err(anyhow!("missing prompt store")));
|
return Task::ready(Err(anyhow!("missing prompt store")));
|
||||||
};
|
};
|
||||||
let text_task = prompt_store.read(cx).load(*prompt_id, cx);
|
let text_task = prompt_store.read(cx).load(*prompt_id, cx);
|
||||||
|
@ -1678,8 +1680,9 @@ impl Addon for MessageEditorAddon {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{ops::Range, path::Path, sync::Arc};
|
use std::{ops::Range, path::Path, sync::Arc};
|
||||||
|
|
||||||
use agent::{TextThreadStore, ThreadStore};
|
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
|
use agent2::HistoryStore;
|
||||||
|
use assistant_context::ContextStore;
|
||||||
use editor::{AnchorRangeExt as _, Editor, EditorMode};
|
use editor::{AnchorRangeExt as _, Editor, EditorMode};
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use futures::StreamExt as _;
|
use futures::StreamExt as _;
|
||||||
|
@ -1710,16 +1713,16 @@ mod tests {
|
||||||
let (workspace, cx) =
|
let (workspace, cx) =
|
||||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
|
||||||
let thread_store = cx.new(|cx| ThreadStore::fake(project.clone(), cx));
|
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||||
|
|
||||||
let message_editor = cx.update(|window, cx| {
|
let message_editor = cx.update(|window, cx| {
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
project.clone(),
|
project.clone(),
|
||||||
thread_store.clone(),
|
history_store.clone(),
|
||||||
text_thread_store.clone(),
|
None,
|
||||||
"Test",
|
"Test",
|
||||||
false,
|
false,
|
||||||
EditorMode::AutoHeight {
|
EditorMode::AutoHeight {
|
||||||
|
@ -1908,8 +1911,8 @@ mod tests {
|
||||||
opened_editors.push(buffer);
|
opened_editors.push(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
let thread_store = cx.new(|cx| ThreadStore::fake(project.clone(), cx));
|
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||||
|
|
||||||
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
|
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||||
let workspace_handle = cx.weak_entity();
|
let workspace_handle = cx.weak_entity();
|
||||||
|
@ -1917,8 +1920,8 @@ mod tests {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace_handle,
|
workspace_handle,
|
||||||
project.clone(),
|
project.clone(),
|
||||||
thread_store.clone(),
|
history_store.clone(),
|
||||||
text_thread_store.clone(),
|
None,
|
||||||
"Test",
|
"Test",
|
||||||
false,
|
false,
|
||||||
EditorMode::AutoHeight {
|
EditorMode::AutoHeight {
|
||||||
|
@ -2011,12 +2014,9 @@ mod tests {
|
||||||
|
|
||||||
let contents = message_editor
|
let contents = message_editor
|
||||||
.update_in(&mut cx, |message_editor, window, cx| {
|
.update_in(&mut cx, |message_editor, window, cx| {
|
||||||
message_editor.mention_set().contents(
|
message_editor
|
||||||
project.clone(),
|
.mention_set()
|
||||||
thread_store.clone(),
|
.contents(&project, None, window, cx)
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -2066,12 +2066,9 @@ mod tests {
|
||||||
|
|
||||||
let contents = message_editor
|
let contents = message_editor
|
||||||
.update_in(&mut cx, |message_editor, window, cx| {
|
.update_in(&mut cx, |message_editor, window, cx| {
|
||||||
message_editor.mention_set().contents(
|
message_editor
|
||||||
project.clone(),
|
.mention_set()
|
||||||
thread_store.clone(),
|
.contents(&project, None, window, cx)
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -2181,7 +2178,7 @@ mod tests {
|
||||||
.update_in(&mut cx, |message_editor, window, cx| {
|
.update_in(&mut cx, |message_editor, window, cx| {
|
||||||
message_editor
|
message_editor
|
||||||
.mention_set()
|
.mention_set()
|
||||||
.contents(project.clone(), thread_store, window, cx)
|
.contents(&project, None, window, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -5,7 +5,6 @@ use acp_thread::{
|
||||||
};
|
};
|
||||||
use acp_thread::{AgentConnection, Plan};
|
use acp_thread::{AgentConnection, Plan};
|
||||||
use action_log::ActionLog;
|
use action_log::ActionLog;
|
||||||
use agent::{TextThreadStore, ThreadStore};
|
|
||||||
use agent_client_protocol::{self as acp};
|
use agent_client_protocol::{self as acp};
|
||||||
use agent_servers::{AgentServer, ClaudeCode};
|
use agent_servers::{AgentServer, ClaudeCode};
|
||||||
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
|
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
|
||||||
|
@ -32,7 +31,7 @@ use language::Buffer;
|
||||||
use language_model::LanguageModelRegistry;
|
use language_model::LanguageModelRegistry;
|
||||||
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
|
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
|
||||||
use project::{Project, ProjectEntryId};
|
use project::{Project, ProjectEntryId};
|
||||||
use prompt_store::PromptId;
|
use prompt_store::{PromptId, PromptStore};
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
use settings::{Settings as _, SettingsStore};
|
use settings::{Settings as _, SettingsStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -158,8 +157,7 @@ impl AcpThreadView {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
history_store: Entity<HistoryStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
thread_store: Entity<ThreadStore>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
text_thread_store: Entity<TextThreadStore>,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -168,8 +166,8 @@ impl AcpThreadView {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
project.clone(),
|
project.clone(),
|
||||||
thread_store.clone(),
|
history_store.clone(),
|
||||||
text_thread_store.clone(),
|
prompt_store.clone(),
|
||||||
"Message the agent — @ to include context",
|
"Message the agent — @ to include context",
|
||||||
prevent_slash_commands,
|
prevent_slash_commands,
|
||||||
editor::EditorMode::AutoHeight {
|
editor::EditorMode::AutoHeight {
|
||||||
|
@ -187,8 +185,8 @@ impl AcpThreadView {
|
||||||
EntryViewState::new(
|
EntryViewState::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
project.clone(),
|
project.clone(),
|
||||||
thread_store.clone(),
|
history_store.clone(),
|
||||||
text_thread_store.clone(),
|
prompt_store.clone(),
|
||||||
prevent_slash_commands,
|
prevent_slash_commands,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -3201,12 +3199,18 @@ impl AcpThreadView {
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
MentionUri::Thread { id, .. } => {
|
MentionUri::Thread { id, name } => {
|
||||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||||
panel.update(cx, |panel, cx| {
|
panel.update(cx, |panel, cx| {
|
||||||
panel
|
panel.load_agent_thread(
|
||||||
.open_thread_by_id(&id, window, cx)
|
DbThreadMetadata {
|
||||||
.detach_and_log_err(cx)
|
id,
|
||||||
|
title: name.into(),
|
||||||
|
updated_at: Default::default(),
|
||||||
|
},
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4075,7 +4079,6 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use acp_thread::StubAgentConnection;
|
use acp_thread::StubAgentConnection;
|
||||||
use agent::{TextThreadStore, ThreadStore};
|
|
||||||
use agent_client_protocol::SessionId;
|
use agent_client_protocol::SessionId;
|
||||||
use assistant_context::ContextStore;
|
use assistant_context::ContextStore;
|
||||||
use editor::EditorSettings;
|
use editor::EditorSettings;
|
||||||
|
@ -4211,10 +4214,6 @@ pub(crate) mod tests {
|
||||||
let (workspace, cx) =
|
let (workspace, cx) =
|
||||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
|
||||||
let thread_store =
|
|
||||||
cx.update(|_window, cx| cx.new(|cx| ThreadStore::fake(project.clone(), cx)));
|
|
||||||
let text_thread_store =
|
|
||||||
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
|
|
||||||
let context_store =
|
let context_store =
|
||||||
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
|
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
|
||||||
let history_store =
|
let history_store =
|
||||||
|
@ -4228,8 +4227,7 @@ pub(crate) mod tests {
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
project,
|
project,
|
||||||
history_store,
|
history_store,
|
||||||
thread_store.clone(),
|
None,
|
||||||
text_thread_store.clone(),
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -4400,6 +4398,7 @@ pub(crate) mod tests {
|
||||||
ThemeSettings::register(cx);
|
ThemeSettings::register(cx);
|
||||||
release_channel::init(SemanticVersion::default(), cx);
|
release_channel::init(SemanticVersion::default(), cx);
|
||||||
EditorSettings::register(cx);
|
EditorSettings::register(cx);
|
||||||
|
prompt_store::init(cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4420,10 +4419,6 @@ pub(crate) mod tests {
|
||||||
let (workspace, cx) =
|
let (workspace, cx) =
|
||||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
|
||||||
let thread_store =
|
|
||||||
cx.update(|_window, cx| cx.new(|cx| ThreadStore::fake(project.clone(), cx)));
|
|
||||||
let text_thread_store =
|
|
||||||
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
|
|
||||||
let context_store =
|
let context_store =
|
||||||
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
|
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
|
||||||
let history_store =
|
let history_store =
|
||||||
|
@ -4438,8 +4433,7 @@ pub(crate) mod tests {
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
project.clone(),
|
project.clone(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
thread_store.clone(),
|
None,
|
||||||
text_thread_store.clone(),
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use acp_thread::AcpThread;
|
||||||
use agent2::{DbThreadMetadata, HistoryEntry};
|
use agent2::{DbThreadMetadata, HistoryEntry};
|
||||||
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -1016,8 +1017,6 @@ impl AgentPanel {
|
||||||
agent: crate::ExternalAgent,
|
agent: crate::ExternalAgent,
|
||||||
}
|
}
|
||||||
|
|
||||||
let thread_store = self.thread_store.clone();
|
|
||||||
let text_thread_store = self.context_store.clone();
|
|
||||||
let history = self.acp_history_store.clone();
|
let history = self.acp_history_store.clone();
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
@ -1075,8 +1074,7 @@ impl AgentPanel {
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
project,
|
project,
|
||||||
this.acp_history_store.clone(),
|
this.acp_history_store.clone(),
|
||||||
thread_store.clone(),
|
this.prompt_store.clone(),
|
||||||
text_thread_store.clone(),
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -1499,6 +1497,14 @@ impl AgentPanel {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub(crate) fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
|
||||||
|
match &self.active_view {
|
||||||
|
ActiveView::ExternalAgentThread { thread_view, .. } => {
|
||||||
|
thread_view.read(cx).thread().cloned()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn delete_thread(
|
pub(crate) fn delete_thread(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -1816,6 +1822,15 @@ impl AgentPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_agent_thread(
|
||||||
|
&mut self,
|
||||||
|
thread: DbThreadMetadata,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.external_thread(Some(ExternalAgent::NativeAgent), Some(thread), window, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for AgentPanel {
|
impl Focusable for AgentPanel {
|
||||||
|
|
|
@ -905,7 +905,7 @@ impl ContextStore {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(assistant_slash_commands::acceptable_prompt)
|
.filter(assistant_slash_commands::acceptable_prompt)
|
||||||
.map(|prompt| {
|
.map(|prompt| {
|
||||||
log::debug!("registering context server command: {:?}", prompt.name);
|
log::info!("registering context server command: {:?}", prompt.name);
|
||||||
slash_command_working_set.insert(Arc::new(
|
slash_command_working_set.insert(Arc::new(
|
||||||
assistant_slash_commands::ContextServerSlashCommand::new(
|
assistant_slash_commands::ContextServerSlashCommand::new(
|
||||||
context_server_store.clone(),
|
context_server_store.clone(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue