mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[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:
committed by
GitHub
Unverified
parent
6e50b22e55
commit
02dce8eb8d
@@ -1,13 +1,12 @@
|
||||
use crate::OPENAI_CURATED_MARKETPLACE_NAME;
|
||||
use crate::app_mcp_routing::apply_app_mcp_routing_policy;
|
||||
use crate::app_mcp_routing::apps_route_available;
|
||||
use crate::is_openai_curated_marketplace_name;
|
||||
use crate::manifest::PluginManifestHooks;
|
||||
use crate::manifest::PluginManifestPaths;
|
||||
use crate::manifest::load_plugin_manifest;
|
||||
use crate::marketplace::MarketplacePluginSource;
|
||||
use crate::marketplace::list_marketplaces;
|
||||
use crate::marketplace::load_marketplace;
|
||||
use crate::marketplace::load_raw_marketplace_plugin_names;
|
||||
use crate::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
|
||||
use crate::remote::RemoteInstalledPlugin;
|
||||
use crate::store::PluginStore;
|
||||
@@ -198,17 +197,21 @@ fn merge_configured_plugins_with_remote_installed(
|
||||
store: &PluginStore,
|
||||
prefer_remote_curated_conflicts: bool,
|
||||
) -> HashMap<String, PluginConfig> {
|
||||
let local_curated_installed_plugin_keys = configured_plugins
|
||||
.keys()
|
||||
.filter_map(|plugin_key| {
|
||||
installed_plugin_name_for_marketplace(
|
||||
plugin_key,
|
||||
OPENAI_CURATED_MARKETPLACE_NAME,
|
||||
store,
|
||||
)
|
||||
.map(|plugin_name| (plugin_name, plugin_key.clone()))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let mut local_curated_installed_plugin_keys = HashMap::<String, Vec<String>>::new();
|
||||
for plugin_key in configured_plugins.keys() {
|
||||
let Ok(plugin_id) = PluginId::parse(plugin_key) else {
|
||||
continue;
|
||||
};
|
||||
if !is_openai_curated_marketplace_name(&plugin_id.marketplace_name)
|
||||
|| store.active_plugin_version(&plugin_id).is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
local_curated_installed_plugin_keys
|
||||
.entry(plugin_id.plugin_name)
|
||||
.or_default()
|
||||
.push(plugin_key.clone());
|
||||
}
|
||||
|
||||
for (plugin_key, plugin_config) in extra_plugins {
|
||||
let remote_curated_plugin_name = installed_plugin_name_for_marketplace(
|
||||
@@ -216,13 +219,15 @@ fn merge_configured_plugins_with_remote_installed(
|
||||
REMOTE_GLOBAL_MARKETPLACE_NAME,
|
||||
store,
|
||||
);
|
||||
let local_curated_plugin_key = remote_curated_plugin_name
|
||||
let local_curated_plugin_keys = remote_curated_plugin_name
|
||||
.as_ref()
|
||||
.and_then(|plugin_name| local_curated_installed_plugin_keys.get(plugin_name));
|
||||
|
||||
if let Some(local_curated_plugin_key) = local_curated_plugin_key {
|
||||
if let Some(local_curated_plugin_keys) = local_curated_plugin_keys {
|
||||
if prefer_remote_curated_conflicts {
|
||||
configured_plugins.remove(local_curated_plugin_key);
|
||||
for local_curated_plugin_key in local_curated_plugin_keys {
|
||||
configured_plugins.remove(local_curated_plugin_key);
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@@ -289,41 +294,53 @@ pub fn refresh_curated_plugin_cache(
|
||||
) -> Result<bool, String> {
|
||||
let cache_plugin_version = curated_plugin_cache_version(plugin_version);
|
||||
let store = PluginStore::try_new(codex_home.to_path_buf()).map_err(|err| err.to_string())?;
|
||||
let curated_marketplace_path = AbsolutePathBuf::try_from(
|
||||
codex_home
|
||||
.join(".tmp/plugins")
|
||||
.join(".agents/plugins/marketplace.json"),
|
||||
)
|
||||
.map_err(|_| "local curated marketplace is not available".to_string())?;
|
||||
let marketplace_plugin_names = load_raw_marketplace_plugin_names(&curated_marketplace_path)
|
||||
.map_err(|err| {
|
||||
format!("failed to load curated marketplace plugin names for cache refresh: {err}")
|
||||
})?;
|
||||
let curated_marketplace = load_marketplace(&curated_marketplace_path)
|
||||
.map_err(|err| format!("failed to load curated marketplace for cache refresh: {err}"))?;
|
||||
|
||||
let curated_marketplace_paths = curated_marketplace_paths_for_cache_refresh(codex_home)?;
|
||||
let mut loaded_marketplace_names = HashSet::<String>::new();
|
||||
let mut marketplace_plugin_keys = HashSet::<String>::new();
|
||||
let mut plugin_sources = HashMap::<String, AbsolutePathBuf>::new();
|
||||
for plugin in curated_marketplace.plugins {
|
||||
let plugin_name = plugin.name;
|
||||
if plugin_sources.contains_key(&plugin_name) {
|
||||
warn!(
|
||||
plugin = plugin_name,
|
||||
marketplace = OPENAI_CURATED_MARKETPLACE_NAME,
|
||||
"ignoring duplicate curated plugin entry during cache refresh"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if let MarketplacePluginSource::Local { path } = plugin.source {
|
||||
plugin_sources.insert(plugin_name, path);
|
||||
|
||||
for curated_marketplace_path in curated_marketplace_paths {
|
||||
let curated_marketplace = load_marketplace(&curated_marketplace_path).map_err(|err| {
|
||||
format!("failed to load curated marketplace for cache refresh: {err}")
|
||||
})?;
|
||||
let marketplace_name = curated_marketplace.name;
|
||||
loaded_marketplace_names.insert(marketplace_name.clone());
|
||||
|
||||
for plugin in curated_marketplace.plugins {
|
||||
let plugin_id =
|
||||
PluginId::new(plugin.name.clone(), marketplace_name.clone()).map_err(|err| {
|
||||
match err {
|
||||
PluginIdError::Invalid(message) => {
|
||||
format!("failed to prepare curated plugin cache refresh: {message}")
|
||||
}
|
||||
}
|
||||
})?;
|
||||
let plugin_key = plugin_id.as_key();
|
||||
marketplace_plugin_keys.insert(plugin_key.clone());
|
||||
if plugin_sources.contains_key(&plugin_key) {
|
||||
warn!(
|
||||
plugin = %plugin.name,
|
||||
marketplace = %marketplace_name,
|
||||
"ignoring duplicate curated plugin entry during cache refresh"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if let MarketplacePluginSource::Local { path } = plugin.source {
|
||||
plugin_sources.insert(plugin_key, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut cache_refreshed = false;
|
||||
for plugin_id in configured_curated_plugin_ids {
|
||||
if !marketplace_plugin_names.contains(&plugin_id.plugin_name) {
|
||||
let plugin_key = plugin_id.as_key();
|
||||
if !marketplace_plugin_keys.contains(&plugin_key) {
|
||||
if !loaded_marketplace_names.contains(&plugin_id.marketplace_name) {
|
||||
continue;
|
||||
}
|
||||
warn!(
|
||||
plugin = plugin_id.plugin_name,
|
||||
marketplace = OPENAI_CURATED_MARKETPLACE_NAME,
|
||||
plugin = %plugin_id.plugin_name,
|
||||
marketplace = %plugin_id.marketplace_name,
|
||||
"configured curated plugin no longer exists in curated marketplace during cache refresh"
|
||||
);
|
||||
if store.plugin_base_root(plugin_id).as_path().exists() {
|
||||
@@ -338,7 +355,7 @@ pub fn refresh_curated_plugin_cache(
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(source_path) = plugin_sources.get(&plugin_id.plugin_name).cloned() else {
|
||||
let Some(source_path) = plugin_sources.get(&plugin_key).cloned() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -361,6 +378,30 @@ pub fn refresh_curated_plugin_cache(
|
||||
Ok(cache_refreshed)
|
||||
}
|
||||
|
||||
fn curated_marketplace_paths_for_cache_refresh(
|
||||
codex_home: &Path,
|
||||
) -> Result<Vec<AbsolutePathBuf>, String> {
|
||||
let curated_marketplace_path = AbsolutePathBuf::try_from(
|
||||
codex_home
|
||||
.join(".tmp/plugins")
|
||||
.join(".agents/plugins/marketplace.json"),
|
||||
)
|
||||
.map_err(|_| "local curated marketplace is not available".to_string())?;
|
||||
let mut paths = vec![curated_marketplace_path];
|
||||
|
||||
let api_marketplace_path = codex_home
|
||||
.join(".tmp/plugins")
|
||||
.join(".agents/plugins/api_marketplace.json");
|
||||
if api_marketplace_path.is_file() {
|
||||
paths.push(
|
||||
AbsolutePathBuf::try_from(api_marketplace_path)
|
||||
.map_err(|_| "local API curated marketplace is not available".to_string())?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
pub fn curated_plugin_cache_version(plugin_version: &str) -> String {
|
||||
if is_full_git_sha(plugin_version) {
|
||||
plugin_version[..CURATED_PLUGIN_CACHE_VERSION_SHA_PREFIX_LEN].to_string()
|
||||
@@ -416,7 +457,7 @@ fn refresh_non_curated_plugin_cache_with_mode(
|
||||
let mut plugin_sources = HashMap::<String, MarketplacePluginSource>::new();
|
||||
|
||||
for marketplace in marketplace_outcome.marketplaces {
|
||||
if marketplace.name == OPENAI_CURATED_MARKETPLACE_NAME {
|
||||
if is_openai_curated_marketplace_name(&marketplace.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -570,7 +611,7 @@ fn curated_plugin_ids_from_config_keys(
|
||||
"ignoring invalid configured plugin key during curated sync setup",
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|plugin_id| plugin_id.marketplace_name == OPENAI_CURATED_MARKETPLACE_NAME)
|
||||
.filter(|plugin_id| is_openai_curated_marketplace_name(&plugin_id.marketplace_name))
|
||||
.collect::<Vec<_>>();
|
||||
configured_curated_plugin_ids.sort_unstable_by_key(PluginId::as_key);
|
||||
configured_curated_plugin_ids
|
||||
@@ -584,7 +625,7 @@ fn non_curated_plugin_ids_from_config_keys(
|
||||
"ignoring invalid plugin key during non-curated cache refresh setup",
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|plugin_id| plugin_id.marketplace_name != OPENAI_CURATED_MARKETPLACE_NAME)
|
||||
.filter(|plugin_id| !is_openai_curated_marketplace_name(&plugin_id.marketplace_name))
|
||||
.collect::<Vec<_>>();
|
||||
configured_non_curated_plugin_ids.sort_unstable_by_key(PluginId::as_key);
|
||||
configured_non_curated_plugin_ids
|
||||
|
||||
Reference in New Issue
Block a user