From 48ebbf5334cbb65dd77ef372b4a7da3b0041964c Mon Sep 17 00:00:00 2001 From: felixxia-oai Date: Thu, 23 Jul 2026 11:27:44 +0000 Subject: [PATCH] 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 --- .../app-server/tests/suite/v2/plugin_list.rs | 56 +++++++++++++++++++ codex-rs/core-plugins/src/manager.rs | 14 +++-- codex-rs/core-plugins/src/manager_tests.rs | 48 ++++++++++++++++ codex-rs/core-plugins/src/test_support.rs | 6 ++ codex-rs/core/src/config/mod.rs | 1 + 5 files changed, 121 insertions(+), 4 deletions(-) diff --git a/codex-rs/app-server/tests/suite/v2/plugin_list.rs b/codex-rs/app-server/tests/suite/v2/plugin_list.rs index a08361a411..0859c69395 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_list.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_list.rs @@ -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()?; diff --git a/codex-rs/core-plugins/src/manager.rs b/codex-rs/core-plugins/src/manager.rs index 3c9b47ec78..8893aa2a45 100644 --- a/codex-rs/core-plugins/src/manager.rs +++ b/codex-rs/core-plugins/src/manager.rs @@ -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 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 diff --git a/codex-rs/core-plugins/src/manager_tests.rs b/codex-rs/core-plugins/src/manager_tests.rs index 41cbd4b0bc..2928e72cae 100644 --- a/codex-rs/core-plugins/src/manager_tests.rs +++ b/codex-rs/core-plugins/src/manager_tests.rs @@ -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(); diff --git a/codex-rs/core-plugins/src/test_support.rs b/codex-rs/core-plugins/src/test_support.rs index da3a4aa670..962f66332c 100644 --- a/codex-rs/core-plugins/src/test_support.rs +++ b/codex-rs/core-plugins/src/test_support.rs @@ -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, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 4271f343f5..3d7387693a 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -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(),