Route curated plugins by authentication mode (#35671)

## Why

Curated plugin capabilities need to follow the active authentication mode, including after an account switch and when the configured model provider differs from the authentication source.

## What changed

- Select the ChatGPT, remote, or API curated marketplace from the current authentication mode, with an API marketplace fallback for ambient Amazon Bedrock credentials.
- Apply that selection consistently to plugin loading, hooks, skills, installed-plugin conflict filtering, marketplace listing, and `codex mcp` discovery.
- Start the local curated repository sync when an account change makes the remote catalog unavailable, and refresh existing thread MCP runtimes when the effective plugin cache changes.

## Testing

Added coverage for account switches, ChatGPT-authenticated Bedrock sessions, API-key MCP discovery, curated marketplace filtering, hook and skill routing, and existing-thread MCP refreshes.

GitOrigin-RevId: dbefdba3a3ea7281e7b6013e057a418770ccfc95
This commit is contained in:
felixxia-oai
2026-07-27 21:17:42 +00:00
committed by copyberry
parent fd41e813cb
commit 294d813263
12 changed files with 1258 additions and 88 deletions
+36 -2
View File
@@ -76,6 +76,14 @@ pub struct PluginHookLoadOutcome {
pub hook_load_warnings: Vec<String>,
}
/// The built-in curated marketplace selection for the current runtime.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetCuratedMarketplace {
OpenAi,
OpenAiWithRemote,
OpenAiApi,
}
enum PluginLoadScope<'a> {
AllCapabilities {
restriction_product: Option<Product>,
@@ -192,9 +200,10 @@ pub async fn load_plugin_hooks_from_layer_stack(
config_layer_stack: &ConfigLayerStack,
extra_plugins: HashMap<String, PluginConfig>,
store: &PluginStore,
target_curated_marketplace: TargetCuratedMarketplace,
remote_global_catalog_active: bool,
) -> PluginHookLoadOutcome {
let plugins = load_plugins_from_layer_stack_with_scope(
let mut plugins = load_plugins_from_layer_stack_with_scope(
config_layer_stack,
extra_plugins,
store,
@@ -202,6 +211,9 @@ pub async fn load_plugin_hooks_from_layer_stack(
PluginLoadScope::HooksOnly,
)
.await;
plugins.retain(|plugin| {
plugin_is_eligible_for_target_marketplace(&plugin.config_name, target_curated_marketplace)
});
PluginHookLoadOutcome {
hook_sources: plugins
.iter()
@@ -236,7 +248,7 @@ fn merge_configured_plugins_with_remote_installed(
let Ok(plugin_id) = PluginId::parse(plugin_key) else {
continue;
};
if !is_openai_curated_marketplace_name(&plugin_id.marketplace_name)
if plugin_id.marketplace_name != crate::OPENAI_CURATED_MARKETPLACE_NAME
|| store.active_plugin_version(&plugin_id).is_none()
{
continue;
@@ -267,6 +279,28 @@ fn merge_configured_plugins_with_remote_installed(
configured_plugins
}
pub(crate) fn plugin_is_eligible_for_target_marketplace(
plugin_key: &str,
target_curated_marketplace: TargetCuratedMarketplace,
) -> bool {
let Ok(plugin_id) = PluginId::parse(plugin_key) else {
return true;
};
match target_curated_marketplace {
TargetCuratedMarketplace::OpenAi => {
plugin_id.marketplace_name != crate::OPENAI_API_CURATED_MARKETPLACE_NAME
&& plugin_id.marketplace_name != REMOTE_GLOBAL_MARKETPLACE_NAME
}
TargetCuratedMarketplace::OpenAiWithRemote => {
plugin_id.marketplace_name != crate::OPENAI_API_CURATED_MARKETPLACE_NAME
}
TargetCuratedMarketplace::OpenAiApi => {
plugin_id.marketplace_name != crate::OPENAI_CURATED_MARKETPLACE_NAME
&& plugin_id.marketplace_name != REMOTE_GLOBAL_MARKETPLACE_NAME
}
}
}
fn installed_plugin_name_for_marketplace(
plugin_key: &str,
marketplace_name: &str,