feat: Add plugin share checkout (#22435)

Adds plugin/share/checkout to turn a shared remote plugin into a local
working copy under ~/plugins/<name>.

Registers the copy in the managed personal marketplace and records the
remote-to-local mapping for later share/save flows.

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
xl-openai
2026-05-13 00:50:29 -07:00
committed by GitHub
Unverified
parent 392e94e9ea
commit 2a67c46de4
21 changed files with 1384 additions and 2 deletions
@@ -649,6 +649,11 @@ client_request_definitions! {
serialization: global("config"),
response: v2::PluginShareListResponse,
},
PluginShareCheckout => "plugin/share/checkout" {
params: v2::PluginShareCheckoutParams,
serialization: global("config"),
response: v2::PluginShareCheckoutResponse,
},
PluginShareDelete => "plugin/share/delete" {
params: v2::PluginShareDeleteParams,
serialization: global("config"),
@@ -242,6 +242,26 @@ pub struct PluginShareListResponse {
pub data: Vec<PluginShareListItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareCheckoutParams {
pub remote_plugin_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareCheckoutResponse {
pub remote_plugin_id: String,
pub plugin_id: String,
pub plugin_name: String,
pub plugin_path: AbsolutePathBuf,
pub marketplace_name: String,
pub marketplace_path: AbsolutePathBuf,
pub remote_version: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2987,6 +2987,52 @@ fn plugin_share_params_and_response_serialization_use_camel_case_fields() {
PluginShareListParams {},
);
assert_eq!(
serde_json::to_value(PluginShareCheckoutParams {
remote_plugin_id: "plugins~Plugin_00000000000000000000000000000000".to_string(),
})
.unwrap(),
json!({
"remotePluginId": "plugins~Plugin_00000000000000000000000000000000",
}),
);
let plugin_path = if cfg!(windows) {
r"C:\Users\me\plugins\gmail"
} else {
"/Users/me/plugins/gmail"
};
let plugin_path = AbsolutePathBuf::try_from(PathBuf::from(plugin_path)).unwrap();
let plugin_path_json = plugin_path.as_path().display().to_string();
let marketplace_path = if cfg!(windows) {
r"C:\Users\me\.agents\plugins\marketplace.json"
} else {
"/Users/me/.agents/plugins/marketplace.json"
};
let marketplace_path = AbsolutePathBuf::try_from(PathBuf::from(marketplace_path)).unwrap();
let marketplace_path_json = marketplace_path.as_path().display().to_string();
assert_eq!(
serde_json::to_value(PluginShareCheckoutResponse {
remote_plugin_id: "plugins~Plugin_00000000000000000000000000000000".to_string(),
plugin_id: "gmail@codex-curated".to_string(),
plugin_name: "gmail".to_string(),
plugin_path,
marketplace_name: "codex-curated".to_string(),
marketplace_path,
remote_version: Some("1.2.3".to_string()),
})
.unwrap(),
json!({
"remotePluginId": "plugins~Plugin_00000000000000000000000000000000",
"pluginId": "gmail@codex-curated",
"pluginName": "gmail",
"pluginPath": plugin_path_json,
"marketplaceName": "codex-curated",
"marketplacePath": marketplace_path_json,
"remoteVersion": "1.2.3",
}),
);
assert_eq!(
serde_json::to_value(PluginShareDeleteParams {
remote_plugin_id: "plugins~Plugin_00000000000000000000000000000000".to_string(),