This commit is contained in:
Conrad Irwin 2025-08-15 14:46:52 -06:00
parent fd8ea2acfc
commit 251baacdab
12 changed files with 75 additions and 33 deletions

1
Cargo.lock generated
View file

@ -11,6 +11,7 @@ dependencies = [
"agent-client-protocol",
"anyhow",
"buffer_diff",
"chrono",
"collections",
"editor",
"env_logger 0.11.8",

View file

@ -21,6 +21,7 @@ agent-client-protocol.workspace = true
agent.workspace = true
anyhow.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
collections.workspace = true
editor.workspace = true
file_icons.workspace = true

View file

@ -6,11 +6,13 @@ mod terminal;
pub use connection::*;
pub use diff::*;
pub use mention::*;
use serde::{Deserialize, Serialize};
pub use terminal::*;
use action_log::ActionLog;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
use chrono::{DateTime, Utc};
use editor::Bias;
use futures::{FutureExt, channel::oneshot, future::BoxFuture};
use gpui::{AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};
@ -632,6 +634,13 @@ impl PlanEntry {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AcpThreadMetadata {
pub id: acp::SessionId,
pub title: SharedString,
pub updated_at: DateTime<Utc>,
}
pub struct AcpThread {
title: SharedString,
entries: Vec<AgentThreadEntry>,
@ -1608,7 +1617,7 @@ mod tests {
use super::*;
use anyhow::anyhow;
use futures::{channel::mpsc, future::LocalBoxFuture, select};
use gpui::{AsyncApp, TestAppContext, WeakEntity};
use gpui::{App, AsyncApp, TestAppContext, WeakEntity};
use indoc::indoc;
use project::{FakeFs, Fs};
use rand::Rng as _;
@ -2284,7 +2293,7 @@ mod tests {
self: Rc<Self>,
project: Entity<Project>,
_cwd: &Path,
cx: &mut gpui::App,
cx: &mut App,
) -> Task<gpui::Result<Entity<AcpThread>>> {
let session_id = acp::SessionId(
rand::thread_rng()
@ -2300,6 +2309,10 @@ mod tests {
Task::ready(Ok(thread))
}
fn list_threads(&self, _: &mut App) -> Task<Result<Vec<AcpThreadMetadata>>> {
unimplemented!()
}
fn authenticate(&self, method: acp::AuthMethodId, _cx: &mut App) -> Task<gpui::Result<()>> {
if self.auth_methods().iter().any(|m| m.id == method) {
Task::ready(Ok(()))

View file

@ -1,4 +1,4 @@
use crate::AcpThread;
use crate::{AcpThread, AcpThreadMetadata};
use agent_client_protocol::{self as acp};
use anyhow::Result;
use collections::IndexMap;
@ -26,6 +26,8 @@ pub trait AgentConnection {
cx: &mut App,
) -> Task<Result<Entity<AcpThread>>>;
fn list_threads(&self, _cx: &mut App) -> Task<Result<Vec<AcpThreadMetadata>>>;
fn auth_methods(&self) -> &[acp::AuthMethod];
fn authenticate(&self, method: acp::AuthMethodId, cx: &mut App) -> Task<Result<()>>;
@ -264,6 +266,10 @@ mod test_support {
unimplemented!()
}
fn list_threads(&self, _: &mut App) -> Task<Result<Vec<AcpThreadMetadata>>> {
unimplemented!()
}
fn prompt(
&self,
_id: Option<UserMessageId>,

View file

@ -1,16 +1,18 @@
use crate::ThreadsDatabase;
use crate::{
AgentResponseEvent, ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DeletePathTool,
DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool, ListDirectoryTool,
MovePathTool, NowTool, OpenTool, ReadFileTool, TerminalTool, ThinkingTool, Thread,
ToolCallAuthorization, UserMessageContent, WebSearchTool, templates::Templates,
};
use acp_thread::AgentModelSelector;
use acp_thread::{AcpThreadMetadata, AgentModelSelector};
use agent_client_protocol as acp;
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result, anyhow};
use collections::{HashSet, IndexMap};
use fs::Fs;
use futures::channel::mpsc;
use futures::future::Shared;
use futures::{StreamExt, future};
use gpui::{
App, AppContext, AsyncApp, Context, Entity, SharedString, Subscription, Task, WeakEntity,
@ -166,6 +168,7 @@ pub struct NativeAgent {
models: LanguageModels,
project: Entity<Project>,
prompt_store: Option<Entity<PromptStore>>,
thread_database: Shared<Task<Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>>,
fs: Arc<dyn Fs>,
_subscriptions: Vec<Subscription>,
}
@ -208,6 +211,7 @@ impl NativeAgent {
context_server_registry: cx.new(|cx| {
ContextServerRegistry::new(project.read(cx).context_server_store(), cx)
}),
thread_database: ThreadsDatabase::connect(cx),
templates,
models: LanguageModels::new(cx),
project,
@ -751,6 +755,23 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
Task::ready(Ok(()))
}
fn list_threads(&self, cx: &mut App) -> Task<Result<Vec<AcpThreadMetadata>>> {
let database = self.0.read(cx).thread_database.clone();
cx.background_executor().spawn(async move {
let database = database.await.map_err(|e| anyhow!(e))?;
let results = database.list_threads().await?;
Ok(results
.into_iter()
.map(|thread| AcpThreadMetadata {
id: thread.id,
title: thread.title,
updated_at: thread.updated_at,
})
.collect())
})
}
fn model_selector(&self) -> Option<Rc<dyn AgentModelSelector>> {
Some(Rc::new(self.clone()) as Rc<dyn AgentModelSelector>)
}

View file

@ -216,10 +216,6 @@ impl Column for DataType {
}
}
struct GlobalThreadsDatabase(Shared<Task<Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>>);
impl Global for GlobalThreadsDatabase {}
pub(crate) struct ThreadsDatabase {
executor: BackgroundExecutor,
connection: Arc<Mutex<Connection>>,
@ -234,34 +230,26 @@ impl ThreadsDatabase {
}
impl ThreadsDatabase {
fn global_future(
cx: &mut App,
) -> Shared<Task<Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>> {
GlobalThreadsDatabase::global(cx).0.clone()
}
fn init(cx: &mut App) {
pub fn connect(cx: &mut App) -> Shared<Task<Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>> {
let executor = cx.background_executor().clone();
let database_future = executor
executor
.spawn({
let executor = executor.clone();
let threads_dir = paths::data_dir().join("threads");
async move {
match ThreadsDatabase::new(threads_dir, executor) {
match ThreadsDatabase::new(executor) {
Ok(db) => Ok(Arc::new(db)),
Err(err) => Err(Arc::new(err)),
}
}
})
.shared();
cx.set_global(GlobalThreadsDatabase(database_future));
.shared()
}
pub fn new(threads_dir: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
pub fn new(executor: BackgroundExecutor) -> Result<Self> {
let connection = if *ZED_STATELESS || cfg!(any(feature = "test-support", test)) {
Connection::open_memory(Some("THREAD_FALLBACK_DB"))
} else {
let threads_dir = paths::data_dir().join("threads");
std::fs::create_dir_all(&threads_dir)?;
let sqlite_path = threads_dir.join("threads.db");
Connection::open_file(&sqlite_path.to_string_lossy())
@ -397,7 +385,6 @@ mod tests {
use gpui::TestAppContext;
use http_client::FakeHttpClient;
use language_model::Role;
use pretty_assertions::assert_matches;
use project::Project;
use settings::SettingsStore;
@ -408,7 +395,6 @@ mod tests {
cx.set_global(settings_store);
Project::init_settings(cx);
language::init(cx);
ThreadsDatabase::init(cx);
let http_client = FakeHttpClient::with_404_response();
let clock = Arc::new(clock::FakeSystemClock::new());
@ -453,10 +439,7 @@ mod tests {
.unwrap();
}
let db = cx
.update(|cx| ThreadsDatabase::global_future(cx))
.await
.unwrap();
let db = cx.update(|cx| ThreadsDatabase::connect(cx)).await.unwrap();
let threads = db.list_threads().await.unwrap();
assert_eq!(threads.len(), 1);
let thread = db

View file

@ -10,7 +10,7 @@ use ui::App;
use util::ResultExt as _;
use crate::AgentServerCommand;
use acp_thread::{AcpThread, AgentConnection, AuthRequired};
use acp_thread::{AcpThread, AcpThreadMetadata, AgentConnection, AuthRequired};
#[derive(Clone)]
struct OldAcpClientDelegate {
@ -451,6 +451,10 @@ impl AgentConnection for AcpConnection {
})
}
fn list_threads(&self, _cx: &mut App) -> Task<Result<Vec<AcpThreadMetadata>>> {
Task::ready(Ok(Vec::default()))
}
fn auth_methods(&self) -> &[acp::AuthMethod] {
&[]
}

View file

@ -11,7 +11,7 @@ use anyhow::{Context as _, Result};
use gpui::{App, AppContext as _, AsyncApp, Entity, Task, WeakEntity};
use crate::{AgentServerCommand, acp::UnsupportedVersion};
use acp_thread::{AcpThread, AgentConnection, AuthRequired};
use acp_thread::{AcpThread, AcpThreadMetadata, AgentConnection, AuthRequired};
pub struct AcpConnection {
server_name: &'static str,
@ -169,6 +169,10 @@ impl AgentConnection for AcpConnection {
})
}
fn list_threads(&self, _cx: &mut App) -> Task<Result<Vec<AcpThreadMetadata>>> {
Task::ready(Ok(Vec::default()))
}
fn prompt(
&self,
_id: Option<acp_thread::UserMessageId>,

View file

@ -30,7 +30,7 @@ use util::{ResultExt, debug_panic};
use crate::claude::mcp_server::{ClaudeZedMcpServer, McpConfig};
use crate::claude::tools::ClaudeTool;
use crate::{AgentServer, AgentServerCommand, AllAgentServersSettings};
use acp_thread::{AcpThread, AgentConnection};
use acp_thread::{AcpThread, AcpThreadMetadata, AgentConnection};
#[derive(Clone)]
pub struct ClaudeCode;
@ -209,6 +209,10 @@ impl AgentConnection for ClaudeAgentConnection {
Task::ready(Err(anyhow!("Authentication not supported")))
}
fn list_threads(&self, _cx: &mut App) -> Task<Result<Vec<AcpThreadMetadata>>> {
Task::ready(Ok(Vec::default()))
}
fn prompt(
&self,
_id: Option<acp_thread::UserMessageId>,

View file

@ -3583,7 +3583,7 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
#[cfg(test)]
pub(crate) mod tests {
use acp_thread::StubAgentConnection;
use acp_thread::{AcpThreadMetadata, StubAgentConnection};
use agent::{TextThreadStore, ThreadStore};
use agent_client_protocol::SessionId;
use editor::EditorSettings;
@ -3819,6 +3819,10 @@ pub(crate) mod tests {
unimplemented!()
}
fn list_threads(&self, _cx: &mut App) -> Task<gpui::Result<Vec<AcpThreadMetadata>>> {
Task::ready(Ok(vec![]))
}
fn prompt(
&self,
_id: Option<acp_thread::UserMessageId>,

View file

@ -9,6 +9,7 @@ mod context_picker;
mod context_server_configuration;
mod context_strip;
mod debug;
mod history_store;
mod inline_assistant;
mod inline_prompt_editor;
mod language_model_selector;

View file

@ -1,5 +1,5 @@
use crate::history_store::{HistoryEntry, HistoryStore};
use crate::{AgentPanel, RemoveSelectedThread};
use agent::history_store::{HistoryEntry, HistoryStore};
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
use editor::{Editor, EditorEvent};
use fuzzy::{StringMatch, StringMatchCandidate};