mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Extract codex-core-skills crate (#15749)
## Summary - move skill loading and management into codex-core-skills - leave codex-core with the thin integration layer and shared wiring ## Testing - CI --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -27,6 +27,7 @@ use super::store::PluginStore;
|
||||
use super::store::PluginStoreError;
|
||||
use super::sync_openai_plugins_repo;
|
||||
use crate::AuthManager;
|
||||
use crate::SkillMetadata;
|
||||
use crate::auth::CodexAuth;
|
||||
use crate::config::Config;
|
||||
use crate::config::ConfigService;
|
||||
@@ -36,12 +37,12 @@ use crate::config::edit::ConfigEditsBuilder;
|
||||
use crate::config::types::McpServerConfig;
|
||||
use crate::config::types::PluginConfig;
|
||||
use crate::config_loader::ConfigLayerStack;
|
||||
use crate::skills::SkillMetadata;
|
||||
use crate::skills::config_rules::SkillConfigRules;
|
||||
use crate::skills::config_rules::resolve_disabled_skill_paths;
|
||||
use crate::skills::config_rules::skill_config_rules_from_stack;
|
||||
use crate::skills::loader::SkillRoot;
|
||||
use crate::skills::loader::load_skills_from_roots;
|
||||
use crate::config_rules::SkillConfigRules;
|
||||
use crate::config_rules::resolve_disabled_skill_paths;
|
||||
use crate::config_rules::skill_config_rules_from_stack;
|
||||
use crate::loader::SkillRoot;
|
||||
use crate::loader::load_skills_from_roots;
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_app_server_protocol::ConfigValueWriteParams;
|
||||
use codex_app_server_protocol::MergeStrategy;
|
||||
use codex_features::Feature;
|
||||
@@ -73,8 +74,6 @@ use toml_edit::value;
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::AnalyticsEventsClient;
|
||||
|
||||
const DEFAULT_SKILLS_DIR_NAME: &str = "skills";
|
||||
const DEFAULT_MCP_CONFIG_FILE: &str = ".mcp.json";
|
||||
const DEFAULT_APP_CONFIG_FILE: &str = ".app.json";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
pub(crate) use codex_utils_plugins::PLUGIN_MANIFEST_PATH;
|
||||
use codex_utils_plugins::PLUGIN_MANIFEST_PATH;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::fs;
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use codex_protocol::user_input::UserInput;
|
||||
|
||||
use crate::connectors;
|
||||
use crate::injection::ToolMentionKind;
|
||||
use crate::injection::app_id_from_path;
|
||||
use crate::injection::extract_tool_mentions_with_sigil;
|
||||
use crate::injection::plugin_config_name_from_path;
|
||||
use crate::injection::tool_kind_for_path;
|
||||
use crate::mention_syntax::PLUGIN_TEXT_MENTION_SIGIL;
|
||||
use crate::mention_syntax::TOOL_MENTION_SIGIL;
|
||||
|
||||
use super::PluginCapabilitySummary;
|
||||
|
||||
pub(crate) struct CollectedToolMentions {
|
||||
pub(crate) plain_names: HashSet<String>,
|
||||
pub(crate) paths: HashSet<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn collect_tool_mentions_from_messages(messages: &[String]) -> CollectedToolMentions {
|
||||
collect_tool_mentions_from_messages_with_sigil(messages, TOOL_MENTION_SIGIL)
|
||||
}
|
||||
|
||||
fn collect_tool_mentions_from_messages_with_sigil(
|
||||
messages: &[String],
|
||||
sigil: char,
|
||||
) -> CollectedToolMentions {
|
||||
let mut plain_names = HashSet::new();
|
||||
let mut paths = HashSet::new();
|
||||
for message in messages {
|
||||
let mentions = extract_tool_mentions_with_sigil(message, sigil);
|
||||
plain_names.extend(mentions.plain_names().map(str::to_string));
|
||||
paths.extend(mentions.paths().map(str::to_string));
|
||||
}
|
||||
CollectedToolMentions { plain_names, paths }
|
||||
}
|
||||
|
||||
pub(crate) fn collect_explicit_app_ids(input: &[UserInput]) -> HashSet<String> {
|
||||
let messages = input
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
UserInput::Text { text, .. } => Some(text.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
input
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
UserInput::Mention { path, .. } => Some(path.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.chain(collect_tool_mentions_from_messages(&messages).paths)
|
||||
.filter(|path| tool_kind_for_path(path.as_str()) == ToolMentionKind::App)
|
||||
.filter_map(|path| app_id_from_path(path.as_str()).map(str::to_string))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Collect explicit structured or linked `plugin://...` mentions.
|
||||
pub(crate) fn collect_explicit_plugin_mentions(
|
||||
input: &[UserInput],
|
||||
plugins: &[PluginCapabilitySummary],
|
||||
) -> Vec<PluginCapabilitySummary> {
|
||||
if plugins.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let messages = input
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
UserInput::Text { text, .. } => Some(text.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let mentioned_config_names: HashSet<String> = input
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
UserInput::Mention { path, .. } => Some(path.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.chain(
|
||||
// Plugin plaintext links use `@`, not the default `$` tool sigil.
|
||||
collect_tool_mentions_from_messages_with_sigil(&messages, PLUGIN_TEXT_MENTION_SIGIL)
|
||||
.paths,
|
||||
)
|
||||
.filter(|path| tool_kind_for_path(path.as_str()) == ToolMentionKind::Plugin)
|
||||
.filter_map(|path| plugin_config_name_from_path(path.as_str()).map(str::to_string))
|
||||
.collect();
|
||||
|
||||
if mentioned_config_names.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
plugins
|
||||
.iter()
|
||||
.filter(|plugin| mentioned_config_names.contains(plugin.config_name.as_str()))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) use crate::build_skill_name_counts;
|
||||
|
||||
pub(crate) fn build_connector_slug_counts(
|
||||
connectors: &[connectors::AppInfo],
|
||||
) -> HashMap<String, usize> {
|
||||
let mut counts: HashMap<String, usize> = HashMap::new();
|
||||
for connector in connectors {
|
||||
let slug = connectors::connector_mention_slug(connector);
|
||||
*counts.entry(slug).or_insert(0) += 1;
|
||||
}
|
||||
counts
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mentions_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,155 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::collect_explicit_app_ids;
|
||||
use super::collect_explicit_plugin_mentions;
|
||||
use crate::plugins::PluginCapabilitySummary;
|
||||
|
||||
fn text_input(text: &str) -> UserInput {
|
||||
UserInput::Text {
|
||||
text: text.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn plugin(config_name: &str, display_name: &str) -> PluginCapabilitySummary {
|
||||
PluginCapabilitySummary {
|
||||
config_name: config_name.to_string(),
|
||||
display_name: display_name.to_string(),
|
||||
description: None,
|
||||
has_skills: true,
|
||||
mcp_server_names: Vec::new(),
|
||||
app_connector_ids: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_explicit_app_ids_from_linked_text_mentions() {
|
||||
let input = vec")];
|
||||
|
||||
let app_ids = collect_explicit_app_ids(&input);
|
||||
|
||||
assert_eq!(app_ids, HashSet::from(["calendar".to_string()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_explicit_app_ids_dedupes_structured_and_linked_mentions() {
|
||||
let input = vec"),
|
||||
UserInput::Mention {
|
||||
name: "calendar".to_string(),
|
||||
path: "app://calendar".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let app_ids = collect_explicit_app_ids(&input);
|
||||
|
||||
assert_eq!(app_ids, HashSet::from(["calendar".to_string()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_explicit_app_ids_ignores_non_app_paths() {
|
||||
let input = vec and [$skill](skill://team/skill) and [$file](/tmp/file.txt)",
|
||||
),
|
||||
UserInput::Mention {
|
||||
name: "docs".to_string(),
|
||||
path: "mcp://docs".to_string(),
|
||||
},
|
||||
UserInput::Mention {
|
||||
name: "skill".to_string(),
|
||||
path: "skill://team/skill".to_string(),
|
||||
},
|
||||
UserInput::Mention {
|
||||
name: "file".to_string(),
|
||||
path: "/tmp/file.txt".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let app_ids = collect_explicit_app_ids(&input);
|
||||
|
||||
assert_eq!(app_ids, HashSet::<String>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_explicit_plugin_mentions_from_structured_paths() {
|
||||
let plugins = vec![
|
||||
plugin("sample@test", "sample"),
|
||||
plugin("other@test", "other"),
|
||||
];
|
||||
|
||||
let mentioned = collect_explicit_plugin_mentions(
|
||||
&[UserInput::Mention {
|
||||
name: "sample".to_string(),
|
||||
path: "plugin://sample@test".to_string(),
|
||||
}],
|
||||
&plugins,
|
||||
);
|
||||
|
||||
assert_eq!(mentioned, vec![plugin("sample@test", "sample")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_explicit_plugin_mentions_from_linked_text_mentions() {
|
||||
let plugins = vec![
|
||||
plugin("sample@test", "sample"),
|
||||
plugin("other@test", "other"),
|
||||
];
|
||||
|
||||
let mentioned = collect_explicit_plugin_mentions(
|
||||
&[text_input("use [@sample](plugin://sample@test)")],
|
||||
&plugins,
|
||||
);
|
||||
|
||||
assert_eq!(mentioned, vec![plugin("sample@test", "sample")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_explicit_plugin_mentions_dedupes_structured_and_linked_mentions() {
|
||||
let plugins = vec![
|
||||
plugin("sample@test", "sample"),
|
||||
plugin("other@test", "other"),
|
||||
];
|
||||
|
||||
let mentioned = collect_explicit_plugin_mentions(
|
||||
&[
|
||||
text_input("use [@sample](plugin://sample@test)"),
|
||||
UserInput::Mention {
|
||||
name: "sample".to_string(),
|
||||
path: "plugin://sample@test".to_string(),
|
||||
},
|
||||
],
|
||||
&plugins,
|
||||
);
|
||||
|
||||
assert_eq!(mentioned, vec![plugin("sample@test", "sample")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_explicit_plugin_mentions_ignores_non_plugin_paths() {
|
||||
let plugins = vec![plugin("sample@test", "sample")];
|
||||
|
||||
let mentioned = collect_explicit_plugin_mentions(
|
||||
&[text_input(
|
||||
"use [$app](app://calendar) and [$skill](skill://team/skill) and [$file](/tmp/file.txt)",
|
||||
)],
|
||||
&plugins,
|
||||
);
|
||||
|
||||
assert_eq!(mentioned, Vec::<PluginCapabilitySummary>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_explicit_plugin_mentions_ignores_dollar_linked_plugin_mentions() {
|
||||
let plugins = vec![plugin("sample@test", "sample")];
|
||||
|
||||
let mentioned = collect_explicit_plugin_mentions(
|
||||
&[text_input("use [$sample](plugin://sample@test)")],
|
||||
&plugins,
|
||||
);
|
||||
|
||||
assert_eq!(mentioned, Vec::<PluginCapabilitySummary>::new());
|
||||
}
|
||||
@@ -5,6 +5,7 @@ mod injection;
|
||||
mod manager;
|
||||
mod manifest;
|
||||
mod marketplace;
|
||||
mod mentions;
|
||||
mod remote;
|
||||
mod render;
|
||||
mod startup_sync;
|
||||
@@ -23,7 +24,6 @@ pub use codex_plugin::PluginTelemetryMetadata;
|
||||
pub type LoadedPlugin = codex_plugin::LoadedPlugin<McpServerConfig>;
|
||||
pub type PluginLoadOutcome = codex_plugin::PluginLoadOutcome<McpServerConfig>;
|
||||
|
||||
pub(crate) use codex_plugin::plugin_namespace_for_skill_path;
|
||||
pub(crate) use discoverable::list_tool_suggest_discoverable_plugins;
|
||||
pub(crate) use injection::build_plugin_injections;
|
||||
pub use manager::ConfiguredMarketplace;
|
||||
@@ -61,3 +61,9 @@ pub(crate) use startup_sync::curated_plugins_repo_path;
|
||||
pub(crate) use startup_sync::read_curated_plugins_sha;
|
||||
pub(crate) use startup_sync::sync_openai_plugins_repo;
|
||||
pub use toggles::collect_plugin_enabled_candidates;
|
||||
|
||||
pub(crate) use mentions::build_connector_slug_counts;
|
||||
pub(crate) use mentions::build_skill_name_counts;
|
||||
pub(crate) use mentions::collect_explicit_app_ids;
|
||||
pub(crate) use mentions::collect_explicit_plugin_mentions;
|
||||
pub(crate) use mentions::collect_tool_mentions_from_messages;
|
||||
|
||||
Reference in New Issue
Block a user