vv-agent 0.6.2

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use std::collections::BTreeMap;

use serde_json::Value;

use crate::skills::errors::SkillValidationError;
use crate::skills::models::SkillProperties;

use super::value::value_to_string;

pub(crate) fn build_properties(
    metadata: &BTreeMap<String, Value>,
) -> Result<SkillProperties, SkillValidationError> {
    let name = required_string(metadata, "name")?;
    let description = required_string(metadata, "description")?;
    Ok(SkillProperties {
        name,
        description,
        license: optional_string(metadata.get("license")),
        allowed_tools: optional_string(metadata.get("allowed-tools")),
        metadata: metadata
            .get("metadata")
            .and_then(Value::as_object)
            .map(string_map_from_json_object)
            .unwrap_or_default(),
    })
}

fn required_string(
    metadata: &BTreeMap<String, Value>,
    key: &str,
) -> Result<String, SkillValidationError> {
    let Some(value) = metadata.get(key).and_then(Value::as_str).map(str::trim) else {
        return Err(SkillValidationError::new(format!(
            "Field '{key}' must be a non-empty string"
        )));
    };
    if value.is_empty() {
        return Err(SkillValidationError::new(format!(
            "Field '{key}' must be a non-empty string"
        )));
    }
    Ok(value.to_string())
}

fn optional_string(value: Option<&Value>) -> Option<String> {
    value
        .and_then(Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_string)
}

fn string_map_from_json_object(
    object: &serde_json::Map<String, Value>,
) -> BTreeMap<String, String> {
    object
        .iter()
        .map(|(key, value)| (key.clone(), value_to_string(value)))
        .collect()
}