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 bfb716c99..097b204f4 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 @@ -9372,6 +9372,12 @@ }, "PluginMarketplaceEntry": { "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, "name": { "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 b726a1876..b298a4980 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 @@ -6160,6 +6160,12 @@ }, "PluginMarketplaceEntry": { "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/PluginListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/PluginListResponse.json index e0140a039..0c5eb0a1f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/PluginListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/PluginListResponse.json @@ -127,6 +127,12 @@ }, "PluginMarketplaceEntry": { "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PluginMarketplaceEntry.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PluginMarketplaceEntry.ts index 32042d1dc..1a6149e22 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/PluginMarketplaceEntry.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PluginMarketplaceEntry.ts @@ -4,4 +4,4 @@ import type { AbsolutePathBuf } from "../AbsolutePathBuf"; import type { PluginSummary } from "./PluginSummary"; -export type PluginMarketplaceEntry = { name: string, path: AbsolutePathBuf, plugins: Array, }; +export type PluginMarketplaceEntry = { name: string, path: AbsolutePathBuf, displayName: string | null, plugins: Array, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index c5546f49b..2250fd73e 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -3276,6 +3276,7 @@ pub struct SkillsListEntry { pub struct PluginMarketplaceEntry { pub name: String, pub path: AbsolutePathBuf, + pub display_name: Option, pub plugins: Vec, } diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index d7ce0f2a1..6659389a4 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -5452,6 +5452,7 @@ impl CodexMessageProcessor { .map(|marketplace| PluginMarketplaceEntry { name: marketplace.name, path: marketplace.path, + display_name: marketplace.display_name, plugins: marketplace .plugins .into_iter() diff --git a/codex-rs/app-server/tests/suite/v2/plugin_list.rs b/codex-rs/app-server/tests/suite/v2/plugin_list.rs index c0fd6e79c..615a164dc 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_list.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_list.rs @@ -152,6 +152,7 @@ async fn plugin_list_includes_install_and_enabled_state_from_config() -> Result< repo_root.path().join(".agents/plugins/marketplace.json"), r#"{ "name": "codex-curated", + "display_name": "ChatGPT Official", "plugins": [ { "name": "enabled-plugin", @@ -220,6 +221,10 @@ enabled = false .expect("expected repo marketplace entry"); assert_eq!(marketplace.name, "codex-curated"); + assert_eq!( + marketplace.display_name.as_deref(), + Some("ChatGPT Official") + ); assert_eq!(marketplace.plugins.len(), 3); assert_eq!(marketplace.plugins[0].id, "enabled-plugin@codex-curated"); assert_eq!(marketplace.plugins[0].name, "enabled-plugin"); diff --git a/codex-rs/core/src/plugins/manager.rs b/codex-rs/core/src/plugins/manager.rs index d9d719e50..11ce8c161 100644 --- a/codex-rs/core/src/plugins/manager.rs +++ b/codex-rs/core/src/plugins/manager.rs @@ -118,6 +118,7 @@ pub struct PluginDetailSummary { pub struct ConfiguredMarketplaceSummary { pub name: String, pub path: AbsolutePathBuf, + pub display_name: Option, pub plugins: Vec, } @@ -801,6 +802,7 @@ impl PluginsManager { (!plugins.is_empty()).then_some(ConfiguredMarketplaceSummary { name: marketplace.name, path: marketplace.path, + display_name: marketplace.display_name, plugins, }) }) diff --git a/codex-rs/core/src/plugins/manager_tests.rs b/codex-rs/core/src/plugins/manager_tests.rs index 77d46ea02..18a293eb7 100644 --- a/codex-rs/core/src/plugins/manager_tests.rs +++ b/codex-rs/core/src/plugins/manager_tests.rs @@ -999,6 +999,7 @@ enabled = false tmp.path().join("repo/.agents/plugins/marketplace.json"), ) .unwrap(), + display_name: None, plugins: vec![ ConfiguredMarketplacePluginSummary { id: "enabled-plugin@debug".to_string(), @@ -1043,6 +1044,7 @@ async fn list_marketplaces_includes_curated_repo_marketplace() { curated_root.join(".agents/plugins/marketplace.json"), r#"{ "name": "openai-curated", + "display_name": "ChatGPT Official", "plugins": [ { "name": "linear", @@ -1077,6 +1079,7 @@ async fn list_marketplaces_includes_curated_repo_marketplace() { name: "openai-curated".to_string(), path: AbsolutePathBuf::try_from(curated_root.join(".agents/plugins/marketplace.json")) .unwrap(), + display_name: Some("ChatGPT Official".to_string()), plugins: vec![ConfiguredMarketplacePluginSummary { id: "linear@openai-curated".to_string(), name: "linear".to_string(), @@ -1281,6 +1284,7 @@ enabled = true tmp.path().join("repo/.agents/plugins/marketplace.json"), ) .unwrap(), + display_name: None, plugins: vec![ConfiguredMarketplacePluginSummary { id: "sample-plugin@debug".to_string(), name: "sample-plugin".to_string(), diff --git a/codex-rs/core/src/plugins/marketplace.rs b/codex-rs/core/src/plugins/marketplace.rs index 622ed9dd1..4e4bfdd0a 100644 --- a/codex-rs/core/src/plugins/marketplace.rs +++ b/codex-rs/core/src/plugins/marketplace.rs @@ -28,6 +28,7 @@ pub struct ResolvedMarketplacePlugin { pub struct MarketplaceSummary { pub name: String, pub path: AbsolutePathBuf, + pub display_name: Option, pub plugins: Vec, } @@ -212,6 +213,7 @@ pub(crate) fn load_marketplace_summary( Ok(MarketplaceSummary { name: marketplace.name, path: path.clone(), + display_name: marketplace.display_name, plugins, }) } @@ -367,6 +369,9 @@ fn marketplace_root_dir( #[derive(Debug, Deserialize)] struct MarketplaceFile { name: String, + #[serde(default)] + #[serde(alias = "displayName")] + display_name: Option, plugins: Vec, } diff --git a/codex-rs/core/src/plugins/marketplace_tests.rs b/codex-rs/core/src/plugins/marketplace_tests.rs index b6b5050e8..ce12d885d 100644 --- a/codex-rs/core/src/plugins/marketplace_tests.rs +++ b/codex-rs/core/src/plugins/marketplace_tests.rs @@ -137,6 +137,7 @@ fn list_marketplaces_returns_home_and_repo_marketplaces() { path: AbsolutePathBuf::try_from(home_root.join(".agents/plugins/marketplace.json"),) .unwrap(), + display_name: None, plugins: vec![ MarketplacePluginSummary { name: "shared-plugin".to_string(), @@ -163,6 +164,7 @@ fn list_marketplaces_returns_home_and_repo_marketplaces() { path: AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json"),) .unwrap(), + display_name: None, plugins: vec![ MarketplacePluginSummary { name: "shared-plugin".to_string(), @@ -245,6 +247,7 @@ fn list_marketplaces_keeps_distinct_entries_for_same_name() { MarketplaceSummary { name: "codex-curated".to_string(), path: AbsolutePathBuf::try_from(home_marketplace).unwrap(), + display_name: None, plugins: vec![MarketplacePluginSummary { name: "local-plugin".to_string(), source: MarketplacePluginSourceSummary::Local { @@ -258,6 +261,7 @@ fn list_marketplaces_keeps_distinct_entries_for_same_name() { MarketplaceSummary { name: "codex-curated".to_string(), path: AbsolutePathBuf::try_from(repo_marketplace.clone()).unwrap(), + display_name: None, plugins: vec![MarketplacePluginSummary { name: "local-plugin".to_string(), source: MarketplacePluginSourceSummary::Local { @@ -324,6 +328,7 @@ fn list_marketplaces_dedupes_multiple_roots_in_same_repo() { name: "codex-curated".to_string(), path: AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json")) .unwrap(), + display_name: None, plugins: vec![MarketplacePluginSummary { name: "local-plugin".to_string(), source: MarketplacePluginSourceSummary::Local { @@ -337,6 +342,41 @@ fn list_marketplaces_dedupes_multiple_roots_in_same_repo() { ); } +#[test] +fn list_marketplaces_reads_marketplace_display_name() { + let tmp = tempdir().unwrap(); + let repo_root = tmp.path().join("repo"); + + fs::create_dir_all(repo_root.join(".git")).unwrap(); + fs::create_dir_all(repo_root.join(".agents/plugins")).unwrap(); + fs::write( + repo_root.join(".agents/plugins/marketplace.json"), + r#"{ + "name": "openai-curated", + "display_name": "ChatGPT Official", + "plugins": [ + { + "name": "local-plugin", + "source": { + "source": "local", + "path": "./plugin" + } + } + ] +}"#, + ) + .unwrap(); + + let marketplaces = + list_marketplaces_with_home(&[AbsolutePathBuf::try_from(repo_root).unwrap()], None) + .unwrap(); + + assert_eq!( + marketplaces[0].display_name, + Some("ChatGPT Official".to_string()) + ); +} + #[test] fn list_marketplaces_resolves_plugin_interface_paths_to_absolute() { let tmp = tempdir().unwrap();