use std::collections::HashMap; use std::fmt; use std::future::Future; use std::hash::Hash; use std::hash::Hasher; use std::pin::Pin; use std::sync::Arc; use codex_protocol::protocol::Product; use codex_utils_absolute_path::AbsolutePathBuf; use crate::SkillMetadata; /// A skill document that could not be read, parsed, or validated. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SkillError { pub path: AbsolutePathBuf, pub message: String, } /// Filesystem-independent metadata discovered beneath one canonical skill root. #[derive(Debug, Clone, PartialEq)] pub struct LoadedSkillRoot { pub root: AbsolutePathBuf, pub skills: Vec, pub skill_discovery_path_by_path: Arc>, pub errors: Vec, pub is_agent_plugin: bool, } /// Skills and errors produced by loading an ordered set of roots. #[derive(Debug, Clone, Default, PartialEq)] pub struct LoadedSkills { pub skills: Vec, pub errors: Vec, } /// Caches parsed roots without assigning ownership of the cache to a loader. /// /// Implementations own storage and determine its lifetime; loaders may reuse an existing snapshot /// or publish a newly loaded one without learning about the caller's broader state. pub trait SkillRootSnapshotCache: Send + Sync { fn get(&self, root: &Root) -> Option; fn insert(&self, root: Root, snapshot: LoadedSkillRoot); } /// Shared access to one owner-managed collection of parsed skill roots. /// /// Equality and hashing use the cache allocation's identity so callers can include the handle in /// their own cache keys without inspecting or taking ownership of its contents. pub struct SkillRootSnapshots { cache: Arc>, } impl SkillRootSnapshots { pub fn new(cache: Arc>) -> Self { Self { cache } } pub fn get(&self, root: &Root) -> Option { self.cache.get(root) } pub fn insert(&self, root: Root, snapshot: LoadedSkillRoot) { self.cache.insert(root, snapshot); } } impl Clone for SkillRootSnapshots { fn clone(&self) -> Self { Self { cache: Arc::clone(&self.cache), } } } impl PartialEq for SkillRootSnapshots { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.cache, &other.cache) } } impl Eq for SkillRootSnapshots {} impl Hash for SkillRootSnapshots { fn hash(&self, state: &mut H) { (Arc::as_ptr(&self.cache) as *const ()).hash(state); } } impl fmt::Debug for SkillRootSnapshots { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SkillRootSnapshots").finish_non_exhaustive() } } /// Inputs for loading roots while optionally reusing snapshots owned by the caller. #[derive(Debug, Clone)] pub struct SkillRootLoadRequest { pub roots: Vec, pub restriction_product: Option, pub snapshots: Option>, } /// Object-safe future returned by a shared skill-root loader. pub type SkillLoadFuture<'a, T> = Pin + Send + 'a>>; /// Loads ordered skill roots without owning their source inventory or capability state. /// /// Implementations are responsible for filesystem access, metadata parsing, root precedence, /// product filtering, and reuse of any snapshots supplied in the request. pub trait SkillRootLoader: Send + Sync { fn load_roots(&self, request: SkillRootLoadRequest) -> SkillLoadFuture<'_, LoadedSkills>; } #[cfg(test)] #[path = "loading_tests.rs"] mod tests;