Use the API plugin marketplace for Amazon Bedrock (#34931)

## Why

Amazon Bedrock configurations can operate without Codex auth, so auth mode alone
cannot determine which curated plugin marketplace to expose.

## What changed

- Pass the resolved model provider into plugin configuration.
- Select `openai-api-curated` when the resolved provider is `amazon-bedrock`,
  while retaining API-key auth as a selection signal.
- Cover provider overrides and app-server plugin listing without an `auth.json`
  file.

GitOrigin-RevId: 972a22e19c41490d8b19d989da052302dc30e8f2
This commit is contained in:
felixxia-oai
2026-07-23 11:32:47 +00:00
committed by copyberry
parent e497325a6a
commit 48ebbf5334
5 changed files with 121 additions and 4 deletions
@@ -2886,6 +2886,62 @@ async fn plugin_list_includes_api_curated_marketplace_for_api_auth_when_remote_p
Ok(())
}
#[tokio::test]
async fn plugin_list_includes_api_curated_marketplace_for_bedrock_without_codex_auth() -> Result<()>
{
let codex_home = TempDir::new()?;
std::fs::write(
codex_home.path().join("config.toml"),
r#"model_provider = "amazon-bedrock"
[model_providers.amazon-bedrock.aws]
region = "us-east-2"
profile = "default"
[features]
plugins = true
"#,
)?;
write_openai_curated_marketplace(codex_home.path(), &["chatgpt-plugin"])?;
write_openai_api_curated_marketplace(codex_home.path(), &["api-plugin"])?;
let mut mcp = TestAppServer::builder()
.with_codex_home(codex_home.path())
.without_auto_env()
.build_initialized_with_timeout(DEFAULT_TIMEOUT)
.await?;
let request_id = mcp
.send_plugin_list_request(PluginListParams {
cwds: None,
marketplace_kinds: None,
force_refetch: false,
})
.await?;
let response: PluginListResponse =
timeout(DEFAULT_TIMEOUT, mcp.read_response(request_id)).await??;
assert!(!codex_home.path().join("auth.json").exists());
let api_curated_marketplace = response
.marketplaces
.iter()
.find(|marketplace| marketplace.name == "openai-api-curated")
.expect("expected API curated marketplace");
assert_eq!(api_curated_marketplace.plugins.len(), 1);
assert_eq!(
api_curated_marketplace.plugins[0].id,
"api-plugin@openai-api-curated"
);
assert!(
response
.marketplaces
.iter()
.all(|marketplace| marketplace.name != "openai-curated")
);
assert!(response.marketplace_load_errors.is_empty());
Ok(())
}
#[tokio::test]
async fn plugin_list_does_not_query_openai_curated_remote_collection_by_default() -> Result<()> {
let codex_home = TempDir::new()?;
+10 -4
View File
@@ -74,6 +74,7 @@ use codex_hooks::plugin_hook_declarations;
use codex_http_client::HttpClientFactory;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_model_provider::AMAZON_BEDROCK_PROVIDER_ID;
use codex_plugin::AppConnectorId;
use codex_plugin::PluginCapabilitySummary;
use codex_plugin::PluginId;
@@ -117,6 +118,7 @@ type EffectivePluginsChangedCallback = Arc<dyn Fn(EffectivePluginsChange) + Send
#[derive(Debug, Clone)]
pub struct PluginsConfigInput {
pub config_layer_stack: ConfigLayerStack,
pub model_provider_id: String,
pub plugins_enabled: bool,
pub remote_plugin_enabled: bool,
pub chatgpt_base_url: String,
@@ -126,6 +128,7 @@ pub struct PluginsConfigInput {
impl PluginsConfigInput {
pub fn new(
config_layer_stack: ConfigLayerStack,
model_provider_id: String,
plugins_enabled: bool,
remote_plugin_enabled: bool,
chatgpt_base_url: String,
@@ -133,6 +136,7 @@ impl PluginsConfigInput {
) -> Self {
Self {
config_layer_stack,
model_provider_id,
plugins_enabled,
remote_plugin_enabled,
chatgpt_base_url,
@@ -2786,10 +2790,12 @@ impl PluginsManager {
self.codex_home.as_path(),
));
let curated_marketplace_path = if include_openai_curated {
if matches!(
self.auth_mode(),
Some(AuthMode::ApiKey | AuthMode::BedrockApiKey)
) {
if config.model_provider_id == AMAZON_BEDROCK_PROVIDER_ID
|| 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
@@ -108,6 +108,7 @@ fn plugins_config_input_with_requirements(
) -> PluginsConfigInput {
PluginsConfigInput::new(
config_layer_stack_with_requirements(codex_home, user_config, requirements),
String::new(),
/*plugins_enabled*/ true,
/*remote_plugin_enabled*/ false,
String::new(),
@@ -2625,6 +2626,7 @@ async fn plugin_cache_ignores_unrelated_session_overrides() {
let config = |session_config| {
PluginsConfigInput::new(
stack(session_config),
String::new(),
/*plugins_enabled*/ true,
/*remote_plugin_enabled*/ false,
"https://chatgpt.com".to_string(),
@@ -4410,6 +4412,52 @@ plugins = true
);
}
#[tokio::test]
async fn list_marketplaces_uses_resolved_provider_instead_of_configured_default() {
for (configured_provider, resolved_provider, expected_marketplace) in [
(
"openai",
AMAZON_BEDROCK_PROVIDER_ID,
OPENAI_API_CURATED_MARKETPLACE_NAME,
),
(
AMAZON_BEDROCK_PROVIDER_ID,
"openai",
OPENAI_CURATED_MARKETPLACE_NAME,
),
] {
let tmp = tempfile::tempdir().unwrap();
let curated_root = curated_plugins_repo_path(tmp.path());
write_file(
&tmp.path().join(CONFIG_TOML_FILE),
&format!(
r#"model_provider = "{configured_provider}"
[features]
plugins = true
"#
),
);
write_openai_curated_marketplace(&curated_root, &["chatgpt-plugin"]);
write_openai_api_curated_marketplace(&curated_root, &["api-plugin"]);
let mut config = load_config(tmp.path(), tmp.path()).await;
config.model_provider_id = resolved_provider.to_string();
let marketplaces = PluginsManager::new(tmp.path().to_path_buf())
.list_marketplaces_for_config(&config, &[], /*include_openai_curated*/ true)
.unwrap()
.marketplaces;
assert!(
marketplaces
.iter()
.any(|marketplace| marketplace.name == expected_marketplace),
"expected `{expected_marketplace}` for resolved provider `{resolved_provider}`"
);
}
}
#[tokio::test]
async fn list_marketplaces_skips_missing_api_curated_manifest() {
let tmp = tempfile::tempdir().unwrap();
@@ -213,8 +213,14 @@ pub(crate) async fn load_plugins_config(codex_home: &Path, cwd: &Path) -> Plugin
.await
.expect("config should load");
let effective_config = config_layer_stack.effective_config();
let model_provider_id = effective_config
.get("model_provider")
.and_then(toml::Value::as_str)
.unwrap_or_default()
.to_string();
PluginsConfigInput::new(
config_layer_stack,
model_provider_id,
feature_enabled(&effective_config, "plugins", /*default_enabled*/ true),
feature_enabled(
&effective_config,
+1
View File
@@ -1535,6 +1535,7 @@ impl Config {
pub fn plugins_config_input(&self) -> PluginsConfigInput {
PluginsConfigInput::new(
self.config_layer_stack.clone(),
self.model_provider_id.clone(),
self.features.enabled(Feature::Plugins),
self.features.enabled(Feature::RemotePlugin),
self.chatgpt_base_url.clone(),