diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 74fb9e739c..0567e64a02 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -241,6 +241,24 @@ "null" ] }, + "iconAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "iconDarkAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, "id": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 3a2af11db3..e090479862 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -6403,6 +6403,24 @@ "null" ] }, + "iconAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "iconDarkAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, "id": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 286fc0e2f6..1915016401 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -529,6 +529,24 @@ "null" ] }, + "iconAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "iconDarkAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, "id": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/AppListUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AppListUpdatedNotification.json index d4e99f5086..0b04e2b22d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/AppListUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/AppListUpdatedNotification.json @@ -78,6 +78,24 @@ "null" ] }, + "iconAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "iconDarkAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, "id": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/AppsListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/AppsListResponse.json index 2fb9092cb0..6dfa1accb1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/AppsListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/AppsListResponse.json @@ -78,6 +78,24 @@ "null" ] }, + "iconAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "iconDarkAssets": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, "id": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AppInfo.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AppInfo.ts index ef1f54aa68..7145ce9a90 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/AppInfo.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AppInfo.ts @@ -7,7 +7,7 @@ import type { AppMetadata } from "./AppMetadata"; /** * EXPERIMENTAL - app metadata returned by app-list APIs. */ -export type AppInfo = { id: string, name: string, description: string | null, logoUrl: string | null, logoUrlDark: string | null, distributionChannel: string | null, branding: AppBranding | null, appMetadata: AppMetadata | null, labels: { [key in string]?: string } | null, installUrl: string | null, isAccessible: boolean, +export type AppInfo = { id: string, name: string, description: string | null, logoUrl: string | null, logoUrlDark: string | null, iconAssets: { [key in string]?: string } | null, iconDarkAssets: { [key in string]?: string } | null, distributionChannel: string | null, branding: AppBranding | null, appMetadata: AppMetadata | null, labels: { [key in string]?: string } | null, installUrl: string | null, isAccessible: boolean, /** * Whether this app is enabled in config.toml. * Example: diff --git a/codex-rs/app-server-protocol/src/protocol/v2/apps.rs b/codex-rs/app-server-protocol/src/protocol/v2/apps.rs index 94545eb788..f14c11b0d8 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/apps.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/apps.rs @@ -83,6 +83,8 @@ pub struct AppInfo { pub description: Option, pub logo_url: Option, pub logo_url_dark: Option, + pub icon_assets: Option>, + pub icon_dark_assets: Option>, pub distribution_channel: Option, pub branding: Option, pub app_metadata: Option, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index e70db8bba1..434852baf0 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1784,7 +1784,7 @@ To disable a non-managed hook, upsert a state entry at `hooks.state` with `confi To re-enable it, upsert the same hook key with `"enabled": true`. ## Apps -Use `app/list` to fetch available apps (connectors). Each entry includes metadata like the app `id`, display `name`, `installUrl`, `branding`, `appMetadata`, `labels`, whether it is currently accessible, and whether it is enabled in config. +Use `app/list` to fetch available apps (connectors). Each entry includes metadata like the app `id`, display `name`, `installUrl`, legacy logo URLs, structured light and dark icon assets, `branding`, `appMetadata`, `labels`, whether it is currently accessible, and whether it is enabled in config. ```json { "method": "app/list", "id": 50, "params": { @@ -1801,6 +1801,10 @@ Use `app/list` to fetch available apps (connectors). Each entry includes metadat "description": "Example connector for documentation.", "logoUrl": "https://example.com/demo-app.png", "logoUrlDark": null, + "iconAssets": { + "256_square": "https://example.com/demo-app-square.png" + }, + "iconDarkAssets": null, "distributionChannel": null, "branding": null, "appMetadata": null, @@ -1831,6 +1835,10 @@ The server also emits `app/list/updated` notifications whenever either source (a "description": "Example connector for documentation.", "logoUrl": "https://example.com/demo-app.png", "logoUrlDark": null, + "iconAssets": { + "256_square": "https://example.com/demo-app-square.png" + }, + "iconDarkAssets": null, "distributionChannel": null, "branding": null, "appMetadata": null, diff --git a/codex-rs/app-server/tests/suite/v2/app_list.rs b/codex-rs/app-server/tests/suite/v2/app_list.rs index daface3447..7d66afa879 100644 --- a/codex-rs/app-server/tests/suite/v2/app_list.rs +++ b/codex-rs/app-server/tests/suite/v2/app_list.rs @@ -97,6 +97,8 @@ async fn list_apps_returns_empty_with_api_key_auth() -> Result<()> { description: Some("Beta connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -162,6 +164,8 @@ async fn list_apps_returns_empty_when_workspace_codex_plugins_disabled() -> Resu description: Some("Beta connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -268,6 +272,8 @@ async fn list_apps_uses_thread_feature_flag_when_thread_id_is_provided() -> Resu description: Some("Beta connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -372,6 +378,8 @@ async fn list_apps_keeps_apps_with_app_only_tools_accessible() -> Result<()> { description: Some("Formerly blocked connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -439,6 +447,8 @@ async fn list_apps_reports_is_enabled_from_config() -> Result<()> { description: Some("Beta connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -549,6 +559,8 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<( description: Some("Alpha connector".to_string()), logo_url: Some("https://example.com/alpha.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: alpha_branding.clone(), app_metadata: alpha_app_metadata.clone(), @@ -564,6 +576,8 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<( description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -613,6 +627,8 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<( description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -633,6 +649,8 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<( description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -648,6 +666,8 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<( description: Some("Alpha connector".to_string()), logo_url: Some("https://example.com/alpha.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: alpha_branding, app_metadata: alpha_app_metadata, @@ -689,6 +709,8 @@ async fn list_apps_waits_for_accessible_data_before_emitting_directory_updates() description: Some("Alpha connector".to_string()), logo_url: Some("https://example.com/alpha.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -704,6 +726,8 @@ async fn list_apps_waits_for_accessible_data_before_emitting_directory_updates() description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -754,6 +778,8 @@ async fn list_apps_waits_for_accessible_data_before_emitting_directory_updates() description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -769,6 +795,8 @@ async fn list_apps_waits_for_accessible_data_before_emitting_directory_updates() description: Some("Alpha connector".to_string()), logo_url: Some("https://example.com/alpha.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -813,6 +841,8 @@ async fn list_apps_does_not_emit_empty_interim_updates() -> Result<()> { description: Some("Alpha connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -869,6 +899,8 @@ async fn list_apps_does_not_emit_empty_interim_updates() -> Result<()> { description: Some("Alpha connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -904,6 +936,8 @@ async fn list_apps_paginates_results() -> Result<()> { description: Some("Alpha connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -919,6 +953,8 @@ async fn list_apps_paginates_results() -> Result<()> { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -977,6 +1013,8 @@ async fn list_apps_paginates_results() -> Result<()> { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1021,6 +1059,8 @@ async fn list_apps_paginates_results() -> Result<()> { description: Some("Alpha connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1046,6 +1086,8 @@ async fn list_apps_force_refetch_preserves_previous_cache_on_failure() -> Result description: Some("Beta connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1151,6 +1193,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu description: Some("Alpha v1".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1166,6 +1210,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu description: Some("Beta v1".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1216,6 +1262,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1237,6 +1285,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu description: Some("Beta v1".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1252,6 +1302,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu description: Some("Alpha v1".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1282,6 +1334,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu description: Some("Alpha v2".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1312,6 +1366,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu description: Some("Beta v1".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1327,6 +1383,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu description: Some("Alpha v1".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1355,6 +1413,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu description: Some("Alpha v2".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/app-server/tests/suite/v2/plugin_install.rs b/codex-rs/app-server/tests/suite/v2/plugin_install.rs index aebc30dfea..d9012c1cf8 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_install.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_install.rs @@ -1093,6 +1093,8 @@ async fn plugin_install_returns_apps_needing_auth() -> Result<()> { description: Some("Alpha connector".to_string()), logo_url: Some("https://example.com/alpha.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: Some("featured".to_string()), branding: None, app_metadata: None, @@ -1108,6 +1110,8 @@ async fn plugin_install_returns_apps_needing_auth() -> Result<()> { description: Some("Beta connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1192,6 +1196,8 @@ async fn plugin_install_skips_mcp_oauth_for_chatgpt_dual_surface_plugin() -> Res description: Some("Sample MCP connector".to_string()), logo_url: Some("https://example.com/alpha.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: Some("featured".to_string()), branding: None, app_metadata: None, @@ -1584,6 +1590,8 @@ async fn plugin_install_includes_formerly_disallowed_apps_needing_auth() -> Resu description: Some("Alpha connector".to_string()), logo_url: Some("https://example.com/alpha.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: Some("featured".to_string()), branding: None, app_metadata: None, diff --git a/codex-rs/app-server/tests/suite/v2/plugin_read.rs b/codex-rs/app-server/tests/suite/v2/plugin_read.rs index 5009beb640..c0cdca4678 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_read.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_read.rs @@ -219,6 +219,8 @@ apps = true description: Some("Example app connector".to_string()), logo_url: Some("https://example.com/example.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: Some("featured".to_string()), branding: None, app_metadata: None, @@ -1564,6 +1566,8 @@ async fn plugin_read_returns_app_metadata_category() -> Result<()> { description: Some("Alpha connector".to_string()), logo_url: Some("https://example.com/alpha.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: Some("featured".to_string()), branding: None, app_metadata: Some(AppMetadata { @@ -1592,6 +1596,8 @@ async fn plugin_read_returns_app_metadata_category() -> Result<()> { description: Some("Beta connector".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1667,6 +1673,8 @@ async fn plugin_read_hides_apps_for_api_key_auth() -> Result<()> { description: Some("Alpha connector".to_string()), logo_url: Some("https://example.com/alpha.png".to_string()), logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: Some("featured".to_string()), branding: None, app_metadata: Some(AppMetadata { diff --git a/codex-rs/chatgpt/src/connectors.rs b/codex-rs/chatgpt/src/connectors.rs index fa1a894cca..ea258255e6 100644 --- a/codex-rs/chatgpt/src/connectors.rs +++ b/codex-rs/chatgpt/src/connectors.rs @@ -198,6 +198,8 @@ mod tests { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -216,6 +218,8 @@ mod tests { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/connectors/src/accessible.rs b/codex-rs/connectors/src/accessible.rs index c44f8d8a38..ffcc86262e 100644 --- a/codex-rs/connectors/src/accessible.rs +++ b/codex-rs/connectors/src/accessible.rs @@ -41,6 +41,8 @@ where description: connector_description, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/connectors/src/filter.rs b/codex-rs/connectors/src/filter.rs index 6d60c89ad6..7824db2174 100644 --- a/codex-rs/connectors/src/filter.rs +++ b/codex-rs/connectors/src/filter.rs @@ -39,6 +39,8 @@ mod tests { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, install_url: None, branding: None, diff --git a/codex-rs/connectors/src/lib.rs b/codex-rs/connectors/src/lib.rs index a75120c0a4..60a07cf5b9 100644 --- a/codex-rs/connectors/src/lib.rs +++ b/codex-rs/connectors/src/lib.rs @@ -81,6 +81,10 @@ pub struct DirectoryApp { logo_url: Option, #[serde(alias = "logoUrlDark")] logo_url_dark: Option, + #[serde(alias = "iconAssets")] + icon_assets: Option>, + #[serde(alias = "iconDarkAssets")] + icon_dark_assets: Option>, #[serde(alias = "distributionChannel")] distribution_channel: Option, visibility: Option, @@ -276,6 +280,8 @@ fn merge_directory_app(existing: &mut DirectoryApp, incoming: DirectoryApp) { labels, logo_url, logo_url_dark, + icon_assets, + icon_dark_assets, distribution_channel, visibility: _, } = incoming; @@ -299,6 +305,12 @@ fn merge_directory_app(existing: &mut DirectoryApp, incoming: DirectoryApp) { if existing.logo_url_dark.is_none() && logo_url_dark.is_some() { existing.logo_url_dark = logo_url_dark; } + if existing.icon_assets.is_none() && icon_assets.is_some() { + existing.icon_assets = icon_assets; + } + if existing.icon_dark_assets.is_none() && icon_dark_assets.is_some() { + existing.icon_dark_assets = icon_dark_assets; + } if existing.distribution_channel.is_none() && distribution_channel.is_some() { existing.distribution_channel = distribution_channel; } @@ -417,6 +429,8 @@ fn directory_app_to_app_info(app: DirectoryApp) -> AppInfo { description: app.description, logo_url: app.logo_url, logo_url_dark: app.logo_url_dark, + icon_assets: app.icon_assets, + icon_dark_assets: app.icon_dark_assets, distribution_channel: app.distribution_channel, branding: app.branding, app_metadata: app.app_metadata, @@ -509,11 +523,61 @@ mod tests { labels: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, visibility: None, } } + #[test] + fn directory_app_icon_assets_reach_app_info_wire_format() -> anyhow::Result<()> { + let response: DirectoryListResponse = serde_json::from_value(serde_json::json!({ + "apps": [{ + "id": "alpha", + "name": "Alpha" + }, { + "id": "alpha", + "name": "", + "icon_assets": { + "256_square": "https://example.com/alpha-square.png" + }, + "icon_dark_assets": { + "256_square": "https://example.com/alpha-square-dark.png" + } + }], + "next_token": null + }))?; + + let app_info = directory_app_to_app_info(merge_directory_apps(response.apps).remove(0)); + + assert_eq!( + serde_json::to_value(app_info)?, + serde_json::json!({ + "id": "alpha", + "name": "Alpha", + "description": null, + "logoUrl": null, + "logoUrlDark": null, + "iconAssets": { + "256_square": "https://example.com/alpha-square.png" + }, + "iconDarkAssets": { + "256_square": "https://example.com/alpha-square-dark.png" + }, + "distributionChannel": null, + "branding": null, + "appMetadata": null, + "labels": null, + "installUrl": null, + "isAccessible": false, + "isEnabled": true, + "pluginDisplayNames": [] + }) + ); + Ok(()) + } + #[tokio::test] #[expect( clippy::await_holding_invalid_type, diff --git a/codex-rs/connectors/src/merge.rs b/codex-rs/connectors/src/merge.rs index a8bee18737..8f9c2fa0ae 100644 --- a/codex-rs/connectors/src/merge.rs +++ b/codex-rs/connectors/src/merge.rs @@ -34,6 +34,12 @@ pub fn merge_connectors( if existing.logo_url_dark.is_none() && connector.logo_url_dark.is_some() { existing.logo_url_dark = connector.logo_url_dark; } + if existing.icon_assets.is_none() && connector.icon_assets.is_some() { + existing.icon_assets = connector.icon_assets; + } + if existing.icon_dark_assets.is_none() && connector.icon_dark_assets.is_some() { + existing.icon_dark_assets = connector.icon_dark_assets; + } if existing.distribution_channel.is_none() && connector.distribution_channel.is_some() { existing.distribution_channel = connector.distribution_channel; } @@ -107,6 +113,8 @@ pub fn plugin_connector_to_app_info(connector_id: String) -> AppInfo { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -136,6 +144,8 @@ mod tests { description: Some("Plan events".to_string()), logo_url: Some("https://example.com/logo.png".to_string()), logo_url_dark: Some("https://example.com/logo-dark.png".to_string()), + icon_assets: None, + icon_dark_assets: None, distribution_channel: Some("workspace".to_string()), branding: None, app_metadata: None, @@ -162,6 +172,8 @@ mod tests { description: Some("Plan events".to_string()), logo_url: Some("https://example.com/logo.png".to_string()), logo_url_dark: Some("https://example.com/logo-dark.png".to_string()), + icon_assets: None, + icon_dark_assets: None, distribution_channel: Some("workspace".to_string()), branding: None, app_metadata: None, @@ -192,6 +204,8 @@ mod tests { description: Some("Plan events".to_string()), logo_url: Some("https://example.com/logo.png".to_string()), logo_url_dark: Some("https://example.com/logo-dark.png".to_string()), + icon_assets: None, + icon_dark_assets: None, distribution_channel: Some("workspace".to_string()), branding: None, app_metadata: None, diff --git a/codex-rs/core/src/apps/render.rs b/codex-rs/core/src/apps/render.rs index 3793231e10..8ee6099c15 100644 --- a/codex-rs/core/src/apps/render.rs +++ b/codex-rs/core/src/apps/render.rs @@ -19,6 +19,8 @@ mod tests { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/core/src/connectors_tests.rs b/codex-rs/core/src/connectors_tests.rs index 5bbd6a1f88..fb28539048 100644 --- a/codex-rs/core/src/connectors_tests.rs +++ b/codex-rs/core/src/connectors_tests.rs @@ -31,6 +31,8 @@ fn app(id: &str) -> AppInfo { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, install_url: None, branding: None, @@ -129,6 +131,8 @@ fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, install_url: Some(connector_install_url("Google Calendar", "calendar")), branding: None, @@ -168,6 +172,8 @@ fn synthetic_links_are_exposed_to_the_agent_but_not_accessible_in_app_list() { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, install_url: Some(connector_install_url("Calendar", "calendar")), branding: None, @@ -191,6 +197,8 @@ fn synthetic_links_are_exposed_to_the_agent_but_not_accessible_in_app_list() { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, install_url: Some(connector_install_url("Gmail", "gmail")), branding: None, @@ -243,6 +251,8 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, install_url: Some(connector_install_url("Google Calendar", "calendar")), branding: None, @@ -258,6 +268,8 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, install_url: Some(connector_install_url("Hidden", "connector_openai_hidden")), branding: None, @@ -298,6 +310,8 @@ fn accessible_connectors_from_mcp_tools_preserves_description() { description: Some("Plan events".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/core/src/mcp_tool_exposure_test.rs b/codex-rs/core/src/mcp_tool_exposure_test.rs index 63a2e33f31..6dbba73823 100644 --- a/codex-rs/core/src/mcp_tool_exposure_test.rs +++ b/codex-rs/core/src/mcp_tool_exposure_test.rs @@ -24,6 +24,8 @@ fn make_connector(id: &str, name: &str) -> AppInfo { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 903ea0b477..1daf7e8315 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -616,6 +616,8 @@ fn make_connector(id: &str, name: &str) -> AppInfo { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/core/src/tools/handlers/request_plugin_install_tests.rs b/codex-rs/core/src/tools/handlers/request_plugin_install_tests.rs index 62c8afaa65..d2d4bad91b 100644 --- a/codex-rs/core/src/tools/handlers/request_plugin_install_tests.rs +++ b/codex-rs/core/src/tools/handlers/request_plugin_install_tests.rs @@ -232,6 +232,8 @@ fn connector_tool(id: &str, name: &str) -> DiscoverableTool { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/tools/src/request_plugin_install_tests.rs b/codex-rs/tools/src/request_plugin_install_tests.rs index 400126dc6d..08bec060f7 100644 --- a/codex-rs/tools/src/request_plugin_install_tests.rs +++ b/codex-rs/tools/src/request_plugin_install_tests.rs @@ -11,6 +11,8 @@ fn build_request_plugin_install_elicitation_request_uses_expected_shape() { description: Some("Plan events and schedules.".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -124,6 +126,8 @@ fn build_request_plugin_install_meta_uses_expected_shape() { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -165,6 +169,8 @@ fn verified_connector_install_completed_requires_accessible_connector() { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -193,6 +199,8 @@ fn all_requested_connectors_picked_up_requires_every_expected_connector() { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/tools/src/tool_discovery_tests.rs b/codex-rs/tools/src/tool_discovery_tests.rs index 5a3310057c..8644c76524 100644 --- a/codex-rs/tools/src/tool_discovery_tests.rs +++ b/codex-rs/tools/src/tool_discovery_tests.rs @@ -26,6 +26,8 @@ fn filter_request_plugin_install_discoverable_tools_for_codex_tui_omits_plugins( description: Some("Plan events and schedules.".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -57,6 +59,8 @@ fn filter_request_plugin_install_discoverable_tools_for_codex_tui_omits_plugins( description: Some("Plan events and schedules.".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 2423647b38..ff094ea829 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -6135,6 +6135,8 @@ mod tests { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -6177,6 +6179,8 @@ mod tests { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -6314,6 +6318,8 @@ mod tests { description: Some("Look up events and availability".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -6428,6 +6434,8 @@ mod tests { description: Some("Look up events and availability".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -6462,6 +6470,8 @@ mod tests { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/tui/src/chatwidget/skills.rs b/codex-rs/tui/src/chatwidget/skills.rs index 53b70be5cf..faae538af1 100644 --- a/codex-rs/tui/src/chatwidget/skills.rs +++ b/codex-rs/tui/src/chatwidget/skills.rs @@ -521,6 +521,8 @@ mod tests { description: None, logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs index 154add1d67..238bc3609c 100644 --- a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs +++ b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs @@ -1284,6 +1284,8 @@ async fn submit_user_message_ignores_inaccessible_app_mentions_from_bindings() { description: Some("Directory-only app".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, diff --git a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs index 281d97305e..8e2d7dd0d7 100644 --- a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs +++ b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs @@ -1858,6 +1858,8 @@ async fn apps_popup_stays_loading_until_final_snapshot_updates() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1892,6 +1894,8 @@ async fn apps_popup_stays_loading_until_final_snapshot_updates() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1907,6 +1911,8 @@ async fn apps_popup_stays_loading_until_final_snapshot_updates() { description: Some("Project tracking".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1953,6 +1959,8 @@ async fn apps_notification_update_excludes_inaccessible_apps_from_mentions() { description: Some("Connected files".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -1968,6 +1976,8 @@ async fn apps_notification_update_excludes_inaccessible_apps_from_mentions() { description: Some("Directory-only app".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2022,6 +2032,8 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2037,6 +2049,8 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() { description: Some("Project tracking".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2062,6 +2076,8 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2111,6 +2127,8 @@ async fn apps_popup_preserves_selected_app_across_refresh() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2126,6 +2144,8 @@ async fn apps_popup_preserves_selected_app_across_refresh() { description: Some("Team chat".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2157,6 +2177,8 @@ async fn apps_popup_preserves_selected_app_across_refresh() { description: Some("Spreadsheets".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2172,6 +2194,8 @@ async fn apps_popup_preserves_selected_app_across_refresh() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2187,6 +2211,8 @@ async fn apps_popup_preserves_selected_app_across_refresh() { description: Some("Team chat".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2230,6 +2256,8 @@ async fn apps_refresh_failure_with_cached_snapshot_triggers_pending_force_refetc description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2273,6 +2301,8 @@ async fn apps_popup_keeps_existing_full_snapshot_while_partial_refresh_loads() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2288,6 +2318,8 @@ async fn apps_popup_keeps_existing_full_snapshot_while_partial_refresh_loads() { description: Some("Project tracking".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2315,6 +2347,8 @@ async fn apps_popup_keeps_existing_full_snapshot_while_partial_refresh_loads() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2330,6 +2364,8 @@ async fn apps_popup_keeps_existing_full_snapshot_while_partial_refresh_loads() { description: Some("Should be filtered".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2378,6 +2414,8 @@ async fn apps_refresh_failure_without_full_snapshot_falls_back_to_installed_apps description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2437,6 +2475,8 @@ async fn apps_popup_shows_disabled_status_for_installed_but_disabled_apps() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2480,6 +2520,8 @@ async fn apps_refresh_preserves_toggled_enabled_state() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2502,6 +2544,8 @@ async fn apps_refresh_preserves_toggled_enabled_state() { description: Some("Workspace docs".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None, @@ -2551,6 +2595,8 @@ async fn apps_popup_for_not_installed_app_uses_install_only_selected_description description: Some("Project tracking".to_string()), logo_url: None, logo_url_dark: None, + icon_assets: None, + icon_dark_assets: None, distribution_channel: None, branding: None, app_metadata: None,