From ee38dbbc200b61d08dc8267dbf5fc2c43753a710 Mon Sep 17 00:00:00 2001 From: nmccormack-oai Date: Thu, 14 May 2026 17:35:17 -0700 Subject: [PATCH] Add plugin default prompt alias --- .../codex_app_server_protocol.schemas.json | 7 ++++ .../codex_app_server_protocol.v2.schemas.json | 7 ++++ .../schema/json/v2/PluginListResponse.json | 7 ++++ .../schema/json/v2/PluginReadResponse.json | 7 ++++ .../json/v2/PluginShareListResponse.json | 7 ++++ .../schema/typescript/v2/PluginInterface.ts | 6 +++- .../src/protocol/v2/plugin.rs | 2 ++ .../src/protocol/v2/tests.rs | 2 ++ .../src/request_processors/plugins.rs | 1 + .../app-server/tests/suite/v2/plugin_list.rs | 1 + .../app-server/tests/suite/v2/plugin_share.rs | 1 + codex-rs/core-plugins/src/manifest.rs | 35 +++++++++++++++++++ .../core-plugins/src/marketplace_tests.rs | 3 ++ codex-rs/core-plugins/src/remote.rs | 2 ++ .../core-plugins/src/remote/share/tests.rs | 1 + codex-rs/tui/src/chatwidget/tests/helpers.rs | 1 + 16 files changed, 89 insertions(+), 1 deletion(-) 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 e996ca5417..e05ff42aad 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 @@ -12089,6 +12089,13 @@ "null" ] }, + "defaultPromptAlias": { + "description": "Mention label used before starter prompts on plugin detail surfaces.", + "type": [ + "string", + "null" + ] + }, "developerName": { "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 7f73f9fe07..7ed250d2d3 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 @@ -8638,6 +8638,13 @@ "null" ] }, + "defaultPromptAlias": { + "description": "Mention label used before starter prompts on plugin detail surfaces.", + "type": [ + "string", + "null" + ] + }, "developerName": { "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 d756f61a68..c69898daee 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/PluginListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/PluginListResponse.json @@ -111,6 +111,13 @@ "null" ] }, + "defaultPromptAlias": { + "description": "Mention label used before starter prompts on plugin detail surfaces.", + "type": [ + "string", + "null" + ] + }, "developerName": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json index 0c9f897dc9..82c46c7533 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json @@ -204,6 +204,13 @@ "null" ] }, + "defaultPromptAlias": { + "description": "Mention label used before starter prompts on plugin detail surfaces.", + "type": [ + "string", + "null" + ] + }, "developerName": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/v2/PluginShareListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/PluginShareListResponse.json index 7cffdeab9a..8d157aef27 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/PluginShareListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/PluginShareListResponse.json @@ -85,6 +85,13 @@ "null" ] }, + "defaultPromptAlias": { + "description": "Mention label used before starter prompts on plugin detail surfaces.", + "type": [ + "string", + "null" + ] + }, "developerName": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PluginInterface.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PluginInterface.ts index 4e97ee66f3..b4d0641aac 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/PluginInterface.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PluginInterface.ts @@ -8,7 +8,11 @@ export type PluginInterface = { displayName: string | null, shortDescription: st * Starter prompts for the plugin. Capped at 3 entries with a maximum of * 128 characters per entry. */ -defaultPrompt: Array | null, brandColor: string | null, +defaultPrompt: Array | null, +/** + * Mention label used before starter prompts on plugin detail surfaces. + */ +defaultPromptAlias: string | null, brandColor: string | null, /** * Local composer icon path, resolved from the installed plugin package. */ diff --git a/codex-rs/app-server-protocol/src/protocol/v2/plugin.rs b/codex-rs/app-server-protocol/src/protocol/v2/plugin.rs index 3ffab53218..5f48c7cf95 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/plugin.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/plugin.rs @@ -644,6 +644,8 @@ pub struct PluginInterface { /// Starter prompts for the plugin. Capped at 3 entries with a maximum of /// 128 characters per entry. pub default_prompt: Option>, + /// Mention label used before starter prompts on plugin detail surfaces. + pub default_prompt_alias: Option, pub brand_color: Option, /// Local composer icon path, resolved from the installed plugin package. pub composer_icon: Option, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index f7041cc721..6a5b040425 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -2671,6 +2671,7 @@ fn plugin_interface_serializes_local_paths_and_remote_urls_separately() { privacy_policy_url: None, terms_of_service_url: None, default_prompt: None, + default_prompt_alias: None, brand_color: None, composer_icon: Some(composer_icon), composer_icon_url: Some("https://example.com/linear/icon.png".to_string()), @@ -2693,6 +2694,7 @@ fn plugin_interface_serializes_local_paths_and_remote_urls_separately() { "privacyPolicyUrl": null, "termsOfServiceUrl": null, "defaultPrompt": null, + "defaultPromptAlias": null, "brandColor": null, "composerIcon": composer_icon_json, "composerIconUrl": "https://example.com/linear/icon.png", diff --git a/codex-rs/app-server/src/request_processors/plugins.rs b/codex-rs/app-server/src/request_processors/plugins.rs index c290d2091a..e9d9b17ef2 100644 --- a/codex-rs/app-server/src/request_processors/plugins.rs +++ b/codex-rs/app-server/src/request_processors/plugins.rs @@ -61,6 +61,7 @@ fn local_plugin_interface_to_info(interface: PluginManifestInterface) -> PluginI privacy_policy_url: interface.privacy_policy_url, terms_of_service_url: interface.terms_of_service_url, default_prompt: interface.default_prompt, + default_prompt_alias: interface.default_prompt_alias, brand_color: interface.brand_color, composer_icon: interface.composer_icon, composer_icon_url: None, 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 4ffa3893d0..c74bac3bfa 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_list.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_list.rs @@ -554,6 +554,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab privacy_policy_url: None, terms_of_service_url: None, default_prompt: None, + default_prompt_alias: None, brand_color: None, composer_icon: None, composer_icon_url: None, diff --git a/codex-rs/app-server/tests/suite/v2/plugin_share.rs b/codex-rs/app-server/tests/suite/v2/plugin_share.rs index 69f93f0b7e..3df38cd458 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_share.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_share.rs @@ -1353,6 +1353,7 @@ fn expected_plugin_interface() -> PluginInterface { privacy_policy_url: None, terms_of_service_url: None, default_prompt: None, + default_prompt_alias: None, brand_color: None, composer_icon: None, composer_icon_url: None, diff --git a/codex-rs/core-plugins/src/manifest.rs b/codex-rs/core-plugins/src/manifest.rs index 6de7f820b8..19e20a06b4 100644 --- a/codex-rs/core-plugins/src/manifest.rs +++ b/codex-rs/core-plugins/src/manifest.rs @@ -70,6 +70,7 @@ pub struct PluginManifestInterface { pub privacy_policy_url: Option, pub terms_of_service_url: Option, pub default_prompt: Option>, + pub default_prompt_alias: Option, pub brand_color: Option, pub composer_icon: Option, pub logo: Option, @@ -103,6 +104,8 @@ struct RawPluginManifestInterface { #[serde(default)] default_prompt: Option, #[serde(default)] + default_prompt_alias: Option, + #[serde(default)] brand_color: Option, #[serde(default)] composer_icon: Option, @@ -175,6 +178,7 @@ pub fn load_plugin_manifest(plugin_root: &Path) -> Option { privacy_policy_url, terms_of_service_url, default_prompt, + default_prompt_alias, brand_color, composer_icon, logo, @@ -192,6 +196,9 @@ pub fn load_plugin_manifest(plugin_root: &Path) -> Option { privacy_policy_url, terms_of_service_url, default_prompt: resolve_default_prompts(plugin_root, default_prompt.as_ref()), + default_prompt_alias: non_empty_collapsed_string( + default_prompt_alias.as_deref(), + ), brand_color, composer_icon: resolve_interface_asset_path( plugin_root, @@ -225,6 +232,7 @@ pub fn load_plugin_manifest(plugin_root: &Path) -> Option { || interface.privacy_policy_url.is_some() || interface.terms_of_service_url.is_some() || interface.default_prompt.is_some() + || interface.default_prompt_alias.is_some() || interface.brand_color.is_some() || interface.composer_icon.is_some() || interface.logo.is_some() @@ -372,6 +380,11 @@ fn resolve_default_prompt_str(plugin_root: &Path, field: &str, prompt: &str) -> Some(prompt) } +fn non_empty_collapsed_string(value: Option<&str>) -> Option { + let value = value?.split_whitespace().collect::>().join(" "); + (!value.is_empty()).then_some(value) +} + fn warn_invalid_default_prompt(plugin_root: &Path, field: &str, message: &str) { if let Some(manifest_path) = find_plugin_manifest_path(plugin_root) { tracing::warn!( @@ -556,6 +569,28 @@ mod tests { assert_eq!(interface.default_prompt, None); } + #[test] + fn plugin_interface_normalizes_default_prompt_alias() { + let tmp = tempdir().expect("tempdir"); + let plugin_root = tmp.path().join("demo-plugin"); + write_manifest( + &plugin_root, + /*version*/ None, + r#"{ + "displayName": "Demo Plugin", + "defaultPromptAlias": " Demo Alias " + }"#, + ); + + let manifest = load_manifest(&plugin_root); + let interface = manifest.interface.expect("plugin interface"); + + assert_eq!( + interface.default_prompt_alias, + Some("Demo Alias".to_string()) + ); + } + #[test] fn plugin_manifest_reads_trimmed_version() { let tmp = tempdir().expect("tempdir"); diff --git a/codex-rs/core-plugins/src/marketplace_tests.rs b/codex-rs/core-plugins/src/marketplace_tests.rs index e134157177..aead16b09b 100644 --- a/codex-rs/core-plugins/src/marketplace_tests.rs +++ b/codex-rs/core-plugins/src/marketplace_tests.rs @@ -409,6 +409,7 @@ fn list_marketplaces_supports_alternate_manifest_layout() { privacy_policy_url: None, terms_of_service_url: None, default_prompt: None, + default_prompt_alias: None, brand_color: None, composer_icon: None, logo: None, @@ -1232,6 +1233,7 @@ fn list_marketplaces_resolves_plugin_interface_paths_to_absolute() { privacy_policy_url: None, terms_of_service_url: None, default_prompt: None, + default_prompt_alias: None, brand_color: None, composer_icon: Some( AbsolutePathBuf::try_from(plugin_root.join("assets/icon.png")).unwrap(), @@ -1348,6 +1350,7 @@ fn list_marketplaces_ignores_plugin_interface_assets_without_dot_slash() { privacy_policy_url: None, terms_of_service_url: None, default_prompt: None, + default_prompt_alias: None, brand_color: None, composer_icon: None, logo: None, diff --git a/codex-rs/core-plugins/src/remote.rs b/codex-rs/core-plugins/src/remote.rs index 4dde224039..5b98af0dd9 100644 --- a/codex-rs/core-plugins/src/remote.rs +++ b/codex-rs/core-plugins/src/remote.rs @@ -1015,6 +1015,7 @@ fn remote_plugin_interface_to_info(plugin: &RemotePluginDirectoryItem) -> Option privacy_policy_url: interface.privacy_policy_url.clone(), terms_of_service_url: interface.terms_of_service_url.clone(), default_prompt, + default_prompt_alias: None, brand_color: interface.brand_color.clone(), composer_icon: None, composer_icon_url: interface.composer_icon_url.clone(), @@ -1033,6 +1034,7 @@ fn remote_plugin_interface_to_info(plugin: &RemotePluginDirectoryItem) -> Option || result.privacy_policy_url.is_some() || result.terms_of_service_url.is_some() || result.default_prompt.is_some() + || result.default_prompt_alias.is_some() || result.brand_color.is_some() || result.composer_icon_url.is_some() || result.logo_url.is_some() diff --git a/codex-rs/core-plugins/src/remote/share/tests.rs b/codex-rs/core-plugins/src/remote/share/tests.rs index f62051374e..d0415cdd83 100644 --- a/codex-rs/core-plugins/src/remote/share/tests.rs +++ b/codex-rs/core-plugins/src/remote/share/tests.rs @@ -152,6 +152,7 @@ fn expected_plugin_interface() -> PluginInterface { privacy_policy_url: None, terms_of_service_url: None, default_prompt: None, + default_prompt_alias: None, brand_color: None, composer_icon: None, composer_icon_url: None, diff --git a/codex-rs/tui/src/chatwidget/tests/helpers.rs b/codex-rs/tui/src/chatwidget/tests/helpers.rs index 28ed69139b..b88014b3c7 100644 --- a/codex-rs/tui/src/chatwidget/tests/helpers.rs +++ b/codex-rs/tui/src/chatwidget/tests/helpers.rs @@ -1293,6 +1293,7 @@ pub(super) fn plugins_test_interface( privacy_policy_url: None, terms_of_service_url: None, default_prompt: None, + default_prompt_alias: None, brand_color: None, composer_icon: None, composer_icon_url: None,