systemprompt-agent 0.64.0

Agent-to-Agent (A2A) protocol for systemprompt.io AI governance: streaming, JSON-RPC models, task lifecycle, .well-known discovery, and governed agent orchestration.
Documentation
//! Filesystem skill authoring helpers and event delivery.
//!
//! Copyright (c) systemprompt.io — Business Source License 1.1.
//! See <https://systemprompt.io> for licensing details.

use crate::services::shared::{AgentServiceError, Result};
use std::path::{Path, PathBuf};
use systemprompt_config::ProfileBootstrap;
use systemprompt_identifiers::SkillId;
use systemprompt_loader::ServicesRootBootstrap;
use systemprompt_manifest::{DiskSkillConfig, SKILL_CONFIG_FILENAME, strip_frontmatter};

pub(super) struct LoadedDiskSkill {
    pub(super) skill_id: SkillId,
    pub(super) name: String,
    pub(super) description: String,
    pub(super) instructions: String,
}

pub(super) fn resolve_skills_root() -> Result<PathBuf> {
    let profile = ProfileBootstrap::get()
        .map_err(|e| AgentServiceError::operation("Profile not initialized for SkillService", e))?;
    Ok(ServicesRootBootstrap::active_path_or(
        &profile.paths.services,
        "skills",
    ))
}

pub(super) fn load_disk_skill(skills_root: &Path, skill_id: &SkillId) -> Result<LoadedDiskSkill> {
    let id_str = skill_id.as_str();
    let skill_dir = skills_root.join(id_str);
    let config_path = skill_dir.join(SKILL_CONFIG_FILENAME);

    if !config_path.exists() {
        return Err(AgentServiceError::SkillNotOnDisk {
            skill_id: skill_id.clone(),
            path: config_path,
        });
    }

    let config_text = std::fs::read_to_string(&config_path).map_err(|e| {
        AgentServiceError::operation(format!("Failed to read {}", config_path.display()), e)
    })?;
    let config: DiskSkillConfig = serde_yaml::from_str(&config_text).map_err(|e| {
        AgentServiceError::operation(format!("Invalid YAML in {}", config_path.display()), e)
    })?;

    let resolved_id = config.id.clone().unwrap_or_else(|| skill_id.clone());

    let content_path = skill_dir.join(config.content_file());
    let instructions = if content_path.exists() {
        let raw = std::fs::read_to_string(&content_path).map_err(|e| {
            AgentServiceError::operation(format!("Failed to read {}", content_path.display()), e)
        })?;
        strip_frontmatter(&raw)
    } else {
        String::new()
    };

    let name = if config.name.is_empty() {
        id_str.to_owned()
    } else {
        config.name
    };

    Ok(LoadedDiskSkill {
        skill_id: resolved_id,
        name,
        description: config.description,
        instructions,
    })
}