mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add fallback source for external official marketplace (#18524)
This commit is contained in:
committed by
GitHub
Unverified
parent
917a85b0d6
commit
cce6002339
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user