feat: Expose plugin share metadata in shareContext (#21495)

Extends PluginSummary.shareContext with shareUrl and reader shareTargets
This commit is contained in:
xl-openai
2026-05-07 10:07:03 -07:00
committed by GitHub
Unverified
parent 3444b0d60a
commit 114bac1409
13 changed files with 309 additions and 4 deletions
@@ -12187,6 +12187,21 @@
},
"remotePluginId": {
"type": "string"
},
"shareTargets": {
"items": {
"$ref": "#/definitions/v2/PluginSharePrincipal"
},
"type": [
"array",
"null"
]
},
"shareUrl": {
"type": [
"string",
"null"
]
}
},
"required": [
@@ -8798,6 +8798,21 @@
},
"remotePluginId": {
"type": "string"
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginSharePrincipal"
},
"type": [
"array",
"null"
]
},
"shareUrl": {
"type": [
"string",
"null"
]
}
},
"required": [
@@ -248,6 +248,21 @@
},
"remotePluginId": {
"type": "string"
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginSharePrincipal"
},
"type": [
"array",
"null"
]
},
"shareUrl": {
"type": [
"string",
"null"
]
}
},
"required": [
@@ -255,6 +270,33 @@
],
"type": "object"
},
"PluginSharePrincipal": {
"properties": {
"name": {
"type": "string"
},
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
}
},
"required": [
"name",
"principalId",
"principalType"
],
"type": "object"
},
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
},
"PluginSource": {
"oneOf": [
{
@@ -302,6 +302,21 @@
},
"remotePluginId": {
"type": "string"
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginSharePrincipal"
},
"type": [
"array",
"null"
]
},
"shareUrl": {
"type": [
"string",
"null"
]
}
},
"required": [
@@ -309,6 +324,33 @@
],
"type": "object"
},
"PluginSharePrincipal": {
"properties": {
"name": {
"type": "string"
},
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
}
},
"required": [
"name",
"principalId",
"principalType"
],
"type": "object"
},
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
},
"PluginSource": {
"oneOf": [
{
@@ -183,6 +183,21 @@
},
"remotePluginId": {
"type": "string"
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginSharePrincipal"
},
"type": [
"array",
"null"
]
},
"shareUrl": {
"type": [
"string",
"null"
]
}
},
"required": [
@@ -215,6 +230,33 @@
],
"type": "object"
},
"PluginSharePrincipal": {
"properties": {
"name": {
"type": "string"
},
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
}
},
"required": [
"name",
"principalId",
"principalType"
],
"type": "object"
},
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
},
"PluginSource": {
"oneOf": [
{
@@ -1,5 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PluginSharePrincipal } from "./PluginSharePrincipal";
export type PluginShareContext = { remotePluginId: string, creatorAccountUserId: string | null, creatorName: string | null, };
export type PluginShareContext = { remotePluginId: string, shareUrl: string | null, creatorAccountUserId: string | null, creatorName: string | null, shareTargets: Array<PluginSharePrincipal> | null, };
@@ -539,8 +539,10 @@ pub struct PluginSummary {
#[ts(export_to = "v2/")]
pub struct PluginShareContext {
pub remote_plugin_id: String,
pub share_url: Option<String>,
pub creator_account_user_id: Option<String>,
pub creator_name: Option<String>,
pub share_targets: Option<Vec<PluginSharePrincipal>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -108,8 +108,10 @@ fn share_context_for_source(
.cloned()
.map(|remote_plugin_id| PluginShareContext {
remote_plugin_id,
share_url: None,
creator_account_user_id: None,
creator_name: None,
share_targets: None,
}),
MarketplacePluginSource::Git { .. } => None,
}
@@ -1473,8 +1475,15 @@ fn remote_plugin_share_context_to_info(
) -> PluginShareContext {
PluginShareContext {
remote_plugin_id: context.remote_plugin_id,
share_url: context.share_url,
creator_account_user_id: context.creator_account_user_id,
creator_name: context.creator_name,
share_targets: context.share_targets.map(|targets| {
targets
.into_iter()
.map(plugin_share_principal_from_remote)
.collect()
}),
}
}
@@ -13,6 +13,8 @@ use codex_app_server_protocol::PluginListMarketplaceKind;
use codex_app_server_protocol::PluginListParams;
use codex_app_server_protocol::PluginListResponse;
use codex_app_server_protocol::PluginMarketplaceEntry;
use codex_app_server_protocol::PluginSharePrincipal;
use codex_app_server_protocol::PluginSharePrincipalType;
use codex_app_server_protocol::PluginSource;
use codex_app_server_protocol::PluginSummary;
use codex_app_server_protocol::RequestId;
@@ -692,8 +694,10 @@ async fn plugin_list_returns_share_context_for_shared_local_plugin() -> Result<(
.as_ref()
.expect("expected share context");
assert_eq!(share_context.remote_plugin_id, "plugins_123");
assert_eq!(share_context.share_url, None);
assert_eq!(share_context.creator_account_user_id, None);
assert_eq!(share_context.creator_name, None);
assert_eq!(share_context.share_targets, None);
Ok(())
}
@@ -1735,6 +1739,18 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
Some("user-gavin__account-123")
);
assert_eq!(share_context.creator_name.as_deref(), Some("Gavin"));
assert_eq!(
share_context.share_url.as_deref(),
Some("https://chatgpt.example/plugins/share/share-key-1")
);
assert_eq!(
share_context.share_targets,
Some(vec![PluginSharePrincipal {
principal_type: PluginSharePrincipalType::User,
principal_id: "user-ada__account-123".to_string(),
name: "Ada".to_string(),
}])
);
wait_for_remote_plugin_request_count(&server, "/ps/plugins/list", /*expected_count*/ 0).await?;
Ok(())
}
@@ -2260,10 +2276,25 @@ fn workspace_remote_plugin_page_body(
"name": "{plugin_name}",
"scope": "WORKSPACE",
"creator_account_user_id": "user-gavin__account-123",
"share_url": "https://chatgpt.example/plugins/share/share-key-1",
"installation_policy": "AVAILABLE",
"authentication_policy": "ON_USE",
"status": "ENABLED",
"creator_name": "Gavin",
"share_principals": [
{{
"principal_type": "user",
"principal_id": "user-gavin__account-123",
"role": "owner",
"name": "Gavin"
}},
{{
"principal_type": "user",
"principal_id": "user-ada__account-123",
"role": "reader",
"name": "Ada"
}}
],
"release": {{
"display_name": "{display_name}",
"description": "Track work",
@@ -23,6 +23,8 @@ use codex_app_server_protocol::PluginAuthPolicy;
use codex_app_server_protocol::PluginInstallPolicy;
use codex_app_server_protocol::PluginReadParams;
use codex_app_server_protocol::PluginReadResponse;
use codex_app_server_protocol::PluginSharePrincipal;
use codex_app_server_protocol::PluginSharePrincipalType;
use codex_app_server_protocol::PluginSkillReadParams;
use codex_app_server_protocol::PluginSkillReadResponse;
use codex_app_server_protocol::PluginSource;
@@ -237,6 +239,21 @@ async fn plugin_read_returns_share_context_for_shared_remote_plugin() -> Result<
"scope": "WORKSPACE",
"creator_account_user_id": "user-gavin__account-123",
"creator_name": "Gavin",
"share_url": "https://chatgpt.example/plugins/share/share-key-1",
"share_principals": [
{
"principal_type": "user",
"principal_id": "user-gavin__account-123",
"role": "owner",
"name": "Gavin"
},
{
"principal_type": "user",
"principal_id": "user-ada__account-123",
"role": "reader",
"name": "Ada"
}
],
"installation_policy": "AVAILABLE",
"authentication_policy": "ON_USE",
"release": {
@@ -307,6 +324,18 @@ async fn plugin_read_returns_share_context_for_shared_remote_plugin() -> Result<
Some("user-gavin__account-123")
);
assert_eq!(share_context.creator_name.as_deref(), Some("Gavin"));
assert_eq!(
share_context.share_url.as_deref(),
Some("https://chatgpt.example/plugins/share/share-key-1")
);
assert_eq!(
share_context.share_targets,
Some(vec![PluginSharePrincipal {
principal_type: PluginSharePrincipalType::User,
principal_id: "user-ada__account-123".to_string(),
name: "Ada".to_string(),
}])
);
Ok(())
}
@@ -766,8 +795,10 @@ async fn plugin_read_returns_share_context_for_shared_local_plugin() -> Result<(
.as_ref()
.expect("expected share context");
assert_eq!(share_context.remote_plugin_id, "plugins_123");
assert_eq!(share_context.share_url, None);
assert_eq!(share_context.creator_account_user_id, None);
assert_eq!(share_context.creator_name, None);
assert_eq!(share_context.share_targets, None);
Ok(())
}
@@ -662,8 +662,10 @@ fn expected_plugin_interface() -> PluginInterface {
fn expected_share_context(plugin_id: &str) -> PluginShareContext {
PluginShareContext {
remote_plugin_id: plugin_id.to_string(),
share_url: Some("https://chatgpt.example/plugins/share/share-key-1".to_string()),
creator_account_user_id: None,
creator_name: None,
share_targets: None,
}
}
+25
View File
@@ -98,8 +98,10 @@ pub struct RemotePluginSummary {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RemotePluginShareContext {
pub remote_plugin_id: String,
pub share_url: Option<String>,
pub creator_account_user_id: Option<String>,
pub creator_name: Option<String>,
pub share_targets: Option<Vec<RemotePluginSharePrincipal>>,
}
#[derive(Debug, Clone, PartialEq)]
@@ -363,6 +365,8 @@ struct RemotePluginDirectoryItem {
creator_name: Option<String>,
#[serde(default)]
share_url: Option<String>,
#[serde(default)]
share_principals: Option<Vec<RemotePluginDirectorySharePrincipal>>,
installation_policy: PluginInstallPolicy,
authentication_policy: PluginAuthPolicy,
#[serde(rename = "status", default)]
@@ -370,6 +374,15 @@ struct RemotePluginDirectoryItem {
release: RemotePluginReleaseResponse,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
struct RemotePluginDirectorySharePrincipal {
principal_type: RemotePluginSharePrincipalType,
principal_id: String,
#[serde(default)]
role: Option<String>,
name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
struct RemotePluginInstalledItem {
#[serde(flatten)]
@@ -831,8 +844,20 @@ fn remote_plugin_share_context(
RemotePluginScope::Global => None,
RemotePluginScope::Workspace => Some(RemotePluginShareContext {
remote_plugin_id: plugin.id.clone(),
share_url: plugin.share_url.clone(),
creator_account_user_id: plugin.creator_account_user_id.clone(),
creator_name: plugin.creator_name.clone(),
share_targets: plugin.share_principals.as_ref().map(|principals| {
principals
.iter()
.filter(|principal| principal.role.as_deref() == Some("reader"))
.map(|principal| RemotePluginSharePrincipal {
principal_type: principal.principal_type,
principal_id: principal.principal_id.clone(),
name: principal.name.clone(),
})
.collect()
}),
}),
}
}
@@ -107,15 +107,17 @@ fn remote_plugin_json(plugin_id: &str) -> serde_json::Value {
})
}
fn remote_plugin_json_with_share_url(
fn remote_plugin_json_with_share_url_and_principals(
plugin_id: &str,
share_url: Option<&str>,
share_principals: serde_json::Value,
) -> serde_json::Value {
let mut plugin = remote_plugin_json(plugin_id);
let serde_json::Value::Object(fields) = &mut plugin else {
unreachable!("plugin json should be an object");
};
fields.insert("share_url".to_string(), json!(share_url));
fields.insert("share_principals".to_string(), share_principals);
plugin
}
@@ -489,9 +491,23 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
))
.and(query_param_is_missing("pageToken"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"plugins": [remote_plugin_json_with_share_url(
"plugins": [remote_plugin_json_with_share_url_and_principals(
"plugins_123",
Some("https://chatgpt.example/plugins/share/share-key-1"),
json!([
{
"principal_type": "user",
"principal_id": "user-owner",
"role": "owner",
"name": "Owner",
},
{
"principal_type": "user",
"principal_id": "user-reader",
"role": "reader",
"name": "Reader",
},
]),
)],
"pagination": {
"next_page_token": "page-2"
@@ -510,7 +526,29 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
))
.and(query_param("pageToken", "page-2"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"plugins": [remote_plugin_json_with_share_url("plugins_456", /*share_url*/ None)],
"plugins": [remote_plugin_json_with_share_url_and_principals(
"plugins_456",
/*share_url*/ None,
json!([
{
"principal_type": "user",
"principal_id": "user-owner",
"role": "owner",
"name": "Owner",
},
{
"principal_type": "user",
"principal_id": "user-editor",
"role": "editor",
"name": "Editor",
},
{
"principal_type": "user",
"principal_id": "user-missing-role",
"name": "Missing Role",
},
]),
)],
"pagination": empty_pagination_json(),
})))
.expect(1)
@@ -540,8 +578,16 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
name: "demo-plugin".to_string(),
share_context: Some(RemotePluginShareContext {
remote_plugin_id: "plugins_123".to_string(),
share_url: Some(
"https://chatgpt.example/plugins/share/share-key-1".to_string(),
),
creator_account_user_id: None,
creator_name: None,
share_targets: Some(vec![RemotePluginSharePrincipal {
principal_type: RemotePluginSharePrincipalType::User,
principal_id: "user-reader".to_string(),
name: "Reader".to_string(),
}]),
}),
installed: false,
enabled: false,
@@ -560,8 +606,10 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
name: "demo-plugin".to_string(),
share_context: Some(RemotePluginShareContext {
remote_plugin_id: "plugins_456".to_string(),
share_url: None,
creator_account_user_id: None,
creator_name: None,
share_targets: Some(Vec::new()),
}),
installed: true,
enabled: true,