Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 69 additions & 11 deletions codex-rs/app-server/tests/suite/v2/plugin_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1824,13 +1824,27 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
))?;
shared_plugin_body["plugins"][0]["share_principals"] = serde_json::Value::Null;
let shared_plugin_body = serde_json::to_string(&shared_plugin_body)?;
let workspace_installed_body = workspace_remote_plugin_page_body(
"plugins~Plugin_22222222222222222222222222222222",
"shared-linear",
"Shared Linear",
"PRIVATE",
/*enabled*/ Some(true),
);
let mut workspace_installed_body: serde_json::Value =
serde_json::from_str(&workspace_remote_plugin_page_body(
"plugins~Plugin_22222222222222222222222222222222",
"shared-linear",
"Shared Linear",
"PRIVATE",
/*enabled*/ Some(true),
))?;
let unlisted_installed_body: serde_json::Value =
serde_json::from_str(&workspace_remote_plugin_page_body(
"plugins~Plugin_33333333333333333333333333333333",
"unlisted-linear",
"Unlisted Linear",
"UNLISTED",
/*enabled*/ Some(false),
))?;
workspace_installed_body["plugins"]
.as_array_mut()
.expect("installed plugins should be an array")
.push(unlisted_installed_body["plugins"][0].clone());
let workspace_installed_body = serde_json::to_string(&workspace_installed_body)?;
mount_shared_workspace_plugins(&server, &shared_plugin_body).await;
mount_remote_installed_plugins(&server, "WORKSPACE", &workspace_installed_body).await;

Expand All @@ -1851,9 +1865,12 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
.await??;
let response: PluginListResponse = to_response(response)?;

assert_eq!(response.marketplaces.len(), 1);
let marketplace = &response.marketplaces[0];
assert_eq!(marketplace.name, "shared-with-me");
assert_eq!(response.marketplaces.len(), 2);
let marketplace = response
.marketplaces
.iter()
.find(|marketplace| marketplace.name == "workspace-shared-with-me-private")
.expect("expected private shared-with-me marketplace");
assert_eq!(
marketplace
.interface
Expand All @@ -1862,7 +1879,10 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
Some("Shared with me")
);
assert_eq!(marketplace.plugins.len(), 1);
assert_eq!(marketplace.plugins[0].id, "shared-linear@shared-with-me");
assert_eq!(
marketplace.plugins[0].id,
"shared-linear@workspace-shared-with-me-private"
);
assert_eq!(
marketplace.plugins[0].remote_plugin_id.as_deref(),
Some("plugins~Plugin_22222222222222222222222222222222")
Expand Down Expand Up @@ -1893,6 +1913,44 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
Some("https://chatgpt.example/plugins/share/share-key-1")
);
assert_eq!(share_context.share_principals, None);

let marketplace = response
.marketplaces
.iter()
.find(|marketplace| marketplace.name == "workspace-shared-with-me-unlisted")
.expect("expected unlisted shared-with-me marketplace");
assert_eq!(
marketplace
.interface
.as_ref()
.and_then(|interface| interface.display_name.as_deref()),
Some("Shared with me (unlisted)")
);
assert_eq!(marketplace.plugins.len(), 1);
assert_eq!(
marketplace.plugins[0].id,
"unlisted-linear@workspace-shared-with-me-unlisted"
);
assert_eq!(
marketplace.plugins[0].remote_plugin_id.as_deref(),
Some("plugins~Plugin_33333333333333333333333333333333")
);
assert_eq!(marketplace.plugins[0].name, "unlisted-linear");
assert_eq!(marketplace.plugins[0].installed, true);
assert_eq!(marketplace.plugins[0].enabled, false);
let share_context = marketplace.plugins[0]
.share_context
.as_ref()
.expect("expected share context");
assert_eq!(
share_context.remote_plugin_id,
"plugins~Plugin_33333333333333333333333333333333"
);
assert_eq!(share_context.remote_version.as_deref(), Some("1.2.3"));
assert_eq!(
share_context.discoverability,
Some(PluginShareDiscoverability::Unlisted)
);
wait_for_remote_plugin_request_count(&server, "/ps/plugins/list", /*expected_count*/ 0).await?;
Ok(())
}
Expand Down
12 changes: 9 additions & 3 deletions codex-rs/app-server/tests/suite/v2/plugin_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ async fn plugin_read_returns_share_context_for_shared_remote_plugin() -> Result<
let request_id = mcp
.send_plugin_read_request(PluginReadParams {
marketplace_path: None,
remote_marketplace_name: Some("shared-with-me".to_string()),
remote_marketplace_name: Some("workspace-shared-with-me-private".to_string()),
plugin_name: "plugins~Plugin_11111111111111111111111111111111".to_string(),
})
.await?;
Expand All @@ -315,8 +315,14 @@ async fn plugin_read_returns_share_context_for_shared_remote_plugin() -> Result<
.await??;
let response: PluginReadResponse = to_response(response)?;

assert_eq!(response.plugin.marketplace_name, "shared-with-me");
assert_eq!(response.plugin.summary.id, "shared-linear@shared-with-me");
assert_eq!(
response.plugin.marketplace_name,
"workspace-shared-with-me-private"
);
assert_eq!(
response.plugin.summary.id,
"shared-linear@workspace-shared-with-me-private"
);
assert_eq!(
response.plugin.summary.remote_plugin_id.as_deref(),
Some("plugins~Plugin_11111111111111111111111111111111")
Expand Down
6 changes: 3 additions & 3 deletions codex-rs/app-server/tests/suite/v2/plugin_share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> {
PluginShareListResponse {
data: vec![PluginShareListItem {
plugin: PluginSummary {
id: "demo-plugin@shared-with-me".to_string(),
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
remote_plugin_id: Some("plugins_123".to_string()),
local_version: None,
name: "demo-plugin".to_string(),
Expand Down Expand Up @@ -566,7 +566,7 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> {
PluginShareListResponse {
data: vec![PluginShareListItem {
plugin: PluginSummary {
id: "demo-plugin@shared-with-me".to_string(),
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
remote_plugin_id: Some("plugins_123".to_string()),
local_version: None,
name: "demo-plugin".to_string(),
Expand Down Expand Up @@ -787,7 +787,7 @@ async fn plugin_share_delete_removes_created_workspace_plugin() -> Result<()> {
PluginShareListResponse {
data: vec![PluginShareListItem {
plugin: PluginSummary {
id: "demo-plugin@shared-with-me".to_string(),
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
remote_plugin_id: Some("plugins_123".to_string()),
local_version: None,
name: "demo-plugin".to_string(),
Expand Down
91 changes: 68 additions & 23 deletions codex-rs/core-plugins/src/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,15 @@ pub use share::update_remote_plugin_share_targets;

pub const REMOTE_GLOBAL_MARKETPLACE_NAME: &str = "chatgpt-global";
pub const REMOTE_WORKSPACE_MARKETPLACE_NAME: &str = "workspace-directory";
pub const REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME: &str = "shared-with-me";
pub const REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME: &str =
"workspace-shared-with-me-private";
pub const REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME: &str =
"workspace-shared-with-me-unlisted";
pub const REMOTE_GLOBAL_MARKETPLACE_DISPLAY_NAME: &str = "ChatGPT Plugins";
pub const REMOTE_WORKSPACE_MARKETPLACE_DISPLAY_NAME: &str = "Workspace Directory";
pub const REMOTE_SHARED_WITH_ME_MARKETPLACE_DISPLAY_NAME: &str = "Shared with me";
pub const REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_DISPLAY_NAME: &str = "Shared with me";
pub const REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_DISPLAY_NAME: &str =
"Shared with me (unlisted)";

const REMOTE_PLUGIN_CATALOG_TIMEOUT: Duration = Duration::from_secs(30);
const REMOTE_PLUGIN_LIST_PAGE_LIMIT: u32 = 200;
Expand Down Expand Up @@ -286,9 +291,9 @@ impl RemotePluginScope {
fn from_marketplace_name(name: &str) -> Option<Self> {
match name {
REMOTE_GLOBAL_MARKETPLACE_NAME => Some(Self::Global),
REMOTE_WORKSPACE_MARKETPLACE_NAME | REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME => {
Some(Self::Workspace)
}
REMOTE_WORKSPACE_MARKETPLACE_NAME
| REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME
| REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME => Some(Self::Workspace),
_ => None,
}
}
Expand Down Expand Up @@ -388,9 +393,11 @@ fn remote_plugin_canonical_marketplace_name(
RemotePluginScope::Global => Ok(REMOTE_GLOBAL_MARKETPLACE_NAME),
RemotePluginScope::Workspace => match workspace_plugin_discoverability(plugin)? {
RemotePluginShareDiscoverability::Listed => Ok(REMOTE_WORKSPACE_MARKETPLACE_NAME),
RemotePluginShareDiscoverability::Unlisted
| RemotePluginShareDiscoverability::Private => {
Ok(REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME)
RemotePluginShareDiscoverability::Unlisted => {
Ok(REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME)
}
RemotePluginShareDiscoverability::Private => {
Ok(REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME)
}
},
}
Expand Down Expand Up @@ -462,43 +469,81 @@ pub async fn fetch_remote_marketplaces(
};

for source in sources {
let marketplace = match source {
match source {
RemoteMarketplaceSource::Global => {
let scope = RemotePluginScope::Global;
let (directory_plugins, installed_plugins) = tokio::try_join!(
fetch_directory_plugins_for_scope(config, auth, scope),
fetch_installed_plugins_for_scope(config, auth, scope),
)?;
build_remote_marketplace(
if let Some(marketplace) = build_remote_marketplace(
scope.marketplace_name(),
scope.marketplace_display_name(),
directory_plugins,
installed_plugins,
/*include_installed_only*/ true,
)?
)? {
marketplaces.push(marketplace);
}
}
RemoteMarketplaceSource::WorkspaceDirectory => {
let scope = RemotePluginScope::Workspace;
let directory_plugins =
fetch_directory_plugins_for_scope(config, auth, scope).await?;
build_remote_marketplace(
if let Some(marketplace) = build_remote_marketplace(
scope.marketplace_name(),
scope.marketplace_display_name(),
directory_plugins,
workspace_installed_plugins.clone().unwrap_or_default(),
/*include_installed_only*/ false,
)?
)? {
marketplaces.push(marketplace);
}
}
RemoteMarketplaceSource::SharedWithMe => {
let private_plugins = fetch_shared_workspace_plugins(config, auth)
.await?
.into_iter()
.filter_map(|plugin| match workspace_plugin_discoverability(&plugin) {
Ok(RemotePluginShareDiscoverability::Private) => Some(Ok(plugin)),
Ok(RemotePluginShareDiscoverability::Listed)
| Ok(RemotePluginShareDiscoverability::Unlisted) => None,
Err(err) => Some(Err(err)),
})
.collect::<Result<Vec<_>, _>>()?;
if let Some(marketplace) = build_remote_marketplace(
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME,
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_DISPLAY_NAME,
private_plugins,
workspace_installed_plugins.clone().unwrap_or_default(),
/*include_installed_only*/ false,
)? {
marketplaces.push(marketplace);
}

let unlisted_installed_plugins = workspace_installed_plugins
.clone()
.unwrap_or_default()
.into_iter()
.filter_map(
|plugin| match workspace_plugin_discoverability(&plugin.plugin) {
Ok(RemotePluginShareDiscoverability::Unlisted) => Some(Ok(plugin)),
Ok(RemotePluginShareDiscoverability::Listed)
| Ok(RemotePluginShareDiscoverability::Private) => None,
Err(err) => Some(Err(err)),
},
)
.collect::<Result<Vec<_>, _>>()?;
if let Some(marketplace) = build_remote_marketplace(
REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME,
REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_DISPLAY_NAME,
Vec::new(),
unlisted_installed_plugins,
/*include_installed_only*/ true,
)? {
marketplaces.push(marketplace);
}
}
RemoteMarketplaceSource::SharedWithMe => build_remote_marketplace(
REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME,
REMOTE_SHARED_WITH_ME_MARKETPLACE_DISPLAY_NAME,
fetch_shared_workspace_plugins(config, auth).await?,
workspace_installed_plugins.clone().unwrap_or_default(),
/*include_installed_only*/ false,
)?,
};
if let Some(marketplace) = marketplace {
marketplaces.push(marketplace);
}
}

Expand Down
35 changes: 26 additions & 9 deletions codex-rs/core-plugins/src/remote/remote_installed_plugin_sync.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::REMOTE_GLOBAL_MARKETPLACE_NAME;
use super::REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME;
use super::REMOTE_WORKSPACE_MARKETPLACE_NAME;
use super::REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME;
use super::REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME;
use super::RemotePluginCatalogError;
use super::RemotePluginScope;
use super::RemotePluginServiceConfig;
Expand Down Expand Up @@ -153,7 +154,11 @@ pub async fn sync_remote_installed_plugin_bundles_once(
BTreeSet::new(),
),
(
REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME.to_string(),
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME.to_string(),
BTreeSet::new(),
),
(
REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME.to_string(),
BTreeSet::new(),
),
]);
Expand Down Expand Up @@ -298,7 +303,8 @@ fn remove_stale_remote_plugin_caches(
for marketplace_name in [
REMOTE_GLOBAL_MARKETPLACE_NAME,
REMOTE_WORKSPACE_MARKETPLACE_NAME,
REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME,
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME,
REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME,
] {
let marketplace_root = codex_home.join(PLUGINS_CACHE_DIR).join(marketplace_name);
if !marketplace_root.exists() {
Expand Down Expand Up @@ -457,7 +463,11 @@ mod tests {
BTreeSet::new(),
),
(
REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME.to_string(),
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME.to_string(),
BTreeSet::new(),
),
(
REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME.to_string(),
BTreeSet::new(),
),
]);
Expand Down Expand Up @@ -500,12 +510,12 @@ mod tests {
}

#[test]
fn stale_remote_plugin_cleanup_removes_shared_with_me_cache() {
fn stale_remote_plugin_cleanup_removes_private_shared_with_me_cache() {
let codex_home = tempfile::tempdir().expect("create codex home");
let cached_manifest = codex_home
.path()
.join(PLUGINS_CACHE_DIR)
.join(REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME)
.join(REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME)
.join("private-plugin")
.join("1.2.3")
.join(".codex-plugin")
Expand All @@ -522,7 +532,11 @@ mod tests {
BTreeSet::new(),
),
(
REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME.to_string(),
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME.to_string(),
BTreeSet::new(),
),
(
REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME.to_string(),
BTreeSet::new(),
),
]);
Expand All @@ -531,9 +545,12 @@ mod tests {
codex_home.path(),
&installed_plugin_names_by_marketplace,
)
.expect("cleanup shared-with-me cache");
.expect("cleanup private shared-with-me cache");

assert_eq!(removed, vec!["private-plugin@shared-with-me".to_string()]);
assert_eq!(
removed,
vec!["private-plugin@workspace-shared-with-me-private".to_string()]
);
assert!(!cached_manifest.exists());
}
}
Loading
Loading