prompty 2.0.0-alpha.8

Prompty is an asset class and format for LLM prompts
Documentation
// Code generated by AgentSchema emitter; DO NOT EDIT.

use super::context::{LoadContext, SaveContext};

use super::model::Model;

use super::property::Property;

use super::template::Template;

use super::tool::Tool;

/// A Prompty is a markdown file format for LLM prompts. The frontmatter defines structured metadata including model configuration, input/output schemas, tools, and template settings. The markdown body becomes the instructions.  This is the single root type for the Prompty schema — there is no abstract base class or kind discriminator. A .prompty file always produces a Prompty instance.
#[derive(Debug, Clone, Default)]
pub struct Prompty {
    /// Human-readable name of the prompt
    pub name: String,
    /// Display name for UI purposes
    pub display_name: Option<String>,
    /// Description of the prompt's purpose
    pub description: Option<String>,
    /// Additional metadata including authors, tags, and other arbitrary properties
    pub metadata: serde_json::Value,
    /// Input parameters that participate in template rendering
    pub inputs: serde_json::Value,
    /// Expected output format and structure
    pub outputs: serde_json::Value,
    /// AI model configuration
    pub model: Model,
    /// Tools available for extended functionality
    pub tools: serde_json::Value,
    /// Template configuration for prompt rendering
    pub template: Option<Template>,
    /// Clear directions on what the prompt should do. In .prompty files, this comes from the markdown body.
    pub instructions: Option<String>,
}

impl Prompty {
    /// Create a new Prompty with default values.
    pub fn new() -> Self {
        Self::default()
    }

    /// Load Prompty from a JSON string.
    pub fn from_json(json: &str, ctx: &LoadContext) -> Result<Self, serde_json::Error> {
        let value: serde_json::Value = serde_json::from_str(json)?;
        Ok(Self::load_from_value(&value, ctx))
    }

    /// Load Prompty from a YAML string.
    pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result<Self, serde_yaml::Error> {
        let value: serde_json::Value = serde_yaml::from_str(yaml)?;
        Ok(Self::load_from_value(&value, ctx))
    }

    /// Load Prompty from a `serde_json::Value`.
    ///
    /// Calls `ctx.process_input` before field extraction.
    pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self {
        let value = ctx.process_input(value.clone());
        Self {
            name: value
                .get("name")
                .and_then(|v| v.as_str())
                .unwrap_or_default()
                .to_string(),
            display_name: value
                .get("displayName")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string()),
            description: value
                .get("description")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string()),
            metadata: value
                .get("metadata")
                .cloned()
                .unwrap_or(serde_json::Value::Null),
            inputs: value
                .get("inputs")
                .cloned()
                .unwrap_or(serde_json::Value::Null),
            outputs: value
                .get("outputs")
                .cloned()
                .unwrap_or(serde_json::Value::Null),
            model: value
                .get("model")
                .filter(|v| v.is_object() || v.is_array() || v.is_string())
                .map(|v| Model::load_from_value(v, ctx))
                .unwrap_or_default(),
            tools: value
                .get("tools")
                .cloned()
                .unwrap_or(serde_json::Value::Null),
            template: value
                .get("template")
                .filter(|v| v.is_object() || v.is_array() || v.is_string())
                .map(|v| Template::load_from_value(v, ctx)),
            instructions: value
                .get("instructions")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string()),
        }
    }

    /// Serialize Prompty to a `serde_json::Value`.
    ///
    /// Calls `ctx.process_dict` after serialization.
    pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value {
        let mut result = serde_json::Map::new();
        if !self.name.is_empty() {
            result.insert(
                "name".to_string(),
                serde_json::Value::String(self.name.clone()),
            );
        }
        if let Some(ref val) = self.display_name {
            result.insert(
                "displayName".to_string(),
                serde_json::Value::String(val.clone()),
            );
        }
        if let Some(ref val) = self.description {
            result.insert(
                "description".to_string(),
                serde_json::Value::String(val.clone()),
            );
        }
        if !self.metadata.is_null() {
            result.insert("metadata".to_string(), self.metadata.clone());
        }
        if !self.inputs.is_null() {
            result.insert("inputs".to_string(), self.inputs.clone());
        }
        if !self.outputs.is_null() {
            result.insert("outputs".to_string(), self.outputs.clone());
        }
        {
            let nested = self.model.to_value(ctx);
            if !nested.is_null() {
                result.insert("model".to_string(), nested);
            }
        }
        if !self.tools.is_null() {
            result.insert("tools".to_string(), self.tools.clone());
        }
        if let Some(ref val) = self.template {
            let nested = val.to_value(ctx);
            if !nested.is_null() {
                result.insert("template".to_string(), nested);
            }
        }
        if let Some(ref val) = self.instructions {
            result.insert(
                "instructions".to_string(),
                serde_json::Value::String(val.clone()),
            );
        }
        ctx.process_dict(serde_json::Value::Object(result))
    }

    /// Serialize Prompty to a JSON string.
    pub fn to_json(&self, ctx: &SaveContext) -> Result<String, serde_json::Error> {
        serde_json::to_string_pretty(&self.to_value(ctx))
    }

    /// Serialize Prompty to a YAML string.
    pub fn to_yaml(&self, ctx: &SaveContext) -> Result<String, serde_yaml::Error> {
        serde_yaml::to_string(&self.to_value(ctx))
    }
    /// Returns typed reference to the map if the field is an object.
    /// Returns `None` if the field is null or not an object.
    pub fn as_metadata_dict(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {
        self.metadata.as_object()
    }
    /// Returns typed `Vec<Property>` by parsing the stored JSON value.
    /// Handles both array format `[{...}]` and dict format `{"name": {...}}`.
    /// Returns `None` if the field is null or cannot be parsed.
    pub fn as_inputs(&self) -> Option<Vec<Property>> {
        match &self.inputs {
            serde_json::Value::Array(arr) => Some(
                arr.iter()
                    .map(|v| Property::load_from_value(v, &LoadContext::default()))
                    .collect(),
            ),
            serde_json::Value::Object(obj) => {
                let result: Vec<Property> = obj
                    .iter()
                    .filter_map(|(name, value)| {
                        if value.is_array() {
                            // Invalid format: skip entries with array values.
                            // 'inputs' must be a flat list or name-keyed dict.
                            return None;
                        }
                        let mut v = if value.is_object() {
                            value.clone()
                        } else {
                            serde_json::json!({ "value": value })
                        };
                        if let serde_json::Value::Object(ref mut m) = v {
                            m.entry("name".to_string())
                                .or_insert_with(|| serde_json::Value::String(name.clone()));
                        }
                        Some(Property::load_from_value(&v, &LoadContext::default()))
                    })
                    .collect();
                Some(result)
            }
            _ => None,
        }
    }
    /// Returns typed `Vec<Property>` by parsing the stored JSON value.
    /// Handles both array format `[{...}]` and dict format `{"name": {...}}`.
    /// Returns `None` if the field is null or cannot be parsed.
    pub fn as_outputs(&self) -> Option<Vec<Property>> {
        match &self.outputs {
            serde_json::Value::Array(arr) => Some(
                arr.iter()
                    .map(|v| Property::load_from_value(v, &LoadContext::default()))
                    .collect(),
            ),
            serde_json::Value::Object(obj) => {
                let result: Vec<Property> = obj
                    .iter()
                    .filter_map(|(name, value)| {
                        if value.is_array() {
                            // Invalid format: skip entries with array values.
                            // 'outputs' must be a flat list or name-keyed dict.
                            return None;
                        }
                        let mut v = if value.is_object() {
                            value.clone()
                        } else {
                            serde_json::json!({ "value": value })
                        };
                        if let serde_json::Value::Object(ref mut m) = v {
                            m.entry("name".to_string())
                                .or_insert_with(|| serde_json::Value::String(name.clone()));
                        }
                        Some(Property::load_from_value(&v, &LoadContext::default()))
                    })
                    .collect();
                Some(result)
            }
            _ => None,
        }
    }
    /// Returns typed `Vec<Tool>` by parsing the stored JSON value.
    /// Handles both array format `[{...}]` and dict format `{"name": {...}}`.
    /// Returns `None` if the field is null or cannot be parsed.
    pub fn as_tools(&self) -> Option<Vec<Tool>> {
        match &self.tools {
            serde_json::Value::Array(arr) => Some(
                arr.iter()
                    .map(|v| Tool::load_from_value(v, &LoadContext::default()))
                    .collect(),
            ),
            serde_json::Value::Object(obj) => {
                let result: Vec<Tool> = obj
                    .iter()
                    .filter_map(|(name, value)| {
                        if value.is_array() {
                            // Invalid format: skip entries with array values.
                            // 'tools' must be a flat list or name-keyed dict.
                            return None;
                        }
                        let mut v = if value.is_object() {
                            value.clone()
                        } else {
                            serde_json::json!({ "value": value })
                        };
                        if let serde_json::Value::Object(ref mut m) = v {
                            m.entry("name".to_string())
                                .or_insert_with(|| serde_json::Value::String(name.clone()));
                        }
                        Some(Tool::load_from_value(&v, &LoadContext::default()))
                    })
                    .collect();
                Some(result)
            }
            _ => None,
        }
    }
}