Cargo.lock 🔗
@@ -161,7 +161,6 @@ dependencies = [
"http_client",
"icons",
"indoc",
- "itertools 0.14.0",
"language",
"language_model",
"log",
Bennet Fenner created
Release Notes:
- N/A
Cargo.lock | 1
crates/agent/Cargo.toml | 1
crates/agent/src/agent.rs | 1
crates/agent/src/history_store.rs | 253 --------------------------------
crates/agent_ui/src/agent_panel.rs | 53 +-----
5 files changed, 11 insertions(+), 298 deletions(-)
@@ -161,7 +161,6 @@ dependencies = [
"http_client",
"icons",
"indoc",
- "itertools 0.14.0",
"language",
"language_model",
"log",
@@ -39,7 +39,6 @@ heed.workspace = true
http_client.workspace = true
icons.workspace = true
indoc.workspace = true
-itertools.workspace = true
language.workspace = true
language_model.workspace = true
log.workspace = true
@@ -2,7 +2,6 @@ pub mod agent_profile;
pub mod context;
pub mod context_server_tool;
pub mod context_store;
-pub mod history_store;
pub mod thread;
pub mod thread_store;
pub mod tool_use;
@@ -1,253 +0,0 @@
-use crate::{ThreadId, thread_store::SerializedThreadMetadata};
-use anyhow::{Context as _, Result};
-use assistant_context::SavedContextMetadata;
-use chrono::{DateTime, Utc};
-use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
-use itertools::Itertools;
-use paths::contexts_dir;
-use serde::{Deserialize, Serialize};
-use std::{collections::VecDeque, path::Path, sync::Arc, time::Duration};
-use util::ResultExt as _;
-
-const MAX_RECENTLY_OPENED_ENTRIES: usize = 6;
-const NAVIGATION_HISTORY_PATH: &str = "agent-navigation-history.json";
-const SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE: Duration = Duration::from_millis(50);
-
-#[derive(Clone, Debug)]
-pub enum HistoryEntry {
- Thread(SerializedThreadMetadata),
- Context(SavedContextMetadata),
-}
-
-impl HistoryEntry {
- pub fn updated_at(&self) -> DateTime<Utc> {
- match self {
- HistoryEntry::Thread(thread) => thread.updated_at,
- HistoryEntry::Context(context) => context.mtime.to_utc(),
- }
- }
-
- pub fn id(&self) -> HistoryEntryId {
- match self {
- HistoryEntry::Thread(thread) => HistoryEntryId::Thread(thread.id.clone()),
- HistoryEntry::Context(context) => HistoryEntryId::Context(context.path.clone()),
- }
- }
-
- pub fn title(&self) -> &SharedString {
- match self {
- HistoryEntry::Thread(thread) => &thread.summary,
- HistoryEntry::Context(context) => &context.title,
- }
- }
-}
-
-/// Generic identifier for a history entry.
-#[derive(Clone, PartialEq, Eq, Debug)]
-pub enum HistoryEntryId {
- Thread(ThreadId),
- Context(Arc<Path>),
-}
-
-#[derive(Serialize, Deserialize)]
-enum SerializedRecentOpen {
- Thread(String),
- ContextName(String),
- /// Old format which stores the full path
- Context(String),
-}
-
-pub struct HistoryStore {
- context_store: Entity<assistant_context::ContextStore>,
- recently_opened_entries: VecDeque<HistoryEntryId>,
- _subscriptions: Vec<gpui::Subscription>,
- _save_recently_opened_entries_task: Task<()>,
-}
-
-impl HistoryStore {
- pub fn new(
- context_store: Entity<assistant_context::ContextStore>,
- initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>,
- cx: &mut Context<Self>,
- ) -> Self {
- let subscriptions = vec![cx.observe(&context_store, |_, _, cx| cx.notify())];
-
- cx.spawn(async move |this, cx| {
- let entries = Self::load_recently_opened_entries(cx).await.log_err()?;
- this.update(cx, |this, _| {
- this.recently_opened_entries
- .extend(
- entries.into_iter().take(
- MAX_RECENTLY_OPENED_ENTRIES
- .saturating_sub(this.recently_opened_entries.len()),
- ),
- );
- })
- .ok()
- })
- .detach();
-
- Self {
- context_store,
- recently_opened_entries: initial_recent_entries.into_iter().collect(),
- _subscriptions: subscriptions,
- _save_recently_opened_entries_task: Task::ready(()),
- }
- }
-
- pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
- let mut history_entries = Vec::new();
-
- #[cfg(debug_assertions)]
- if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
- return history_entries;
- }
-
- history_entries.extend(
- self.context_store
- .read(cx)
- .unordered_contexts()
- .cloned()
- .map(HistoryEntry::Context),
- );
-
- history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
- history_entries
- }
-
- 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> {
- #[cfg(debug_assertions)]
- if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
- return Vec::new();
- }
-
- let context_entries =
- self.context_store
- .read(cx)
- .unordered_contexts()
- .flat_map(|context| {
- self.recently_opened_entries
- .iter()
- .enumerate()
- .flat_map(|(index, entry)| match entry {
- HistoryEntryId::Context(path) if &context.path == path => {
- Some((index, HistoryEntry::Context(context.clone())))
- }
- _ => None,
- })
- });
-
- context_entries
- // optimization to halt iteration early
- .take(self.recently_opened_entries.len())
- .sorted_unstable_by_key(|(index, _)| *index)
- .map(|(_, entry)| entry)
- .collect()
- }
-
- fn save_recently_opened_entries(&mut self, cx: &mut Context<Self>) {
- let serialized_entries = self
- .recently_opened_entries
- .iter()
- .filter_map(|entry| match entry {
- HistoryEntryId::Context(path) => path.file_name().map(|file| {
- SerializedRecentOpen::ContextName(file.to_string_lossy().into_owned())
- }),
- HistoryEntryId::Thread(id) => Some(SerializedRecentOpen::Thread(id.to_string())),
- })
- .collect::<Vec<_>>();
-
- self._save_recently_opened_entries_task = cx.spawn(async move |_, cx| {
- cx.background_executor()
- .timer(SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE)
- .await;
- cx.background_spawn(async move {
- let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
- let content = serde_json::to_string(&serialized_entries)?;
- std::fs::write(path, content)?;
- anyhow::Ok(())
- })
- .await
- .log_err();
- });
- }
-
- fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<Vec<HistoryEntryId>>> {
- cx.background_spawn(async move {
- let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
- let contents = match smol::fs::read_to_string(path).await {
- Ok(it) => it,
- Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
- return Ok(Vec::new());
- }
- Err(e) => {
- return Err(e)
- .context("deserializing persisted agent panel navigation history");
- }
- };
- let entries = serde_json::from_str::<Vec<SerializedRecentOpen>>(&contents)
- .context("deserializing persisted agent panel navigation history")?
- .into_iter()
- .take(MAX_RECENTLY_OPENED_ENTRIES)
- .flat_map(|entry| match entry {
- SerializedRecentOpen::Thread(id) => {
- Some(HistoryEntryId::Thread(id.as_str().into()))
- }
- SerializedRecentOpen::ContextName(file_name) => Some(HistoryEntryId::Context(
- contexts_dir().join(file_name).into(),
- )),
- SerializedRecentOpen::Context(path) => {
- Path::new(&path).file_name().map(|file_name| {
- HistoryEntryId::Context(contexts_dir().join(file_name).into())
- })
- }
- })
- .collect::<Vec<_>>();
- Ok(entries)
- })
- }
-
- pub fn push_recently_opened_entry(&mut self, entry: HistoryEntryId, cx: &mut Context<Self>) {
- self.recently_opened_entries
- .retain(|old_entry| old_entry != &entry);
- self.recently_opened_entries.push_front(entry);
- self.recently_opened_entries
- .truncate(MAX_RECENTLY_OPENED_ENTRIES);
- self.save_recently_opened_entries(cx);
- }
-
- pub fn remove_recently_opened_thread(&mut self, id: ThreadId, cx: &mut Context<Self>) {
- self.recently_opened_entries.retain(
- |entry| !matches!(entry, HistoryEntryId::Thread(thread_id) if thread_id == &id),
- );
- self.save_recently_opened_entries(cx);
- }
-
- pub fn replace_recently_opened_text_thread(
- &mut self,
- old_path: &Path,
- new_path: &Arc<Path>,
- cx: &mut Context<Self>,
- ) {
- for entry in &mut self.recently_opened_entries {
- match entry {
- HistoryEntryId::Context(path) if path.as_ref() == old_path => {
- *entry = HistoryEntryId::Context(new_path.clone());
- break;
- }
- _ => {}
- }
- }
- self.save_recently_opened_entries(cx);
- }
-
- pub fn remove_recently_opened_entry(&mut self, entry: &HistoryEntryId, cx: &mut Context<Self>) {
- self.recently_opened_entries
- .retain(|old_entry| old_entry != entry);
- self.save_recently_opened_entries(cx);
- }
-}
@@ -34,7 +34,6 @@ use crate::{
};
use agent::{
context_store::ContextStore,
- history_store::{HistoryEntryId, HistoryStore},
thread_store::{TextThreadStore, ThreadStore},
};
use agent_settings::AgentSettings;
@@ -309,7 +308,6 @@ impl ActiveView {
pub fn prompt_editor(
context_editor: Entity<TextThreadEditor>,
- history_store: Entity<HistoryStore>,
acp_history_store: Entity<agent2::HistoryStore>,
language_registry: Arc<LanguageRegistry>,
window: &mut Window,
@@ -377,18 +375,6 @@ impl ActiveView {
})
}
ContextEvent::PathChanged { old_path, new_path } => {
- history_store.update(cx, |history_store, cx| {
- if let Some(old_path) = old_path {
- history_store
- .replace_recently_opened_text_thread(old_path, new_path, cx);
- } else {
- history_store.push_recently_opened_entry(
- HistoryEntryId::Context(new_path.clone()),
- cx,
- );
- }
- });
-
acp_history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
@@ -430,7 +416,7 @@ pub struct AgentPanel {
language_registry: Arc<LanguageRegistry>,
thread_store: Entity<ThreadStore>,
acp_history: Entity<AcpThreadHistory>,
- acp_history_store: Entity<agent2::HistoryStore>,
+ history_store: Entity<agent2::HistoryStore>,
context_store: Entity<TextThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
inline_assist_context_store: Entity<ContextStore>,
@@ -438,7 +424,6 @@ pub struct AgentPanel {
configuration_subscription: Option<Subscription>,
active_view: ActiveView,
previous_view: Option<ActiveView>,
- history_store: Entity<HistoryStore>,
new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
@@ -571,10 +556,8 @@ impl AgentPanel {
let inline_assist_context_store =
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
- let history_store = cx.new(|cx| HistoryStore::new(context_store.clone(), [], cx));
-
- let acp_history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx));
- let acp_history = cx.new(|cx| AcpThreadHistory::new(acp_history_store.clone(), window, cx));
+ let history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx));
+ let acp_history = cx.new(|cx| AcpThreadHistory::new(history_store.clone(), window, cx));
cx.subscribe_in(
&acp_history,
window,
@@ -596,14 +579,12 @@ impl AgentPanel {
)
.detach();
- cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
-
let panel_type = AgentSettings::get_global(cx).default_view;
let active_view = match panel_type {
DefaultView::Thread => ActiveView::native_agent(
fs.clone(),
prompt_store.clone(),
- acp_history_store.clone(),
+ history_store.clone(),
project.clone(),
workspace.clone(),
window,
@@ -629,7 +610,6 @@ impl AgentPanel {
ActiveView::prompt_editor(
context_editor,
history_store.clone(),
- acp_history_store.clone(),
language_registry.clone(),
window,
cx,
@@ -695,7 +675,6 @@ impl AgentPanel {
configuration_subscription: None,
inline_assist_context_store,
previous_view: None,
- history_store: history_store.clone(),
new_thread_menu_handle: PopoverMenuHandle::default(),
agent_panel_menu_handle: PopoverMenuHandle::default(),
assistant_navigation_menu_handle: PopoverMenuHandle::default(),
@@ -706,7 +685,7 @@ impl AgentPanel {
pending_serialization: None,
onboarding,
acp_history,
- acp_history_store,
+ history_store,
selected_agent: AgentType::default(),
loading: false,
}
@@ -760,7 +739,7 @@ impl AgentPanel {
cx: &mut Context<Self>,
) {
let Some(thread) = self
- .acp_history_store
+ .history_store
.read(cx)
.thread_from_session_id(&action.from_session_id)
else {
@@ -809,7 +788,6 @@ impl AgentPanel {
ActiveView::prompt_editor(
context_editor.clone(),
self.history_store.clone(),
- self.acp_history_store.clone(),
self.language_registry.clone(),
window,
cx,
@@ -841,7 +819,7 @@ impl AgentPanel {
}
let loading = self.loading;
- let history = self.acp_history_store.clone();
+ let history = self.history_store.clone();
cx.spawn_in(window, async move |this, cx| {
let ext_agent = match agent_choice {
@@ -902,7 +880,7 @@ impl AgentPanel {
summarize_thread,
workspace.clone(),
project,
- this.acp_history_store.clone(),
+ this.history_store.clone(),
this.prompt_store.clone(),
window,
cx,
@@ -1000,7 +978,6 @@ impl AgentPanel {
ActiveView::prompt_editor(
editor,
self.history_store.clone(),
- self.acp_history_store.clone(),
self.language_registry.clone(),
window,
cx,
@@ -1264,11 +1241,6 @@ impl AgentPanel {
match &new_view {
ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| {
- if let Some(path) = context_editor.read(cx).context().read(cx).path() {
- store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
- }
- });
- self.acp_history_store.update(cx, |store, cx| {
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
store.push_recently_opened_entry(
agent2::HistoryEntryId::TextThread(path.clone()),
@@ -1302,7 +1274,7 @@ impl AgentPanel {
) -> ContextMenu {
let entries = panel
.read(cx)
- .acp_history_store
+ .history_store
.read(cx)
.recently_opened_entries(cx);
@@ -1347,7 +1319,7 @@ impl AgentPanel {
move |_window, cx| {
panel
.update(cx, |this, cx| {
- this.acp_history_store.update(cx, |history_store, cx| {
+ this.history_store.update(cx, |history_store, cx| {
history_store.remove_recently_opened_entry(&id, cx);
});
})
@@ -2178,10 +2150,7 @@ impl AgentPanel {
false
}
_ => {
- let history_is_empty = self.acp_history_store.read(cx).is_empty(cx)
- && self
- .history_store
- .update(cx, |store, cx| store.recent_entries(1, cx).is_empty());
+ let history_is_empty = self.history_store.read(cx).is_empty(cx);
let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
.providers()