mod db;mod edit_agent;mod legacy_thread;mod native_agent_server;pub mod outline;mod pattern_extraction;mod templates;#[cfg(test)]mod tests;mod thread;mod thread_store;mod tool_permissions;mod tools; use context_server::ContextServerId;pub use db::*;pub use native_agent_server::NativeAgentServer;pub use pattern_extraction::*;pub use shell_command_parser::extract_commands;pub use templates::*;pub use thread::*;pub use thread_store::*;pub use tool_permissions::*;pub use tools::*; use acp_thread::{ AcpThread, AgentModelSelector, AgentSessionInfo, AgentSessionList, AgentSessionListRequest, AgentSessionListResponse, UserMessageId,};use agent_client_protocol as acp;use anyhow::{Context as _, Result, anyhow};use chrono::{DateTime, Utc};use collections::{HashMap, HashSet, IndexMap};use fs::Fs;use futures::channel::{mpsc, oneshot};use futures::future::Shared;use futures::{FutureExt as _, StreamExt as _, future};use gpui::{ App, AppContext, AsyncApp, Context, Entity, SharedString, Subscription, Task, WeakEntity,};use language_model::{IconOrSvg, LanguageModel, LanguageModelProvider, LanguageModelRegistry};use project::{Project, ProjectItem, ProjectPath, Worktree};use prompt_store::{ ProjectContext, PromptStore, RULES_FILE_NAMES, RulesFileContext, UserRulesContext, WorktreeContext,};use serde::{Deserialize, Serialize};use settings::{LanguageModelSelection, update_settings_file};use std::any::Any;use std::path::{Path, PathBuf};use std::rc::Rc;use std::sync::Arc;use std::time::Duration;use util::ResultExt;use util::rel_path::RelPath; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]pub struct ProjectSnapshot { pub worktree_snapshots: Vec<project::telemetry_snapshot::TelemetryWorktreeSnapshot>, pub timestamp: DateTime<Utc>,} pub struct RulesLoadingError { pub message: SharedString,} /// Holds both the internal Thread and the AcpThread for a sessionstruct Session { /// The internal thread that processes messages thread: Entity<Thread>, /// The ACP thread that handles protocol communication acp_thread: Entity<acp_thread::AcpThread>, pending_save: Task<()>, _subscriptions: Vec<Subscription>,} pub struct LanguageModels { /// Access language model by ID models: HashMap<acp::ModelId, Arc<dyn LanguageModel>>, /// Cached list for returning language model information model_list: acp_thread::AgentModelList, refresh_models_rx: watch::Receiver<()>, refresh_models_tx: watch::Sender<()>, _authenticate_all_providers_task: Task<()>,} impl LanguageModels { fn new(cx: &mut App) -> Self { let (refresh_models_tx, refresh_models_rx) = watch::channel(()); let mut this = Self { models: HashMap::default(), model_list: acp_thread::AgentModelList::Grouped(IndexMap::default()), refresh_models_rx, refresh_models_tx, _authenticate_all_providers_task: Self::authenticate_all_language_model_providers(cx), }; this.refresh_list(cx); this } fn refresh_list(&mut self, cx: &App) { let providers = LanguageModelRegistry::global(cx) .read(cx) .visible_providers() .into_iter() .filter(|provider| provider.is_authenticated(cx)) .collect::<Vec<_>>(); let mut language_model_list = IndexMap::default(); let mut recommended_models = HashSet::default(); let mut recommended = Vec::new(); for provider in &providers { for model in provider.recommended_models(cx) { recommended_models.insert((model.provider_id(), model.id())); recommended.push(Self::map_language_model_to_info(&model, provider)); } } if !recommended.is_empty() { language_model_list.insert( acp_thread::AgentModelGroupName("Recommended".into()), recommended, ); } let mut models = HashMap::default(); for provider in providers { let mut provider_models = Vec::new(); for model in provider.provided_models(cx) { let model_info = Self::map_language_model_to_info(&model, &provider); let model_id = model_info.id.clone(); provider_models.push(model_info); models.insert(model_id, model); } if !provider_models.is_empty() { language_model_list.insert( acp_thread::AgentModelGroupName(provider.name().0.clone()), provider_models, ); } } self.models = models; self.model_list = acp_thread::AgentModelList::Grouped(language_model_list); self.refresh_models_tx.send(()).ok(); } fn watch(&self) -> watch::Receiver<()> { self.refresh_models_rx.clone() } pub fn model_from_id(&self, model_id: &acp::ModelId) -> Option<Arc<dyn LanguageModel>> { self.models.get(model_id).cloned() } fn map_language_model_to_info( model: &Arc<dyn LanguageModel>, provider: &Arc<dyn LanguageModelProvider>, ) -> acp_thread::AgentModelInfo { acp_thread::AgentModelInfo { id: Self::model_id(model), name: model.name().0, description: None, icon: Some(match provider.icon() { IconOrSvg::Svg(path) => acp_thread::AgentModelIcon::Path(path), IconOrSvg::Icon(name) => acp_thread::AgentModelIcon::Named(name), }), is_latest: model.is_latest(), cost: model.model_cost_info().map(|cost| cost.to_shared_string()), } } fn model_id(model: &Arc<dyn LanguageModel>) -> acp::ModelId { acp::ModelId::new(format!("{}/{}", model.provider_id().0, model.id().0)) } fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> { let authenticate_all_providers = LanguageModelRegistry::global(cx) .read(cx) .visible_providers() .iter() .map(|provider| (provider.id(), provider.name(), provider.authenticate(cx))) .collect::<Vec<_>>(); cx.background_spawn(async move { for (provider_id, provider_name, authenticate_task) in authenticate_all_providers { if let Err(err) = authenticate_task.await { match err { language_model::AuthenticateError::CredentialsNotFound => { // Since we're authenticating these providers in the // background for the purposes of populating the // language selector, we don't care about providers // where the credentials are not found. } language_model::AuthenticateError::ConnectionRefused => { // Not logging connection refused errors as they are mostly from LM Studio's noisy auth failures. // LM Studio only has one auth method (endpoint call) which fails for users who haven't enabled it. // TODO: Better manage LM Studio auth logic to avoid these noisy failures. } _ => { // Some providers have noisy failure states that we // don't want to spam the logs with every time the // language model selector is initialized. // // Ideally these should have more clear failure modes // that we know are safe to ignore here, like what we do // with `CredentialsNotFound` above. match provider_id.0.as_ref() { "lmstudio" | "ollama" => { // LM Studio and Ollama both make fetch requests to the local APIs to determine if they are "authenticated". // // These fail noisily, so we don't log them. } "copilot_chat" => { // Copilot Chat returns an error if Copilot is not enabled, so we don't log those errors. } _ => { log::error!( "Failed to authenticate provider: {}: {err:#}", provider_name.0 ); } } } } } } }) }} pub struct NativeAgent { /// Session ID -> Session mapping sessions: HashMap<acp::SessionId, Session>, thread_store: Entity<ThreadStore>, /// Shared project context for all threads project_context: Entity<ProjectContext>, project_context_needs_refresh: watch::Sender<()>, _maintain_project_context: Task<Result<()>>, context_server_registry: Entity<ContextServerRegistry>, /// Shared templates for all threads templates: Arc<Templates>, /// Cached model information models: LanguageModels, project: Entity<Project>, prompt_store: Option<Entity<PromptStore>>, fs: Arc<dyn Fs>, _subscriptions: Vec<Subscription>,} impl NativeAgent { pub async fn new( project: Entity<Project>, thread_store: Entity<ThreadStore>, templates: Arc<Templates>, prompt_store: Option<Entity<PromptStore>>, fs: Arc<dyn Fs>, cx: &mut AsyncApp, ) -> Result<Entity<NativeAgent>> { log::debug!("Creating new NativeAgent"); let project_context = cx .update(|cx| Self::build_project_context(&project, prompt_store.as_ref(), cx)) .await; Ok(cx.new(|cx| { let context_server_store = project.read(cx).context_server_store(); let context_server_registry = cx.new(|cx| ContextServerRegistry::new(context_server_store.clone(), cx)); let mut subscriptions = vec![ cx.subscribe(&project, Self::handle_project_event), cx.subscribe( &LanguageModelRegistry::global(cx), Self::handle_models_updated_event, ), cx.subscribe( &context_server_store, Self::handle_context_server_store_updated, ), cx.subscribe( &context_server_registry, Self::handle_context_server_registry_event, ), ]; if let Some(prompt_store) = prompt_store.as_ref() { subscriptions.push(cx.subscribe(prompt_store, Self::handle_prompts_updated_event)) } let (project_context_needs_refresh_tx, project_context_needs_refresh_rx) = watch::channel(()); Self { sessions: HashMap::default(), thread_store, project_context: cx.new(|_| project_context), project_context_needs_refresh: project_context_needs_refresh_tx, _maintain_project_context: cx.spawn(async move |this, cx| { Self::maintain_project_context(this, project_context_needs_refresh_rx, cx).await }), context_server_registry, templates, models: LanguageModels::new(cx), project, prompt_store, fs, _subscriptions: subscriptions, } })) } fn new_session( &mut self, project: Entity<Project>, cx: &mut Context<Self>, ) -> Entity<AcpThread> { // Create Thread // Fetch default model from registry settings let registry = LanguageModelRegistry::read_global(cx); // Log available models for debugging let available_count = registry.available_models(cx).count(); log::debug!("Total available models: {}", available_count); let default_model = registry.default_model().and_then(|default_model| { self.models .model_from_id(&LanguageModels::model_id(&default_model.model)) }); let thread = cx.new(|cx| { Thread::new( project.clone(), self.project_context.clone(), self.context_server_registry.clone(), self.templates.clone(), default_model, cx, ) }); self.register_session(thread, None, cx) } fn register_session( &mut self, thread_handle: Entity<Thread>, allowed_tool_names: Option<Vec<&str>>, cx: &mut Context<Self>, ) -> Entity<AcpThread> { let connection = Rc::new(NativeAgentConnection(cx.entity())); let thread = thread_handle.read(cx); let session_id = thread.id().clone(); let parent_session_id = thread.parent_thread_id(); let title = thread.title(); let project = thread.project.clone(); let action_log = thread.action_log.clone(); let prompt_capabilities_rx = thread.prompt_capabilities_rx.clone(); let acp_thread = cx.new(|cx| { acp_thread::AcpThread::new( parent_session_id, title, connection, project.clone(), action_log.clone(), session_id.clone(), prompt_capabilities_rx, cx, ) }); let registry = LanguageModelRegistry::read_global(cx); let summarization_model = registry.thread_summary_model().map(|c| c.model); let weak = cx.weak_entity(); thread_handle.update(cx, |thread, cx| { thread.set_summarization_model(summarization_model, cx); thread.add_default_tools( allowed_tool_names, Rc::new(NativeThreadEnvironment { acp_thread: acp_thread.downgrade(), agent: weak, }) as _, cx, ) }); let subscriptions = vec![ cx.subscribe(&thread_handle, Self::handle_thread_title_updated), cx.subscribe(&thread_handle, Self::handle_thread_token_usage_updated), cx.observe(&thread_handle, move |this, thread, cx| { this.save_thread(thread, cx) }), ]; self.sessions.insert( session_id, Session { thread: thread_handle, acp_thread: acp_thread.clone(), _subscriptions: subscriptions, pending_save: Task::ready(()), }, ); self.update_available_commands(cx); acp_thread } pub fn models(&self) -> &LanguageModels { &self.models } async fn maintain_project_context( this: WeakEntity<Self>, mut needs_refresh: watch::Receiver<()>, cx: &mut AsyncApp, ) -> Result<()> { while needs_refresh.changed().await.is_ok() { let project_context = this .update(cx, |this, cx| { Self::build_project_context(&this.project, this.prompt_store.as_ref(), cx) })? .await; this.update(cx, |this, cx| { this.project_context = cx.new(|_| project_context); })?; } Ok(()) } fn build_project_context( project: &Entity<Project>, prompt_store: Option<&Entity<PromptStore>>, cx: &mut App, ) -> Task<ProjectContext> { let worktrees = project.read(cx).visible_worktrees(cx).collect::<Vec<_>>(); let worktree_tasks = worktrees .into_iter() .map(|worktree| { Self::load_worktree_info_for_system_prompt(worktree, project.clone(), cx) }) .collect::<Vec<_>>(); let default_user_rules_task = if let Some(prompt_store) = prompt_store.as_ref() { prompt_store.read_with(cx, |prompt_store, cx| { let prompts = prompt_store.default_prompt_metadata(); let load_tasks = prompts.into_iter().map(|prompt_metadata| { let contents = prompt_store.load(prompt_metadata.id, cx); async move { (contents.await, prompt_metadata) } }); cx.background_spawn(future::join_all(load_tasks)) }) } else { Task::ready(vec![]) }; cx.spawn(async move |_cx| { let (worktrees, default_user_rules) = future::join(future::join_all(worktree_tasks), default_user_rules_task).await; let worktrees = worktrees .into_iter() .map(|(worktree, _rules_error)| { // TODO: show error message // if let Some(rules_error) = rules_error { // this.update(cx, |_, cx| cx.emit(rules_error)).ok(); // } worktree }) .collect::<Vec<_>>(); let default_user_rules = default_user_rules .into_iter() .flat_map(|(contents, prompt_metadata)| match contents { Ok(contents) => Some(UserRulesContext { uuid: prompt_metadata.id.as_user()?, title: prompt_metadata.title.map(|title| title.to_string()), contents, }), Err(_err) => { // TODO: show error message // this.update(cx, |_, cx| { // cx.emit(RulesLoadingError { // message: format!("{err:?}").into(), // }); // }) // .ok(); None } }) .collect::<Vec<_>>(); ProjectContext::new(worktrees, default_user_rules) }) } fn load_worktree_info_for_system_prompt( worktree: Entity<Worktree>, project: Entity<Project>, cx: &mut App,Lines 1–500 of 2617. Load more
mod db; mod edit_agent; mod legacy_thread; mod native_agent_server; pub mod outline; mod pattern_extraction; mod templates; #[cfg(test)] mod tests; mod thread; mod thread_store; mod tool_permissions; mod tools; use context_server::ContextServerId; pub use db::*; pub use native_agent_server::NativeAgentServer; pub use pattern_extraction::*; pub use shell_command_parser::extract_commands; pub use templates::*; pub use thread::*; pub use thread_store::*; pub use tool_permissions::*; pub use tools::*; use acp_thread::{ AcpThread, AgentModelSelector, AgentSessionInfo, AgentSessionList, AgentSessionListRequest, AgentSessionListResponse, UserMessageId, }; use agent_client_protocol as acp; use anyhow::{Context as _, Result, anyhow}; use chrono::{DateTime, Utc}; use collections::{HashMap, HashSet, IndexMap}; use fs::Fs; use futures::channel::{mpsc, oneshot}; use futures::future::Shared; use futures::{FutureExt as _, StreamExt as _, future}; use gpui::{ App, AppContext, AsyncApp, Context, Entity, SharedString, Subscription, Task, WeakEntity, }; use language_model::{IconOrSvg, LanguageModel, LanguageModelProvider, LanguageModelRegistry}; use project::{Project, ProjectItem, ProjectPath, Worktree}; use prompt_store::{ ProjectContext, PromptStore, RULES_FILE_NAMES, RulesFileContext, UserRulesContext, WorktreeContext, }; use serde::{Deserialize, Serialize}; use settings::{LanguageModelSelection, update_settings_file}; use std::any::Any; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::Arc; use std::time::Duration; use util::ResultExt; use util::rel_path::RelPath; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ProjectSnapshot { pub worktree_snapshots: Vec<project::telemetry_snapshot::TelemetryWorktreeSnapshot>, pub timestamp: DateTime<Utc>, } pub struct RulesLoadingError { pub message: SharedString, } /// Holds both the internal Thread and the AcpThread for a session struct Session { /// The internal thread that processes messages thread: Entity<Thread>, /// The ACP thread that handles protocol communication acp_thread: Entity<acp_thread::AcpThread>, pending_save: Task<()>, _subscriptions: Vec<Subscription>, } pub struct LanguageModels { /// Access language model by ID models: HashMap<acp::ModelId, Arc<dyn LanguageModel>>, /// Cached list for returning language model information model_list: acp_thread::AgentModelList, refresh_models_rx: watch::Receiver<()>, refresh_models_tx: watch::Sender<()>, _authenticate_all_providers_task: Task<()>, } impl LanguageModels { fn new(cx: &mut App) -> Self { let (refresh_models_tx, refresh_models_rx) = watch::channel(()); let mut this = Self { models: HashMap::default(), model_list: acp_thread::AgentModelList::Grouped(IndexMap::default()), refresh_models_rx, refresh_models_tx, _authenticate_all_providers_task: Self::authenticate_all_language_model_providers(cx), }; this.refresh_list(cx); this } fn refresh_list(&mut self, cx: &App) { let providers = LanguageModelRegistry::global(cx) .read(cx) .visible_providers() .into_iter() .filter(|provider| provider.is_authenticated(cx)) .collect::<Vec<_>>(); let mut language_model_list = IndexMap::default(); let mut recommended_models = HashSet::default(); let mut recommended = Vec::new(); for provider in &providers { for model in provider.recommended_models(cx) { recommended_models.insert((model.provider_id(), model.id())); recommended.push(Self::map_language_model_to_info(&model, provider)); } } if !recommended.is_empty() { language_model_list.insert( acp_thread::AgentModelGroupName("Recommended".into()), recommended, ); } let mut models = HashMap::default(); for provider in providers { let mut provider_models = Vec::new(); for model in provider.provided_models(cx) { let model_info = Self::map_language_model_to_info(&model, &provider); let model_id = model_info.id.clone(); provider_models.push(model_info); models.insert(model_id, model); } if !provider_models.is_empty() { language_model_list.insert( acp_thread::AgentModelGroupName(provider.name().0.clone()), provider_models, ); } } self.models = models; self.model_list = acp_thread::AgentModelList::Grouped(language_model_list); self.refresh_models_tx.send(()).ok(); } fn watch(&self) -> watch::Receiver<()> { self.refresh_models_rx.clone() } pub fn model_from_id(&self, model_id: &acp::ModelId) -> Option<Arc<dyn LanguageModel>> { self.models.get(model_id).cloned() } fn map_language_model_to_info( model: &Arc<dyn LanguageModel>, provider: &Arc<dyn LanguageModelProvider>, ) -> acp_thread::AgentModelInfo { acp_thread::AgentModelInfo { id: Self::model_id(model), name: model.name().0, description: None, icon: Some(match provider.icon() { IconOrSvg::Svg(path) => acp_thread::AgentModelIcon::Path(path), IconOrSvg::Icon(name) => acp_thread::AgentModelIcon::Named(name), }), is_latest: model.is_latest(), cost: model.model_cost_info().map(|cost| cost.to_shared_string()), } } fn model_id(model: &Arc<dyn LanguageModel>) -> acp::ModelId { acp::ModelId::new(format!("{}/{}", model.provider_id().0, model.id().0)) } fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> { let authenticate_all_providers = LanguageModelRegistry::global(cx) .read(cx) .visible_providers() .iter() .map(|provider| (provider.id(), provider.name(), provider.authenticate(cx))) .collect::<Vec<_>>(); cx.background_spawn(async move { for (provider_id, provider_name, authenticate_task) in authenticate_all_providers { if let Err(err) = authenticate_task.await { match err { language_model::AuthenticateError::CredentialsNotFound => { // Since we're authenticating these providers in the // background for the purposes of populating the // language selector, we don't care about providers // where the credentials are not found. } language_model::AuthenticateError::ConnectionRefused => { // Not logging connection refused errors as they are mostly from LM Studio's noisy auth failures. // LM Studio only has one auth method (endpoint call) which fails for users who haven't enabled it. // TODO: Better manage LM Studio auth logic to avoid these noisy failures. } _ => { // Some providers have noisy failure states that we // don't want to spam the logs with every time the // language model selector is initialized. // // Ideally these should have more clear failure modes // that we know are safe to ignore here, like what we do // with `CredentialsNotFound` above. match provider_id.0.as_ref() { "lmstudio" | "ollama" => { // LM Studio and Ollama both make fetch requests to the local APIs to determine if they are "authenticated". // // These fail noisily, so we don't log them. } "copilot_chat" => { // Copilot Chat returns an error if Copilot is not enabled, so we don't log those errors. } _ => { log::error!( "Failed to authenticate provider: {}: {err:#}", provider_name.0 ); } } } } } } }) } } pub struct NativeAgent { /// Session ID -> Session mapping sessions: HashMap<acp::SessionId, Session>, thread_store: Entity<ThreadStore>, /// Shared project context for all threads project_context: Entity<ProjectContext>, project_context_needs_refresh: watch::Sender<()>, _maintain_project_context: Task<Result<()>>, context_server_registry: Entity<ContextServerRegistry>, /// Shared templates for all threads templates: Arc<Templates>, /// Cached model information models: LanguageModels, project: Entity<Project>, prompt_store: Option<Entity<PromptStore>>, fs: Arc<dyn Fs>, _subscriptions: Vec<Subscription>, } impl NativeAgent { pub async fn new( project: Entity<Project>, thread_store: Entity<ThreadStore>, templates: Arc<Templates>, prompt_store: Option<Entity<PromptStore>>, fs: Arc<dyn Fs>, cx: &mut AsyncApp, ) -> Result<Entity<NativeAgent>> { log::debug!("Creating new NativeAgent"); let project_context = cx .update(|cx| Self::build_project_context(&project, prompt_store.as_ref(), cx)) .await; Ok(cx.new(|cx| { let context_server_store = project.read(cx).context_server_store(); let context_server_registry = cx.new(|cx| ContextServerRegistry::new(context_server_store.clone(), cx)); let mut subscriptions = vec![ cx.subscribe(&project, Self::handle_project_event), cx.subscribe( &LanguageModelRegistry::global(cx), Self::handle_models_updated_event, ), cx.subscribe( &context_server_store, Self::handle_context_server_store_updated, ), cx.subscribe( &context_server_registry, Self::handle_context_server_registry_event, ), ]; if let Some(prompt_store) = prompt_store.as_ref() { subscriptions.push(cx.subscribe(prompt_store, Self::handle_prompts_updated_event)) } let (project_context_needs_refresh_tx, project_context_needs_refresh_rx) = watch::channel(()); Self { sessions: HashMap::default(), thread_store, project_context: cx.new(|_| project_context), project_context_needs_refresh: project_context_needs_refresh_tx, _maintain_project_context: cx.spawn(async move |this, cx| { Self::maintain_project_context(this, project_context_needs_refresh_rx, cx).await }), context_server_registry, templates, models: LanguageModels::new(cx), project, prompt_store, fs, _subscriptions: subscriptions, } })) } fn new_session( &mut self, project: Entity<Project>, cx: &mut Context<Self>, ) -> Entity<AcpThread> { // Create Thread // Fetch default model from registry settings let registry = LanguageModelRegistry::read_global(cx); // Log available models for debugging let available_count = registry.available_models(cx).count(); log::debug!("Total available models: {}", available_count); let default_model = registry.default_model().and_then(|default_model| { self.models .model_from_id(&LanguageModels::model_id(&default_model.model)) }); let thread = cx.new(|cx| { Thread::new( project.clone(), self.project_context.clone(), self.context_server_registry.clone(), self.templates.clone(), default_model, cx, ) }); self.register_session(thread, None, cx) } fn register_session( &mut self, thread_handle: Entity<Thread>, allowed_tool_names: Option<Vec<&str>>, cx: &mut Context<Self>, ) -> Entity<AcpThread> { let connection = Rc::new(NativeAgentConnection(cx.entity())); let thread = thread_handle.read(cx); let session_id = thread.id().clone(); let parent_session_id = thread.parent_thread_id(); let title = thread.title(); let project = thread.project.clone(); let action_log = thread.action_log.clone(); let prompt_capabilities_rx = thread.prompt_capabilities_rx.clone(); let acp_thread = cx.new(|cx| { acp_thread::AcpThread::new( parent_session_id, title, connection, project.clone(), action_log.clone(), session_id.clone(), prompt_capabilities_rx, cx, ) }); let registry = LanguageModelRegistry::read_global(cx); let summarization_model = registry.thread_summary_model().map(|c| c.model); let weak = cx.weak_entity(); thread_handle.update(cx, |thread, cx| { thread.set_summarization_model(summarization_model, cx); thread.add_default_tools( allowed_tool_names, Rc::new(NativeThreadEnvironment { acp_thread: acp_thread.downgrade(), agent: weak, }) as _, cx, ) }); let subscriptions = vec![ cx.subscribe(&thread_handle, Self::handle_thread_title_updated), cx.subscribe(&thread_handle, Self::handle_thread_token_usage_updated), cx.observe(&thread_handle, move |this, thread, cx| { this.save_thread(thread, cx) }), ]; self.sessions.insert( session_id, Session { thread: thread_handle, acp_thread: acp_thread.clone(), _subscriptions: subscriptions, pending_save: Task::ready(()), }, ); self.update_available_commands(cx); acp_thread } pub fn models(&self) -> &LanguageModels { &self.models } async fn maintain_project_context( this: WeakEntity<Self>, mut needs_refresh: watch::Receiver<()>, cx: &mut AsyncApp, ) -> Result<()> { while needs_refresh.changed().await.is_ok() { let project_context = this .update(cx, |this, cx| { Self::build_project_context(&this.project, this.prompt_store.as_ref(), cx) })? .await; this.update(cx, |this, cx| { this.project_context = cx.new(|_| project_context); })?; } Ok(()) } fn build_project_context( project: &Entity<Project>, prompt_store: Option<&Entity<PromptStore>>, cx: &mut App, ) -> Task<ProjectContext> { let worktrees = project.read(cx).visible_worktrees(cx).collect::<Vec<_>>(); let worktree_tasks = worktrees .into_iter() .map(|worktree| { Self::load_worktree_info_for_system_prompt(worktree, project.clone(), cx) }) .collect::<Vec<_>>(); let default_user_rules_task = if let Some(prompt_store) = prompt_store.as_ref() { prompt_store.read_with(cx, |prompt_store, cx| { let prompts = prompt_store.default_prompt_metadata(); let load_tasks = prompts.into_iter().map(|prompt_metadata| { let contents = prompt_store.load(prompt_metadata.id, cx); async move { (contents.await, prompt_metadata) } }); cx.background_spawn(future::join_all(load_tasks)) }) } else { Task::ready(vec![]) }; cx.spawn(async move |_cx| { let (worktrees, default_user_rules) = future::join(future::join_all(worktree_tasks), default_user_rules_task).await; let worktrees = worktrees .into_iter() .map(|(worktree, _rules_error)| { // TODO: show error message // if let Some(rules_error) = rules_error { // this.update(cx, |_, cx| cx.emit(rules_error)).ok(); // } worktree }) .collect::<Vec<_>>(); let default_user_rules = default_user_rules .into_iter() .flat_map(|(contents, prompt_metadata)| match contents { Ok(contents) => Some(UserRulesContext { uuid: prompt_metadata.id.as_user()?, title: prompt_metadata.title.map(|title| title.to_string()), contents, }), Err(_err) => { // TODO: show error message // this.update(cx, |_, cx| { // cx.emit(RulesLoadingError { // message: format!("{err:?}").into(), // }); // }) // .ok(); None } }) .collect::<Vec<_>>(); ProjectContext::new(worktrees, default_user_rules) }) } fn load_worktree_info_for_system_prompt( worktree: Entity<Worktree>, project: Entity<Project>, cx: &mut App,