History mostly working
Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
parent
4b1a48e4de
commit
fc076e84ca
9 changed files with 95 additions and 15 deletions
|
@ -66,6 +66,7 @@ assistant_context.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
agent = { workspace = true, "features" = ["test-support"] }
|
agent = { workspace = true, "features" = ["test-support"] }
|
||||||
|
acp_thread = { workspace = true, "features" = ["test-support"] }
|
||||||
ctor.workspace = true
|
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"] }
|
||||||
|
|
|
@ -263,15 +263,20 @@ impl NativeAgent {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_thread(&mut self, thread: Entity<Thread>, cx: &mut Context<Self>) {
|
fn save_thread(&mut self, thread: Entity<Thread>, cx: &mut Context<Self>) {
|
||||||
|
dbg!();
|
||||||
let id = thread.read(cx).id().clone();
|
let id = thread.read(cx).id().clone();
|
||||||
|
dbg!();
|
||||||
let Some(session) = self.sessions.get_mut(&id) else {
|
let Some(session) = self.sessions.get_mut(&id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
dbg!();
|
||||||
|
|
||||||
let thread = thread.downgrade();
|
let thread = thread.downgrade();
|
||||||
let thread_database = self.thread_database.clone();
|
let thread_database = self.thread_database.clone();
|
||||||
|
dbg!();
|
||||||
session.save_task = cx.spawn(async move |this, cx| {
|
session.save_task = cx.spawn(async move |this, cx| {
|
||||||
cx.background_executor().timer(SAVE_THREAD_DEBOUNCE).await;
|
cx.background_executor().timer(SAVE_THREAD_DEBOUNCE).await;
|
||||||
|
dbg!();
|
||||||
let db_thread = thread.update(cx, |thread, cx| thread.to_db(cx))?.await;
|
let db_thread = thread.update(cx, |thread, cx| thread.to_db(cx))?.await;
|
||||||
thread_database.save_thread(id, db_thread).await?;
|
thread_database.save_thread(id, db_thread).await?;
|
||||||
this.update(cx, |this, cx| this.reload_history(cx))?;
|
this.update(cx, |this, cx| this.reload_history(cx))?;
|
||||||
|
@ -1049,12 +1054,15 @@ impl acp_thread::AgentSessionResume for NativeAgentSessionResume {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::{HistoryEntry, HistoryStore};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use acp_thread::{AgentConnection, AgentModelGroupName, AgentModelId, AgentModelInfo};
|
use acp_thread::{AgentConnection, AgentModelGroupName, AgentModelId, AgentModelInfo};
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
|
use util::path;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_maintaining_project_context(cx: &mut TestAppContext) {
|
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) {
|
fn init_test(cx: &mut TestAppContext) {
|
||||||
env_logger::try_init().ok();
|
env_logger::try_init().ok();
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod agent;
|
mod agent;
|
||||||
mod db;
|
mod db;
|
||||||
pub mod history_store;
|
mod history_store;
|
||||||
mod native_agent_server;
|
mod native_agent_server;
|
||||||
mod templates;
|
mod templates;
|
||||||
mod thread;
|
mod thread;
|
||||||
|
@ -11,6 +11,7 @@ mod tests;
|
||||||
|
|
||||||
pub use agent::*;
|
pub use agent::*;
|
||||||
pub use db::*;
|
pub use db::*;
|
||||||
|
pub use history_store::*;
|
||||||
pub use native_agent_server::NativeAgentServer;
|
pub use native_agent_server::NativeAgentServer;
|
||||||
pub use templates::*;
|
pub use templates::*;
|
||||||
pub use thread::*;
|
pub use thread::*;
|
||||||
|
|
|
@ -386,6 +386,9 @@ impl ThreadsDatabase {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::NativeAgent;
|
||||||
|
use crate::Templates;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use agent::MessageSegment;
|
use agent::MessageSegment;
|
||||||
use agent::context::LoadedContext;
|
use agent::context::LoadedContext;
|
||||||
|
|
|
@ -13,33 +13,34 @@ const MAX_RECENTLY_OPENED_ENTRIES: usize = 6;
|
||||||
const NAVIGATION_HISTORY_PATH: &str = "agent-navigation-history.json";
|
const NAVIGATION_HISTORY_PATH: &str = "agent-navigation-history.json";
|
||||||
const SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE: Duration = Duration::from_millis(50);
|
const SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
|
// todo!(put this in the UI)
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum HistoryEntry {
|
pub enum HistoryEntry {
|
||||||
Thread(AcpThreadMetadata),
|
AcpThread(AcpThreadMetadata),
|
||||||
Context(SavedContextMetadata),
|
TextThread(SavedContextMetadata),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistoryEntry {
|
impl HistoryEntry {
|
||||||
pub fn updated_at(&self) -> DateTime<Utc> {
|
pub fn updated_at(&self) -> DateTime<Utc> {
|
||||||
match self {
|
match self {
|
||||||
HistoryEntry::Thread(thread) => thread.updated_at,
|
HistoryEntry::AcpThread(thread) => thread.updated_at,
|
||||||
HistoryEntry::Context(context) => context.mtime.to_utc(),
|
HistoryEntry::TextThread(context) => context.mtime.to_utc(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> HistoryEntryId {
|
pub fn id(&self) -> HistoryEntryId {
|
||||||
match self {
|
match self {
|
||||||
HistoryEntry::Thread(thread) => {
|
HistoryEntry::AcpThread(thread) => {
|
||||||
HistoryEntryId::Thread(thread.agent.clone(), thread.id.clone())
|
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 {
|
pub fn title(&self) -> &SharedString {
|
||||||
match self {
|
match self {
|
||||||
HistoryEntry::Thread(thread) => &thread.title,
|
HistoryEntry::AcpThread(thread) => &thread.title,
|
||||||
HistoryEntry::Context(context) => &context.title,
|
HistoryEntry::TextThread(context) => &context.title,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +108,7 @@ impl HistoryStore {
|
||||||
self.agents
|
self.agents
|
||||||
.values_mut()
|
.values_mut()
|
||||||
.flat_map(|history| history.entries.borrow().clone().unwrap_or_default()) // todo!("surface the loading state?")
|
.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.
|
// todo!() include the text threads in here.
|
||||||
|
|
||||||
|
|
|
@ -1283,6 +1283,7 @@ impl Thread {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.messages.push(Message::Agent(message));
|
self.messages.push(Message::Agent(message));
|
||||||
|
dbg!("!!!!!!!!!!!!!!!!!!!!!!!");
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -236,10 +236,10 @@ impl AcpThreadHistory {
|
||||||
|
|
||||||
for (idx, entry) in all_entries.iter().enumerate() {
|
for (idx, entry) in all_entries.iter().enumerate() {
|
||||||
match entry {
|
match entry {
|
||||||
HistoryEntry::Thread(thread) => {
|
HistoryEntry::AcpThread(thread) => {
|
||||||
candidates.push(StringMatchCandidate::new(idx, &thread.title));
|
candidates.push(StringMatchCandidate::new(idx, &thread.title));
|
||||||
}
|
}
|
||||||
HistoryEntry::Context(context) => {
|
HistoryEntry::TextThread(context) => {
|
||||||
candidates.push(StringMatchCandidate::new(idx, &context.title));
|
candidates.push(StringMatchCandidate::new(idx, &context.title));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::time::Duration;
|
||||||
|
|
||||||
use acp_thread::AcpThreadMetadata;
|
use acp_thread::AcpThreadMetadata;
|
||||||
use agent_servers::AgentServer;
|
use agent_servers::AgentServer;
|
||||||
use agent2::history_store::HistoryEntry;
|
use agent2::HistoryEntry;
|
||||||
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -752,7 +752,7 @@ impl AgentPanel {
|
||||||
&acp_history,
|
&acp_history,
|
||||||
window,
|
window,
|
||||||
|this, _, event, window, cx| match event {
|
|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() {
|
let agent_choice = match thread.agent.0.as_ref() {
|
||||||
"Claude Code" => Some(ExternalAgent::ClaudeCode),
|
"Claude Code" => Some(ExternalAgent::ClaudeCode),
|
||||||
"Gemini" => Some(ExternalAgent::Gemini),
|
"Gemini" => Some(ExternalAgent::Gemini),
|
||||||
|
@ -761,7 +761,7 @@ impl AgentPanel {
|
||||||
};
|
};
|
||||||
this.new_external_thread(agent_choice, Some(thread.clone()), window, cx);
|
this.new_external_thread(agent_choice, Some(thread.clone()), window, cx);
|
||||||
}
|
}
|
||||||
ThreadHistoryEvent::Open(HistoryEntry::Context(thread)) => {
|
ThreadHistoryEvent::Open(HistoryEntry::TextThread(thread)) => {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -102,6 +102,8 @@ pub struct FakeLanguageModel {
|
||||||
|
|
||||||
impl Default for FakeLanguageModel {
|
impl Default for FakeLanguageModel {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
dbg!("default......");
|
||||||
|
eprintln!("{}", std::backtrace::Backtrace::force_capture());
|
||||||
Self {
|
Self {
|
||||||
provider_id: LanguageModelProviderId::from("fake".to_string()),
|
provider_id: LanguageModelProviderId::from("fake".to_string()),
|
||||||
provider_name: LanguageModelProviderName::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) {
|
pub fn end_completion_stream(&self, request: &LanguageModelRequest) {
|
||||||
|
dbg!("remove...");
|
||||||
self.current_completion_txs
|
self.current_completion_txs
|
||||||
.lock()
|
.lock()
|
||||||
.retain(|(req, _)| req != request);
|
.retain(|(req, _)| req != request);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_last_completion_stream_text_chunk(&self, chunk: impl Into<String>) {
|
pub fn send_last_completion_stream_text_chunk(&self, chunk: impl Into<String>) {
|
||||||
|
dbg!("read...");
|
||||||
self.send_completion_stream_text_chunk(self.pending_completions().last().unwrap(), chunk);
|
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();
|
let (tx, rx) = mpsc::unbounded();
|
||||||
|
dbg!("insert...");
|
||||||
self.current_completion_txs.lock().push((request, tx));
|
self.current_completion_txs.lock().push((request, tx));
|
||||||
async move { Ok(rx.map(Ok).boxed()) }.boxed()
|
async move { Ok(rx.map(Ok).boxed()) }.boxed()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue