mirror of
https://github.com/openai/codex.git
synced 2026-11-07 08:34:29 +00:00
Route curated plugin catalogs by authentication mode (#38429)
## Why The model provider does not reliably identify which curated plugin catalog is available. ChatGPT authentication can be used with a custom provider, while an unauthenticated session should use the API-compatible catalog regardless of its provider. ## What changed - Select the ChatGPT curated catalog only for authentication modes that use the Codex backend; use the API curated catalog for API-key and unauthenticated sessions. - Initialize standalone plugin managers with the current authentication mode across CLI, app-server, MCP, and external-agent migration paths. - Preserve authentication mode while detecting and importing migrated plugins. ## Testing - Cover catalog and skill routing across ChatGPT, API-key, unauthenticated, Bedrock, and custom-provider configurations. - Verify authenticated plugin migration uses the ChatGPT curated marketplace. GitOrigin-RevId: 660a339ee8891c33aad961078d3a979242a6a166
This commit is contained in:
1 parent
507ef0b371
commit
f898ebcafd
28 files changed
+348
-139
No files matched your search
@@ -98,6 +98,7 @@ impl ExternalAgentConfigRequestProcessor {
|
|||||||
} = args;
|
} = args;
|
||||||
let migration_service = ExternalAgentConfigService::new(
|
let migration_service = ExternalAgentConfigService::new(
|
||||||
codex_home.clone(),
|
codex_home.clone(),
|
||||||
|
thread_manager.plugins_manager().auth_mode(),
|
||||||
analytics_events_client.clone(),
|
analytics_events_client.clone(),
|
||||||
state_db.clone(),
|
state_db.clone(),
|
||||||
);
|
);
|
||||||
@@ -127,6 +128,7 @@ impl ExternalAgentConfigRequestProcessor {
|
|||||||
) -> Result<ExternalAgentConfigDetectResponse, JSONRPCErrorError> {
|
) -> Result<ExternalAgentConfigDetectResponse, JSONRPCErrorError> {
|
||||||
let migration_service = self
|
let migration_service = self
|
||||||
.migration_service
|
.migration_service
|
||||||
|
.with_auth_mode(self.thread_manager.plugins_manager().auth_mode())
|
||||||
.with_migration_source(params.migration_source.as_deref());
|
.with_migration_source(params.migration_source.as_deref());
|
||||||
let default_session_import_limits = ExternalAgentSessionImportLimits::default();
|
let default_session_import_limits = ExternalAgentSessionImportLimits::default();
|
||||||
let migration_service =
|
let migration_service =
|
||||||
@@ -198,6 +200,7 @@ impl ExternalAgentConfigRequestProcessor {
|
|||||||
let provider_id = params.provider_id.clone();
|
let provider_id = params.provider_id.clone();
|
||||||
let migration_service = self
|
let migration_service = self
|
||||||
.migration_service
|
.migration_service
|
||||||
|
.with_auth_mode(self.thread_manager.plugins_manager().auth_mode())
|
||||||
.with_migration_source(params.migration_source.as_deref());
|
.with_migration_source(params.migration_source.as_deref());
|
||||||
let needs_runtime_refresh = migration_items_need_runtime_refresh(¶ms.migration_items);
|
let needs_runtime_refresh = migration_items_need_runtime_refresh(¶ms.migration_items);
|
||||||
let has_migration_items = !params.migration_items.is_empty();
|
let has_migration_items = !params.migration_items.is_empty();
|
||||||
|
|||||||
@@ -162,17 +162,17 @@ async fn plugin_list_skips_invalid_marketplace_file_and_reports_error() -> Resul
|
|||||||
async fn plugin_installed_includes_installed_plugins_and_explicit_install_suggestions() -> Result<()>
|
async fn plugin_installed_includes_installed_plugins_and_explicit_install_suggestions() -> Result<()>
|
||||||
{
|
{
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
write_openai_curated_marketplace(
|
write_openai_api_curated_marketplace(
|
||||||
codex_home.path(),
|
codex_home.path(),
|
||||||
&["linear", "computer-use", "not-mentioned"],
|
&["linear", "computer-use", "not-mentioned"],
|
||||||
)?;
|
)?;
|
||||||
write_installed_plugin(&codex_home, "openai-curated", "linear")?;
|
write_installed_plugin(&codex_home, "openai-api-curated", "linear")?;
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
codex_home.path().join("config.toml"),
|
codex_home.path().join("config.toml"),
|
||||||
r#"[features]
|
r#"[features]
|
||||||
plugins = true
|
plugins = true
|
||||||
|
|
||||||
[plugins."linear@openai-curated"]
|
[plugins."linear@openai-api-curated"]
|
||||||
enabled = true
|
enabled = true
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
@@ -193,7 +193,7 @@ enabled = true
|
|||||||
timeout(DEFAULT_TIMEOUT, mcp.read_response(request_id)).await??;
|
timeout(DEFAULT_TIMEOUT, mcp.read_response(request_id)).await??;
|
||||||
|
|
||||||
assert_eq!(response.marketplaces.len(), 1);
|
assert_eq!(response.marketplaces.len(), 1);
|
||||||
assert_eq!(response.marketplaces[0].name, "openai-curated");
|
assert_eq!(response.marketplaces[0].name, "openai-api-curated");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
response.marketplaces[0]
|
response.marketplaces[0]
|
||||||
.plugins
|
.plugins
|
||||||
@@ -201,8 +201,8 @@ enabled = true
|
|||||||
.map(|plugin| (plugin.id.clone(), plugin.installed, plugin.enabled))
|
.map(|plugin| (plugin.id.clone(), plugin.installed, plugin.enabled))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![
|
vec![
|
||||||
("linear@openai-curated".to_string(), true, true),
|
("linear@openai-api-curated".to_string(), true, true),
|
||||||
("computer-use@openai-curated".to_string(), false, false),
|
("computer-use@openai-api-curated".to_string(), false, false),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(response.marketplace_load_errors, Vec::new());
|
assert_eq!(response.marketplace_load_errors, Vec::new());
|
||||||
@@ -4548,16 +4548,19 @@ remote_plugin = true
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn plugin_list_fetches_featured_plugin_ids_without_chatgpt_auth() -> Result<()> {
|
async fn plugin_list_omits_featured_plugin_ids_without_chatgpt_auth() -> Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
let server = MockServer::start().await;
|
let server = MockServer::start().await;
|
||||||
write_plugin_sync_config(codex_home.path(), &format!("{}/backend-api/", server.uri()))?;
|
write_plugin_sync_config(codex_home.path(), &format!("{}/backend-api/", server.uri()))?;
|
||||||
write_openai_curated_marketplace(codex_home.path(), &["linear", "gmail"])?;
|
write_openai_api_curated_marketplace(codex_home.path(), &["linear", "gmail"])?;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/backend-api/plugins/featured"))
|
.and(path("/backend-api/plugins/featured"))
|
||||||
.and(query_param("platform", "codex"))
|
.and(query_param("platform", "codex"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_string(r#"["linear@openai-curated"]"#))
|
.respond_with(
|
||||||
|
ResponseTemplate::new(200).set_body_string(r#"["linear@openai-api-curated"]"#),
|
||||||
|
)
|
||||||
|
.expect(0)
|
||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -4577,10 +4580,8 @@ async fn plugin_list_fetches_featured_plugin_ids_without_chatgpt_auth() -> Resul
|
|||||||
let response: PluginListResponse =
|
let response: PluginListResponse =
|
||||||
timeout(DEFAULT_TIMEOUT, mcp.read_response(request_id)).await??;
|
timeout(DEFAULT_TIMEOUT, mcp.read_response(request_id)).await??;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(response.featured_plugin_ids, Vec::<String>::new());
|
||||||
response.featured_plugin_ids,
|
assert_eq!(response.marketplaces[0].name, "openai-api-curated");
|
||||||
vec!["linear@openai-curated".to_string()]
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4590,6 +4591,7 @@ async fn plugin_list_uses_warmed_featured_plugin_ids_cache_on_first_request() ->
|
|||||||
let server = MockServer::start().await;
|
let server = MockServer::start().await;
|
||||||
write_plugin_sync_config(codex_home.path(), &format!("{}/backend-api/", server.uri()))?;
|
write_plugin_sync_config(codex_home.path(), &format!("{}/backend-api/", server.uri()))?;
|
||||||
write_openai_curated_marketplace(codex_home.path(), &["linear", "gmail"])?;
|
write_openai_curated_marketplace(codex_home.path(), &["linear", "gmail"])?;
|
||||||
|
write_remote_plugin_test_auth(codex_home.path())?;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/backend-api/plugins/featured"))
|
.and(path("/backend-api/plugins/featured"))
|
||||||
@@ -5290,6 +5292,7 @@ chatgpt_base_url = "{base_url}"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugins = true
|
plugins = true
|
||||||
|
remote_plugin = false
|
||||||
|
|
||||||
[plugins."linear@openai-curated"]
|
[plugins."linear@openai-curated"]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
|||||||
@@ -142,8 +142,13 @@ fn write_cached_remote_plugin_with_skill(
|
|||||||
Ok(skill_path)
|
Ok(skill_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_cached_local_curated_plugin_with_skill(codex_home: &std::path::Path) -> Result<()> {
|
fn write_cached_local_curated_plugin_with_skill(
|
||||||
let plugin_root = codex_home.join("plugins/cache/openai-curated/google-calendar/local");
|
codex_home: &std::path::Path,
|
||||||
|
marketplace_name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let plugin_root = codex_home.join(format!(
|
||||||
|
"plugins/cache/{marketplace_name}/google-calendar/local"
|
||||||
|
));
|
||||||
std::fs::create_dir_all(plugin_root.join(".codex-plugin"))?;
|
std::fs::create_dir_all(plugin_root.join(".codex-plugin"))?;
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
plugin_root.join(".codex-plugin/plugin.json"),
|
plugin_root.join(".codex-plugin/plugin.json"),
|
||||||
@@ -397,7 +402,7 @@ async fn runtime_remote_plugin_toggle_updates_local_curated_plugin_skills() -> R
|
|||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
let cwd = TempDir::new()?;
|
let cwd = TempDir::new()?;
|
||||||
let server = MockServer::start().await;
|
let server = MockServer::start().await;
|
||||||
write_cached_local_curated_plugin_with_skill(codex_home.path())?;
|
write_cached_local_curated_plugin_with_skill(codex_home.path(), "openai-curated")?;
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
codex_home.path().join("config.toml"),
|
codex_home.path().join("config.toml"),
|
||||||
format!(
|
format!(
|
||||||
@@ -842,13 +847,13 @@ async fn skills_list_preserves_requested_cwd_order() -> Result<()> {
|
|||||||
let first_cwd = TempDir::new()?;
|
let first_cwd = TempDir::new()?;
|
||||||
let second_cwd = TempDir::new()?;
|
let second_cwd = TempDir::new()?;
|
||||||
write_skill(&codex_home, "shared-skill")?;
|
write_skill(&codex_home, "shared-skill")?;
|
||||||
write_cached_local_curated_plugin_with_skill(codex_home.path())?;
|
write_cached_local_curated_plugin_with_skill(codex_home.path(), "openai-api-curated")?;
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
codex_home.path().join("config.toml"),
|
codex_home.path().join("config.toml"),
|
||||||
r#"[features]
|
r#"[features]
|
||||||
plugins = true
|
plugins = true
|
||||||
|
|
||||||
[plugins."google-calendar@openai-curated"]
|
[plugins."google-calendar@openai-api-curated"]
|
||||||
enabled = true
|
enabled = true
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
@@ -858,7 +863,9 @@ enabled = true
|
|||||||
std::fs::create_dir_all(cwd.join(".codex"))?;
|
std::fs::create_dir_all(cwd.join(".codex"))?;
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
cwd.join(".codex/config.toml"),
|
cwd.join(".codex/config.toml"),
|
||||||
format!("[plugins.\"google-calendar@openai-curated\"]\nenabled = {plugin_enabled}\n"),
|
format!(
|
||||||
|
"[plugins.\"google-calendar@openai-api-curated\"]\nenabled = {plugin_enabled}\n"
|
||||||
|
),
|
||||||
)?;
|
)?;
|
||||||
set_project_trust_level(codex_home.path(), cwd, TrustLevel::Trusted)?;
|
set_project_trust_level(codex_home.path(), cwd, TrustLevel::Trusted)?;
|
||||||
}
|
}
|
||||||
@@ -955,13 +962,13 @@ async fn skills_list_force_reload_refreshes_cached_plugin_roots() -> Result<()>
|
|||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
let first_cwd = TempDir::new()?;
|
let first_cwd = TempDir::new()?;
|
||||||
let second_cwd = TempDir::new()?;
|
let second_cwd = TempDir::new()?;
|
||||||
write_cached_local_curated_plugin_with_skill(codex_home.path())?;
|
write_cached_local_curated_plugin_with_skill(codex_home.path(), "openai-api-curated")?;
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
codex_home.path().join("config.toml"),
|
codex_home.path().join("config.toml"),
|
||||||
r#"[features]
|
r#"[features]
|
||||||
plugins = true
|
plugins = true
|
||||||
|
|
||||||
[plugins."google-calendar@openai-curated"]
|
[plugins."google-calendar@openai-api-curated"]
|
||||||
enabled = true
|
enabled = true
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
@@ -991,7 +998,7 @@ enabled = true
|
|||||||
if force_reload {
|
if force_reload {
|
||||||
let plugin_root = codex_home
|
let plugin_root = codex_home
|
||||||
.path()
|
.path()
|
||||||
.join("plugins/cache/openai-curated/google-calendar/local");
|
.join("plugins/cache/openai-api-curated/google-calendar/local");
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
plugin_root.join(".codex-plugin/plugin.json"),
|
plugin_root.join(".codex-plugin/plugin.json"),
|
||||||
r#"{"name":"google-calendar","skills":"./replacement-skills"}"#,
|
r#"{"name":"google-calendar","skills":"./replacement-skills"}"#,
|
||||||
|
|||||||
@@ -4414,7 +4414,7 @@ async fn command_execution_notifications_include_trusted_plugin_id() -> Result<(
|
|||||||
let curated_sha = "0123456789abcdef0123456789abcdef01234567";
|
let curated_sha = "0123456789abcdef0123456789abcdef01234567";
|
||||||
let plugin_root = codex_home
|
let plugin_root = codex_home
|
||||||
.path()
|
.path()
|
||||||
.join("plugins/cache/openai-curated/google-calendar/01234567");
|
.join("plugins/cache/openai-api-curated/google-calendar/01234567");
|
||||||
let script_path = plugin_root.join("scripts/run.sh");
|
let script_path = plugin_root.join("scripts/run.sh");
|
||||||
let synced_root = codex_home.path().join(".tmp/plugins");
|
let synced_root = codex_home.path().join(".tmp/plugins");
|
||||||
for path in [
|
for path in [
|
||||||
@@ -4437,9 +4437,9 @@ async fn command_execution_notifications_include_trusted_plugin_id() -> Result<(
|
|||||||
format!("{curated_sha}\n"),
|
format!("{curated_sha}\n"),
|
||||||
)?;
|
)?;
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
synced_root.join(".agents/plugins/marketplace.json"),
|
synced_root.join(".agents/plugins/api_marketplace.json"),
|
||||||
r#"{
|
r#"{
|
||||||
"name": "openai-curated",
|
"name": "openai-api-curated",
|
||||||
"plugins": [{
|
"plugins": [{
|
||||||
"name": "google-calendar",
|
"name": "google-calendar",
|
||||||
"source": {"source": "local", "path": "./plugins/google-calendar"}
|
"source": {"source": "local", "path": "./plugins/google-calendar"}
|
||||||
@@ -4464,7 +4464,7 @@ async fn command_execution_notifications_include_trusted_plugin_id() -> Result<(
|
|||||||
.with_sandbox_mode("danger-full-access")
|
.with_sandbox_mode("danger-full-access")
|
||||||
.enable_feature(Feature::Plugins)
|
.enable_feature(Feature::Plugins)
|
||||||
.disable_feature(Feature::RemotePlugin)
|
.disable_feature(Feature::RemotePlugin)
|
||||||
.with_extra_config("[plugins.\"google-calendar@openai-curated\"]\nenabled = true")
|
.with_extra_config("[plugins.\"google-calendar@openai-api-curated\"]\nenabled = true")
|
||||||
.write(codex_home.path())?;
|
.write(codex_home.path())?;
|
||||||
|
|
||||||
let mut mcp = TestAppServer::builder()
|
let mut mcp = TestAppServer::builder()
|
||||||
@@ -4506,7 +4506,7 @@ async fn command_execution_notifications_include_trusted_plugin_id() -> Result<(
|
|||||||
.expect("command execution item should include scriptPath");
|
.expect("command execution item should include scriptPath");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(item_json["pluginId"].as_str(), emitted_script_path),
|
(item_json["pluginId"].as_str(), emitted_script_path),
|
||||||
(Some("google-calendar@openai-curated"), "scripts/run.sh")
|
(Some("google-calendar@openai-api-curated"), "scripts/run.sh")
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
!emitted_script_path.contains(script_path.to_string_lossy().as_ref()),
|
!emitted_script_path.contains(script_path.to_string_lossy().as_ref()),
|
||||||
|
|||||||
@@ -211,8 +211,7 @@ async fn run_list(overrides: Vec<(String, toml::Value)>, args: ListMarketplaceAr
|
|||||||
let config = Config::load_with_cli_overrides(overrides)
|
let config = Config::load_with_cli_overrides(overrides)
|
||||||
.await
|
.await
|
||||||
.context("failed to load configuration")?;
|
.context("failed to load configuration")?;
|
||||||
let manager = plugins_manager_for_config(&config);
|
let manager = plugins_manager_for_config(&config, load_cli_auth_mode(&config).await?);
|
||||||
manager.set_auth_mode(load_cli_auth_mode(&config).await?);
|
|
||||||
let plugins_input = config.plugins_config_input();
|
let plugins_input = config.plugins_config_input();
|
||||||
let marketplace_listing = manager
|
let marketplace_listing = manager
|
||||||
.discover_marketplaces_for_config(&plugins_input, &[])
|
.discover_marketplaces_for_config(&plugins_input, &[])
|
||||||
@@ -378,7 +377,7 @@ async fn run_upgrade(
|
|||||||
let config = Config::load_with_cli_overrides(overrides)
|
let config = Config::load_with_cli_overrides(overrides)
|
||||||
.await
|
.await
|
||||||
.context("failed to load configuration")?;
|
.context("failed to load configuration")?;
|
||||||
let manager = plugins_manager_for_config(&config);
|
let manager = plugins_manager_for_config(&config, load_cli_auth_mode(&config).await?);
|
||||||
let plugins_input = config.plugins_config_input();
|
let plugins_input = config.plugins_config_input();
|
||||||
let outcome = manager
|
let outcome = manager
|
||||||
.upgrade_configured_marketplaces_for_config(&plugins_input, marketplace_name.as_deref())
|
.upgrade_configured_marketplaces_for_config(&plugins_input, marketplace_name.as_deref())
|
||||||
|
|||||||
@@ -524,8 +524,10 @@ async fn run_remove(config_overrides: &CliConfigOverrides, remove_args: RemoveAr
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn load_mcp_manager(config: &Config) -> Result<McpManager> {
|
async fn load_mcp_manager(config: &Config) -> Result<McpManager> {
|
||||||
let plugins_manager = Arc::new(plugins_manager_for_config(config));
|
let plugins_manager = Arc::new(plugins_manager_for_config(
|
||||||
plugins_manager.set_auth_mode(load_cli_auth_mode(config).await?);
|
config,
|
||||||
|
load_cli_auth_mode(config).await?,
|
||||||
|
));
|
||||||
Ok(McpManager::new(plugins_manager))
|
Ok(McpManager::new(plugins_manager))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -590,8 +590,7 @@ async fn load_plugin_command_context(
|
|||||||
.await
|
.await
|
||||||
.context("failed to load configuration")?;
|
.context("failed to load configuration")?;
|
||||||
let plugins_input = config.plugins_config_input();
|
let plugins_input = config.plugins_config_input();
|
||||||
let manager = plugins_manager_for_config(&config);
|
let manager = plugins_manager_for_config(&config, load_cli_auth_mode(&config).await?);
|
||||||
manager.set_auth_mode(load_cli_auth_mode(&config).await?);
|
|
||||||
Ok(PluginCommandContext {
|
Ok(PluginCommandContext {
|
||||||
codex_home: codex_home.to_path_buf(),
|
codex_home: codex_home.to_path_buf(),
|
||||||
plugins_input,
|
plugins_input,
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ async fn returns_microsoft_fallback_plugins() {
|
|||||||
|
|
||||||
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
||||||
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
||||||
|
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
let discoverable_plugins = list_discoverable_plugins(
|
let discoverable_plugins = list_discoverable_plugins(
|
||||||
&plugins_manager,
|
&plugins_manager,
|
||||||
discovery_input(plugins, &[], &[], &[]),
|
discovery_input(plugins, &[], &[], &[]),
|
||||||
@@ -190,10 +191,10 @@ source = "/tmp/{bundled_marketplace_name}"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn includes_openai_curated_when_remote_enabled_without_auth() {
|
async fn includes_openai_api_curated_when_remote_enabled_without_auth() {
|
||||||
let codex_home = tempdir().expect("tempdir should succeed");
|
let codex_home = tempdir().expect("tempdir should succeed");
|
||||||
let curated_root = curated_plugins_repo_path(codex_home.path());
|
let curated_root = curated_plugins_repo_path(codex_home.path());
|
||||||
write_openai_curated_marketplace(&curated_root, &["slack"]);
|
write_openai_api_curated_marketplace(&curated_root, &["slack"]);
|
||||||
|
|
||||||
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
||||||
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
||||||
@@ -209,7 +210,7 @@ async fn includes_openai_curated_when_remote_enabled_without_auth() {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|plugin| plugin.id)
|
.map(|plugin| plugin.id)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec!["slack@openai-curated".to_string()]
|
vec!["slack@openai-api-curated".to_string()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,6 +301,7 @@ async fn reprojects_cached_skill_availability_for_current_config() {
|
|||||||
|
|
||||||
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
||||||
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
||||||
|
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
let expected = ToolSuggestDiscoverablePlugin {
|
let expected = ToolSuggestDiscoverablePlugin {
|
||||||
id: "slack@openai-curated".to_string(),
|
id: "slack@openai-curated".to_string(),
|
||||||
remote_plugin_id: None,
|
remote_plugin_id: None,
|
||||||
@@ -354,6 +356,7 @@ async fn does_not_advertise_skills_when_skill_loading_fails() {
|
|||||||
|
|
||||||
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
||||||
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
||||||
|
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
let discoverable_plugins = list_discoverable_plugins(
|
let discoverable_plugins = list_discoverable_plugins(
|
||||||
&plugins_manager,
|
&plugins_manager,
|
||||||
discovery_input(plugins, &[], &[], &[]),
|
discovery_input(plugins, &[], &[], &[]),
|
||||||
@@ -393,6 +396,7 @@ async fn clear_cache_invalidates_cached_tool_suggest_metadata() {
|
|||||||
|
|
||||||
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
||||||
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
||||||
|
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
let input = discovery_input(plugins, &[], &[], &[]);
|
let input = discovery_input(plugins, &[], &[], &[]);
|
||||||
let expected_cached = vec![ToolSuggestDiscoverablePlugin {
|
let expected_cached = vec![ToolSuggestDiscoverablePlugin {
|
||||||
id: "slack@openai-curated".to_string(),
|
id: "slack@openai-curated".to_string(),
|
||||||
@@ -465,6 +469,7 @@ source = "/tmp/{marketplace_name}"
|
|||||||
|
|
||||||
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
||||||
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
||||||
|
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
let discoverable_plugins = list_discoverable_plugins(
|
let discoverable_plugins = list_discoverable_plugins(
|
||||||
&plugins_manager,
|
&plugins_manager,
|
||||||
discovery_input(plugins, &[], &[], &[]),
|
discovery_input(plugins, &[], &[], &[]),
|
||||||
@@ -492,6 +497,7 @@ async fn normalizes_description() {
|
|||||||
|
|
||||||
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
||||||
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
||||||
|
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
let discoverable_plugins = list_discoverable_plugins(
|
let discoverable_plugins = list_discoverable_plugins(
|
||||||
&plugins_manager,
|
&plugins_manager,
|
||||||
discovery_input(plugins, &[], &[], &[]),
|
discovery_input(plugins, &[], &[], &[]),
|
||||||
@@ -576,6 +582,7 @@ async fn omits_not_available_curated_plugins() {
|
|||||||
|
|
||||||
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
||||||
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
||||||
|
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
let discoverable_plugins = list_discoverable_plugins(
|
let discoverable_plugins = list_discoverable_plugins(
|
||||||
&plugins_manager,
|
&plugins_manager,
|
||||||
discovery_input(plugins, &[], &[], &[]),
|
discovery_input(plugins, &[], &[], &[]),
|
||||||
@@ -617,6 +624,7 @@ async fn does_not_reload_marketplace_per_plugin() {
|
|||||||
|
|
||||||
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
|
||||||
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
|
||||||
|
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
let buffer: &'static std::sync::Mutex<Vec<u8>> =
|
let buffer: &'static std::sync::Mutex<Vec<u8>> =
|
||||||
Box::leak(Box::new(std::sync::Mutex::new(Vec::new())));
|
Box::leak(Box::new(std::sync::Mutex::new(Vec::new())));
|
||||||
let subscriber = tracing_subscriber::fmt()
|
let subscriber = tracing_subscriber::fmt()
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ use codex_hooks::plugin_hook_declarations;
|
|||||||
use codex_http_client::HttpClientFactory;
|
use codex_http_client::HttpClientFactory;
|
||||||
use codex_login::AuthManager;
|
use codex_login::AuthManager;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
use codex_model_provider::AMAZON_BEDROCK_PROVIDER_ID;
|
|
||||||
use codex_plugin::AppConnectorId;
|
use codex_plugin::AppConnectorId;
|
||||||
use codex_plugin::PluginCapabilitySummary;
|
use codex_plugin::PluginCapabilitySummary;
|
||||||
use codex_plugin::PluginId;
|
use codex_plugin::PluginId;
|
||||||
@@ -491,35 +490,24 @@ impl PluginLoadCacheKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn target_curated_marketplace(
|
fn target_curated_marketplace(auth_mode: Option<AuthMode>) -> TargetCuratedMarketplace {
|
||||||
auth_mode: Option<AuthMode>,
|
if auth_mode.is_some_and(AuthMode::uses_codex_backend) {
|
||||||
model_provider_id: &str,
|
TargetCuratedMarketplace::OpenAiWithRemote
|
||||||
) -> TargetCuratedMarketplace {
|
} else {
|
||||||
match auth_mode {
|
TargetCuratedMarketplace::OpenAiApi
|
||||||
Some(auth_mode) => {
|
|
||||||
if auth_mode.uses_codex_backend() {
|
|
||||||
TargetCuratedMarketplace::OpenAiWithRemote
|
|
||||||
} else {
|
|
||||||
TargetCuratedMarketplace::OpenAiApi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Bedrock can use ambient AWS credentials without producing a stored auth mode.
|
|
||||||
None if model_provider_id == AMAZON_BEDROCK_PROVIDER_ID => {
|
|
||||||
TargetCuratedMarketplace::OpenAiApi
|
|
||||||
}
|
|
||||||
None => TargetCuratedMarketplace::OpenAi,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PluginsManager {
|
impl PluginsManager {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
codex_home: PathBuf,
|
codex_home: PathBuf,
|
||||||
|
auth_mode: Option<AuthMode>,
|
||||||
skill_root_loader: Arc<dyn SkillRootLoader<PluginSkillRoot>>,
|
skill_root_loader: Arc<dyn SkillRootLoader<PluginSkillRoot>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new_with_options(
|
Self::new_with_options(
|
||||||
codex_home,
|
codex_home,
|
||||||
Some(Product::Codex),
|
Some(Product::Codex),
|
||||||
/*auth_mode*/ None,
|
auth_mode,
|
||||||
skill_root_loader,
|
skill_root_loader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -683,7 +671,7 @@ impl PluginsManager {
|
|||||||
remote_global_catalog_active,
|
remote_global_catalog_active,
|
||||||
);
|
);
|
||||||
if !force_reload && let Some(plugins) = self.cached_loaded_plugins(&cache_key) {
|
if !force_reload && let Some(plugins) = self.cached_loaded_plugins(&cache_key) {
|
||||||
return self.resolve_loaded_plugins_for_auth(plugins, &config.model_provider_id);
|
return self.resolve_loaded_plugins_for_auth(plugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok(_load_permit) = self.loaded_plugins_load_semaphore.acquire().await else {
|
let Ok(_load_permit) = self.loaded_plugins_load_semaphore.acquire().await else {
|
||||||
@@ -691,7 +679,7 @@ impl PluginsManager {
|
|||||||
return PluginLoadOutcome::default();
|
return PluginLoadOutcome::default();
|
||||||
};
|
};
|
||||||
if !force_reload && let Some(plugins) = self.cached_loaded_plugins(&cache_key) {
|
if !force_reload && let Some(plugins) = self.cached_loaded_plugins(&cache_key) {
|
||||||
return self.resolve_loaded_plugins_for_auth(plugins, &config.model_provider_id);
|
return self.resolve_loaded_plugins_for_auth(plugins);
|
||||||
}
|
}
|
||||||
let cache_generation = self.loaded_plugins_cache_generation();
|
let cache_generation = self.loaded_plugins_cache_generation();
|
||||||
let plugin_skill_snapshots = new_plugin_skill_snapshots();
|
let plugin_skill_snapshots = new_plugin_skill_snapshots();
|
||||||
@@ -712,16 +700,12 @@ impl PluginsManager {
|
|||||||
plugins.clone(),
|
plugins.clone(),
|
||||||
plugin_skill_snapshots,
|
plugin_skill_snapshots,
|
||||||
);
|
);
|
||||||
self.resolve_loaded_plugins_for_auth(plugins, &config.model_provider_id)
|
self.resolve_loaded_plugins_for_auth(plugins)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_loaded_plugins_for_auth(
|
fn resolve_loaded_plugins_for_auth(&self, mut plugins: Vec<LoadedPlugin>) -> PluginLoadOutcome {
|
||||||
&self,
|
|
||||||
mut plugins: Vec<LoadedPlugin>,
|
|
||||||
model_provider_id: &str,
|
|
||||||
) -> PluginLoadOutcome {
|
|
||||||
let auth_mode = self.auth_mode();
|
let auth_mode = self.auth_mode();
|
||||||
let target_curated_marketplace = target_curated_marketplace(auth_mode, model_provider_id);
|
let target_curated_marketplace = target_curated_marketplace(auth_mode);
|
||||||
plugins.retain(|plugin| {
|
plugins.retain(|plugin| {
|
||||||
plugin_is_eligible_for_target_marketplace(
|
plugin_is_eligible_for_target_marketplace(
|
||||||
&plugin.config_name,
|
&plugin.config_name,
|
||||||
@@ -796,8 +780,7 @@ impl PluginsManager {
|
|||||||
if !config.plugins_enabled {
|
if !config.plugins_enabled {
|
||||||
return PluginHookLoadOutcome::default();
|
return PluginHookLoadOutcome::default();
|
||||||
}
|
}
|
||||||
let target_curated_marketplace =
|
let target_curated_marketplace = target_curated_marketplace(self.auth_mode());
|
||||||
target_curated_marketplace(self.auth_mode(), &config.model_provider_id);
|
|
||||||
load_plugin_hooks_from_layer_stack(
|
load_plugin_hooks_from_layer_stack(
|
||||||
config_layer_stack,
|
config_layer_stack,
|
||||||
self.remote_installed_plugin_configs(),
|
self.remote_installed_plugin_configs(),
|
||||||
@@ -2909,7 +2892,7 @@ impl PluginsManager {
|
|||||||
self.codex_home.as_path(),
|
self.codex_home.as_path(),
|
||||||
));
|
));
|
||||||
let curated_marketplace_path = if include_openai_curated {
|
let curated_marketplace_path = if include_openai_curated {
|
||||||
match target_curated_marketplace(self.auth_mode(), &config.model_provider_id) {
|
match target_curated_marketplace(self.auth_mode()) {
|
||||||
TargetCuratedMarketplace::OpenAi | TargetCuratedMarketplace::OpenAiWithRemote => {
|
TargetCuratedMarketplace::OpenAi | TargetCuratedMarketplace::OpenAiWithRemote => {
|
||||||
let curated_repo_root = curated_plugins_repo_path(self.codex_home.as_path());
|
let curated_repo_root = curated_plugins_repo_path(self.codex_home.as_path());
|
||||||
curated_repo_root.is_dir().then_some(curated_repo_root)
|
curated_repo_root.is_dir().then_some(curated_repo_root)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ use codex_config::SkillConfigRules;
|
|||||||
use codex_config::compose_requirements;
|
use codex_config::compose_requirements;
|
||||||
use codex_config::types::McpServerTransportConfig;
|
use codex_config::types::McpServerTransportConfig;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
|
use codex_model_provider::AMAZON_BEDROCK_PROVIDER_ID;
|
||||||
use codex_plugin::AppDeclaration;
|
use codex_plugin::AppDeclaration;
|
||||||
use codex_plugin::PluginId;
|
use codex_plugin::PluginId;
|
||||||
use codex_protocol::auth::AuthMode;
|
use codex_protocol::auth::AuthMode;
|
||||||
@@ -1637,7 +1638,7 @@ enabled = true
|
|||||||
manager.set_auth_mode(/*auth_mode*/ None);
|
manager.set_auth_mode(/*auth_mode*/ None);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
loaded_plugin_names(&manager, &config).await,
|
loaded_plugin_names(&manager, &config).await,
|
||||||
vec!["linear@openai-curated".to_string()]
|
vec!["linear@openai-api-curated".to_string()]
|
||||||
);
|
);
|
||||||
|
|
||||||
for auth_mode in [AuthMode::ApiKey, AuthMode::BedrockApiKey] {
|
for auth_mode in [AuthMode::ApiKey, AuthMode::BedrockApiKey] {
|
||||||
@@ -1650,11 +1651,13 @@ enabled = true
|
|||||||
}
|
}
|
||||||
|
|
||||||
manager.set_auth_mode(/*auth_mode*/ None);
|
manager.set_auth_mode(/*auth_mode*/ None);
|
||||||
config.model_provider_id = AMAZON_BEDROCK_PROVIDER_ID.to_string();
|
for model_provider_id in ["openai", AMAZON_BEDROCK_PROVIDER_ID, "ollama"] {
|
||||||
assert_eq!(
|
config.model_provider_id = model_provider_id.to_string();
|
||||||
loaded_plugin_names(&manager, &config).await,
|
assert_eq!(
|
||||||
vec!["linear@openai-api-curated".to_string()]
|
loaded_plugin_names(&manager, &config).await,
|
||||||
);
|
vec!["linear@openai-api-curated".to_string()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
manager.set_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -4679,10 +4682,14 @@ plugins = true
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let config = load_config(tmp.path(), tmp.path()).await;
|
let config = load_config(tmp.path(), tmp.path()).await;
|
||||||
let marketplaces = test_plugins_manager(tmp.path().to_path_buf())
|
let marketplaces = test_plugins_manager_with_options(
|
||||||
.list_marketplaces_for_config(&config, &[], /*include_openai_curated*/ true)
|
tmp.path().to_path_buf(),
|
||||||
.unwrap()
|
Some(Product::Codex),
|
||||||
.marketplaces;
|
Some(AuthMode::Chatgpt),
|
||||||
|
)
|
||||||
|
.list_marketplaces_for_config(&config, &[], /*include_openai_curated*/ true)
|
||||||
|
.unwrap()
|
||||||
|
.marketplaces;
|
||||||
|
|
||||||
let curated_marketplace = marketplaces
|
let curated_marketplace = marketplaces
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -4843,16 +4850,36 @@ plugins = true
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn list_marketplaces_uses_resolved_provider_instead_of_configured_default() {
|
async fn list_marketplaces_selects_curated_catalog_only_from_authentication() {
|
||||||
for (configured_provider, resolved_provider, expected_marketplace) in [
|
for (configured_provider, resolved_provider, auth_mode, expected_marketplace) in [
|
||||||
(
|
(
|
||||||
"openai",
|
"openai",
|
||||||
AMAZON_BEDROCK_PROVIDER_ID,
|
AMAZON_BEDROCK_PROVIDER_ID,
|
||||||
|
None,
|
||||||
OPENAI_API_CURATED_MARKETPLACE_NAME,
|
OPENAI_API_CURATED_MARKETPLACE_NAME,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
AMAZON_BEDROCK_PROVIDER_ID,
|
AMAZON_BEDROCK_PROVIDER_ID,
|
||||||
"openai",
|
"openai",
|
||||||
|
None,
|
||||||
|
OPENAI_API_CURATED_MARKETPLACE_NAME,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"openai",
|
||||||
|
"ollama",
|
||||||
|
None,
|
||||||
|
OPENAI_API_CURATED_MARKETPLACE_NAME,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"openai",
|
||||||
|
"ollama",
|
||||||
|
Some(AuthMode::ApiKey),
|
||||||
|
OPENAI_API_CURATED_MARKETPLACE_NAME,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"openai",
|
||||||
|
"ollama",
|
||||||
|
Some(AuthMode::Chatgpt),
|
||||||
OPENAI_CURATED_MARKETPLACE_NAME,
|
OPENAI_CURATED_MARKETPLACE_NAME,
|
||||||
),
|
),
|
||||||
] {
|
] {
|
||||||
@@ -4874,16 +4901,22 @@ plugins = true
|
|||||||
|
|
||||||
let mut config = load_config(tmp.path(), tmp.path()).await;
|
let mut config = load_config(tmp.path(), tmp.path()).await;
|
||||||
config.model_provider_id = resolved_provider.to_string();
|
config.model_provider_id = resolved_provider.to_string();
|
||||||
let marketplaces = test_plugins_manager(tmp.path().to_path_buf())
|
let marketplaces = test_plugins_manager_with_options(
|
||||||
.list_marketplaces_for_config(&config, &[], /*include_openai_curated*/ true)
|
tmp.path().to_path_buf(),
|
||||||
.unwrap()
|
Some(Product::Codex),
|
||||||
.marketplaces;
|
auth_mode,
|
||||||
|
)
|
||||||
|
.list_marketplaces_for_config(&config, &[], /*include_openai_curated*/ true)
|
||||||
|
.unwrap()
|
||||||
|
.marketplaces;
|
||||||
|
|
||||||
assert!(
|
assert_eq!(
|
||||||
marketplaces
|
marketplaces
|
||||||
.iter()
|
.iter()
|
||||||
.any(|marketplace| marketplace.name == expected_marketplace),
|
.map(|marketplace| marketplace.name.as_str())
|
||||||
"expected `{expected_marketplace}` for resolved provider `{resolved_provider}`"
|
.collect::<Vec<_>>(),
|
||||||
|
vec![expected_marketplace],
|
||||||
|
"unexpected curated catalog for provider `{resolved_provider}` with auth {auth_mode:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ pub(crate) const TEST_CURATED_PLUGIN_SHA: &str = "0123456789abcdef0123456789abcd
|
|||||||
pub(crate) const TEST_CURATED_PLUGIN_CACHE_VERSION: &str = "01234567";
|
pub(crate) const TEST_CURATED_PLUGIN_CACHE_VERSION: &str = "01234567";
|
||||||
|
|
||||||
pub(crate) fn test_plugins_manager(codex_home: PathBuf) -> PluginsManager {
|
pub(crate) fn test_plugins_manager(codex_home: PathBuf) -> PluginsManager {
|
||||||
PluginsManager::new(codex_home, test_skill_root_loader())
|
PluginsManager::new(
|
||||||
|
codex_home,
|
||||||
|
/*auth_mode*/ None,
|
||||||
|
test_skill_root_loader(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn test_plugins_manager_with_options(
|
pub(crate) fn test_plugins_manager_with_options(
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ enabled = false
|
|||||||
.await
|
.await
|
||||||
.expect("custom role should apply");
|
.expect("custom role should apply");
|
||||||
|
|
||||||
let plugins_manager = Arc::new(plugins_manager_for_config(&config));
|
let plugins_manager = Arc::new(plugins_manager_for_config(&config, /*auth_mode*/ None));
|
||||||
let skills_service =
|
let skills_service =
|
||||||
HostSkillsService::new(home.path().abs(), /*bundled_skills_enabled*/ true);
|
HostSkillsService::new(home.path().abs(), /*bundled_skills_enabled*/ true);
|
||||||
let plugins_input = config.plugins_config_input();
|
let plugins_input = config.plugins_config_input();
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ async fn load_config_resolves_non_prefixed_mcp_tool_servers() -> std::io::Result
|
|||||||
|
|
||||||
assert_eq!(config.non_prefixed_mcp_tool_servers, expected_servers);
|
assert_eq!(config.non_prefixed_mcp_tool_servers, expected_servers);
|
||||||
assert_eq!(config.prefix_mcp_tool_names(), expected_prefix);
|
assert_eq!(config.prefix_mcp_tool_names(), expected_prefix);
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
||||||
assert_eq!(mcp_config.prefix_mcp_tool_names, expected_prefix);
|
assert_eq!(mcp_config.prefix_mcp_tool_names, expected_prefix);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -5234,7 +5234,7 @@ async fn rebuild_preserving_session_layers_refreshes_plugin_derived_mcp_config()
|
|||||||
let config = thread_config
|
let config = thread_config
|
||||||
.rebuild_preserving_session_layers(&refreshed_config)
|
.rebuild_preserving_session_layers(&refreshed_config)
|
||||||
.await?;
|
.await?;
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
||||||
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
|
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
|
||||||
|
|
||||||
@@ -5296,7 +5296,7 @@ enabled = true
|
|||||||
.codex_home(codex_home.path().to_path_buf())
|
.codex_home(codex_home.path().to_path_buf())
|
||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
||||||
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
|
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
|
||||||
|
|
||||||
@@ -5364,7 +5364,7 @@ url = "https://sample.example/mcp"
|
|||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
||||||
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
|
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
|
||||||
|
|
||||||
@@ -5466,7 +5466,7 @@ enabled = true
|
|||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
||||||
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
|
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
|
||||||
|
|
||||||
@@ -6483,7 +6483,7 @@ async fn to_mcp_config_preserves_apps_feature_from_config() -> std::io::Result<(
|
|||||||
codex_home.abs(),
|
codex_home.abs(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
|
|
||||||
config.apps_mcp_product_sku = Some("tpp".to_string());
|
config.apps_mcp_product_sku = Some("tpp".to_string());
|
||||||
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
||||||
@@ -6510,7 +6510,7 @@ async fn to_mcp_config_flows_mcp_tool_prefix_from_feature() -> std::io::Result<(
|
|||||||
codex_home.abs(),
|
codex_home.abs(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
|
|
||||||
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
||||||
assert!(mcp_config.prefix_mcp_tool_names);
|
assert!(mcp_config.prefix_mcp_tool_names);
|
||||||
@@ -6546,7 +6546,7 @@ async fn to_mcp_config_flows_mcp_2026_feature_from_config() -> std::io::Result<(
|
|||||||
codex_home.abs(),
|
codex_home.abs(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
|
|
||||||
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
||||||
assert_eq!(mcp_config.protocol_mode, codex_mcp::McpProtocolMode::Legacy);
|
assert_eq!(mcp_config.protocol_mode, codex_mcp::McpProtocolMode::Legacy);
|
||||||
@@ -6570,7 +6570,7 @@ async fn to_mcp_config_preserves_auth_elicitation_feature_from_config() -> std::
|
|||||||
codex_home.abs(),
|
codex_home.abs(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
|
|
||||||
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
let mcp_config = config.to_mcp_config(&plugins_manager).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -189,7 +189,13 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
|
|||||||
force_refetch: bool,
|
force_refetch: bool,
|
||||||
environment_manager: Arc<EnvironmentManager>,
|
environment_manager: Arc<EnvironmentManager>,
|
||||||
) -> anyhow::Result<AccessibleConnectorsStatus> {
|
) -> anyhow::Result<AccessibleConnectorsStatus> {
|
||||||
let plugins_manager = Arc::new(plugins_manager_for_config(config));
|
let auth_manager =
|
||||||
|
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await?;
|
||||||
|
let auth = auth_manager.auth().await;
|
||||||
|
let plugins_manager = Arc::new(plugins_manager_for_config(
|
||||||
|
config,
|
||||||
|
auth.as_ref().map(CodexAuth::api_auth_mode),
|
||||||
|
));
|
||||||
let mcp_manager = Arc::new(McpManager::new(plugins_manager));
|
let mcp_manager = Arc::new(McpManager::new(plugins_manager));
|
||||||
list_accessible_connectors_from_mcp_tools_with_mcp_manager(
|
list_accessible_connectors_from_mcp_tools_with_mcp_manager(
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ discoverables = [
|
|||||||
.await
|
.await
|
||||||
.expect("config should load");
|
.expect("config should load");
|
||||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, Some(auth.api_auth_mode()));
|
||||||
|
|
||||||
let discoverable_tools = list_tool_suggest_discoverable_tools_with_auth(
|
let discoverable_tools = list_tool_suggest_discoverable_tools_with_auth(
|
||||||
&config,
|
&config,
|
||||||
@@ -575,7 +575,7 @@ apps = true
|
|||||||
.expect("config should load");
|
.expect("config should load");
|
||||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||||
let loaded_plugin_app_connector_ids = vec!["asdk_app_databricks_workspace".to_string()];
|
let loaded_plugin_app_connector_ids = vec!["asdk_app_databricks_workspace".to_string()];
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, Some(auth.api_auth_mode()));
|
||||||
|
|
||||||
let discoverable_tools = list_tool_suggest_discoverable_tools_with_auth(
|
let discoverable_tools = list_tool_suggest_discoverable_tools_with_auth(
|
||||||
&config,
|
&config,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::plugins::plugins_manager_for_config;
|
use crate::plugins::plugins_manager_for_config;
|
||||||
use crate::plugins::test_support::load_plugins_config;
|
use crate::plugins::test_support::load_plugins_config;
|
||||||
use crate::plugins::test_support::write_file;
|
use crate::plugins::test_support::write_file;
|
||||||
use crate::plugins::test_support::write_openai_curated_marketplace;
|
use crate::plugins::test_support::write_openai_api_curated_marketplace;
|
||||||
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
|
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
|
||||||
use codex_tools::DiscoverablePluginInfo;
|
use codex_tools::DiscoverablePluginInfo;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
@@ -11,7 +11,7 @@ async fn list_discoverable_plugins(
|
|||||||
config: &crate::config::Config,
|
config: &crate::config::Config,
|
||||||
loaded_plugin_app_connector_ids: &[String],
|
loaded_plugin_app_connector_ids: &[String],
|
||||||
) -> anyhow::Result<Vec<DiscoverablePluginInfo>> {
|
) -> anyhow::Result<Vec<DiscoverablePluginInfo>> {
|
||||||
let plugins_manager = plugins_manager_for_config(config);
|
let plugins_manager = plugins_manager_for_config(config, /*auth_mode*/ None);
|
||||||
super::list_tool_suggest_discoverable_plugins(
|
super::list_tool_suggest_discoverable_plugins(
|
||||||
config,
|
config,
|
||||||
&plugins_manager,
|
&plugins_manager,
|
||||||
@@ -25,7 +25,7 @@ async fn list_discoverable_plugins(
|
|||||||
async fn list_tool_suggest_discoverable_plugins_returns_empty_when_plugins_feature_disabled() {
|
async fn list_tool_suggest_discoverable_plugins_returns_empty_when_plugins_feature_disabled() {
|
||||||
let codex_home = tempdir().expect("tempdir should succeed");
|
let codex_home = tempdir().expect("tempdir should succeed");
|
||||||
let curated_root = curated_plugins_repo_path(codex_home.path());
|
let curated_root = curated_plugins_repo_path(codex_home.path());
|
||||||
write_openai_curated_marketplace(&curated_root, &["slack"]);
|
write_openai_api_curated_marketplace(&curated_root, &["slack"]);
|
||||||
write_file(
|
write_file(
|
||||||
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
|
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
|
||||||
r#"[features]
|
r#"[features]
|
||||||
@@ -43,7 +43,7 @@ plugins = false
|
|||||||
async fn list_tool_suggest_discoverable_plugins_omits_disabled_tool_suggestions() {
|
async fn list_tool_suggest_discoverable_plugins_omits_disabled_tool_suggestions() {
|
||||||
let codex_home = tempdir().expect("tempdir should succeed");
|
let codex_home = tempdir().expect("tempdir should succeed");
|
||||||
let curated_root = curated_plugins_repo_path(codex_home.path());
|
let curated_root = curated_plugins_repo_path(codex_home.path());
|
||||||
write_openai_curated_marketplace(&curated_root, &["slack"]);
|
write_openai_api_curated_marketplace(&curated_root, &["slack"]);
|
||||||
write_file(
|
write_file(
|
||||||
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
|
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
|
||||||
r#"[features]
|
r#"[features]
|
||||||
@@ -51,7 +51,7 @@ plugins = true
|
|||||||
|
|
||||||
[tool_suggest]
|
[tool_suggest]
|
||||||
disabled_tools = [
|
disabled_tools = [
|
||||||
{ type = "plugin", id = "slack@openai-curated" }
|
{ type = "plugin", id = "slack@openai-api-curated" }
|
||||||
]
|
]
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
@@ -66,14 +66,14 @@ disabled_tools = [
|
|||||||
async fn list_tool_suggest_discoverable_plugins_includes_configured_plugin_ids() {
|
async fn list_tool_suggest_discoverable_plugins_includes_configured_plugin_ids() {
|
||||||
let codex_home = tempdir().expect("tempdir should succeed");
|
let codex_home = tempdir().expect("tempdir should succeed");
|
||||||
let curated_root = curated_plugins_repo_path(codex_home.path());
|
let curated_root = curated_plugins_repo_path(codex_home.path());
|
||||||
write_openai_curated_marketplace(&curated_root, &["sample"]);
|
write_openai_api_curated_marketplace(&curated_root, &["sample"]);
|
||||||
write_file(
|
write_file(
|
||||||
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
|
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
|
||||||
r#"[features]
|
r#"[features]
|
||||||
plugins = true
|
plugins = true
|
||||||
|
|
||||||
[tool_suggest]
|
[tool_suggest]
|
||||||
discoverables = [{ type = "plugin", id = "sample@openai-curated" }]
|
discoverables = [{ type = "plugin", id = "sample@openai-api-curated" }]
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ discoverables = [{ type = "plugin", id = "sample@openai-curated" }]
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
discoverable_plugins,
|
discoverable_plugins,
|
||||||
vec![DiscoverablePluginInfo {
|
vec![DiscoverablePluginInfo {
|
||||||
id: "sample@openai-curated".to_string(),
|
id: "sample@openai-api-curated".to_string(),
|
||||||
remote_plugin_id: None,
|
remote_plugin_id: None,
|
||||||
name: "sample".to_string(),
|
name: "sample".to_string(),
|
||||||
description: Some(
|
description: Some(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub(crate) mod test_support;
|
|||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use codex_core_plugins::PluginsManager;
|
use codex_core_plugins::PluginsManager;
|
||||||
|
use codex_protocol::auth::AuthMode;
|
||||||
use codex_skills_extension::HostSkillsService;
|
use codex_skills_extension::HostSkillsService;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -28,10 +29,14 @@ pub(crate) use mentions::collect_tool_mentions_from_messages;
|
|||||||
/// Constructs a standalone plugin manager with extension-owned plugin skill loading.
|
/// Constructs a standalone plugin manager with extension-owned plugin skill loading.
|
||||||
///
|
///
|
||||||
/// Callers that already own a host skills service should inject that existing service instead.
|
/// Callers that already own a host skills service should inject that existing service instead.
|
||||||
pub fn plugins_manager_for_config(config: &Config) -> PluginsManager {
|
pub fn plugins_manager_for_config(config: &Config, auth_mode: Option<AuthMode>) -> PluginsManager {
|
||||||
let skill_root_loader = Arc::new(HostSkillsService::new(
|
let skill_root_loader = Arc::new(HostSkillsService::new(
|
||||||
config.codex_home.clone(),
|
config.codex_home.clone(),
|
||||||
/*bundled_skills_enabled*/ false,
|
/*bundled_skills_enabled*/ false,
|
||||||
));
|
));
|
||||||
PluginsManager::new(config.codex_home.to_path_buf(), skill_root_loader)
|
PluginsManager::new(
|
||||||
|
config.codex_home.to_path_buf(),
|
||||||
|
auth_mode,
|
||||||
|
skill_root_loader,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ use crate::config::ConfigBuilder;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
|
use codex_core_plugins::OPENAI_API_CURATED_MARKETPLACE_NAME;
|
||||||
|
|
||||||
pub(crate) const TEST_CURATED_PLUGIN_SHA: &str = "0123456789abcdef0123456789abcdef01234567";
|
pub(crate) const TEST_CURATED_PLUGIN_SHA: &str = "0123456789abcdef0123456789abcdef01234567";
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ pub(crate) fn write_curated_plugin(root: &Path, plugin_name: &str) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str]) {
|
pub(crate) fn write_openai_api_curated_marketplace(root: &Path, plugin_names: &[&str]) {
|
||||||
let plugins = plugin_names
|
let plugins = plugin_names
|
||||||
.iter()
|
.iter()
|
||||||
.map(|plugin_name| {
|
.map(|plugin_name| {
|
||||||
@@ -67,10 +67,10 @@ pub(crate) fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(",\n");
|
.join(",\n");
|
||||||
write_file(
|
write_file(
|
||||||
&root.join(".agents/plugins/marketplace.json"),
|
&root.join(".agents/plugins/api_marketplace.json"),
|
||||||
&format!(
|
&format!(
|
||||||
r#"{{
|
r#"{{
|
||||||
"name": "{OPENAI_CURATED_MARKETPLACE_NAME}",
|
"name": "{OPENAI_API_CURATED_MARKETPLACE_NAME}",
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{plugins}
|
{plugins}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5653,7 +5653,10 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() {
|
|||||||
|
|
||||||
let (tx_event, _rx_event) = async_channel::unbounded();
|
let (tx_event, _rx_event) = async_channel::unbounded();
|
||||||
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
||||||
let plugins_manager = Arc::new(plugins_manager_for_config(&config));
|
let plugins_manager = Arc::new(plugins_manager_for_config(
|
||||||
|
&config,
|
||||||
|
auth_manager.get_api_auth_mode(),
|
||||||
|
));
|
||||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||||
let skills_service = Arc::new(HostSkillsService::new(
|
let skills_service = Arc::new(HostSkillsService::new(
|
||||||
config.codex_home.clone(),
|
config.codex_home.clone(),
|
||||||
@@ -5818,7 +5821,10 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
|||||||
.expect("primary environment")
|
.expect("primary environment")
|
||||||
.environment,
|
.environment,
|
||||||
);
|
);
|
||||||
let plugins_manager = Arc::new(plugins_manager_for_config(&config));
|
let plugins_manager = Arc::new(plugins_manager_for_config(
|
||||||
|
&config,
|
||||||
|
auth_manager.get_api_auth_mode(),
|
||||||
|
));
|
||||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||||
let skills_service = Arc::new(HostSkillsService::new(
|
let skills_service = Arc::new(HostSkillsService::new(
|
||||||
config.codex_home.clone(),
|
config.codex_home.clone(),
|
||||||
@@ -6073,7 +6079,10 @@ async fn make_session_with_config_and_rx(
|
|||||||
|
|
||||||
let (tx_event, rx_event) = async_channel::unbounded();
|
let (tx_event, rx_event) = async_channel::unbounded();
|
||||||
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
||||||
let plugins_manager = Arc::new(plugins_manager_for_config(&config));
|
let plugins_manager = Arc::new(plugins_manager_for_config(
|
||||||
|
&config,
|
||||||
|
auth_manager.get_api_auth_mode(),
|
||||||
|
));
|
||||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||||
let skills_service = Arc::new(HostSkillsService::new(
|
let skills_service = Arc::new(HostSkillsService::new(
|
||||||
config.codex_home.clone(),
|
config.codex_home.clone(),
|
||||||
@@ -6188,7 +6197,10 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
|
|||||||
|
|
||||||
let (tx_event, rx_event) = async_channel::unbounded();
|
let (tx_event, rx_event) = async_channel::unbounded();
|
||||||
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
||||||
let plugins_manager = Arc::new(plugins_manager_for_config(&config));
|
let plugins_manager = Arc::new(plugins_manager_for_config(
|
||||||
|
&config,
|
||||||
|
auth_manager.get_api_auth_mode(),
|
||||||
|
));
|
||||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||||
let skills_service = Arc::new(HostSkillsService::new(
|
let skills_service = Arc::new(HostSkillsService::new(
|
||||||
config.codex_home.clone(),
|
config.codex_home.clone(),
|
||||||
@@ -7997,7 +8009,10 @@ where
|
|||||||
.expect("primary environment")
|
.expect("primary environment")
|
||||||
.environment,
|
.environment,
|
||||||
);
|
);
|
||||||
let plugins_manager = Arc::new(plugins_manager_for_config(&config));
|
let plugins_manager = Arc::new(plugins_manager_for_config(
|
||||||
|
&config,
|
||||||
|
auth_manager.get_api_auth_mode(),
|
||||||
|
));
|
||||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||||
let skills_service = Arc::new(HostSkillsService::new(
|
let skills_service = Arc::new(HostSkillsService::new(
|
||||||
config.codex_home.clone(),
|
config.codex_home.clone(),
|
||||||
@@ -8524,6 +8539,10 @@ async fn mcp_refresh_updates_plugin_auth_mode_before_checking_pending_state() {
|
|||||||
CodexAuth::from_api_key("old-api-key"),
|
CodexAuth::from_api_key("old-api-key"),
|
||||||
codex_home.path().to_path_buf(),
|
codex_home.path().to_path_buf(),
|
||||||
);
|
);
|
||||||
|
session
|
||||||
|
.services
|
||||||
|
.plugins_manager
|
||||||
|
.set_auth_mode(/*auth_mode*/ None);
|
||||||
let session = Arc::new(session);
|
let session = Arc::new(session);
|
||||||
let auth_mode = session.services.auth_manager.get_api_auth_mode();
|
let auth_mode = session.services.auth_manager.get_api_auth_mode();
|
||||||
|
|
||||||
|
|||||||
@@ -750,7 +750,10 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
|
|||||||
auth_manager.clone(),
|
auth_manager.clone(),
|
||||||
config.model_provider.clone(),
|
config.model_provider.clone(),
|
||||||
);
|
);
|
||||||
let plugins_manager = Arc::new(plugins_manager_for_config(&config));
|
let plugins_manager = Arc::new(plugins_manager_for_config(
|
||||||
|
&config,
|
||||||
|
auth_manager.get_api_auth_mode(),
|
||||||
|
));
|
||||||
let skills_service = Arc::new(HostSkillsService::new(
|
let skills_service = Arc::new(HostSkillsService::new(
|
||||||
config.codex_home.clone(),
|
config.codex_home.clone(),
|
||||||
/*bundled_skills_enabled*/ true,
|
/*bundled_skills_enabled*/ true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use super::*;
|
|||||||
use crate::plugins::plugins_manager_for_config;
|
use crate::plugins::plugins_manager_for_config;
|
||||||
use crate::plugins::test_support::load_plugins_config;
|
use crate::plugins::test_support::load_plugins_config;
|
||||||
use crate::plugins::test_support::write_curated_plugin_sha;
|
use crate::plugins::test_support::write_curated_plugin_sha;
|
||||||
use crate::plugins::test_support::write_openai_curated_marketplace;
|
use crate::plugins::test_support::write_openai_api_curated_marketplace;
|
||||||
use crate::plugins::test_support::write_plugins_feature_config;
|
use crate::plugins::test_support::write_plugins_feature_config;
|
||||||
use codex_config::CONFIG_TOML_FILE;
|
use codex_config::CONFIG_TOML_FILE;
|
||||||
use codex_config::config_toml::ConfigToml;
|
use codex_config::config_toml::ConfigToml;
|
||||||
@@ -35,15 +35,15 @@ fn request_plugin_install_does_not_support_parallel_tool_calls() {
|
|||||||
async fn verified_plugin_install_completed_requires_installed_plugin() {
|
async fn verified_plugin_install_completed_requires_installed_plugin() {
|
||||||
let codex_home = tempdir().expect("tempdir should succeed");
|
let codex_home = tempdir().expect("tempdir should succeed");
|
||||||
let curated_root = curated_plugins_repo_path(codex_home.path());
|
let curated_root = curated_plugins_repo_path(codex_home.path());
|
||||||
write_openai_curated_marketplace(&curated_root, &["sample"]);
|
write_openai_api_curated_marketplace(&curated_root, &["sample"]);
|
||||||
write_curated_plugin_sha(codex_home.path());
|
write_curated_plugin_sha(codex_home.path());
|
||||||
write_plugins_feature_config(codex_home.path());
|
write_plugins_feature_config(codex_home.path());
|
||||||
|
|
||||||
let config = load_plugins_config(codex_home.path()).await;
|
let config = load_plugins_config(codex_home.path()).await;
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
|
||||||
|
|
||||||
assert!(!verified_plugin_install_completed(
|
assert!(!verified_plugin_install_completed(
|
||||||
"sample@openai-curated",
|
"sample@openai-api-curated",
|
||||||
&config,
|
&config,
|
||||||
&plugins_manager,
|
&plugins_manager,
|
||||||
));
|
));
|
||||||
@@ -54,7 +54,7 @@ async fn verified_plugin_install_completed_requires_installed_plugin() {
|
|||||||
PluginInstallRequest {
|
PluginInstallRequest {
|
||||||
plugin_name: "sample".to_string(),
|
plugin_name: "sample".to_string(),
|
||||||
marketplace_path: AbsolutePathBuf::try_from(
|
marketplace_path: AbsolutePathBuf::try_from(
|
||||||
curated_root.join(".agents/plugins/marketplace.json"),
|
curated_root.join(".agents/plugins/api_marketplace.json"),
|
||||||
)
|
)
|
||||||
.expect("marketplace path"),
|
.expect("marketplace path"),
|
||||||
},
|
},
|
||||||
@@ -64,7 +64,7 @@ async fn verified_plugin_install_completed_requires_installed_plugin() {
|
|||||||
|
|
||||||
let refreshed_config = load_plugins_config(codex_home.path()).await;
|
let refreshed_config = load_plugins_config(codex_home.path()).await;
|
||||||
assert!(verified_plugin_install_completed(
|
assert!(verified_plugin_install_completed(
|
||||||
"sample@openai-curated",
|
"sample@openai-api-curated",
|
||||||
&refreshed_config,
|
&refreshed_config,
|
||||||
&plugins_manager,
|
&plugins_manager,
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ async fn hosted_plugin_runtime_ps_mcp_tool_calls_use_current_auth_manager_token(
|
|||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::Never);
|
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::Never);
|
||||||
let plugins_manager = plugins_manager_for_config(&config);
|
let plugins_manager = plugins_manager_for_config(&config, auth_manager.get_api_auth_mode());
|
||||||
let mcp_config = Arc::new(config.to_mcp_config(&plugins_manager).await);
|
let mcp_config = Arc::new(config.to_mcp_config(&plugins_manager).await);
|
||||||
let runtime = McpRuntime::new(McpRuntimeInput {
|
let runtime = McpRuntime::new(McpRuntimeInput {
|
||||||
startup_policy: McpStartupPolicy::Eager,
|
startup_policy: McpStartupPolicy::Eager,
|
||||||
|
|||||||
@@ -826,6 +826,14 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
|
|||||||
expected_target_loaded_plugin_skills: &[CHATGPT_CURATED_PLUGIN_SKILL],
|
expected_target_loaded_plugin_skills: &[CHATGPT_CURATED_PLUGIN_SKILL],
|
||||||
expected_target_skill_description: "chatgpt description",
|
expected_target_skill_description: "chatgpt description",
|
||||||
},
|
},
|
||||||
|
Fixture {
|
||||||
|
name: "ChatGPT with a custom provider",
|
||||||
|
target_auth: TargetAuth::Chatgpt,
|
||||||
|
target_model_provider_id: "ollama",
|
||||||
|
target_prompt: "chatgpt custom provider target turn",
|
||||||
|
expected_target_loaded_plugin_skills: &[CHATGPT_CURATED_PLUGIN_SKILL],
|
||||||
|
expected_target_skill_description: "chatgpt description",
|
||||||
|
},
|
||||||
Fixture {
|
Fixture {
|
||||||
name: "API key",
|
name: "API key",
|
||||||
target_auth: TargetAuth::ApiKey,
|
target_auth: TargetAuth::ApiKey,
|
||||||
@@ -850,6 +858,22 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
|
|||||||
expected_target_loaded_plugin_skills: &[API_CURATED_PLUGIN_SKILL],
|
expected_target_loaded_plugin_skills: &[API_CURATED_PLUGIN_SKILL],
|
||||||
expected_target_skill_description: "api description before",
|
expected_target_skill_description: "api description before",
|
||||||
},
|
},
|
||||||
|
Fixture {
|
||||||
|
name: "unauthenticated OpenAI",
|
||||||
|
target_auth: TargetAuth::NoCodexAuth,
|
||||||
|
target_model_provider_id: OPENAI_PROVIDER_ID,
|
||||||
|
target_prompt: "unauthenticated openai target turn",
|
||||||
|
expected_target_loaded_plugin_skills: &[API_CURATED_PLUGIN_SKILL],
|
||||||
|
expected_target_skill_description: "api description before",
|
||||||
|
},
|
||||||
|
Fixture {
|
||||||
|
name: "unauthenticated custom provider",
|
||||||
|
target_auth: TargetAuth::NoCodexAuth,
|
||||||
|
target_model_provider_id: "ollama",
|
||||||
|
target_prompt: "unauthenticated custom provider target turn",
|
||||||
|
expected_target_loaded_plugin_skills: &[API_CURATED_PLUGIN_SKILL],
|
||||||
|
expected_target_skill_description: "api description before",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
async fn skills_for_agent_turn(
|
async fn skills_for_agent_turn(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ async fn contributes_hosted_plugin_runtime_without_an_executor() -> TestResult {
|
|||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||||
let manager = installed_manager(&config);
|
let manager = installed_manager(&config, Some(auth.api_auth_mode()));
|
||||||
|
|
||||||
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
||||||
let server = servers
|
let server = servers
|
||||||
@@ -60,7 +60,7 @@ async fn runtime_overlay_preserves_disabled_server() -> TestResult {
|
|||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||||
let manager = installed_manager(&config);
|
let manager = installed_manager(&config, Some(auth.api_auth_mode()));
|
||||||
|
|
||||||
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
||||||
let server = servers
|
let server = servers
|
||||||
@@ -87,7 +87,10 @@ async fn default_fallback_overwrites_reserved_config_without_an_extension() -> T
|
|||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||||
let manager = McpManager::new(Arc::new(plugins_manager_for_config(&config)));
|
let manager = McpManager::new(Arc::new(plugins_manager_for_config(
|
||||||
|
&config,
|
||||||
|
Some(auth.api_auth_mode()),
|
||||||
|
)));
|
||||||
|
|
||||||
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
||||||
let server = servers
|
let server = servers
|
||||||
@@ -116,7 +119,10 @@ async fn later_extension_can_remove_same_name_registration() -> TestResult {
|
|||||||
codex_mcp_extension::install(&mut builder);
|
codex_mcp_extension::install(&mut builder);
|
||||||
builder.mcp_server_contributor(Arc::new(RemoveCodexApps));
|
builder.mcp_server_contributor(Arc::new(RemoveCodexApps));
|
||||||
let manager = McpManager::new_with_extensions(
|
let manager = McpManager::new_with_extensions(
|
||||||
Arc::new(plugins_manager_for_config(&config)),
|
Arc::new(plugins_manager_for_config(
|
||||||
|
&config,
|
||||||
|
Some(auth.api_auth_mode()),
|
||||||
|
)),
|
||||||
Arc::new(builder.build()),
|
Arc::new(builder.build()),
|
||||||
codex_core::CodexAppsToolsCache::default(),
|
codex_core::CodexAppsToolsCache::default(),
|
||||||
);
|
);
|
||||||
@@ -137,7 +143,7 @@ async fn hosted_apps_mcp_requires_chatgpt_auth() -> TestResult {
|
|||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
let auth = CodexAuth::from_api_key("test");
|
let auth = CodexAuth::from_api_key("test");
|
||||||
let manager = installed_manager(&config);
|
let manager = installed_manager(&config, Some(auth.api_auth_mode()));
|
||||||
|
|
||||||
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
||||||
assert!(!servers.contains_key(CODEX_APPS_MCP_SERVER_NAME));
|
assert!(!servers.contains_key(CODEX_APPS_MCP_SERVER_NAME));
|
||||||
@@ -161,8 +167,10 @@ async fn disabled_apps_remove_reserved_server_config_for_all_hosts() -> TestResu
|
|||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
let managers = [
|
let managers = [
|
||||||
installed_manager(&config),
|
installed_manager(&config, /*auth_mode*/ None),
|
||||||
McpManager::new(Arc::new(plugins_manager_for_config(&config))),
|
McpManager::new(Arc::new(plugins_manager_for_config(
|
||||||
|
&config, /*auth_mode*/ None,
|
||||||
|
))),
|
||||||
];
|
];
|
||||||
for manager in managers {
|
for manager in managers {
|
||||||
let servers = manager.runtime_servers(&config).await;
|
let servers = manager.runtime_servers(&config).await;
|
||||||
@@ -171,11 +179,14 @@ async fn disabled_apps_remove_reserved_server_config_for_all_hosts() -> TestResu
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn installed_manager(config: &Config) -> McpManager {
|
fn installed_manager(
|
||||||
|
config: &Config,
|
||||||
|
auth_mode: Option<codex_protocol::auth::AuthMode>,
|
||||||
|
) -> McpManager {
|
||||||
let mut builder = ExtensionRegistryBuilder::new();
|
let mut builder = ExtensionRegistryBuilder::new();
|
||||||
codex_mcp_extension::install(&mut builder);
|
codex_mcp_extension::install(&mut builder);
|
||||||
McpManager::new_with_extensions(
|
McpManager::new_with_extensions(
|
||||||
Arc::new(plugins_manager_for_config(config)),
|
Arc::new(plugins_manager_for_config(config, auth_mode)),
|
||||||
Arc::new(builder.build()),
|
Arc::new(builder.build()),
|
||||||
codex_core::CodexAppsToolsCache::default(),
|
codex_core::CodexAppsToolsCache::default(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ impl ExternalAgentConfigService {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let configured_marketplace_plugins = configured_marketplace_plugins(
|
let configured_marketplace_plugins = configured_marketplace_plugins(
|
||||||
&config,
|
&config,
|
||||||
&plugins_manager_for_config(&config),
|
&plugins_manager_for_config(&config, self.auth_mode),
|
||||||
)?;
|
)?;
|
||||||
let source_root = repo_root.unwrap_or(self.external_agent_home.as_path());
|
let source_root = repo_root.unwrap_or(self.external_agent_home.as_path());
|
||||||
if let Some(detected) =
|
if let Some(detected) =
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ impl ExternalAgentConfigService {
|
|||||||
.map_err(|err| io::Error::other(format!("failed to load config: {err}")))?;
|
.map_err(|err| io::Error::other(format!("failed to load config: {err}")))?;
|
||||||
let requirements = config.config_layer_stack.requirements().clone();
|
let requirements = config.config_layer_stack.requirements().clone();
|
||||||
let mut outcome = PluginImportOutcome::default();
|
let mut outcome = PluginImportOutcome::default();
|
||||||
let plugins_manager = plugins_manager_for_config(&config)
|
let plugins_manager = plugins_manager_for_config(&config, self.auth_mode)
|
||||||
.with_plugin_install_source(PluginInstallSource::ExternalAgentMigration);
|
.with_plugin_install_source(PluginInstallSource::ExternalAgentMigration);
|
||||||
if let Some(analytics_events_client) = self.analytics_events_client.clone() {
|
if let Some(analytics_events_client) = self.analytics_events_client.clone() {
|
||||||
plugins_manager.set_analytics_events_client(analytics_events_client);
|
plugins_manager.set_analytics_events_client(analytics_events_client);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ use codex_analytics::AnalyticsEventsClient;
|
|||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core_plugins::PluginsManager;
|
use codex_core_plugins::PluginsManager;
|
||||||
use codex_core_plugins::marketplace::MarketplacePluginInstallPolicy;
|
use codex_core_plugins::marketplace::MarketplacePluginInstallPolicy;
|
||||||
|
use codex_protocol::auth::AuthMode;
|
||||||
use codex_protocol::protocol::Product;
|
use codex_protocol::protocol::Product;
|
||||||
use codex_rollout::StateDbHandle;
|
use codex_rollout::StateDbHandle;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
@@ -68,6 +69,7 @@ pub struct ExternalAgentConfigService {
|
|||||||
pub(super) connector_metadata_roots: Vec<PathBuf>,
|
pub(super) connector_metadata_roots: Vec<PathBuf>,
|
||||||
pub(crate) external_agent_home: PathBuf,
|
pub(crate) external_agent_home: PathBuf,
|
||||||
pub(crate) analytics_events_client: Option<AnalyticsEventsClient>,
|
pub(crate) analytics_events_client: Option<AnalyticsEventsClient>,
|
||||||
|
pub(crate) auth_mode: Option<AuthMode>,
|
||||||
pub(crate) source: ExternalAgentSource,
|
pub(crate) source: ExternalAgentSource,
|
||||||
pub(crate) session_import_limits: ExternalAgentSessionImportLimits,
|
pub(crate) session_import_limits: ExternalAgentSessionImportLimits,
|
||||||
state_db: Option<StateDbHandle>,
|
state_db: Option<StateDbHandle>,
|
||||||
@@ -76,6 +78,7 @@ pub struct ExternalAgentConfigService {
|
|||||||
impl ExternalAgentConfigService {
|
impl ExternalAgentConfigService {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
codex_home: PathBuf,
|
codex_home: PathBuf,
|
||||||
|
auth_mode: Option<AuthMode>,
|
||||||
analytics_events_client: AnalyticsEventsClient,
|
analytics_events_client: AnalyticsEventsClient,
|
||||||
state_db: Option<StateDbHandle>,
|
state_db: Option<StateDbHandle>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -87,6 +90,7 @@ impl ExternalAgentConfigService {
|
|||||||
connector_metadata_roots,
|
connector_metadata_roots,
|
||||||
external_agent_home,
|
external_agent_home,
|
||||||
analytics_events_client: Some(analytics_events_client),
|
analytics_events_client: Some(analytics_events_client),
|
||||||
|
auth_mode,
|
||||||
source,
|
source,
|
||||||
session_import_limits: ExternalAgentSessionImportLimits::default(),
|
session_import_limits: ExternalAgentSessionImportLimits::default(),
|
||||||
state_db,
|
state_db,
|
||||||
@@ -102,6 +106,7 @@ impl ExternalAgentConfigService {
|
|||||||
connector_metadata_roots,
|
connector_metadata_roots,
|
||||||
external_agent_home,
|
external_agent_home,
|
||||||
analytics_events_client: self.analytics_events_client.clone(),
|
analytics_events_client: self.analytics_events_client.clone(),
|
||||||
|
auth_mode: self.auth_mode,
|
||||||
source,
|
source,
|
||||||
session_import_limits: self.session_import_limits,
|
session_import_limits: self.session_import_limits,
|
||||||
state_db: self.state_db.clone(),
|
state_db: self.state_db.clone(),
|
||||||
@@ -114,6 +119,12 @@ impl ExternalAgentConfigService {
|
|||||||
service
|
service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_auth_mode(&self, auth_mode: Option<AuthMode>) -> Self {
|
||||||
|
let mut service = self.clone();
|
||||||
|
service.auth_mode = auth_mode;
|
||||||
|
service
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_metadata_mode(&self) -> SessionMetadataMode {
|
pub fn session_metadata_mode(&self) -> SessionMetadataMode {
|
||||||
self.source.session_metadata_mode()
|
self.source.session_metadata_mode()
|
||||||
}
|
}
|
||||||
@@ -157,6 +168,7 @@ impl ExternalAgentConfigService {
|
|||||||
connector_metadata_roots,
|
connector_metadata_roots,
|
||||||
external_agent_home,
|
external_agent_home,
|
||||||
analytics_events_client: None,
|
analytics_events_client: None,
|
||||||
|
auth_mode: None,
|
||||||
source,
|
source,
|
||||||
session_import_limits: ExternalAgentSessionImportLimits::default(),
|
session_import_limits: ExternalAgentSessionImportLimits::default(),
|
||||||
state_db: None,
|
state_db: None,
|
||||||
|
|||||||
@@ -1,8 +1,96 @@
|
|||||||
use super::super::*;
|
use super::super::*;
|
||||||
use crate::migration_source::MarketplaceImportSource;
|
use crate::migration_source::MarketplaceImportSource;
|
||||||
use crate::source_cla;
|
use crate::source_cla;
|
||||||
|
use codex_protocol::auth::AuthMode;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn authenticated_plugin_migration_uses_chatgpt_curated_marketplace() {
|
||||||
|
let (_root, external_agent_home, codex_home) = fixture_paths();
|
||||||
|
let curated_root = codex_home.join(".tmp/plugins");
|
||||||
|
let plugin_root = curated_root.join("plugins/sample");
|
||||||
|
fs::create_dir_all(&external_agent_home).expect("create external agent home");
|
||||||
|
fs::create_dir_all(curated_root.join(".agents/plugins"))
|
||||||
|
.expect("create curated marketplace directory");
|
||||||
|
fs::create_dir_all(plugin_root.join(".codex-plugin")).expect("create curated plugin directory");
|
||||||
|
fs::write(
|
||||||
|
external_agent_home.join("settings.json"),
|
||||||
|
r#"{"enabledPlugins":{"sample@openai-curated":true}}"#,
|
||||||
|
)
|
||||||
|
.expect("write external agent settings");
|
||||||
|
fs::write(
|
||||||
|
codex_home.join("config.toml"),
|
||||||
|
"[features]\nplugins = true\n",
|
||||||
|
)
|
||||||
|
.expect("write Codex config");
|
||||||
|
fs::write(
|
||||||
|
codex_home.join(".tmp/plugins.sha"),
|
||||||
|
"0123456789abcdef0123456789abcdef01234567\n",
|
||||||
|
)
|
||||||
|
.expect("write curated plugin version");
|
||||||
|
fs::write(
|
||||||
|
curated_root.join(".agents/plugins/marketplace.json"),
|
||||||
|
r#"{
|
||||||
|
"name": "openai-curated",
|
||||||
|
"plugins": [{
|
||||||
|
"name": "sample",
|
||||||
|
"source": {"source": "local", "path": "./plugins/sample"}
|
||||||
|
}]
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.expect("write curated marketplace");
|
||||||
|
fs::write(
|
||||||
|
plugin_root.join(".codex-plugin/plugin.json"),
|
||||||
|
r#"{"name":"sample","version":"0.1.0"}"#,
|
||||||
|
)
|
||||||
|
.expect("write curated plugin manifest");
|
||||||
|
|
||||||
|
let service = service_for_paths(external_agent_home.clone(), codex_home)
|
||||||
|
.with_auth_mode(Some(AuthMode::Chatgpt));
|
||||||
|
let items = service
|
||||||
|
.detect(ExternalAgentConfigDetectOptions {
|
||||||
|
include_home: true,
|
||||||
|
include_memory: false,
|
||||||
|
cwds: None,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("detect authenticated curated plugin");
|
||||||
|
let details = Some(MigrationDetails {
|
||||||
|
plugins: vec![PluginsMigration {
|
||||||
|
marketplace_name: "openai-curated".to_string(),
|
||||||
|
plugin_names: vec!["sample".to_string()],
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
items,
|
||||||
|
vec![ExternalAgentConfigMigrationItem {
|
||||||
|
item_type: ExternalAgentConfigMigrationItemType::Plugins,
|
||||||
|
description: format!(
|
||||||
|
"Migrate enabled plugins from {}",
|
||||||
|
external_agent_home.join("settings.json").display()
|
||||||
|
),
|
||||||
|
cwd: None,
|
||||||
|
details: details.clone(),
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|
||||||
|
let outcome = service
|
||||||
|
.import_plugins(/*cwd*/ None, details)
|
||||||
|
.await
|
||||||
|
.expect("import authenticated curated plugin");
|
||||||
|
assert_eq!(
|
||||||
|
outcome,
|
||||||
|
PluginImportOutcome {
|
||||||
|
succeeded_marketplaces: vec!["openai-curated".to_string()],
|
||||||
|
succeeded_plugin_ids: vec!["sample@openai-curated".to_string()],
|
||||||
|
failed_marketplaces: Vec::new(),
|
||||||
|
failed_plugin_ids: Vec::new(),
|
||||||
|
raw_errors: Vec::new(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn detect_home_lists_enabled_plugins_from_settings() {
|
async fn detect_home_lists_enabled_plugins_from_settings() {
|
||||||
let (_root, external_agent_home, codex_home) = fixture_paths();
|
let (_root, external_agent_home, codex_home) = fixture_paths();
|
||||||
|
|||||||
Reference in new issue
Block a user