[codex] Load API curated marketplace by auth (#28383)

## Summary
- choose the local OpenAI curated marketplace manifest based on auth:
Codex backend auth gets the existing marketplace, direct provider auth
gets `api_marketplace.json`
- include Bedrock API key auth in the direct-provider API marketplace
path
- safely skip the API marketplace when `api_marketplace.json` is absent

## Validation
- `just fmt`
- `git diff --check origin/main...HEAD`
- CI should run the full validation

## Manual Testing

### - New api marketplace not available for API key sign
1. Safely not display anything from api marketplace
<img width="1161" height="289" alt="Screenshot 2026-06-15 at 21 37 43"
src="https://github.com/user-attachments/assets/a5f16642-8a20-4ac1-a0de-1274a4c7b5b2"
/>

### - New api marketplace for API key sign in
1. Setup api_marketplace.json
```
{
  "name": "openai-curated",
  "interface": {
    "displayName": "Codex official"
  },
  "plugins": [
    {
      "name": "linear",
      "source": {
        "source": "local",
        "path": "./plugins/linear"
      },
      "policy": {
        "installation": "AVAILABLE",
        "authentication": "ON_INSTALL"
      },
      "category": "Productivity"
    }
  ]
}
```

2. Log in with API key, observe that only the defined plugin from
api_marketplace.json is available from "Codex Official" (outside of
local testing marketplaces)
<img width="1167" height="446" alt="Screenshot 2026-06-15 at 21 16 53"
src="https://github.com/user-attachments/assets/7cf61477-d826-4ef6-bc05-0a23ac1c0259"
/>

also checked functionality on codex app

### - SiWC users 
Still uses 'default' marketplace.json and renders all plugins
<img width="1171" height="502" alt="Screenshot 2026-06-15 at 21 40 25"
src="https://github.com/user-attachments/assets/d212ea9b-0aa5-470b-8ea4-450efe65bb2b"
/>

also checked functionality on codex app


## Notes
- `just test -p codex-core-plugins` was started locally before splitting
branches, but I stopped relying on local tests per follow-up and left
final validation to PR CI.
This commit is contained in:
felixxia-oai
2026-06-16 01:16:11 +00:00
committed by GitHub
parent 6e50b22e55
commit 02dce8eb8d
16 changed files with 682 additions and 114 deletions
@@ -8,6 +8,7 @@ use codex_app_server_protocol::PluginShareTargetRole;
use codex_config::types::McpServerConfig;
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
use codex_core_plugins::PluginListBackgroundTaskOptions;
use codex_core_plugins::is_openai_curated_marketplace_name;
use codex_core_plugins::remote::REMOTE_CREATED_BY_ME_MARKETPLACE_NAME;
use codex_core_plugins::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
use codex_core_plugins::remote::REMOTE_WORKSPACE_MARKETPLACE_NAME;
@@ -174,9 +175,9 @@ fn filter_openai_curated_installed_conflicts(
) {
let local_installed_plugin_names = marketplaces
.iter()
.find(|marketplace| marketplace.name == OPENAI_CURATED_MARKETPLACE_NAME)
.map(|marketplace| installed_plugin_names(&marketplace.plugins))
.unwrap_or_default();
.filter(|marketplace| is_openai_curated_marketplace_name(&marketplace.name))
.flat_map(|marketplace| installed_plugin_names(&marketplace.plugins))
.collect::<HashSet<_>>();
let remote_installed_plugin_names = marketplaces
.iter()
.find(|marketplace| marketplace.name == REMOTE_GLOBAL_MARKETPLACE_NAME)
@@ -190,13 +191,12 @@ fn filter_openai_curated_installed_conflicts(
return;
}
let marketplace_to_filter = if prefer_remote_curated_conflicts {
OPENAI_CURATED_MARKETPLACE_NAME
} else {
REMOTE_GLOBAL_MARKETPLACE_NAME
};
for marketplace in marketplaces.iter_mut() {
if marketplace.name != marketplace_to_filter {
if prefer_remote_curated_conflicts {
if !is_openai_curated_marketplace_name(&marketplace.name) {
continue;
}
} else if marketplace.name != REMOTE_GLOBAL_MARKETPLACE_NAME {
continue;
}
marketplace
@@ -551,6 +551,8 @@ impl PluginRequestProcessor {
{
return Ok(empty_response());
}
let auth_mode = auth.as_ref().map(CodexAuth::api_auth_mode);
plugins_manager.set_auth_mode(auth_mode);
let plugins_input = config.plugins_config_input();
let include_shared_with_me =
marketplace_kinds.contains(&PluginListMarketplaceKind::SharedWithMe);
@@ -559,10 +561,12 @@ impl PluginRequestProcessor {
&& config.features.enabled(Feature::RemotePlugin);
let include_global_remote =
!explicit_marketplace_kinds && config.features.enabled(Feature::RemotePlugin);
let use_remote_global_catalog =
include_global_remote && auth_mode.is_some_and(AuthMode::uses_codex_backend);
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let refresh_global_remote_catalog_cache = include_global_remote
let refresh_global_remote_catalog_cache = use_remote_global_catalog
&& codex_core_plugins::remote::has_cached_global_remote_plugin_catalog(
config.codex_home.as_path(),
&remote_plugin_service_config,
@@ -578,7 +582,7 @@ impl PluginRequestProcessor {
.list_marketplaces_for_config(
&config_for_marketplace_listing,
&roots_for_marketplace_listing,
/*include_openai_curated*/ true,
/*include_openai_curated*/ !use_remote_global_catalog,
)?;
Ok::<
(
@@ -649,16 +653,14 @@ impl PluginRequestProcessor {
data.push(remote_marketplace_to_info(remote_marketplace));
}
Ok(None) => {}
Err(RemotePluginCatalogError::UnsupportedAuthMode) => {}
Err(err) if explicit_marketplace_kinds => {
return Err(remote_plugin_catalog_error_to_jsonrpc(
err,
"list OpenAI Curated remote plugin catalog",
));
}
Err(
RemotePluginCatalogError::AuthRequired
| RemotePluginCatalogError::UnsupportedAuthMode,
) => {}
Err(RemotePluginCatalogError::AuthRequired) => {}
Err(err) => {
warn!(
error = %err,
@@ -669,7 +671,7 @@ impl PluginRequestProcessor {
}
let mut remote_sources = Vec::new();
if include_global_remote {
if use_remote_global_catalog {
remote_sources.push(RemoteMarketplaceSource::Global);
}
if include_created_by_me_remote {
@@ -741,9 +743,10 @@ impl PluginRequestProcessor {
);
}
let featured_plugin_ids = if data
.iter()
.any(|marketplace| marketplace.name == OPENAI_CURATED_MARKETPLACE_NAME)
let featured_plugin_ids = if !plugins_input.remote_plugin_enabled
&& data
.iter()
.any(|marketplace| marketplace.name == OPENAI_CURATED_MARKETPLACE_NAME)
{
match plugins_manager
.featured_plugin_ids_for_config(&plugins_input, auth.as_ref())
@@ -799,6 +802,7 @@ impl PluginRequestProcessor {
{
return Ok(empty_response());
}
plugins_manager.set_auth_mode(auth.as_ref().map(CodexAuth::api_auth_mode));
let plugins_input = config.plugins_config_input();
let remote_installed_plugin_visible_marketplaces =