use std::collections::HashMap;use std::collections::HashSet;use std::fmt;use std::io;use std::sync::Arc;use codex_exec_server::ExecutorFileSystem;use codex_exec_server::LOCAL_FS;use codex_protocol::protocol::Product;use codex_protocol::protocol::SkillScope;use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_path_uri::PathUri;#[derive(Debug, Clone, PartialEq)]pub struct SkillMetadata { pub name: String, pub description: String, pub short_description: Option<String>, pub interface: Option<SkillInterface>, pub dependencies: Option<SkillDependencies>, pub policy: Option<SkillPolicy>, /// Path to the SKILLS.md file that declares this skill. pub path_to_skills_md: AbsolutePathBuf, pub scope: SkillScope, pub plugin_id: Option<String>,}impl SkillMetadata { pub fn allows_implicit_invocation(&self) -> bool { self.policy .as_ref() .and_then(|policy| policy.allow_implicit_invocation) .unwrap_or(true) } pub fn matches_product_restriction_for_product( &self, restriction_product: Option<Product>, ) -> bool { match &self.policy { Some(policy) => { policy.products.is_empty() || restriction_product.is_some_and(|product| { product.matches_product_restriction(&policy.products) }) } None => true, } }}#[derive(Debug, Clone, PartialEq, Eq, Default)]pub struct SkillPolicy { pub allow_implicit_invocation: Option<bool>, // TODO: Enforce product gating in Codex skill selection/injection instead of only parsing and // storing this metadata. pub products: Vec<Product>,}#[derive(Debug, Clone, PartialEq, Eq)]pub struct SkillInterface { pub display_name: Option<String>, pub short_description: Option<String>, pub icon_small: Option<AbsolutePathBuf>, pub icon_large: Option<AbsolutePathBuf>, pub brand_color: Option<String>, pub default_prompt: Option<String>,}#[derive(Debug, Clone, PartialEq, Eq)]pub struct SkillDependencies { pub tools: Vec<SkillToolDependency>,}#[derive(Debug, Clone, PartialEq, Eq)]pub struct SkillToolDependency { pub r#type: String, pub value: String, pub description: Option<String>, pub transport: Option<String>, pub command: Option<String>, pub url: Option<String>,}#[derive(Debug, Clone, PartialEq, Eq)]pub struct SkillError { pub path: AbsolutePathBuf, pub message: String,}#[derive(Debug, Clone, Default)]pub struct SkillLoadOutcome { pub skills: Vec<SkillMetadata>, pub errors: Vec<SkillError>, pub disabled_paths: HashSet<AbsolutePathBuf>, pub(crate) skill_roots: Vec<AbsolutePathBuf>, pub(crate) skill_root_by_path: Arc<HashMap<AbsolutePathBuf, AbsolutePathBuf>>, pub(crate) file_systems_by_skill_path: SkillFileSystemsByPath, pub(crate) implicit_skills_by_scripts_dir: Arc<HashMap<AbsolutePathBuf, SkillMetadata>>, pub(crate) implicit_skills_by_doc_path: Arc<HashMap<AbsolutePathBuf, SkillMetadata>>,}impl SkillLoadOutcome { pub fn is_skill_enabled(&self, skill: &SkillMetadata) -> bool { !self.disabled_paths.contains(&skill.path_to_skills_md) } pub fn is_skill_allowed_for_implicit_invocation(&self, skill: &SkillMetadata) -> bool { self.is_skill_enabled(skill) && skill.allows_implicit_invocation() } pub fn allowed_skills_for_implicit_invocation(&self) -> Vec<SkillMetadata> { self.skills .iter() .filter(|skill| self.is_skill_allowed_for_implicit_invocation(skill)) .cloned() .collect() } pub fn skills_with_enabled(&self) -> impl Iterator<Item = (&SkillMetadata, bool)> { self.skills .iter() .map(|skill| (skill, self.is_skill_enabled(skill))) } pub(crate) fn file_system_for_skill( &self, skill: &SkillMetadata, ) -> Option<Arc<dyn ExecutorFileSystem>> { self.file_systems_by_skill_path .get(&skill.path_to_skills_md) }}/// Host-loaded skills for one turn, including the filesystem mapping needed to/// read skill bodies through the environment that loaded them.#[derive(Debug, Clone)]pub struct HostLoadedSkills { outcome: Arc<SkillLoadOutcome>,}impl HostLoadedSkills { pub fn new(outcome: Arc<SkillLoadOutcome>) -> Self { Self { outcome } } pub fn outcome(&self) -> &SkillLoadOutcome { self.outcome.as_ref() } pub async fn read_skill_text(&self, skill: &SkillMetadata) -> io::Result<String> { let fs = self .outcome .file_system_for_skill(skill) .unwrap_or_else(|| Arc::clone(&LOCAL_FS)); let path = PathUri::from_abs_path(&skill.path_to_skills_md); fs.read_file_text(&path, /*sandbox*/ None).await }}#[derive(Clone, Default)]pub(crate) struct SkillFileSystemsByPath { values: Arc<HashMap<AbsolutePathBuf, Arc<dyn ExecutorFileSystem>>>,}impl SkillFileSystemsByPath { pub(crate) fn new(values: HashMap<AbsolutePathBuf, Arc<dyn ExecutorFileSystem>>) -> Self { Self { values: Arc::new(values), } } fn get(&self, path: &AbsolutePathBuf) -> Option<Arc<dyn ExecutorFileSystem>> { self.values.get(path).map(Arc::clone) } fn retain_paths(&mut self, paths: &HashSet<AbsolutePathBuf>) { self.values = Arc::new( self.values .iter() .filter(|(path, _)| paths.contains(*path)) .map(|(path, fs)| (path.clone(), Arc::clone(fs))) .collect(), ); }}impl fmt::Debug for SkillFileSystemsByPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SkillFileSystemsByPath") .field("len", &self.values.len()) .finish() }}pub fn filter_skill_load_outcome_for_product( mut outcome: SkillLoadOutcome, restriction_product: Option<Product>,) -> SkillLoadOutcome { outcome .skills .retain(|skill| skill.matches_product_restriction_for_product(restriction_product)); let retained_paths: HashSet<AbsolutePathBuf> = outcome .skills .iter() .map(|skill| skill.path_to_skills_md.clone()) .collect(); outcome .file_systems_by_skill_path .retain_paths(&retained_paths); outcome.skill_root_by_path = Arc::new( outcome .skill_root_by_path .iter() .filter(|(path, _)| retained_paths.contains(*path)) .map(|(path, root)| (path.clone(), root.clone())) .collect(), ); let retained_roots: HashSet<AbsolutePathBuf> = outcome.skill_root_by_path.values().cloned().collect(); outcome .skill_roots .retain(|root| retained_roots.contains(root)); outcome.implicit_skills_by_scripts_dir = Arc::new( outcome .implicit_skills_by_scripts_dir .iter() .filter(|(_, skill)| skill.matches_product_restriction_for_product(restriction_product)) .map(|(path, skill)| (path.clone(), skill.clone())) .collect(), ); outcome.implicit_skills_by_doc_path = Arc::new( outcome .implicit_skills_by_doc_path .iter() .filter(|(_, skill)| skill.matches_product_restriction_for_product(restriction_product)) .map(|(path, skill)| (path.clone(), skill.clone())) .collect(), ); outcome}