Route curated plugin catalogs by authentication mode (#38429)

## Why

The model provider does not reliably identify which curated plugin catalog is
available. ChatGPT authentication can be used with a custom provider, while an
unauthenticated session should use the API-compatible catalog regardless of its
provider.

## What changed

- Select the ChatGPT curated catalog only for authentication modes that use the
  Codex backend; use the API curated catalog for API-key and unauthenticated
  sessions.
- Initialize standalone plugin managers with the current authentication mode
  across CLI, app-server, MCP, and external-agent migration paths.
- Preserve authentication mode while detecting and importing migrated plugins.

## Testing

- Cover catalog and skill routing across ChatGPT, API-key, unauthenticated,
  Bedrock, and custom-provider configurations.
- Verify authenticated plugin migration uses the ChatGPT curated marketplace.

GitOrigin-RevId: 660a339ee8891c33aad961078d3a979242a6a166
This commit is contained in:
willwang-openai
2026-08-13 20:20:01 +00:00
committed by copyberry
parent 507ef0b371
commit f898ebcafd
28 changed files with 348 additions and 139 deletions
@@ -1,7 +1,7 @@
use crate::plugins::plugins_manager_for_config;
use crate::plugins::test_support::load_plugins_config;
use crate::plugins::test_support::write_file;
use crate::plugins::test_support::write_openai_curated_marketplace;
use crate::plugins::test_support::write_openai_api_curated_marketplace;
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
use codex_tools::DiscoverablePluginInfo;
use pretty_assertions::assert_eq;
@@ -11,7 +11,7 @@ async fn list_discoverable_plugins(
config: &crate::config::Config,
loaded_plugin_app_connector_ids: &[String],
) -> anyhow::Result<Vec<DiscoverablePluginInfo>> {
let plugins_manager = plugins_manager_for_config(config);
let plugins_manager = plugins_manager_for_config(config, /*auth_mode*/ None);
super::list_tool_suggest_discoverable_plugins(
config,
&plugins_manager,
@@ -25,7 +25,7 @@ async fn list_discoverable_plugins(
async fn list_tool_suggest_discoverable_plugins_returns_empty_when_plugins_feature_disabled() {
let codex_home = tempdir().expect("tempdir should succeed");
let curated_root = curated_plugins_repo_path(codex_home.path());
write_openai_curated_marketplace(&curated_root, &["slack"]);
write_openai_api_curated_marketplace(&curated_root, &["slack"]);
write_file(
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
r#"[features]
@@ -43,7 +43,7 @@ plugins = false
async fn list_tool_suggest_discoverable_plugins_omits_disabled_tool_suggestions() {
let codex_home = tempdir().expect("tempdir should succeed");
let curated_root = curated_plugins_repo_path(codex_home.path());
write_openai_curated_marketplace(&curated_root, &["slack"]);
write_openai_api_curated_marketplace(&curated_root, &["slack"]);
write_file(
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
r#"[features]
@@ -51,7 +51,7 @@ plugins = true
[tool_suggest]
disabled_tools = [
{ type = "plugin", id = "slack@openai-curated" }
{ type = "plugin", id = "slack@openai-api-curated" }
]
"#,
);
@@ -66,14 +66,14 @@ disabled_tools = [
async fn list_tool_suggest_discoverable_plugins_includes_configured_plugin_ids() {
let codex_home = tempdir().expect("tempdir should succeed");
let curated_root = curated_plugins_repo_path(codex_home.path());
write_openai_curated_marketplace(&curated_root, &["sample"]);
write_openai_api_curated_marketplace(&curated_root, &["sample"]);
write_file(
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
r#"[features]
plugins = true
[tool_suggest]
discoverables = [{ type = "plugin", id = "sample@openai-curated" }]
discoverables = [{ type = "plugin", id = "sample@openai-api-curated" }]
"#,
);
@@ -83,7 +83,7 @@ discoverables = [{ type = "plugin", id = "sample@openai-curated" }]
assert_eq!(
discoverable_plugins,
vec![DiscoverablePluginInfo {
id: "sample@openai-curated".to_string(),
id: "sample@openai-api-curated".to_string(),
remote_plugin_id: None,
name: "sample".to_string(),
description: Some(
+7 -2
View File
@@ -11,6 +11,7 @@ pub(crate) mod test_support;
use crate::config::Config;
use codex_core_plugins::PluginsManager;
use codex_protocol::auth::AuthMode;
use codex_skills_extension::HostSkillsService;
use std::sync::Arc;
@@ -28,10 +29,14 @@ pub(crate) use mentions::collect_tool_mentions_from_messages;
/// Constructs a standalone plugin manager with extension-owned plugin skill loading.
///
/// Callers that already own a host skills service should inject that existing service instead.
pub fn plugins_manager_for_config(config: &Config) -> PluginsManager {
pub fn plugins_manager_for_config(config: &Config, auth_mode: Option<AuthMode>) -> PluginsManager {
let skill_root_loader = Arc::new(HostSkillsService::new(
config.codex_home.clone(),
/*bundled_skills_enabled*/ false,
));
PluginsManager::new(config.codex_home.to_path_buf(), skill_root_loader)
PluginsManager::new(
config.codex_home.to_path_buf(),
auth_mode,
skill_root_loader,
)
}
+4 -4
View File
@@ -3,7 +3,7 @@ use crate::config::ConfigBuilder;
use std::fs;
use std::path::Path;
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
use codex_core_plugins::OPENAI_API_CURATED_MARKETPLACE_NAME;
pub(crate) const TEST_CURATED_PLUGIN_SHA: &str = "0123456789abcdef0123456789abcdef01234567";
@@ -50,7 +50,7 @@ pub(crate) fn write_curated_plugin(root: &Path, plugin_name: &str) {
);
}
pub(crate) fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str]) {
pub(crate) fn write_openai_api_curated_marketplace(root: &Path, plugin_names: &[&str]) {
let plugins = plugin_names
.iter()
.map(|plugin_name| {
@@ -67,10 +67,10 @@ pub(crate) fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str
.collect::<Vec<_>>()
.join(",\n");
write_file(
&root.join(".agents/plugins/marketplace.json"),
&root.join(".agents/plugins/api_marketplace.json"),
&format!(
r#"{{
"name": "{OPENAI_CURATED_MARKETPLACE_NAME}",
"name": "{OPENAI_API_CURATED_MARKETPLACE_NAME}",
"plugins": [
{plugins}
]