use std::path::{Path, PathBuf};
use super::{contain, PluginScope};
pub(crate) const RHO_EXTENSION_NAMESPACE: &str = "io.github.matthewyjiang.rho";
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct PluginAgentFiles {
pub(crate) files: Vec<PathBuf>,
}
impl PluginAgentFiles {
pub(super) fn file_names(&self) -> Vec<String> {
self.files
.iter()
.filter_map(|path| path.file_name()?.to_str().map(str::to_owned))
.collect()
}
pub(crate) fn read_sources(&self) -> Vec<crate::agent::AgentSource> {
self.files
.iter()
.map(|path| crate::agent::AgentSource {
path: path.clone(),
contents: crate::workflow::read_resolved_utf8(path)
.map_err(|error| error.to_string()),
})
.collect()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct PluginAgentTiers {
pub(crate) user: Vec<PluginAgentFiles>,
pub(crate) project: Vec<PluginAgentFiles>,
}
impl PluginAgentTiers {
pub(super) fn push(&mut self, scope: PluginScope, files: PluginAgentFiles) {
match scope {
PluginScope::User => self.user.push(files),
PluginScope::Project => self.project.push(files),
}
}
}
pub(super) fn discover_plugin_agents(
root: &Path,
problems: &mut Vec<String>,
) -> Option<PluginAgentFiles> {
let location = root.join(RHO_EXTENSION_NAMESPACE).join("agents");
if !location.exists() {
return None;
}
if !location.is_dir() {
problems.push(format!(
"agents location {RHO_EXTENSION_NAMESPACE}/agents exists but is not a directory"
));
return None;
}
let dir = match contain::contained_path(root, &location) {
Ok(dir) => dir,
Err(error) => {
problems.push(format!("agents component invalid: {error}"));
return None;
}
};
let entries = match std::fs::read_dir(&dir) {
Ok(entries) => entries,
Err(error) => {
problems.push(format!("agents directory unreadable: {error}"));
return None;
}
};
let mut entries: Vec<_> = entries
.filter_map(Result::ok)
.filter(|entry| entry.path().extension().is_some_and(|ext| ext == "md"))
.collect();
entries.sort_by_key(std::fs::DirEntry::file_name);
let mut files = Vec::new();
for entry in entries {
match entry.file_type() {
Ok(kind) if kind.is_file() => files.push(entry.path()),
Ok(kind) if kind.is_symlink() => problems.push(format!(
"skipping agent file `{}`: symlinked agent files are not supported",
entry.file_name().to_string_lossy()
)),
Ok(_) => {}
Err(error) => problems.push(format!(
"skipping agent file `{}`: {error}",
entry.file_name().to_string_lossy()
)),
}
}
Some(PluginAgentFiles { files })
}