feat: Normalize remote plugin summary identities. (#22265)

Makes plugin summaries use config-style plugin@marketplace IDs while
exposing backend remote IDs separately as remotePluginId.

Also fix the consistency issue of REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME
This commit is contained in:
xl-openai
2026-05-12 00:58:37 -07:00
committed by GitHub
parent 46f30d0282
commit 5b1a4c2fa7
17 changed files with 209 additions and 44 deletions
@@ -482,6 +482,7 @@ impl PluginRequestProcessor {
);
PluginSummary {
id: plugin.id,
remote_plugin_id: None,
installed: plugin.installed,
enabled: plugin.enabled,
name: plugin.name,
@@ -723,6 +724,7 @@ impl PluginRequestProcessor {
marketplace_path: outcome.marketplace_path,
summary: PluginSummary {
id: outcome.plugin.id,
remote_plugin_id: None,
name: outcome.plugin.name,
share_context,
source: marketplace_plugin_source_to_info(outcome.plugin.source),
@@ -1107,7 +1109,6 @@ impl PluginRequestProcessor {
)
})?;
if remote_detail.summary.availability == PluginAvailability::DisabledByAdmin {
let remote_plugin_id = &remote_detail.summary.id;
return Err(invalid_request(format!(
"remote plugin {remote_plugin_id} is disabled by admin"
)));
@@ -1587,6 +1588,7 @@ fn remote_marketplace_to_info(marketplace: RemoteMarketplace) -> PluginMarketpla
fn remote_plugin_summary_to_info(summary: RemoteCatalogPluginSummary) -> PluginSummary {
PluginSummary {
id: summary.id,
remote_plugin_id: Some(summary.remote_plugin_id),
name: summary.name,
share_context: summary
.share_context
@@ -240,6 +240,7 @@ async fn plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_
interface: None,
plugins: vec![PluginSummary {
id: "valid-plugin@valid-marketplace".to_string(),
remote_plugin_id: None,
name: "valid-plugin".to_string(),
share_context: None,
source: PluginSource::Local {
@@ -529,6 +530,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
plugins: vec![
PluginSummary {
id: "valid-plugin@alternate-marketplace".to_string(),
remote_plugin_id: None,
name: "valid-plugin".to_string(),
share_context: None,
source: PluginSource::Local {
@@ -562,6 +564,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
},
PluginSummary {
id: "missing-plugin@alternate-marketplace".to_string(),
remote_plugin_id: None,
name: "missing-plugin".to_string(),
share_context: None,
source: PluginSource::Local {
@@ -688,6 +691,7 @@ async fn plugin_list_returns_share_context_for_shared_local_plugin() -> Result<(
.flat_map(|marketplace| marketplace.plugins.iter())
.find(|plugin| plugin.name == "demo-plugin")
.expect("expected demo-plugin entry");
assert_eq!(plugin.remote_plugin_id, None);
let share_context = plugin
.share_context
.as_ref()
@@ -1464,11 +1468,7 @@ async fn plugin_list_sync_upgrades_and_removes_remote_installed_plugin_bundles()
.into_iter()
.map(|plugin| (plugin.id, plugin.installed, plugin.enabled))
.collect::<Vec<_>>(),
vec![(
"plugins~Plugin_00000000000000000000000000000000".to_string(),
true,
true
)]
vec![("linear@chatgpt-global".to_string(), true, true)]
);
wait_for_path_exists(&new_path.join(".codex-plugin/plugin.json")).await?;
@@ -1629,9 +1629,10 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
Some("ChatGPT Plugins")
);
assert_eq!(remote_marketplace.plugins.len(), 1);
assert_eq!(remote_marketplace.plugins[0].id, "linear@chatgpt-global");
assert_eq!(
remote_marketplace.plugins[0].id,
"plugins~Plugin_00000000000000000000000000000000"
remote_marketplace.plugins[0].remote_plugin_id.as_deref(),
Some("plugins~Plugin_00000000000000000000000000000000")
);
assert_eq!(remote_marketplace.plugins[0].name, "linear");
assert_eq!(remote_marketplace.plugins[0].source, PluginSource::Remote);
@@ -1725,12 +1726,14 @@ async fn plugin_list_fetches_workspace_directory_kind_without_remote_plugin_flag
"plugins~Plugin_11111111111111111111111111111111",
"workspace-linear",
"Workspace Linear",
"LISTED",
/*enabled*/ None,
);
let workspace_installed_body = workspace_remote_plugin_page_body(
"plugins~Plugin_11111111111111111111111111111111",
"workspace-linear",
"Workspace Linear",
"LISTED",
/*enabled*/ Some(false),
);
mount_remote_plugin_list(&server, "WORKSPACE", &workspace_plugin_body).await;
@@ -1764,6 +1767,14 @@ async fn plugin_list_fetches_workspace_directory_kind_without_remote_plugin_flag
Some("Workspace Directory")
);
assert_eq!(marketplace.plugins.len(), 1);
assert_eq!(
marketplace.plugins[0].id,
"workspace-linear@workspace-directory"
);
assert_eq!(
marketplace.plugins[0].remote_plugin_id.as_deref(),
Some("plugins~Plugin_11111111111111111111111111111111")
);
assert_eq!(marketplace.plugins[0].name, "workspace-linear");
assert_eq!(marketplace.plugins[0].installed, true);
assert_eq!(marketplace.plugins[0].enabled, false);
@@ -1803,6 +1814,7 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
"plugins~Plugin_22222222222222222222222222222222",
"shared-linear",
"Shared Linear",
"PRIVATE",
/*enabled*/ None,
))?;
shared_plugin_body["plugins"][0]["share_principals"] = serde_json::Value::Null;
@@ -1811,6 +1823,7 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
"plugins~Plugin_22222222222222222222222222222222",
"shared-linear",
"Shared Linear",
"PRIVATE",
/*enabled*/ Some(true),
);
mount_shared_workspace_plugins(&server, &shared_plugin_body).await;
@@ -1844,6 +1857,11 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
Some("Shared with me")
);
assert_eq!(marketplace.plugins.len(), 1);
assert_eq!(marketplace.plugins[0].id, "shared-linear@shared-with-me");
assert_eq!(
marketplace.plugins[0].remote_plugin_id.as_deref(),
Some("plugins~Plugin_22222222222222222222222222222222")
);
assert_eq!(marketplace.plugins[0].name, "shared-linear");
assert_eq!(marketplace.plugins[0].installed, true);
assert_eq!(marketplace.plugins[0].enabled, true);
@@ -2381,6 +2399,7 @@ fn workspace_remote_plugin_page_body(
remote_plugin_id: &str,
plugin_name: &str,
display_name: &str,
discoverability: &str,
enabled: Option<bool>,
) -> String {
let enabled_field = enabled
@@ -2393,7 +2412,7 @@ fn workspace_remote_plugin_page_body(
"id": "{remote_plugin_id}",
"name": "{plugin_name}",
"scope": "WORKSPACE",
"discoverability": "PRIVATE",
"discoverability": "{discoverability}",
"creator_account_user_id": "user-gavin__account-123",
"share_url": "https://chatgpt.example/plugins/share/share-key-1",
"installation_policy": "AVAILABLE",
@@ -209,9 +209,10 @@ plugins = true
let response: PluginReadResponse = to_response(response)?;
assert_eq!(response.plugin.marketplace_name, "chatgpt-global");
assert_eq!(response.plugin.summary.id, "linear@chatgpt-global");
assert_eq!(
response.plugin.summary.id,
"plugins~Plugin_00000000000000000000000000000000"
response.plugin.summary.remote_plugin_id.as_deref(),
Some("plugins~Plugin_00000000000000000000000000000000")
);
assert_eq!(response.plugin.summary.name, "linear");
assert_eq!(response.plugin.summary.source, PluginSource::Remote);
@@ -313,6 +314,12 @@ async fn plugin_read_returns_share_context_for_shared_remote_plugin() -> Result<
.await??;
let response: PluginReadResponse = to_response(response)?;
assert_eq!(response.plugin.marketplace_name, "shared-with-me");
assert_eq!(response.plugin.summary.id, "shared-linear@shared-with-me");
assert_eq!(
response.plugin.summary.remote_plugin_id.as_deref(),
Some("plugins~Plugin_11111111111111111111111111111111")
);
let share_context = response
.plugin
.summary
@@ -482,9 +489,10 @@ async fn plugin_read_reads_remote_plugin_details_when_remote_plugin_enabled() ->
assert_eq!(response.plugin.marketplace_name, "chatgpt-global");
assert_eq!(response.plugin.marketplace_path, None);
assert_eq!(response.plugin.summary.source, PluginSource::Remote);
assert_eq!(response.plugin.summary.id, "linear@chatgpt-global");
assert_eq!(
response.plugin.summary.id,
"plugins~Plugin_00000000000000000000000000000000"
response.plugin.summary.remote_plugin_id.as_deref(),
Some("plugins~Plugin_00000000000000000000000000000000")
);
assert_eq!(response.plugin.summary.name, "linear");
assert_eq!(response.plugin.summary.installed, true);
@@ -856,6 +864,7 @@ async fn plugin_read_returns_share_context_for_shared_local_plugin() -> Result<(
.await??;
let response: PluginReadResponse = to_response(response)?;
assert_eq!(response.plugin.summary.remote_plugin_id, None);
let share_context = response
.plugin
.summary
@@ -931,6 +940,7 @@ async fn plugin_read_falls_back_to_local_share_context_without_remote_auth() ->
.await??;
let response: PluginReadResponse = to_response(response)?;
assert_eq!(response.plugin.summary.remote_plugin_id, None);
let share_context = response
.plugin
.summary
@@ -162,7 +162,8 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> {
PluginShareListResponse {
data: vec![PluginShareListItem {
plugin: PluginSummary {
id: "plugins_123".to_string(),
id: "demo-plugin@shared-with-me".to_string(),
remote_plugin_id: Some("plugins_123".to_string()),
name: "demo-plugin".to_string(),
share_context: Some(expected_share_context("plugins_123")),
source: PluginSource::Remote,
@@ -506,7 +507,8 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> {
PluginShareListResponse {
data: vec![PluginShareListItem {
plugin: PluginSummary {
id: "plugins_123".to_string(),
id: "demo-plugin@shared-with-me".to_string(),
remote_plugin_id: Some("plugins_123".to_string()),
name: "demo-plugin".to_string(),
share_context: Some(expected_share_context("plugins_123")),
source: PluginSource::Remote,
@@ -725,7 +727,8 @@ async fn plugin_share_delete_removes_created_workspace_plugin() -> Result<()> {
PluginShareListResponse {
data: vec![PluginShareListItem {
plugin: PluginSummary {
id: "plugins_123".to_string(),
id: "demo-plugin@shared-with-me".to_string(),
remote_plugin_id: Some("plugins_123".to_string()),
name: "demo-plugin".to_string(),
share_context: Some(expected_share_context("plugins_123")),
source: PluginSource::Remote,
@@ -617,11 +617,17 @@ async fn mount_remote_plugin_detail_with_name(
release_version: &str,
scope: &str,
) {
let discoverability = if scope == "WORKSPACE" {
r#"
"discoverability": "LISTED","#
} else {
""
};
let detail_body = format!(
r#"{{
"id": "{remote_plugin_id}",
"name": "{plugin_name}",
"scope": "{scope}",
"scope": "{scope}",{discoverability}
"installation_policy": "AVAILABLE",
"authentication_policy": "ON_USE",
"release": {{