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::*;use itertools::Itertools;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, TokenUsageRatio, 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, EntityId, SharedString, Subscription, Task, WeakEntity,};use language_model::{IconOrSvg, LanguageModel, LanguageModelProvider, LanguageModelRegistry};use project::{AgentId, 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::PathBuf;use std::rc::Rc;use std::sync::{Arc, LazyLock};use util::ResultExt;use util::path_list::PathList;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,} struct ProjectState { project: Entity<Project>, project_context: Entity<ProjectContext>, project_context_needs_refresh: watch::Sender<()>, _maintain_project_context: Task<Result<()>>, context_server_registry: Entity<ContextServerRegistry>, _subscriptions: Vec<Subscription>,} /// 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>, project_id: EntityId, pending_save: Task<Result<()>>, _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>, /// Project-specific state keyed by project EntityId projects: HashMap<EntityId, ProjectState>, /// Shared templates for all threads templates: Arc<Templates>, /// Cached model information models: LanguageModels, prompt_store: Option<Entity<PromptStore>>, fs: Arc<dyn Fs>, _subscriptions: Vec<Subscription>,} impl NativeAgent { pub fn new( thread_store: Entity<ThreadStore>, templates: Arc<Templates>, prompt_store: Option<Entity<PromptStore>>, fs: Arc<dyn Fs>, cx: &mut App, ) -> Entity<NativeAgent> { log::debug!("Creating new NativeAgent"); cx.new(|cx| { let mut subscriptions = vec![cx.subscribe( &LanguageModelRegistry::global(cx), Self::handle_models_updated_event, )]; if let Some(prompt_store) = prompt_store.as_ref() { subscriptions.push(cx.subscribe(prompt_store, Self::handle_prompts_updated_event)) } Self { sessions: HashMap::default(), thread_store, projects: HashMap::default(), templates, models: LanguageModels::new(cx), prompt_store, fs, _subscriptions: subscriptions, } }) } fn new_session( &mut self, project: Entity<Project>, cx: &mut Context<Self>, ) -> Entity<AcpThread> { let project_id = self.get_or_create_project_state(&project, cx); let project_state = &self.projects[&project_id]; let registry = LanguageModelRegistry::read_global(cx); 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, project_state.project_context.clone(), project_state.context_server_registry.clone(), self.templates.clone(), default_model, cx, ) }); self.register_session(thread, project_id, cx) } fn register_session( &mut self, thread_handle: Entity<Thread>, project_id: EntityId, 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 draft_prompt = thread.draft_prompt().map(Vec::from); let scroll_position = thread.ui_scroll_position(); let token_usage = thread.latest_token_usage(); 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| { let mut acp_thread = acp_thread::AcpThread::new( parent_session_id, title, None, connection, project.clone(), action_log.clone(), session_id.clone(), prompt_capabilities_rx, cx, ); acp_thread.set_draft_prompt(draft_prompt); acp_thread.set_ui_scroll_position(scroll_position); acp_thread.update_token_usage(token_usage, cx); acp_thread }); let registry = LanguageModelRegistry::read_global(cx); let summarization_model = registry.thread_summary_model().map(|c| c.model); let weak = cx.weak_entity(); let weak_thread = thread_handle.downgrade(); thread_handle.update(cx, |thread, cx| { thread.set_summarization_model(summarization_model, cx); thread.add_default_tools( Rc::new(NativeThreadEnvironment { acp_thread: acp_thread.downgrade(), thread: weak_thread, 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(), project_id, _subscriptions: subscriptions, pending_save: Task::ready(Ok(())), }, ); self.update_available_commands_for_project(project_id, cx); acp_thread } pub fn models(&self) -> &LanguageModels { &self.models } fn get_or_create_project_state( &mut self, project: &Entity<Project>, cx: &mut Context<Self>, ) -> EntityId { let project_id = project.entity_id(); if self.projects.contains_key(&project_id) { return project_id; } let project_context = cx.new(|_| ProjectContext::new(vec![], vec![])); self.register_project_with_initial_context(project.clone(), project_context, cx); if let Some(state) = self.projects.get_mut(&project_id) { state.project_context_needs_refresh.send(()).ok(); } project_id } fn register_project_with_initial_context( &mut self, project: Entity<Project>, project_context: Entity<ProjectContext>, cx: &mut Context<Self>, ) { let project_id = project.entity_id(); 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 subscriptions = vec![ cx.subscribe(&project, Self::handle_project_event), cx.subscribe( &context_server_store, Self::handle_context_server_store_updated, ), cx.subscribe( &context_server_registry, Self::handle_context_server_registry_event, ), ]; let (project_context_needs_refresh_tx, project_context_needs_refresh_rx) = watch::channel(()); self.projects.insert( project_id, ProjectState { project, 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_id, project_context_needs_refresh_rx, cx, ) .await }), context_server_registry, _subscriptions: subscriptions, }, ); } fn session_project_state(&self, session_id: &acp::SessionId) -> Option<&ProjectState> { self.sessions .get(session_id) .and_then(|session| self.projects.get(&session.project_id)) } async fn maintain_project_context( this: WeakEntity<Self>, project_id: EntityId, mut needs_refresh: watch::Receiver<()>, cx: &mut AsyncApp, ) -> Result<()> { while needs_refresh.changed().await.is_ok() { let project_context = this .update(cx, |this, cx| { let state = this .projects .get(&project_id) .context("project state not found")?; anyhow::Ok(Self::build_project_context( &state.project, this.prompt_store.as_ref(), cx, )) })?? .await; this.update(cx, |this, cx| { if let Some(state) = this.projects.get(&project_id) { state .project_context .update(cx, |current_project_context, _cx| { *current_project_context = project_context;Lines 1–500 of 3127. 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::*; use itertools::Itertools; 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, TokenUsageRatio, 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, EntityId, SharedString, Subscription, Task, WeakEntity, }; use language_model::{IconOrSvg, LanguageModel, LanguageModelProvider, LanguageModelRegistry}; use project::{AgentId, 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::PathBuf; use std::rc::Rc; use std::sync::{Arc, LazyLock}; use util::ResultExt; use util::path_list::PathList; 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, } struct ProjectState { project: Entity<Project>, project_context: Entity<ProjectContext>, project_context_needs_refresh: watch::Sender<()>, _maintain_project_context: Task<Result<()>>, context_server_registry: Entity<ContextServerRegistry>, _subscriptions: Vec<Subscription>, } /// 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>, project_id: EntityId, pending_save: Task<Result<()>>, _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>, /// Project-specific state keyed by project EntityId projects: HashMap<EntityId, ProjectState>, /// Shared templates for all threads templates: Arc<Templates>, /// Cached model information models: LanguageModels, prompt_store: Option<Entity<PromptStore>>, fs: Arc<dyn Fs>, _subscriptions: Vec<Subscription>, } impl NativeAgent { pub fn new( thread_store: Entity<ThreadStore>, templates: Arc<Templates>, prompt_store: Option<Entity<PromptStore>>, fs: Arc<dyn Fs>, cx: &mut App, ) -> Entity<NativeAgent> { log::debug!("Creating new NativeAgent"); cx.new(|cx| { let mut subscriptions = vec![cx.subscribe( &LanguageModelRegistry::global(cx), Self::handle_models_updated_event, )]; if let Some(prompt_store) = prompt_store.as_ref() { subscriptions.push(cx.subscribe(prompt_store, Self::handle_prompts_updated_event)) } Self { sessions: HashMap::default(), thread_store, projects: HashMap::default(), templates, models: LanguageModels::new(cx), prompt_store, fs, _subscriptions: subscriptions, } }) } fn new_session( &mut self, project: Entity<Project>, cx: &mut Context<Self>, ) -> Entity<AcpThread> { let project_id = self.get_or_create_project_state(&project, cx); let project_state = &self.projects[&project_id]; let registry = LanguageModelRegistry::read_global(cx); 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, project_state.project_context.clone(), project_state.context_server_registry.clone(), self.templates.clone(), default_model, cx, ) }); self.register_session(thread, project_id, cx) } fn register_session( &mut self, thread_handle: Entity<Thread>, project_id: EntityId, 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 draft_prompt = thread.draft_prompt().map(Vec::from); let scroll_position = thread.ui_scroll_position(); let token_usage = thread.latest_token_usage(); 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| { let mut acp_thread = acp_thread::AcpThread::new( parent_session_id, title, None, connection, project.clone(), action_log.clone(), session_id.clone(), prompt_capabilities_rx, cx, ); acp_thread.set_draft_prompt(draft_prompt); acp_thread.set_ui_scroll_position(scroll_position); acp_thread.update_token_usage(token_usage, cx); acp_thread }); let registry = LanguageModelRegistry::read_global(cx); let summarization_model = registry.thread_summary_model().map(|c| c.model); let weak = cx.weak_entity(); let weak_thread = thread_handle.downgrade(); thread_handle.update(cx, |thread, cx| { thread.set_summarization_model(summarization_model, cx); thread.add_default_tools( Rc::new(NativeThreadEnvironment { acp_thread: acp_thread.downgrade(), thread: weak_thread, 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(), project_id, _subscriptions: subscriptions, pending_save: Task::ready(Ok(())), }, ); self.update_available_commands_for_project(project_id, cx); acp_thread } pub fn models(&self) -> &LanguageModels { &self.models } fn get_or_create_project_state( &mut self, project: &Entity<Project>, cx: &mut Context<Self>, ) -> EntityId { let project_id = project.entity_id(); if self.projects.contains_key(&project_id) { return project_id; } let project_context = cx.new(|_| ProjectContext::new(vec![], vec![])); self.register_project_with_initial_context(project.clone(), project_context, cx); if let Some(state) = self.projects.get_mut(&project_id) { state.project_context_needs_refresh.send(()).ok(); } project_id } fn register_project_with_initial_context( &mut self, project: Entity<Project>, project_context: Entity<ProjectContext>, cx: &mut Context<Self>, ) { let project_id = project.entity_id(); 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 subscriptions = vec![ cx.subscribe(&project, Self::handle_project_event), cx.subscribe( &context_server_store, Self::handle_context_server_store_updated, ), cx.subscribe( &context_server_registry, Self::handle_context_server_registry_event, ), ]; let (project_context_needs_refresh_tx, project_context_needs_refresh_rx) = watch::channel(()); self.projects.insert( project_id, ProjectState { project, 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_id, project_context_needs_refresh_rx, cx, ) .await }), context_server_registry, _subscriptions: subscriptions, }, ); } fn session_project_state(&self, session_id: &acp::SessionId) -> Option<&ProjectState> { self.sessions .get(session_id) .and_then(|session| self.projects.get(&session.project_id)) } async fn maintain_project_context( this: WeakEntity<Self>, project_id: EntityId, mut needs_refresh: watch::Receiver<()>, cx: &mut AsyncApp, ) -> Result<()> { while needs_refresh.changed().await.is_ok() { let project_context = this .update(cx, |this, cx| { let state = this .projects .get(&project_id) .context("project state not found")?; anyhow::Ok(Self::build_project_context( &state.project, this.prompt_store.as_ref(), cx, )) })?? .await; this.update(cx, |this, cx| { if let Some(state) = this.projects.get(&project_id) { state .project_context .update(cx, |current_project_context, _cx| { *current_project_context = project_context;