Extract codex-plugin crate (#15747)

## Summary
- extract plugin identifiers and load-outcome types into codex-plugin
- update codex-core to consume the new plugin crate

## Testing
- CI

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-03-25 11:07:31 -07:00
committed by GitHub
Unverified
parent ad74543a6f
commit 2bb1027e37
14 changed files with 394 additions and 297 deletions
+20 -34
View File
@@ -7,7 +7,9 @@ use crate::config_loader::ConfigLayerEntry;
use crate::config_loader::ConfigLayerStack;
use crate::config_loader::ConfigRequirements;
use crate::config_loader::ConfigRequirementsToml;
use crate::plugins::LoadedPlugin;
use crate::plugins::MarketplacePluginInstallPolicy;
use crate::plugins::PluginLoadOutcome;
use crate::plugins::test_support::TEST_CURATED_PLUGIN_SHA;
use crate::plugins::test_support::write_curated_plugin_sha_with as write_curated_plugin_sha;
use crate::plugins::test_support::write_file;
@@ -26,6 +28,8 @@ use wiremock::matchers::method;
use wiremock::matchers::path;
use wiremock::matchers::query_param;
const MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN: usize = 1024;
fn write_plugin(root: &Path, dir_name: &str, manifest_name: &str) {
let plugin_root = root.join(dir_name);
fs::create_dir_all(plugin_root.join(".codex-plugin")).unwrap();
@@ -130,7 +134,7 @@ fn load_plugins_loads_default_skills_and_mcp_servers() {
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
assert_eq!(
outcome.plugins,
outcome.plugins(),
vec![LoadedPlugin {
config_name: "sample@test".to_string(),
manifest_name: Some("sample".to_string()),
@@ -220,10 +224,10 @@ enabled = true
let skill_path = dunce::canonicalize(skill_path).expect("skill path should canonicalize");
assert_eq!(
outcome.plugins[0].disabled_skill_paths,
outcome.plugins()[0].disabled_skill_paths,
HashSet::from([skill_path])
);
assert!(!outcome.plugins[0].has_enabled_skills);
assert!(!outcome.plugins()[0].has_enabled_skills);
assert!(outcome.capability_summaries().is_empty());
}
@@ -256,8 +260,8 @@ enabled = true
"#;
let outcome = load_plugins_from_config(config_toml, codex_home.path());
assert!(outcome.plugins[0].disabled_skill_paths.is_empty());
assert!(outcome.plugins[0].has_enabled_skills);
assert!(outcome.plugins()[0].disabled_skill_paths.is_empty());
assert!(outcome.plugins()[0].has_enabled_skills);
assert_eq!(
outcome.capability_summaries(),
&[PluginCapabilitySummary {
@@ -338,7 +342,7 @@ fn capability_summary_sanitizes_plugin_descriptions_to_one_line() {
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
assert_eq!(
outcome.plugins[0].manifest_description.as_deref(),
outcome.plugins()[0].manifest_description.as_deref(),
Some("Plugin that\n includes the sample\tserver")
);
assert_eq!(
@@ -373,7 +377,7 @@ fn capability_summary_truncates_overlong_plugin_descriptions() {
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
assert_eq!(
outcome.plugins[0].manifest_description.as_deref(),
outcome.plugins()[0].manifest_description.as_deref(),
Some(too_long.as_str())
);
assert_eq!(
@@ -453,14 +457,14 @@ fn load_plugins_uses_manifest_configured_component_paths() {
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
assert_eq!(
outcome.plugins[0].skill_roots,
outcome.plugins()[0].skill_roots,
vec![
plugin_root.join("custom-skills"),
plugin_root.join("skills")
]
);
assert_eq!(
outcome.plugins[0].mcp_servers,
outcome.plugins()[0].mcp_servers,
HashMap::from([(
"custom".to_string(),
McpServerConfig {
@@ -483,7 +487,7 @@ fn load_plugins_uses_manifest_configured_component_paths() {
)])
);
assert_eq!(
outcome.plugins[0].apps,
outcome.plugins()[0].apps,
vec![AppConnectorId("connector_custom".to_string())]
);
}
@@ -559,11 +563,11 @@ fn load_plugins_ignores_manifest_component_paths_without_dot_slash() {
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
assert_eq!(
outcome.plugins[0].skill_roots,
outcome.plugins()[0].skill_roots,
vec![plugin_root.join("skills")]
);
assert_eq!(
outcome.plugins[0].mcp_servers,
outcome.plugins()[0].mcp_servers,
HashMap::from([(
"default".to_string(),
McpServerConfig {
@@ -586,7 +590,7 @@ fn load_plugins_ignores_manifest_component_paths_without_dot_slash() {
)])
);
assert_eq!(
outcome.plugins[0].apps,
outcome.plugins()[0].apps,
vec![AppConnectorId("connector_default".to_string())]
);
}
@@ -618,7 +622,7 @@ fn load_plugins_preserves_disabled_plugins_without_effective_contributions() {
let outcome = load_plugins_from_config(&plugin_config_toml(false, true), codex_home.path());
assert_eq!(
outcome.plugins,
outcome.plugins(),
vec![LoadedPlugin {
config_name: "sample@test".to_string(),
manifest_name: None,
@@ -805,24 +809,6 @@ fn capability_index_filters_inactive_and_zero_capability_plugins() {
);
}
#[test]
fn plugin_namespace_for_skill_path_uses_manifest_name() {
let codex_home = TempDir::new().unwrap();
let plugin_root = codex_home.path().join("plugins/sample");
let skill_path = plugin_root.join("skills/search/SKILL.md");
write_file(
&plugin_root.join(".codex-plugin/plugin.json"),
r#"{"name":"sample"}"#,
);
write_file(&skill_path, "---\ndescription: search\n---\n");
assert_eq!(
plugin_namespace_for_skill_path(&skill_path),
Some("sample".to_string())
);
}
#[test]
fn load_plugins_returns_empty_when_feature_disabled() {
let codex_home = TempDir::new().unwrap();
@@ -880,9 +866,9 @@ fn load_plugins_rejects_invalid_plugin_keys() {
codex_home.path(),
);
assert_eq!(outcome.plugins.len(), 1);
assert_eq!(outcome.plugins().len(), 1);
assert_eq!(
outcome.plugins[0].error.as_deref(),
outcome.plugins()[0].error.as_deref(),
Some("invalid plugin key `sample`; expected <plugin>@<marketplace>")
);
assert!(outcome.effective_skill_roots().is_empty());