diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 4a1718c8b9..49e3c44e42 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1926,6 +1926,7 @@ dependencies = [ "codex-utils-image", "codex-utils-output-truncation", "codex-utils-path", + "codex-utils-plugins", "codex-utils-pty", "codex-utils-readiness", "codex-utils-stream-parser", @@ -2004,10 +2005,10 @@ dependencies = [ "codex-instructions", "codex-login", "codex-otel", - "codex-plugin", "codex-protocol", "codex-skills", "codex-utils-absolute-path", + "codex-utils-plugins", "dirs", "dunce", "pretty_assertions", @@ -2449,9 +2450,9 @@ name = "codex-plugin" version = "0.0.0" dependencies = [ "codex-utils-absolute-path", + "codex-utils-plugins", "serde", "serde_json", - "tempfile", "thiserror 2.0.18", ] @@ -3009,6 +3010,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "codex-utils-plugins" +version = "0.0.0" +dependencies = [ + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "codex-utils-pty" version = "0.0.0" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 97e520e320..0b47afb2ea 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -71,6 +71,7 @@ members = [ "utils/oss", "utils/output-truncation", "utils/path-utils", + "utils/plugins", "utils/fuzzy-match", "utils/stream-parser", "codex-client", @@ -168,6 +169,7 @@ codex-utils-json-to-toml = { path = "utils/json-to-toml" } codex-utils-oss = { path = "utils/oss" } codex-utils-output-truncation = { path = "utils/output-truncation" } codex-utils-path = { path = "utils/path-utils" } +codex-utils-plugins = { path = "utils/plugins" } codex-utils-pty = { path = "utils/pty" } codex-utils-readiness = { path = "utils/readiness" } codex-utils-rustls-provider = { path = "utils/rustls-provider" } diff --git a/codex-rs/core-skills/Cargo.toml b/codex-rs/core-skills/Cargo.toml index 816f5e4483..d5e118c52a 100644 --- a/codex-rs/core-skills/Cargo.toml +++ b/codex-rs/core-skills/Cargo.toml @@ -20,10 +20,10 @@ codex-config = { workspace = true } codex-instructions = { workspace = true } codex-login = { workspace = true } codex-otel = { workspace = true } -codex-plugin = { workspace = true } codex-protocol = { workspace = true } codex-skills = { workspace = true } codex-utils-absolute-path = { workspace = true } +codex-utils-plugins = { workspace = true } dirs = { workspace = true } dunce = { workspace = true } reqwest = { workspace = true, features = ["json", "stream"] } diff --git a/codex-rs/core-skills/src/skills/injection.rs b/codex-rs/core-skills/src/skills/injection.rs index 09dc947ff5..4cfb27f963 100644 --- a/codex-rs/core-skills/src/skills/injection.rs +++ b/codex-rs/core-skills/src/skills/injection.rs @@ -10,7 +10,7 @@ use codex_analytics::SkillInvocation; use codex_analytics::TrackEventsContext; use codex_instructions::SkillInstructions; use codex_otel::SessionTelemetry; -use codex_plugin::mention_syntax::TOOL_MENTION_SIGIL; +use codex_utils_plugins::mention_syntax::TOOL_MENTION_SIGIL; use codex_protocol::models::ResponseItem; use codex_protocol::user_input::UserInput; use tokio::fs; diff --git a/codex-rs/core-skills/src/skills/loader.rs b/codex-rs/core-skills/src/skills/loader.rs index b671a668cf..7e5fd19a08 100644 --- a/codex-rs/core-skills/src/skills/loader.rs +++ b/codex-rs/core-skills/src/skills/loader.rs @@ -13,7 +13,7 @@ use codex_config::ConfigLayerStackOrdering; use codex_config::default_project_root_markers; use codex_config::merge_toml_values; use codex_config::project_root_markers_from_config; -use codex_plugin::plugin_namespace_for_skill_path; +use codex_utils_plugins::plugin_namespace_for_skill_path; use codex_protocol::models::FileSystemPermissions; use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::NetworkPermissions; diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 6727f985d7..55ce9bfb16 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -61,6 +61,7 @@ codex-utils-image = { workspace = true } codex-utils-home-dir = { workspace = true } codex-utils-output-truncation = { workspace = true } codex-utils-path = { workspace = true } +codex-utils-plugins = { workspace = true } codex-utils-pty = { workspace = true } codex-utils-readiness = { workspace = true } codex-secrets = { workspace = true } diff --git a/codex-rs/core/src/mention_syntax.rs b/codex-rs/core/src/mention_syntax.rs index 48bfb99eca..690cd1a2f5 100644 --- a/codex-rs/core/src/mention_syntax.rs +++ b/codex-rs/core/src/mention_syntax.rs @@ -1,2 +1,2 @@ -pub use codex_plugin::mention_syntax::PLUGIN_TEXT_MENTION_SIGIL; -pub use codex_plugin::mention_syntax::TOOL_MENTION_SIGIL; +pub use codex_utils_plugins::mention_syntax::PLUGIN_TEXT_MENTION_SIGIL; +pub use codex_utils_plugins::mention_syntax::TOOL_MENTION_SIGIL; diff --git a/codex-rs/core/src/plugins/manifest.rs b/codex-rs/core/src/plugins/manifest.rs index 0df4613719..6ccc6e239b 100644 --- a/codex-rs/core/src/plugins/manifest.rs +++ b/codex-rs/core/src/plugins/manifest.rs @@ -1,4 +1,4 @@ -use codex_plugin::PLUGIN_MANIFEST_PATH; +use codex_utils_plugins::PLUGIN_MANIFEST_PATH; use codex_utils_absolute_path::AbsolutePathBuf; use serde::Deserialize; use serde_json::Value as JsonValue; diff --git a/codex-rs/core/src/plugins/store.rs b/codex-rs/core/src/plugins/store.rs index 2020863809..b6cd7f6803 100644 --- a/codex-rs/core/src/plugins/store.rs +++ b/codex-rs/core/src/plugins/store.rs @@ -1,5 +1,5 @@ use super::load_plugin_manifest; -use codex_plugin::PLUGIN_MANIFEST_PATH; +use codex_utils_plugins::PLUGIN_MANIFEST_PATH; use codex_plugin::PluginId; use codex_plugin::validate_plugin_segment; use codex_utils_absolute_path::AbsolutePathBuf; diff --git a/codex-rs/plugin/Cargo.toml b/codex-rs/plugin/Cargo.toml index 339e04d278..3bffeae066 100644 --- a/codex-rs/plugin/Cargo.toml +++ b/codex-rs/plugin/Cargo.toml @@ -14,9 +14,8 @@ workspace = true [dependencies] codex-utils-absolute-path = { workspace = true } +codex-utils-plugins = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } -[dev-dependencies] -tempfile = { workspace = true } diff --git a/codex-rs/plugin/src/lib.rs b/codex-rs/plugin/src/lib.rs index c387044365..ae3eae7d9f 100644 --- a/codex-rs/plugin/src/lib.rs +++ b/codex-rs/plugin/src/lib.rs @@ -1,10 +1,11 @@ //! Shared plugin identifiers and telemetry-facing summaries. -pub mod mention_syntax; +pub use codex_utils_plugins::mention_syntax; +pub use codex_utils_plugins::PLUGIN_MANIFEST_PATH; +pub use codex_utils_plugins::plugin_namespace_for_skill_path; mod load_outcome; mod plugin_id; -mod plugin_namespace; pub use load_outcome::EffectiveSkillRoots; pub use load_outcome::LoadedPlugin; @@ -13,8 +14,6 @@ pub use load_outcome::prompt_safe_plugin_description; pub use plugin_id::PluginId; pub use plugin_id::PluginIdError; pub use plugin_id::validate_plugin_segment; -pub use plugin_namespace::PLUGIN_MANIFEST_PATH; -pub use plugin_namespace::plugin_namespace_for_skill_path; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AppConnectorId(pub String); diff --git a/codex-rs/plugin/src/mention_syntax.rs b/codex-rs/plugin/src/mention_syntax.rs deleted file mode 100644 index de5d1432b9..0000000000 --- a/codex-rs/plugin/src/mention_syntax.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Sigils for tool/plugin mentions in plaintext (shared across Codex crates). - -/// Default plaintext sigil for tools. -pub const TOOL_MENTION_SIGIL: char = '$'; - -/// Plugins use `@` in linked plaintext outside TUI. -pub const PLUGIN_TEXT_MENTION_SIGIL: char = '@'; diff --git a/codex-rs/plugin/src/plugin_namespace.rs b/codex-rs/plugin/src/plugin_namespace.rs deleted file mode 100644 index 6688ae0469..0000000000 --- a/codex-rs/plugin/src/plugin_namespace.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Resolve plugin namespace from skill file paths by walking ancestors for `plugin.json`. - -use std::fs; -use std::path::Path; - -/// Relative path from a plugin root to its manifest file. -pub const PLUGIN_MANIFEST_PATH: &str = ".codex-plugin/plugin.json"; - -#[derive(serde::Deserialize)] -#[serde(rename_all = "camelCase")] -struct RawPluginManifestName { - #[serde(default)] - name: String, -} - -fn plugin_manifest_name(plugin_root: &Path) -> Option { - let manifest_path = plugin_root.join(PLUGIN_MANIFEST_PATH); - if !manifest_path.is_file() { - return None; - } - let contents = fs::read_to_string(&manifest_path).ok()?; - let RawPluginManifestName { name: raw_name } = serde_json::from_str(&contents).ok()?; - Some( - plugin_root - .file_name() - .and_then(|entry| entry.to_str()) - .filter(|_| raw_name.trim().is_empty()) - .unwrap_or(raw_name.as_str()) - .to_string(), - ) -} - -/// Returns the plugin manifest `name` for the nearest ancestor of `path` that contains a valid -/// plugin manifest (same `name` rules as full manifest loading in codex-core). -pub fn plugin_namespace_for_skill_path(path: &Path) -> Option { - for ancestor in path.ancestors() { - if let Some(name) = plugin_manifest_name(ancestor) { - return Some(name); - } - } - None -} - -#[cfg(test)] -mod tests { - use super::plugin_namespace_for_skill_path; - use std::fs; - use tempfile::tempdir; - - #[test] - fn uses_manifest_name() { - let tmp = tempdir().expect("tempdir"); - let plugin_root = tmp.path().join("plugins/sample"); - let skill_path = plugin_root.join("skills/search/SKILL.md"); - - fs::create_dir_all(skill_path.parent().expect("parent")).expect("mkdir"); - fs::create_dir_all(plugin_root.join(".codex-plugin")).expect("mkdir manifest"); - fs::write( - plugin_root.join(".codex-plugin/plugin.json"), - r#"{"name":"sample"}"#, - ) - .expect("write manifest"); - fs::write(&skill_path, "---\ndescription: search\n---\n").expect("write skill"); - - assert_eq!( - plugin_namespace_for_skill_path(&skill_path), - Some("sample".to_string()) - ); - } -}