diff --git a/crates/agent2/Cargo.toml b/crates/agent2/Cargo.toml index 88da875930..a32b4fe939 100644 --- a/crates/agent2/Cargo.toml +++ b/crates/agent2/Cargo.toml @@ -66,6 +66,7 @@ assistant_context.workspace = true [dev-dependencies] agent = { workspace = true, "features" = ["test-support"] } +acp_thread = { workspace = true, "features" = ["test-support"] } ctor.workspace = true client = { workspace = true, "features" = ["test-support"] } clock = { workspace = true, "features" = ["test-support"] } diff --git a/crates/agent2/src/agent.rs b/crates/agent2/src/agent.rs index 6de5445d80..82ee339426 100644 --- a/crates/agent2/src/agent.rs +++ b/crates/agent2/src/agent.rs @@ -263,15 +263,20 @@ impl NativeAgent { } fn save_thread(&mut self, thread: Entity, cx: &mut Context) { + dbg!(); let id = thread.read(cx).id().clone(); + dbg!(); let Some(session) = self.sessions.get_mut(&id) else { return; }; + dbg!(); let thread = thread.downgrade(); let thread_database = self.thread_database.clone(); + dbg!(); session.save_task = cx.spawn(async move |this, cx| { cx.background_executor().timer(SAVE_THREAD_DEBOUNCE).await; + dbg!(); let db_thread = thread.update(cx, |thread, cx| thread.to_db(cx))?.await; thread_database.save_thread(id, db_thread).await?; this.update(cx, |this, cx| this.reload_history(cx))?; @@ -1049,12 +1054,15 @@ impl acp_thread::AgentSessionResume for NativeAgentSessionResume { #[cfg(test)] mod tests { + use crate::{HistoryEntry, HistoryStore}; + use super::*; use acp_thread::{AgentConnection, AgentModelGroupName, AgentModelId, AgentModelInfo}; use fs::FakeFs; use gpui::TestAppContext; use serde_json::json; use settings::SettingsStore; + use util::path; #[gpui::test] async fn test_maintaining_project_context(cx: &mut TestAppContext) { @@ -1229,6 +1237,66 @@ mod tests { ); } + #[gpui::test] + async fn test_history(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs.clone(), [], cx).await; + + let agent = NativeAgent::new( + project.clone(), + Templates::new(), + None, + fs.clone(), + &mut cx.to_async(), + ) + .await + .unwrap(); + let model = cx.update(|cx| { + LanguageModelRegistry::global(cx) + .read(cx) + .default_model() + .unwrap() + .model + }); + let connection = NativeAgentConnection(agent.clone()); + let history_store = cx.new(|cx| { + let mut store = HistoryStore::new(cx); + store.register_agent(NATIVE_AGENT_SERVER_NAME.clone(), &connection, cx); + store + }); + + let acp_thread = cx + .update(|cx| { + Rc::new(connection.clone()).new_thread(project.clone(), Path::new(path!("")), cx) + }) + .await + .unwrap(); + let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone()); + let selector = connection.model_selector().unwrap(); + + let model = cx + .update(|cx| selector.selected_model(&session_id, cx)) + .await + .expect("selected_model should succeed"); + let model = cx + .update(|cx| agent.read(cx).models().model_from_id(&model.id)) + .unwrap(); + let model = model.as_fake(); + + let send = acp_thread.update(cx, |thread, cx| thread.send_raw("Hi", cx)); + let send = cx.foreground_executor().spawn(send); + cx.run_until_parked(); + model.send_last_completion_stream_text_chunk("Hey"); + model.end_last_completion_stream(); + dbg!(send.await.unwrap()); + cx.executor().advance_clock(SAVE_THREAD_DEBOUNCE); + + let history = history_store.update(cx, |store, cx| store.entries(cx)); + assert_eq!(history.len(), 1); + assert_eq!(history[0].title(), "Hi"); + } + fn init_test(cx: &mut TestAppContext) { env_logger::try_init().ok(); cx.update(|cx| { diff --git a/crates/agent2/src/agent2.rs b/crates/agent2/src/agent2.rs index eee9810cef..6d1d266ada 100644 --- a/crates/agent2/src/agent2.rs +++ b/crates/agent2/src/agent2.rs @@ -1,6 +1,6 @@ mod agent; mod db; -pub mod history_store; +mod history_store; mod native_agent_server; mod templates; mod thread; @@ -11,6 +11,7 @@ mod tests; pub use agent::*; pub use db::*; +pub use history_store::*; pub use native_agent_server::NativeAgentServer; pub use templates::*; pub use thread::*; diff --git a/crates/agent2/src/db.rs b/crates/agent2/src/db.rs index a7240df5c7..d5d882bcde 100644 --- a/crates/agent2/src/db.rs +++ b/crates/agent2/src/db.rs @@ -386,6 +386,9 @@ impl ThreadsDatabase { #[cfg(test)] mod tests { + use crate::NativeAgent; + use crate::Templates; + use super::*; use agent::MessageSegment; use agent::context::LoadedContext; diff --git a/crates/agent2/src/history_store.rs b/crates/agent2/src/history_store.rs index f4e53c4c23..d7d0ba2874 100644 --- a/crates/agent2/src/history_store.rs +++ b/crates/agent2/src/history_store.rs @@ -13,33 +13,34 @@ 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); +// todo!(put this in the UI) #[derive(Clone, Debug)] pub enum HistoryEntry { - Thread(AcpThreadMetadata), - Context(SavedContextMetadata), + AcpThread(AcpThreadMetadata), + TextThread(SavedContextMetadata), } impl HistoryEntry { pub fn updated_at(&self) -> DateTime { match self { - HistoryEntry::Thread(thread) => thread.updated_at, - HistoryEntry::Context(context) => context.mtime.to_utc(), + HistoryEntry::AcpThread(thread) => thread.updated_at, + HistoryEntry::TextThread(context) => context.mtime.to_utc(), } } pub fn id(&self) -> HistoryEntryId { match self { - HistoryEntry::Thread(thread) => { + HistoryEntry::AcpThread(thread) => { HistoryEntryId::Thread(thread.agent.clone(), thread.id.clone()) } - HistoryEntry::Context(context) => HistoryEntryId::Context(context.path.clone()), + HistoryEntry::TextThread(context) => HistoryEntryId::Context(context.path.clone()), } } pub fn title(&self) -> &SharedString { match self { - HistoryEntry::Thread(thread) => &thread.title, - HistoryEntry::Context(context) => &context.title, + HistoryEntry::AcpThread(thread) => &thread.title, + HistoryEntry::TextThread(context) => &context.title, } } } @@ -107,7 +108,7 @@ impl HistoryStore { self.agents .values_mut() .flat_map(|history| history.entries.borrow().clone().unwrap_or_default()) // todo!("surface the loading state?") - .map(HistoryEntry::Thread), + .map(HistoryEntry::AcpThread), ); // todo!() include the text threads in here. diff --git a/crates/agent2/src/thread.rs b/crates/agent2/src/thread.rs index 7ea5ff7cc6..0d81da7a92 100644 --- a/crates/agent2/src/thread.rs +++ b/crates/agent2/src/thread.rs @@ -1283,6 +1283,7 @@ impl Thread { } self.messages.push(Message::Agent(message)); + dbg!("!!!!!!!!!!!!!!!!!!!!!!!"); cx.notify() } diff --git a/crates/agent_ui/src/acp/thread_history.rs b/crates/agent_ui/src/acp/thread_history.rs index f4750281f9..ed13912682 100644 --- a/crates/agent_ui/src/acp/thread_history.rs +++ b/crates/agent_ui/src/acp/thread_history.rs @@ -236,10 +236,10 @@ impl AcpThreadHistory { for (idx, entry) in all_entries.iter().enumerate() { match entry { - HistoryEntry::Thread(thread) => { + HistoryEntry::AcpThread(thread) => { candidates.push(StringMatchCandidate::new(idx, &thread.title)); } - HistoryEntry::Context(context) => { + HistoryEntry::TextThread(context) => { candidates.push(StringMatchCandidate::new(idx, &context.title)); } } diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 102facadd1..6d1e7eb846 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -6,7 +6,7 @@ use std::time::Duration; use acp_thread::AcpThreadMetadata; use agent_servers::AgentServer; -use agent2::history_store::HistoryEntry; +use agent2::HistoryEntry; use db::kvp::{Dismissable, KEY_VALUE_STORE}; use serde::{Deserialize, Serialize}; @@ -752,7 +752,7 @@ impl AgentPanel { &acp_history, window, |this, _, event, window, cx| match event { - ThreadHistoryEvent::Open(HistoryEntry::Thread(thread)) => { + ThreadHistoryEvent::Open(HistoryEntry::AcpThread(thread)) => { let agent_choice = match thread.agent.0.as_ref() { "Claude Code" => Some(ExternalAgent::ClaudeCode), "Gemini" => Some(ExternalAgent::Gemini), @@ -761,7 +761,7 @@ impl AgentPanel { }; this.new_external_thread(agent_choice, Some(thread.clone()), window, cx); } - ThreadHistoryEvent::Open(HistoryEntry::Context(thread)) => { + ThreadHistoryEvent::Open(HistoryEntry::TextThread(thread)) => { todo!() } }, diff --git a/crates/language_model/src/fake_provider.rs b/crates/language_model/src/fake_provider.rs index a9c7d5c034..d219cb6e35 100644 --- a/crates/language_model/src/fake_provider.rs +++ b/crates/language_model/src/fake_provider.rs @@ -102,6 +102,8 @@ pub struct FakeLanguageModel { impl Default for FakeLanguageModel { fn default() -> Self { + dbg!("default......"); + eprintln!("{}", std::backtrace::Backtrace::force_capture()); Self { provider_id: LanguageModelProviderId::from("fake".to_string()), provider_name: LanguageModelProviderName::from("Fake".to_string()), @@ -149,12 +151,14 @@ impl FakeLanguageModel { } pub fn end_completion_stream(&self, request: &LanguageModelRequest) { + dbg!("remove..."); self.current_completion_txs .lock() .retain(|(req, _)| req != request); } pub fn send_last_completion_stream_text_chunk(&self, chunk: impl Into) { + dbg!("read..."); self.send_completion_stream_text_chunk(self.pending_completions().last().unwrap(), chunk); } @@ -223,6 +227,7 @@ impl LanguageModel for FakeLanguageModel { >, > { let (tx, rx) = mpsc::unbounded(); + dbg!("insert..."); self.current_completion_txs.lock().push((request, tx)); async move { Ok(rx.map(Ok).boxed()) }.boxed() }