758 lines · 25.5 KB · Rust
RawDownload
use std::borrow::Cow;use std::cell::{Ref, RefCell};use std::path::{Path, PathBuf};use std::rc::Rc;use std::sync::Arc; use anyhow::{Context as _, Result, anyhow};use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings};use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};use chrono::{DateTime, Utc};use collections::HashMap;use context_server::manager::ContextServerManager;use context_server::{ContextServerFactoryRegistry, ContextServerTool};use fs::Fs;use futures::FutureExt as _;use futures::future::{self, BoxFuture, Shared};use gpui::{    App, BackgroundExecutor, Context, Entity, EventEmitter, Global, ReadGlobal, SharedString,    Subscription, Task, prelude::*,};use heed::Database;use heed::types::SerdeBincode;use language_model::{LanguageModelToolUseId, Role, TokenUsage};use project::{Project, Worktree};use prompt_store::{ProjectContext, PromptBuilder, RulesFileContext, WorktreeContext};use serde::{Deserialize, Serialize};use settings::{Settings as _, SettingsStore};use util::ResultExt as _; use crate::thread::{    DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,}; const RULES_FILE_NAMES: [&'static str; 6] = [    ".rules",    ".cursorrules",    ".windsurfrules",    ".clinerules",    ".github/copilot-instructions.md",    "CLAUDE.md",]; pub fn init(cx: &mut App) {    ThreadsDatabase::init(cx);} /// A system prompt shared by all threads created by this ThreadStore#[derive(Clone, Default)]pub struct SharedProjectContext(Rc<RefCell<Option<ProjectContext>>>); impl SharedProjectContext {    pub fn borrow(&self) -> Ref<Option<ProjectContext>> {        self.0.borrow()    }} pub struct ThreadStore {    project: Entity<Project>,    tools: Entity<ToolWorkingSet>,    prompt_builder: Arc<PromptBuilder>,    context_server_manager: Entity<ContextServerManager>,    context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,    threads: Vec<SerializedThreadMetadata>,    project_context: SharedProjectContext,    _subscriptions: Vec<Subscription>,} pub struct RulesLoadingError {    pub message: SharedString,} impl EventEmitter<RulesLoadingError> for ThreadStore {} impl ThreadStore {    pub fn load(        project: Entity<Project>,        tools: Entity<ToolWorkingSet>,        prompt_builder: Arc<PromptBuilder>,        cx: &mut App,    ) -> Task<Entity<Self>> {        let thread_store = cx.new(|cx| Self::new(project, tools, prompt_builder, cx));        let reload = thread_store.update(cx, |store, cx| store.reload_system_prompt(cx));        cx.foreground_executor().spawn(async move {            reload.await;            thread_store        })    }     fn new(        project: Entity<Project>,        tools: Entity<ToolWorkingSet>,        prompt_builder: Arc<PromptBuilder>,        cx: &mut Context<Self>,    ) -> Self {        let context_server_factory_registry = ContextServerFactoryRegistry::default_global(cx);        let context_server_manager = cx.new(|cx| {            ContextServerManager::new(context_server_factory_registry, project.clone(), cx)        });        let settings_subscription =            cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {                this.load_default_profile(cx);            });        let project_subscription = cx.subscribe(&project, Self::handle_project_event);         let this = Self {            project,            tools,            prompt_builder,            context_server_manager,            context_server_tool_ids: HashMap::default(),            threads: Vec::new(),            project_context: SharedProjectContext::default(),            _subscriptions: vec![settings_subscription, project_subscription],        };        this.load_default_profile(cx);        this.register_context_server_handlers(cx);        this.reload(cx).detach_and_log_err(cx);        this    }     fn handle_project_event(        &mut self,        _project: Entity<Project>,        event: &project::Event,        cx: &mut Context<Self>,    ) {        match event {            project::Event::WorktreeAdded(_) | project::Event::WorktreeRemoved(_) => {                self.reload_system_prompt(cx).detach();            }            project::Event::WorktreeUpdatedEntries(_, items) => {                if items.iter().any(|(path, _, _)| {                    RULES_FILE_NAMES                        .iter()                        .any(|name| path.as_ref() == Path::new(name))                }) {                    self.reload_system_prompt(cx).detach();                }            }            _ => {}        }    }     pub fn reload_system_prompt(&self, cx: &mut Context<Self>) -> Task<()> {        let project = self.project.read(cx);        let tasks = project            .visible_worktrees(cx)            .map(|worktree| {                Self::load_worktree_info_for_system_prompt(                    project.fs().clone(),                    worktree.read(cx),                    cx,                )            })            .collect::<Vec<_>>();         cx.spawn(async move |this, cx| {            let results = futures::future::join_all(tasks).await;            let worktrees = results                .into_iter()                .map(|(worktree, rules_error)| {                    if let Some(rules_error) = rules_error {                        this.update(cx, |_, cx| cx.emit(rules_error)).ok();                    }                    worktree                })                .collect::<Vec<_>>();            this.update(cx, |this, _cx| {                *this.project_context.0.borrow_mut() = Some(ProjectContext::new(worktrees));            })            .ok();        })    }     fn load_worktree_info_for_system_prompt(        fs: Arc<dyn Fs>,        worktree: &Worktree,        cx: &App,    ) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {        let root_name = worktree.root_name().into();        let abs_path = worktree.abs_path();         let rules_task = Self::load_worktree_rules_file(fs, worktree, cx);        let Some(rules_task) = rules_task else {            return Task::ready((                WorktreeContext {                    root_name,                    abs_path,                    rules_file: None,                },                None,            ));        };         cx.spawn(async move |_| {            let (rules_file, rules_file_error) = match rules_task.await {                Ok(rules_file) => (Some(rules_file), None),                Err(err) => (                    None,                    Some(RulesLoadingError {                        message: format!("{err}").into(),                    }),                ),            };            let worktree_info = WorktreeContext {                root_name,                abs_path,                rules_file,            };            (worktree_info, rules_file_error)        })    }     fn load_worktree_rules_file(        fs: Arc<dyn Fs>,        worktree: &Worktree,        cx: &App,    ) -> Option<Task<Result<RulesFileContext>>> {        let selected_rules_file = RULES_FILE_NAMES            .into_iter()            .filter_map(|name| {                worktree                    .entry_for_path(name)                    .filter(|entry| entry.is_file())                    .map(|entry| (entry.path.clone(), worktree.absolutize(&entry.path)))            })            .next();         // Note that Cline supports `.clinerules` being a directory, but that is not currently        // supported. This doesn't seem to occur often in GitHub repositories.        selected_rules_file.map(|(path_in_worktree, abs_path)| {            let fs = fs.clone();            cx.background_spawn(async move {                let abs_path = abs_path?;                let text = fs.load(&abs_path).await.with_context(|| {                    format!("Failed to load assistant rules file {:?}", abs_path)                })?;                anyhow::Ok(RulesFileContext {                    path_in_worktree,                    abs_path: abs_path.into(),                    text: text.trim().to_string(),                })            })        })    }     pub fn context_server_manager(&self) -> Entity<ContextServerManager> {        self.context_server_manager.clone()    }     pub fn tools(&self) -> Entity<ToolWorkingSet> {        self.tools.clone()    }     /// Returns the number of threads.    pub fn thread_count(&self) -> usize {        self.threads.len()    }     pub fn threads(&self) -> Vec<SerializedThreadMetadata> {        let mut threads = self.threads.iter().cloned().collect::<Vec<_>>();        threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.updated_at));        threads    }     pub fn recent_threads(&self, limit: usize) -> Vec<SerializedThreadMetadata> {        self.threads().into_iter().take(limit).collect()    }     pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {        cx.new(|cx| {            Thread::new(                self.project.clone(),                self.tools.clone(),                self.prompt_builder.clone(),                self.project_context.clone(),                cx,            )        })    }     pub fn open_thread(        &self,        id: &ThreadId,        cx: &mut Context<Self>,    ) -> Task<Result<Entity<Thread>>> {        let id = id.clone();        let database_future = ThreadsDatabase::global_future(cx);        cx.spawn(async move |this, cx| {            let database = database_future.await.map_err(|err| anyhow!(err))?;            let thread = database                .try_find_thread(id.clone())                .await?                .ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?;             let thread = this.update(cx, |this, cx| {                cx.new(|cx| {                    Thread::deserialize(                        id.clone(),                        thread,                        this.project.clone(),                        this.tools.clone(),                        this.prompt_builder.clone(),                        this.project_context.clone(),                        cx,                    )                })            })?;             Ok(thread)        })    }     pub fn save_thread(&self, thread: &Entity<Thread>, cx: &mut Context<Self>) -> Task<Result<()>> {        let (metadata, serialized_thread) =            thread.update(cx, |thread, cx| (thread.id().clone(), thread.serialize(cx)));         let database_future = ThreadsDatabase::global_future(cx);        cx.spawn(async move |this, cx| {            let serialized_thread = serialized_thread.await?;            let database = database_future.await.map_err(|err| anyhow!(err))?;            database.save_thread(metadata, serialized_thread).await?;             this.update(cx, |this, cx| this.reload(cx))?.await        })    }     pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut Context<Self>) -> Task<Result<()>> {        let id = id.clone();        let database_future = ThreadsDatabase::global_future(cx);        cx.spawn(async move |this, cx| {            let database = database_future.await.map_err(|err| anyhow!(err))?;            database.delete_thread(id.clone()).await?;             this.update(cx, |this, cx| {                this.threads.retain(|thread| thread.id != id);                cx.notify();            })        })    }     pub fn reload(&self, cx: &mut Context<Self>) -> Task<Result<()>> {        let database_future = ThreadsDatabase::global_future(cx);        cx.spawn(async move |this, cx| {            let threads = database_future                .await                .map_err(|err| anyhow!(err))?                .list_threads()                .await?;             this.update(cx, |this, cx| {                this.threads = threads;                cx.notify();            })        })    }     fn load_default_profile(&self, cx: &mut Context<Self>) {        let assistant_settings = AssistantSettings::get_global(cx);         self.load_profile_by_id(assistant_settings.default_profile.clone(), cx);    }     pub fn load_profile_by_id(&self, profile_id: AgentProfileId, cx: &mut Context<Self>) {        let assistant_settings = AssistantSettings::get_global(cx);         if let Some(profile) = assistant_settings.profiles.get(&profile_id) {            self.load_profile(profile.clone(), cx);        }    }     pub fn load_profile(&self, profile: AgentProfile, cx: &mut Context<Self>) {        self.tools.update(cx, |tools, cx| {            tools.disable_all_tools(cx);            tools.enable(                ToolSource::Native,                &profile                    .tools                    .iter()                    .filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))                    .collect::<Vec<_>>(),                cx,            );        });         if profile.enable_all_context_servers {            for context_server in self.context_server_manager.read(cx).all_servers() {                self.tools.update(cx, |tools, cx| {                    tools.enable_source(                        ToolSource::ContextServer {                            id: context_server.id().into(),                        },                        cx,                    );                });            }        } else {            for (context_server_id, preset) in &profile.context_servers {                self.tools.update(cx, |tools, cx| {                    tools.enable(                        ToolSource::ContextServer {                            id: context_server_id.clone().into(),                        },                        &preset                            .tools                            .iter()                            .filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))                            .collect::<Vec<_>>(),                        cx,                    )                })            }        }    }     fn register_context_server_handlers(&self, cx: &mut Context<Self>) {        cx.subscribe(            &self.context_server_manager.clone(),            Self::handle_context_server_event,        )        .detach();    }     fn handle_context_server_event(        &mut self,        context_server_manager: Entity<ContextServerManager>,        event: &context_server::manager::Event,        cx: &mut Context<Self>,    ) {        let tool_working_set = self.tools.clone();        match event {            context_server::manager::Event::ServerStarted { server_id } => {                if let Some(server) = context_server_manager.read(cx).get_server(server_id) {                    let context_server_manager = context_server_manager.clone();                    cx.spawn({                        let server = server.clone();                        let server_id = server_id.clone();                        async move |this, cx| {                            let Some(protocol) = server.client() else {                                return;                            };                             if protocol.capable(context_server::protocol::ServerCapability::Tools) {                                if let Some(tools) = protocol.list_tools().await.log_err() {                                    let tool_ids = tool_working_set                                        .update(cx, |tool_working_set, _| {                                            tools                                                .tools                                                .into_iter()                                                .map(|tool| {                                                    log::info!(                                                        "registering context server tool: {:?}",                                                        tool.name                                                    );                                                    tool_working_set.insert(Arc::new(                                                        ContextServerTool::new(                                                            context_server_manager.clone(),                                                            server.id(),                                                            tool,                                                        ),                                                    ))                                                })                                                .collect::<Vec<_>>()                                        })                                        .log_err();                                     if let Some(tool_ids) = tool_ids {                                        this.update(cx, |this, cx| {                                            this.context_server_tool_ids                                                .insert(server_id, tool_ids);                                            this.load_default_profile(cx);                                        })                                        .log_err();                                    }                                }                            }                        }                    })                    .detach();                }            }            context_server::manager::Event::ServerStopped { server_id } => {                if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {                    tool_working_set.update(cx, |tool_working_set, _| {                        tool_working_set.remove(&tool_ids);                    });                    self.load_default_profile(cx);                }            }        }    }} #[derive(Debug, Clone, Serialize, Deserialize)]pub struct SerializedThreadMetadata {    pub id: ThreadId,    pub summary: SharedString,    pub updated_at: DateTime<Utc>,} 

Lines 1–500 of 758.