use crate::agent::{AgentRegistry, AgentRole, AgentStorage, XdgAgentStorage};
use crate::config::{xdg, AgentConfig};
use crate::error::ApiError;
pub const DEFAULT_PROMPTS: &[(&str, &str)] = &[
(
"code-analyzer.md",
include_str!("../prompts/code-analyzer.md"),
),
("docs-writer.md", include_str!("../prompts/docs-writer.md")),
];
struct DefaultAgent {
id: &'static str,
role: AgentRole,
prompt_file: Option<&'static str>,
user_prompt_file: Option<&'static str>,
user_prompt_directory: Option<&'static str>,
}
const DEFAULT_AGENTS: &[DefaultAgent] = &[
DefaultAgent {
id: "reader",
role: AgentRole::Reader,
prompt_file: None,
user_prompt_file: None,
user_prompt_directory: None,
},
DefaultAgent {
id: "code-analyzer",
role: AgentRole::Writer,
prompt_file: Some("prompts/code-analyzer.md"),
user_prompt_file: Some("Analyze the code file at {path}. Provide a comprehensive analysis including:\n- Code structure and organization\n- Key functions and their purposes\n- Dependencies and relationships\n- Notable patterns or conventions\n- Potential issues or improvements"),
user_prompt_directory: Some("Analyze the directory structure at {path}. Provide an overview including:\n- Directory purpose and organization\n- Key files and their roles\n- Module relationships\n- Overall architecture patterns"),
},
DefaultAgent {
id: "docs-writer",
role: AgentRole::Writer,
prompt_file: Some("prompts/docs-writer.md"),
user_prompt_file: Some("Generate comprehensive documentation for the code file at {path}. Include:\n- Purpose and overview\n- API documentation\n- Usage examples\n- Important notes and warnings\n- Related components"),
user_prompt_directory: Some("Generate documentation for the directory at {path}. Include:\n- Directory purpose and structure\n- Module overview\n- Key components and their roles\n- Usage guidelines"),
},
];
#[derive(Debug, Clone)]
pub struct InitResult {
pub created: Vec<String>,
pub skipped: Vec<String>,
pub errors: Vec<String>,
}
impl InitResult {
fn new() -> Self {
Self {
created: Vec::new(),
skipped: Vec::new(),
errors: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct InitSummary {
pub prompts: InitResult,
pub agents: InitResult,
pub validation: ValidationSummary,
}
#[derive(Debug, Clone)]
pub struct InitPreview {
pub prompts: Vec<String>,
pub agents: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ValidationSummary {
pub results: Vec<(String, bool, Vec<String>)>, }
pub fn initialize_prompts(force: bool) -> Result<InitResult, ApiError> {
let prompts_dir = xdg::prompts_dir()?;
let mut result = InitResult::new();
for (filename, content) in DEFAULT_PROMPTS {
let prompt_path = prompts_dir.join(filename);
if prompt_path.exists() && !force {
result.skipped.push(filename.to_string());
continue;
}
match std::fs::write(&prompt_path, *content) {
Ok(_) => {
result.created.push(filename.to_string());
}
Err(e) => {
result.errors.push(format!(
"Failed to write prompt file {}: {}",
prompt_path.display(),
e
));
}
}
}
Ok(result)
}
pub fn initialize_agents(force: bool) -> Result<InitResult, ApiError> {
let agents_dir = XdgAgentStorage::new().agents_dir()?;
let mut result = InitResult::new();
for agent in DEFAULT_AGENTS {
let config_path = agents_dir.join(format!("{}.toml", agent.id));
if config_path.exists() && !force {
result.skipped.push(agent.id.to_string());
continue;
}
let mut agent_config = AgentConfig {
agent_id: agent.id.to_string(),
role: agent.role,
system_prompt: None,
system_prompt_path: agent.prompt_file.map(|s| s.to_string()),
metadata: Default::default(),
};
if let Some(user_prompt_file) = agent.user_prompt_file {
agent_config
.metadata
.insert("user_prompt_file".to_string(), user_prompt_file.to_string());
}
if let Some(user_prompt_directory) = agent.user_prompt_directory {
agent_config.metadata.insert(
"user_prompt_directory".to_string(),
user_prompt_directory.to_string(),
);
}
let toml_content = match toml::to_string_pretty(&agent_config) {
Ok(content) => content,
Err(e) => {
result.errors.push(format!(
"Failed to serialize agent config for {}: {}",
agent.id, e
));
continue;
}
};
match std::fs::write(&config_path, toml_content) {
Ok(_) => {
result.created.push(agent.id.to_string());
}
Err(e) => {
result.errors.push(format!(
"Failed to write agent config {}: {}",
config_path.display(),
e,
));
}
}
}
Ok(result)
}
pub fn initialize_all(force: bool) -> Result<InitSummary, ApiError> {
XdgAgentStorage::new().agents_dir()?;
xdg::providers_dir()?;
xdg::prompts_dir()?;
let prompts_result = initialize_prompts(force)?;
let agents_result = initialize_agents(force)?;
let validation = validate_initialization()?;
Ok(InitSummary {
prompts: prompts_result,
agents: agents_result,
validation,
})
}
pub fn list_initialization() -> Result<InitPreview, ApiError> {
let prompts_dir = xdg::prompts_dir()?;
let agents_dir = XdgAgentStorage::new().agents_dir()?;
let mut prompts = Vec::new();
let mut agents = Vec::new();
for (filename, _) in DEFAULT_PROMPTS {
let prompt_path = prompts_dir.join(filename);
if !prompt_path.exists() {
prompts.push(filename.to_string());
}
}
for agent in DEFAULT_AGENTS {
let config_path = agents_dir.join(format!("{}.toml", agent.id));
if !config_path.exists() {
agents.push(agent.id.to_string());
}
}
Ok(InitPreview { prompts, agents })
}
pub fn validate_initialization() -> Result<ValidationSummary, ApiError> {
let mut registry = AgentRegistry::new();
registry.load_from_xdg()?;
let agent_ids = ["reader", "code-analyzer", "docs-writer"];
let mut results = Vec::new();
for agent_id in &agent_ids {
match registry.validate_agent(agent_id) {
Ok(validation_result) => {
let is_valid = validation_result.is_valid();
let errors = validation_result.errors.clone();
results.push((agent_id.to_string(), is_valid, errors));
}
Err(e) => {
results.push((
agent_id.to_string(),
false,
vec![format!("Validation error: {}", e)],
));
}
}
}
Ok(ValidationSummary { results })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prompts_embedded() {
assert!(!DEFAULT_PROMPTS.is_empty());
for (filename, content) in DEFAULT_PROMPTS {
assert!(!filename.is_empty());
assert!(!content.is_empty());
}
}
#[test]
fn test_prompt_content_valid() {
for (_, content) in DEFAULT_PROMPTS {
let _ = content.to_string();
}
}
#[test]
fn test_agent_configs_valid() {
for agent in DEFAULT_AGENTS {
let mut agent_config = AgentConfig {
agent_id: agent.id.to_string(),
role: agent.role,
system_prompt: None,
system_prompt_path: agent.prompt_file.map(|s| s.to_string()),
metadata: Default::default(),
};
if let Some(user_prompt_file) = agent.user_prompt_file {
agent_config
.metadata
.insert("user_prompt_file".to_string(), user_prompt_file.to_string());
}
if let Some(user_prompt_directory) = agent.user_prompt_directory {
agent_config.metadata.insert(
"user_prompt_directory".to_string(),
user_prompt_directory.to_string(),
);
}
let toml_content = toml::to_string_pretty(&agent_config);
assert!(
toml_content.is_ok(),
"Failed to serialize agent config for {}",
agent.id
);
}
}
}