Allow installed plugin metadata to skip display enrichment (#56483)

## Why

Runtime metadata and tool suggestions do not require resolved image URLs or creator names. Callers that need display enrichment must still be able to distinguish a complete display snapshot from one fetched without it.

## What changed

- Add `InstalledPluginDisplayAssets` to select whether installed plugin requests include display enrichment, preserving `Include` as the default.
- Send `includeDisplayAssets=false` on every request page when `Skip` is selected.
- Track display enrichment in the installed plugin cache. Allow tool suggestions to use snapshots without enrichment, while treating those snapshots as cache misses for display callers.
- Avoid invalidating effective runtime metadata when only the cache's display enrichment state changes.

## Testing

Add coverage for paginated requests without display enrichment, cache eligibility and enrichment state transitions, and discovery using snapshots fetched with or without display assets.

GitOrigin-RevId: 1fdfd6443a300a03753416f2f6c633b52a97d0fb
This commit is contained in:
bkotsopoulos
2026-10-23 14:15:03 +00:00
committed by copyberry
parent 7d592f6dd9
commit de06fd0be4
8 changed files with 238 additions and 13 deletions
@@ -4,6 +4,7 @@ use crate::OPENAI_BUNDLED_MARKETPLACE_NAME;
use crate::PluginInstallRequest;
use crate::PluginsConfigInput;
use crate::PluginsManager;
use crate::remote::InstalledPluginDisplayAssets;
use crate::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
use crate::remote::RemotePluginServiceConfig;
use crate::remote::fetch_and_cache_global_remote_plugin_catalog;
@@ -840,6 +841,18 @@ source = "/tmp/{sales_marketplace_name}"
#[tokio::test]
async fn cached_remote_discovery_requires_installed_cache_and_filters_candidates() {
assert_cached_remote_discovery_filters_candidates(InstalledPluginDisplayAssets::Include).await;
}
/// Tool suggestions need installed IDs even when display enrichment was skipped.
#[tokio::test]
async fn cached_remote_discovery_accepts_snapshot_without_display_assets() {
assert_cached_remote_discovery_filters_candidates(InstalledPluginDisplayAssets::Skip).await;
}
async fn assert_cached_remote_discovery_filters_candidates(
display_assets: InstalledPluginDisplayAssets,
) {
let codex_home = tempdir().expect("tempdir should succeed");
write_file(
&codex_home.path().join(CONFIG_TOML_FILE),
@@ -973,10 +986,19 @@ plugins = true
Vec::new()
);
Mock::given(method("GET"))
let installed_request = Mock::given(method("GET"))
.and(path("/backend-api/ps/plugins/installed"))
.and(query_param_is_missing("scope"))
.and(query_param_is_missing("includeDownloadUrls"))
.and(query_param_is_missing("includeDownloadUrls"));
let installed_request = match display_assets {
InstalledPluginDisplayAssets::Include => {
installed_request.and(query_param_is_missing("includeDisplayAssets"))
}
InstalledPluginDisplayAssets::Skip => {
installed_request.and(query_param("includeDisplayAssets", "false"))
}
};
installed_request
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"plugins": [],
"pagination": {
@@ -987,11 +1009,12 @@ plugins = true
.mount(&server)
.await;
plugins_manager
.build_and_cache_remote_installed_plugin_marketplaces(
.build_and_cache_remote_installed_plugin_marketplaces_with_display_assets(
&plugins,
Some(&auth),
&[REMOTE_GLOBAL_MARKETPLACE_NAME],
/*on_effective_plugins_changed*/ None,
display_assets,
)
.await
.expect("remote installed plugin cache should write");