Expose plugin manifest keywords in app server (#21271)

## Summary
- Add plugin manifest keywords to core plugin marketplace/detail models
- Expose keywords on app-server v2 PluginSummary and generated
schema/types
- Populate keywords in plugin/list and plugin/read responses for local
plugins

Depends on https://github.com/openai/openai/pull/891087

## Validation
- just fmt
- just write-app-server-schema
- cargo test -p codex-app-server-protocol
- cargo test -p codex-core-plugins
- cargo test -p codex-app-server
plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_load
- cargo test -p codex-app-server
plugin_read_returns_plugin_details_with_bundle_contents
This commit is contained in:
Abdulrahman Alfozan
2026-05-05 22:09:05 -04:00
committed by GitHub
parent 136e442e95
commit 94db03d5af
20 changed files with 140 additions and 2 deletions

View File

@@ -197,7 +197,7 @@ async fn plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_
valid_repo_root
.path()
.join("plugins/valid-plugin/.codex-plugin/plugin.json"),
r#"{"name":"valid-plugin"}"#,
r#"{"name":"valid-plugin","keywords":["api-key","developer tools"]}"#,
)?;
std::fs::write(invalid_marketplace_path.as_path(), "{not json")?;
@@ -246,6 +246,7 @@ async fn plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_
auth_policy: PluginAuthPolicy::OnInstall,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: None,
keywords: vec!["api-key".to_string(), "developer tools".to_string()],
}],
}]
);
@@ -548,6 +549,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
screenshots: Vec::new(),
screenshot_urls: Vec::new(),
}),
keywords: Vec::new(),
},
PluginSummary {
id: "missing-plugin@alternate-marketplace".to_string(),
@@ -563,6 +565,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
auth_policy: PluginAuthPolicy::OnInstall,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: None,
keywords: Vec::new(),
},
],
}]
@@ -1295,6 +1298,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
"display_name": "Linear",
"description": "Track work in Linear",
"app_ids": [],
"keywords": ["issue-tracking", "project management"],
"interface": {
"short_description": "Plan and track work",
"capabilities": ["Read", "Write"],
@@ -1430,6 +1434,13 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
.and_then(|interface| interface.display_name.as_deref()),
Some("Linear")
);
assert_eq!(
remote_marketplace.plugins[0].keywords,
vec![
"issue-tracking".to_string(),
"project management".to_string()
]
);
assert_eq!(response.featured_plugin_ids, Vec::<String>::new());
Ok(())
}

View File

@@ -172,6 +172,7 @@ async fn plugin_read_reads_remote_plugin_details_when_remote_plugin_enabled() ->
"display_name": "Linear",
"description": "Track work in Linear",
"app_ids": [],
"keywords": ["issue-tracking", "project management"],
"interface": {
"short_description": "Plan and track work",
"capabilities": ["Read", "Write"],
@@ -281,6 +282,13 @@ async fn plugin_read_reads_remote_plugin_details_when_remote_plugin_enabled() ->
response.plugin.description.as_deref(),
Some("Track work in Linear")
);
assert_eq!(
response.plugin.summary.keywords,
vec![
"issue-tracking".to_string(),
"project management".to_string()
]
);
assert_eq!(response.plugin.skills.len(), 1);
assert_eq!(response.plugin.skills[0].name, "plan-work");
assert_eq!(response.plugin.skills[0].path, None);
@@ -580,6 +588,7 @@ async fn plugin_read_returns_plugin_details_with_bundle_contents() -> Result<()>
r##"{
"name": "demo-plugin",
"description": "Longer manifest description",
"keywords": ["api-key", "developer tools"],
"interface": {
"displayName": "Plugin Display Name",
"shortDescription": "Short description for subtitle",
@@ -740,6 +749,10 @@ enabled = true
"Find my next action".to_string()
])
);
assert_eq!(
response.plugin.summary.keywords,
vec!["api-key".to_string(), "developer tools".to_string()]
);
assert_eq!(response.plugin.skills.len(), 1);
assert_eq!(
response.plugin.skills[0].name,

View File

@@ -164,6 +164,7 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> {
auth_policy: PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
keywords: Vec::new(),
},
share_url: "https://chatgpt.example/plugins/share/share-key-1".to_string(),
local_plugin_path: Some(expected_plugin_path),
@@ -239,6 +240,7 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> {
auth_policy: PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
keywords: Vec::new(),
},
share_url: "https://chatgpt.example/plugins/share/share-key-1".to_string(),
local_plugin_path: None,
@@ -342,6 +344,7 @@ async fn plugin_share_delete_removes_created_workspace_plugin() -> Result<()> {
auth_policy: PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
keywords: Vec::new(),
},
share_url: "https://chatgpt.example/plugins/share/share-key-1".to_string(),
local_plugin_path: None,