Add fallback source for external official marketplace (#18524)

This commit is contained in:
alexsong-oai
2026-04-19 11:04:13 -07:00
committed by GitHub
Unverified
parent 917a85b0d6
commit cce6002339
2 changed files with 144 additions and 36 deletions
+61 -36
View File
@@ -25,6 +25,8 @@ const EXTERNAL_AGENT_CONFIG_DETECT_METRIC: &str = "codex.external_agent_config.d
const EXTERNAL_AGENT_CONFIG_IMPORT_METRIC: &str = "codex.external_agent_config.import";
const EXTERNAL_AGENT_DIR: &str = ".claude";
const EXTERNAL_AGENT_CONFIG_MD: &str = "CLAUDE.md";
const EXTERNAL_OFFICIAL_MARKETPLACE_NAME: &str = "claude-plugins-official";
const EXTERNAL_OFFICIAL_MARKETPLACE_SOURCE: &str = "anthropics/claude-plugins-official";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExternalAgentConfigDetectOptions {
@@ -706,6 +708,16 @@ fn collect_enabled_plugins(settings: &JsonValue) -> Vec<String> {
.collect()
}
fn has_enabled_plugin_for_marketplace(settings: &JsonValue, marketplace_name: &str) -> bool {
collect_enabled_plugins(settings)
.into_iter()
.any(|plugin_id| {
PluginId::parse(&plugin_id)
.map(|plugin_id| plugin_id.marketplace_name == marketplace_name)
.unwrap_or(false)
})
}
fn configured_marketplace_plugins(
config: &Config,
plugins_manager: &PluginsManager,
@@ -741,48 +753,61 @@ fn collect_marketplace_import_sources(
settings: &JsonValue,
source_root: &Path,
) -> BTreeMap<String, MarketplaceImportSource> {
let Some(extra_known_marketplaces) = settings
let mut import_sources: BTreeMap<String, MarketplaceImportSource> = settings
.as_object()
.and_then(|settings| settings.get("extraKnownMarketplaces"))
.and_then(JsonValue::as_object)
else {
return BTreeMap::new();
};
.map(|extra_known_marketplaces| {
extra_known_marketplaces
.iter()
.filter_map(|(name, value)| {
let source_fields = if let Some(source) = value.get("source")
&& source.is_object()
{
source.as_object()?
} else {
value.as_object()?
};
let source = source_fields
.get("repo")
.or_else(|| source_fields.get("url"))
.or_else(|| source_fields.get("path"))
.or_else(|| value.get("source"))?
.as_str()?
.trim()
.to_string();
if source.is_empty() {
return None;
}
let source = resolve_external_marketplace_source(&source, source_root);
extra_known_marketplaces
.iter()
.filter_map(|(name, value)| {
let source_fields = if let Some(source) = value.get("source")
&& source.is_object()
{
source.as_object()?
} else {
value.as_object()?
};
let source = source_fields
.get("repo")
.or_else(|| source_fields.get("url"))
.or_else(|| source_fields.get("path"))
.or_else(|| value.get("source"))?
.as_str()?
.trim()
.to_string();
if source.is_empty() {
return None;
}
let source = resolve_external_marketplace_source(&source, source_root);
let ref_name = source_fields
.get("ref")
.or_else(|| value.get("ref"))
.and_then(JsonValue::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned);
let ref_name = source_fields
.get("ref")
.or_else(|| value.get("ref"))
.and_then(JsonValue::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned);
Some((name.clone(), MarketplaceImportSource { source, ref_name }))
Some((name.clone(), MarketplaceImportSource { source, ref_name }))
})
.collect()
})
.collect()
.unwrap_or_default();
if has_enabled_plugin_for_marketplace(settings, EXTERNAL_OFFICIAL_MARKETPLACE_NAME)
&& !import_sources.contains_key(EXTERNAL_OFFICIAL_MARKETPLACE_NAME)
{
import_sources.insert(
EXTERNAL_OFFICIAL_MARKETPLACE_NAME.to_string(),
MarketplaceImportSource {
source: EXTERNAL_OFFICIAL_MARKETPLACE_SOURCE.to_string(),
ref_name: None,
},
);
}
import_sources
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -1289,6 +1289,49 @@ async fn detect_home_supports_relative_external_agent_plugin_marketplace_path()
);
}
#[tokio::test]
async fn detect_home_infers_claude_official_marketplace_when_missing_from_settings() {
let (_root, external_agent_home, codex_home) = fixture_paths();
fs::create_dir_all(&external_agent_home).expect("create external agent home");
fs::create_dir_all(&codex_home).expect("create codex home");
fs::write(
external_agent_home.join("settings.json"),
r#"{
"enabledPlugins": {
"sample@claude-plugins-official": true
}
}"#,
)
.expect("write settings");
let items = service_for_paths(external_agent_home.clone(), codex_home)
.detect(ExternalAgentConfigDetectOptions {
include_home: true,
cwds: None,
})
.await
.expect("detect");
assert_eq!(
items,
vec![ExternalAgentConfigMigrationItem {
item_type: ExternalAgentConfigMigrationItemType::Plugins,
description: format!(
"Migrate enabled plugins from {}",
external_agent_home.join("settings.json").display()
),
cwd: None,
details: Some(MigrationDetails {
plugins: vec![PluginsMigration {
marketplace_name: "claude-plugins-official".to_string(),
plugin_names: vec!["sample".to_string()],
}],
}),
}]
);
}
#[tokio::test]
async fn import_plugins_supports_relative_external_agent_plugin_marketplace_path() {
let (_root, external_agent_home, codex_home) = fixture_paths();
@@ -1362,6 +1405,46 @@ async fn import_plugins_supports_relative_external_agent_plugin_marketplace_path
assert!(config.contains("enabled = true"));
}
#[tokio::test]
async fn import_plugins_infers_claude_official_marketplace_when_missing_from_settings() {
let (_root, external_agent_home, codex_home) = fixture_paths();
fs::create_dir_all(&external_agent_home).expect("create external agent home");
fs::create_dir_all(&codex_home).expect("create codex home");
fs::write(
external_agent_home.join("settings.json"),
r#"{
"enabledPlugins": {
"sample@claude-plugins-official": true
}
}"#,
)
.expect("write settings");
let outcome = service_for_paths(external_agent_home, codex_home)
.import_plugins(
/*cwd*/ None,
Some(MigrationDetails {
plugins: vec![PluginsMigration {
marketplace_name: "claude-plugins-official".to_string(),
plugin_names: vec!["sample".to_string()],
}],
}),
)
.await
.expect("import plugins");
assert_eq!(
outcome,
PluginImportOutcome {
succeeded_marketplaces: vec!["claude-plugins-official".to_string()],
succeeded_plugin_ids: Vec::new(),
failed_marketplaces: Vec::new(),
failed_plugin_ids: vec!["sample@claude-plugins-official".to_string()],
}
);
}
#[tokio::test]
async fn detect_repo_supports_project_relative_external_agent_plugin_marketplace_path() {
let root = TempDir::new().expect("create tempdir");