[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 02:16:11 +01:00
committed by GitHub
Unverified
parent 6e50b22e55
commit 02dce8eb8d
16 changed files with 682 additions and 114 deletions
+32 -12
View File
@@ -1,7 +1,7 @@
use super::PluginLoadOutcome;
use crate::OPENAI_CURATED_MARKETPLACE_NAME;
use crate::app_mcp_routing::apply_app_mcp_routing_policy;
use crate::installed_marketplaces::installed_marketplace_roots_from_layer_stack;
use crate::is_openai_curated_marketplace_name;
use crate::loader::PluginHookLoadOutcome;
use crate::loader::configured_curated_plugin_ids_from_codex_home;
use crate::loader::curated_plugin_cache_version;
@@ -42,6 +42,7 @@ use crate::remote::RemotePluginCatalogError;
use crate::remote::RemotePluginServiceConfig;
use crate::remote_legacy::RemotePluginFetchError;
use crate::remote_legacy::RemotePluginMutationError;
use crate::startup_sync::curated_plugins_api_marketplace_path;
use crate::startup_sync::curated_plugins_repo_path;
use crate::startup_sync::read_curated_plugins_sha;
use crate::startup_sync::sync_openai_plugins_repo;
@@ -933,7 +934,7 @@ impl PluginsManager {
) -> Result<PluginInstallOutcome, PluginInstallError> {
let auth_policy = resolved.policy.authentication;
let plugin_version =
if resolved.plugin_id.marketplace_name == OPENAI_CURATED_MARKETPLACE_NAME {
if is_openai_curated_marketplace_name(&resolved.plugin_id.marketplace_name) {
let curated_plugin_version = read_curated_plugins_sha(self.codex_home.as_path())
.ok_or_else(|| {
PluginStoreError::Invalid(
@@ -1053,11 +1054,8 @@ impl PluginsManager {
}
let (installed_plugins, enabled_plugins) = self.configured_plugin_states(config);
let mut marketplace_roots = self.marketplace_roots(config, additional_roots);
if !include_openai_curated {
let curated_repo_root = curated_plugins_repo_path(self.codex_home.as_path());
marketplace_roots.retain(|root| root.as_path() != curated_repo_root.as_path());
}
let marketplace_roots =
self.marketplace_roots(config, additional_roots, include_openai_curated);
let marketplace_outcome = list_marketplaces(&marketplace_roots)?;
let mut seen_plugin_keys = HashSet::new();
let marketplaces = marketplace_outcome
@@ -1145,7 +1143,11 @@ impl PluginsManager {
return Ok(MarketplaceListOutcome::default());
}
list_marketplaces(&self.marketplace_roots(config, additional_roots))
list_marketplaces(&self.marketplace_roots(
config,
additional_roots,
/*include_openai_curated*/ true,
))
}
pub async fn read_plugin_for_config(
@@ -1879,6 +1881,7 @@ impl PluginsManager {
&self,
config: &PluginsConfigInput,
additional_roots: &[AbsolutePathBuf],
include_openai_curated: bool,
) -> Vec<AbsolutePathBuf> {
// Treat the curated catalog as an extra marketplace root so plugin listing can surface it
// without requiring every caller to know where it is stored.
@@ -1887,11 +1890,28 @@ impl PluginsManager {
&config.config_layer_stack,
self.codex_home.as_path(),
));
let curated_repo_root = curated_plugins_repo_path(self.codex_home.as_path());
if curated_repo_root.is_dir()
&& let Ok(curated_repo_root) = AbsolutePathBuf::try_from(curated_repo_root)
let curated_marketplace_path = if include_openai_curated {
if matches!(
self.auth_mode(),
Some(AuthMode::ApiKey | AuthMode::BedrockApiKey)
) {
let api_marketplace_path =
curated_plugins_api_marketplace_path(self.codex_home.as_path());
api_marketplace_path
.is_file()
.then_some(api_marketplace_path)
} else {
let curated_repo_root = curated_plugins_repo_path(self.codex_home.as_path());
curated_repo_root.is_dir().then_some(curated_repo_root)
}
} else {
None
};
if let Some(curated_marketplace_path) = curated_marketplace_path
&& let Ok(curated_marketplace_path) =
AbsolutePathBuf::try_from(curated_marketplace_path)
{
roots.push(curated_repo_root);
roots.push(curated_marketplace_path);
}
roots.sort_unstable();
roots.dedup();