Files
codex/codex-rs/app-server/src/app_info.rs
T
stevenlee-oai b72079a2cf Use batch metadata for plugin app summaries (#34851)
## What changed

- Load app metadata for plugin read and install responses through the authenticated batch API, splitting requests into batches of 100 and retaining cached metadata if a batch fails.
- Preserve every declared app in plugin responses, using its ID as the fallback name and retaining categories from the plugin declaration when metadata is unavailable.
- Add `isEnabled`, `disabledReason`, and `isReadOnly` to `AppToolSummary`, with defaults that keep legacy responses compatible.

## Testing

- Cover plugin reads with more than 100 apps, partial batch failures, install-time authentication results, tool-state propagation, and legacy tool summaries.

GitOrigin-RevId: 14000df9c5c94dde781358e292a8bef741c5dd23
2026-07-23 01:00:35 +00:00

176 lines
5.0 KiB
Rust

use codex_app_server_protocol::AppBranding as ApiAppBranding;
use codex_app_server_protocol::AppInfo as ApiAppInfo;
use codex_app_server_protocol::AppMetadata as ApiAppMetadata;
use codex_app_server_protocol::AppReview as ApiAppReview;
use codex_app_server_protocol::AppScreenshot as ApiAppScreenshot;
use codex_app_server_protocol::AppToolSummary as ApiAppToolSummary;
use codex_app_server_protocol::ConnectorMetadata as ApiConnectorMetadata;
use codex_connectors::AppBranding;
use codex_connectors::AppInfo;
use codex_connectors::AppMetadata;
use codex_connectors::AppReview;
use codex_connectors::AppScreenshot;
use codex_connectors::ConnectorMetadata;
use codex_connectors::ConnectorToolSummary;
use codex_connectors::metadata::connector_install_url;
/// Converts connector-domain app metadata owned by `codex-connectors` into the app-server wire
/// type owned by `codex-app-server-protocol`.
///
/// The types stay separate so app-server protocol ownership does not leak into the connector
/// domain crate. Because this crate owns neither type, Rust's orphan rules require an explicit
/// conversion function instead of a `From` implementation.
pub(crate) fn app_info_to_api(app: AppInfo) -> ApiAppInfo {
let AppInfo {
id,
name,
description,
logo_url,
logo_url_dark,
icon_assets,
icon_dark_assets,
distribution_channel,
branding,
app_metadata,
labels,
install_url,
is_accessible,
is_enabled,
plugin_display_names,
} = app;
ApiAppInfo {
id,
name,
description,
logo_url,
logo_url_dark,
icon_assets,
icon_dark_assets,
distribution_channel,
branding: branding.map(app_branding_to_api),
app_metadata: app_metadata.map(app_metadata_to_api),
labels,
install_url,
is_accessible,
is_enabled,
plugin_display_names,
}
}
/// Converts metadata-only connector data into the app-server wire type.
///
/// Keeping this separate from app_info_to_api makes it impossible for app/read to accidentally
/// expose full runtime tool state from the broader app/list path.
pub(crate) fn connector_metadata_to_api(metadata: ConnectorMetadata) -> ApiConnectorMetadata {
let ConnectorMetadata {
id,
name,
description,
icon_url,
icon_url_dark,
distribution_channel,
tool_summaries,
} = metadata;
let install_url = Some(connector_install_url(&name, &id));
ApiConnectorMetadata {
id,
name,
description,
icon_url,
icon_url_dark,
distribution_channel,
install_url,
plugin_display_names: Vec::new(),
tool_summaries: tool_summaries.map(|tools| {
tools
.into_iter()
.map(|tool| {
let ConnectorToolSummary {
name,
title,
description,
is_enabled,
disabled_reason,
is_read_only,
} = tool;
ApiAppToolSummary {
name,
title,
description,
is_enabled,
disabled_reason,
is_read_only,
}
})
.collect()
}),
}
}
fn app_branding_to_api(branding: AppBranding) -> ApiAppBranding {
let AppBranding {
category,
developer,
website,
privacy_policy,
terms_of_service,
is_discoverable_app,
} = branding;
ApiAppBranding {
category,
developer,
website,
privacy_policy,
terms_of_service,
is_discoverable_app,
}
}
fn app_review_to_api(review: AppReview) -> ApiAppReview {
let AppReview { status } = review;
ApiAppReview { status }
}
fn app_screenshot_to_api(screenshot: AppScreenshot) -> ApiAppScreenshot {
let AppScreenshot {
url,
file_id,
user_prompt,
} = screenshot;
ApiAppScreenshot {
url,
file_id,
user_prompt,
}
}
fn app_metadata_to_api(metadata: AppMetadata) -> ApiAppMetadata {
let AppMetadata {
review,
categories,
sub_categories,
seo_description,
screenshots,
developer,
version,
version_id,
version_notes,
first_party_requires_install,
show_in_composer_when_unlinked,
} = metadata;
ApiAppMetadata {
review: review.map(app_review_to_api),
categories,
sub_categories,
seo_description,
screenshots: screenshots
.map(|screenshots| screenshots.into_iter().map(app_screenshot_to_api).collect()),
developer,
version,
version_id,
version_notes,
first_party_requires_install,
show_in_composer_when_unlinked,
}
}