Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions codex-rs/app-server/src/request_processors/account_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,17 @@ impl AccountRequestProcessor {
}
}

async fn maybe_refresh_remote_installed_plugins_cache_for_current_config(
async fn maybe_refresh_plugin_caches_for_current_config(
config_manager: &ConfigManager,
thread_manager: &Arc<ThreadManager>,
auth: Option<CodexAuth>,
) {
thread_manager
.plugins_manager()
.set_auth_mode(auth.as_ref().map(CodexAuth::api_auth_mode));
thread_manager
.plugins_manager()
.clear_recommended_plugins_cache();

match config_manager
.load_latest_config(/*fallback_cwd*/ None)
Expand All @@ -191,7 +194,7 @@ impl AccountRequestProcessor {
let refresh_config_manager = config_manager.clone();
thread_manager
.plugins_manager()
.maybe_start_remote_installed_plugins_cache_refresh(
.maybe_start_remote_plugin_caches_refresh(
&config.plugins_config_input(),
auth,
Some(Arc::new(move || {
Expand Down Expand Up @@ -618,7 +621,7 @@ impl AccountRequestProcessor {
}

async fn send_login_success_notifications(&self, login_id: Option<Uuid>) {
Self::maybe_refresh_remote_installed_plugins_cache_for_current_config(
Self::maybe_refresh_plugin_caches_for_current_config(
&self.config_manager,
&self.thread_manager,
self.auth_manager.auth_cached(),
Expand Down Expand Up @@ -671,7 +674,7 @@ impl AccountRequestProcessor {
.await;

let auth = auth_manager.auth_cached();
Self::maybe_refresh_remote_installed_plugins_cache_for_current_config(
Self::maybe_refresh_plugin_caches_for_current_config(
&config_manager,
&thread_manager,
auth.clone(),
Expand Down Expand Up @@ -703,7 +706,7 @@ impl AccountRequestProcessor {
}
}

Self::maybe_refresh_remote_installed_plugins_cache_for_current_config(
Self::maybe_refresh_plugin_caches_for_current_config(
&self.config_manager,
&self.thread_manager,
self.auth_manager.auth_cached(),
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core-plugins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,5 @@ pub use marketplace_upgrade::ConfiguredMarketplaceUpgradeOutcome as PluginMarket
pub use provider::ExecutorPluginProvider;
pub use provider::ExecutorPluginProviderError;
pub use provider::ResolvedExecutorPlugin;
pub use remote::RecommendedPlugin;
pub use remote::RecommendedPluginsMode;
132 changes: 126 additions & 6 deletions codex-rs/core-plugins/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use crate::marketplace_upgrade::ConfiguredMarketplaceUpgradeError;
use crate::marketplace_upgrade::ConfiguredMarketplaceUpgradeOutcome;
use crate::marketplace_upgrade::configured_git_marketplace_names;
use crate::marketplace_upgrade::upgrade_configured_git_marketplaces;
use crate::remote::RecommendedPluginsMode;
use crate::remote::RemoteInstalledPlugin;
use crate::remote::RemotePluginCatalogError;
use crate::remote::RemotePluginServiceConfig;
Expand Down Expand Up @@ -80,6 +81,7 @@ use std::sync::RwLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Instant;
use tokio::sync::OnceCell;
use tokio::sync::Semaphore;
use tracing::instrument;
use tracing::warn;
Expand Down Expand Up @@ -120,6 +122,11 @@ struct FeaturedPluginIdsCacheKey {
is_workspace_account: bool,
}

#[derive(Clone, Hash, PartialEq, Eq)]
struct RecommendedPluginsCacheKey {
chatgpt_base_url: String,
}

#[derive(Clone)]
struct CachedFeaturedPluginIds {
key: FeaturedPluginIdsCacheKey,
Expand Down Expand Up @@ -209,6 +216,12 @@ fn featured_plugin_ids_cache_key(
}
}

fn recommended_plugins_cache_key(config: &PluginsConfigInput) -> RecommendedPluginsCacheKey {
RecommendedPluginsCacheKey {
chatgpt_base_url: config.chatgpt_base_url.clone(),
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginInstallRequest {
pub plugin_name: String,
Expand Down Expand Up @@ -318,6 +331,9 @@ pub struct PluginsManager {
codex_home: PathBuf,
store: PluginStore,
featured_plugin_ids_cache: RwLock<Option<CachedFeaturedPluginIds>>,
recommended_plugins_cache: RwLock<HashMap<RecommendedPluginsCacheKey, RecommendedPluginsMode>>,
recommended_plugins_refreshes:
RwLock<HashMap<RecommendedPluginsCacheKey, Arc<OnceCell<RecommendedPluginsMode>>>>,
configured_marketplace_upgrade_state: RwLock<ConfiguredMarketplaceUpgradeState>,
non_curated_cache_refresh_state: RwLock<NonCuratedCacheRefreshState>,
// Keep the cache auth-independent so auth changes only need to resolve capabilities again.
Expand Down Expand Up @@ -371,6 +387,8 @@ impl PluginsManager {
codex_home: codex_home.clone(),
store: PluginStore::new(codex_home),
featured_plugin_ids_cache: RwLock::new(None),
recommended_plugins_cache: RwLock::new(HashMap::new()),
recommended_plugins_refreshes: RwLock::new(HashMap::new()),
configured_marketplace_upgrade_state: RwLock::new(
ConfiguredMarketplaceUpgradeState::default(),
),
Expand Down Expand Up @@ -499,6 +517,19 @@ impl PluginsManager {
*featured_plugin_ids_cache = None;
}

pub fn clear_recommended_plugins_cache(&self) {
let mut refreshes = match self.recommended_plugins_refreshes.write() {
Ok(refreshes) => refreshes,
Err(err) => err.into_inner(),
};
refreshes.clear();
let mut cache = match self.recommended_plugins_cache.write() {
Ok(cache) => cache,
Err(err) => err.into_inner(),
};
cache.clear();
}

fn clear_loaded_plugins_cache(&self) {
let mut cache = match self.loaded_plugins_cache.write() {
Ok(cache) => cache,
Expand Down Expand Up @@ -700,18 +731,26 @@ impl PluginsManager {
true
}

pub fn maybe_start_remote_installed_plugins_cache_refresh(
pub fn maybe_start_remote_plugin_caches_refresh(
self: &Arc<Self>,
config: &PluginsConfigInput,
auth: Option<CodexAuth>,
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
) {
self.maybe_start_remote_installed_plugins_cache_refresh_with_notify(
config,
auth,
auth.clone(),
RemoteInstalledPluginsCacheRefreshNotify::IfCacheChanged,
on_effective_plugins_changed,
);

let manager = Arc::clone(self);
let config = config.clone();
tokio::spawn(async move {
manager
.recommended_plugins_mode_for_config(&config, auth.as_ref())
.await;
});
}

pub fn maybe_start_remote_installed_plugins_cache_refresh_after_mutation(
Expand Down Expand Up @@ -805,7 +844,7 @@ impl PluginsManager {
if options.refresh_global_remote_catalog_cache {
self.maybe_start_global_remote_catalog_cache_refresh(config, auth.clone());
}
self.maybe_start_remote_installed_plugins_cache_refresh(
self.maybe_start_remote_plugin_caches_refresh(
config,
auth.clone(),
on_effective_plugins_changed.clone(),
Expand Down Expand Up @@ -888,6 +927,87 @@ impl PluginsManager {
Ok(featured_plugin_ids)
}

pub async fn recommended_plugins_mode_for_config(
&self,
config: &PluginsConfigInput,
auth: Option<&CodexAuth>,
) -> RecommendedPluginsMode {
if !config.plugins_enabled
|| !config.remote_plugin_enabled
|| !auth.is_some_and(CodexAuth::uses_codex_backend)
{
return RecommendedPluginsMode::Legacy;
}

let cache_key = recommended_plugins_cache_key(config);
Comment thread
adaley-openai marked this conversation as resolved.
if let Some(cached) = self.cached_recommended_plugins_mode(&cache_key) {
return cached;
}

let refresh = {
let mut refreshes = match self.recommended_plugins_refreshes.write() {
Ok(refreshes) => refreshes,
Err(err) => err.into_inner(),
};
if let Some(cached) = self.cached_recommended_plugins_mode(&cache_key) {
return cached;
}
refreshes
.entry(cache_key.clone())
.or_insert_with(|| Arc::new(OnceCell::new()))
.clone()
};

let mode = refresh
.get_or_init(|| async {
match crate::remote::fetch_recommended_plugins(
&remote_plugin_service_config(config),
auth,
)
.await
{
Ok(mode) => {
let mut cache = match self.recommended_plugins_cache.write() {
Ok(cache) => cache,
Err(err) => err.into_inner(),
};
cache.insert(cache_key.clone(), mode.clone());
mode
}
Err(err) => {
warn!(error = %err, "failed to load recommended plugins");
RecommendedPluginsMode::Legacy
}
}
})
.await
.clone();

let mut refreshes = match self.recommended_plugins_refreshes.write() {
Ok(refreshes) => refreshes,
Err(err) => err.into_inner(),
};
if refreshes
.get(&cache_key)
.is_some_and(|current| Arc::ptr_eq(current, &refresh))
{
refreshes.remove(&cache_key);
}

mode
}

fn cached_recommended_plugins_mode(
&self,
cache_key: &RecommendedPluginsCacheKey,
) -> Option<RecommendedPluginsMode> {
let cache = match self.recommended_plugins_cache.read() {
Ok(cache) => cache,
Err(err) => err.into_inner(),
};
cache.get(cache_key).cloned()
}

pub async fn install_plugin(
&self,
request: PluginInstallRequest,
Expand Down Expand Up @@ -1420,7 +1540,7 @@ impl PluginsManager {
let on_effective_plugins_changed = on_effective_plugins_changed.clone();
tokio::spawn(async move {
let auth = auth_manager_for_remote_sync.auth().await;
manager.maybe_start_remote_installed_plugins_cache_refresh(
manager.maybe_start_remote_plugin_caches_refresh(
&config_for_remote_sync,
auth.clone(),
on_effective_plugins_changed.clone(),
Expand Down Expand Up @@ -1453,12 +1573,12 @@ impl PluginsManager {
}
});

let config = config.clone();
let config_for_featured_plugins = config.clone();
let manager = Arc::clone(self);
tokio::spawn(async move {
let auth = auth_manager.auth().await;
if let Err(err) = manager
.featured_plugin_ids_for_config(&config, auth.as_ref())
.featured_plugin_ids_for_config(&config_for_featured_plugins, auth.as_ref())
.await
{
warn!(
Expand Down
Loading
Loading