prompty 2.0.0-alpha.9

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::binding::Binding;

use super::mcp_approval_mode::McpApprovalMode;

/// Variant-specific data for [`Tool`], discriminated by `kind`.
#[derive(Debug, Clone)]
pub enum ToolKind {
    /// `kind` = `"function"`
    Function {
        /// Parameters accepted by the function tool
        parameters: serde_json::Value,
        /// Indicates whether the function tool enforces strict validation on its parameters
        strict: Option<bool>,
    },
    /// `kind` = `"mcp"`
    Mcp {
        /// The connection configuration for the MCP tool
        connection: serde_json::Value,
        /// The server name of the MCP tool
        server_name: String,
        /// The description of the MCP tool
        server_description: Option<String>,
        /// The approval mode for the MCP tool
        approval_mode: McpApprovalMode,
        /// List of allowed operations or resources for the MCP tool
        allowed_tools: Option<Vec<String>>,
    },
    /// `kind` = `"openapi"`
    OpenApi {
        /// The connection configuration for the OpenAPI tool
        connection: serde_json::Value,
        /// The full OpenAPI specification
        specification: String,
    },
    /// `kind` = `"prompty"`
    Prompty {
        /// Path to the child .prompty file, relative to the parent
        path: String,
        /// Execution mode: 'single' for one LLM call, 'agentic' for full agent loop
        mode: String,
    },
    /// Wildcard / catch-all variant for unrecognized `kind` values.
    Custom {
        /// Connection configuration for the server tool
        connection: serde_json::Value,
        /// Configuration options for the server tool
        options: serde_json::Value,
        /// The raw `kind` string for this unknown variant.
        kind_name: String,
    },
}

impl Default for ToolKind {
    fn default() -> Self {
        ToolKind::Custom {
            connection: serde_json::Value::Null,
            options: serde_json::Value::Null,
            kind_name: String::new(),
        }
    }
}
/// Represents a tool that can be used in prompts.
#[derive(Debug, Clone, Default)]
pub struct Tool {
    /// Name of the tool. If a function tool, this is the function name, otherwise it is the type
    pub name: String,
    /// A short description of the tool for metadata purposes
    pub description: Option<String>,
    /// Tool argument bindings to input properties
    pub bindings: serde_json::Value,
    /// Variant-specific data, discriminated by `kind`.
    pub kind: ToolKind,
}

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

    /// Load Tool 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 Tool 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 Tool 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());
        let kind_str = value.get("kind").and_then(|v| v.as_str()).unwrap_or("");
        let kind = match kind_str {
            "function" => ToolKind::Function {
                parameters: value
                    .get("parameters")
                    .cloned()
                    .unwrap_or(serde_json::Value::Null),
                strict: value.get("strict").and_then(|v| v.as_bool()),
            },
            "mcp" => ToolKind::Mcp {
                connection: value
                    .get("connection")
                    .cloned()
                    .unwrap_or(serde_json::Value::Null),
                server_name: value
                    .get("serverName")
                    .and_then(|v| v.as_str())
                    .unwrap_or_default()
                    .to_string(),
                server_description: value
                    .get("serverDescription")
                    .and_then(|v| v.as_str())
                    .map(|s| s.to_string()),
                approval_mode: value
                    .get("approvalMode")
                    .filter(|v| v.is_object() || v.is_array() || v.is_string())
                    .map(|v| McpApprovalMode::load_from_value(v, ctx))
                    .unwrap_or_default(),
                allowed_tools: value
                    .get("allowedTools")
                    .and_then(|v| v.as_array())
                    .map(|arr| {
                        arr.iter()
                            .filter_map(|v| v.as_str().map(|s| s.to_string()))
                            .collect()
                    }),
            },
            "openapi" => ToolKind::OpenApi {
                connection: value
                    .get("connection")
                    .cloned()
                    .unwrap_or(serde_json::Value::Null),
                specification: value
                    .get("specification")
                    .and_then(|v| v.as_str())
                    .unwrap_or_default()
                    .to_string(),
            },
            "prompty" => ToolKind::Prompty {
                path: value
                    .get("path")
                    .and_then(|v| v.as_str())
                    .unwrap_or_default()
                    .to_string(),
                mode: value
                    .get("mode")
                    .and_then(|v| v.as_str())
                    .unwrap_or_default()
                    .to_string(),
            },
            _ => ToolKind::Custom {
                connection: value
                    .get("connection")
                    .cloned()
                    .unwrap_or(serde_json::Value::Null),
                options: value
                    .get("options")
                    .cloned()
                    .unwrap_or(serde_json::Value::Null),
                kind_name: kind_str.to_string(),
            },
        };
        Self {
            name: value
                .get("name")
                .and_then(|v| v.as_str())
                .unwrap_or_default()
                .to_string(),
            description: value
                .get("description")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string()),
            bindings: value
                .get("bindings")
                .cloned()
                .unwrap_or(serde_json::Value::Null),
            kind,
        }
    }

    /// Returns the `kind` discriminator string for this instance.
    pub fn kind_str(&self) -> &str {
        match &self.kind {
            ToolKind::Function { .. } => "function",
            ToolKind::Mcp { .. } => "mcp",
            ToolKind::OpenApi { .. } => "openapi",
            ToolKind::Prompty { .. } => "prompty",
            ToolKind::Custom { kind_name, .. } => kind_name.as_str(),
        }
    }

    /// Serialize Tool 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();
        // Write the discriminator
        result.insert(
            "kind".to_string(),
            serde_json::Value::String(self.kind_str().to_string()),
        );
        // Write base fields
        if !self.name.is_empty() {
            result.insert(
                "name".to_string(),
                serde_json::Value::String(self.name.clone()),
            );
        }
        if let Some(ref val) = self.description {
            result.insert(
                "description".to_string(),
                serde_json::Value::String(val.clone()),
            );
        }
        if !self.bindings.is_null() {
            result.insert("bindings".to_string(), self.bindings.clone());
        }
        // Write variant-specific fields
        match &self.kind {
            ToolKind::Function {
                parameters, strict, ..
            } => {
                if !parameters.is_null() {
                    result.insert("parameters".to_string(), parameters.clone());
                }
                if let Some(val) = strict {
                    result.insert("strict".to_string(), serde_json::Value::Bool(*val));
                }
            }
            ToolKind::Mcp {
                connection,
                server_name,
                server_description,
                approval_mode,
                allowed_tools,
                ..
            } => {
                if !connection.is_null() {
                    result.insert("connection".to_string(), connection.clone());
                }
                if !server_name.is_empty() {
                    result.insert(
                        "serverName".to_string(),
                        serde_json::Value::String(server_name.clone()),
                    );
                }
                if let Some(val) = server_description {
                    result.insert(
                        "serverDescription".to_string(),
                        serde_json::Value::String(val.clone()),
                    );
                }
                {
                    let nested = approval_mode.to_value(ctx);
                    if !nested.is_null() {
                        result.insert("approvalMode".to_string(), nested);
                    }
                }
                if let Some(items) = allowed_tools {
                    result.insert(
                        "allowedTools".to_string(),
                        serde_json::to_value(items).unwrap_or(serde_json::Value::Null),
                    );
                }
            }
            ToolKind::OpenApi {
                connection,
                specification,
                ..
            } => {
                if !connection.is_null() {
                    result.insert("connection".to_string(), connection.clone());
                }
                if !specification.is_empty() {
                    result.insert(
                        "specification".to_string(),
                        serde_json::Value::String(specification.clone()),
                    );
                }
            }
            ToolKind::Prompty { path, mode, .. } => {
                if !path.is_empty() {
                    result.insert("path".to_string(), serde_json::Value::String(path.clone()));
                }
                if !mode.is_empty() {
                    result.insert("mode".to_string(), serde_json::Value::String(mode.clone()));
                }
            }
            ToolKind::Custom {
                connection,
                options,
                kind_name: _,
                ..
            } => {
                if !connection.is_null() {
                    result.insert("connection".to_string(), connection.clone());
                }
                if !options.is_null() {
                    result.insert("options".to_string(), options.clone());
                }
            }
        }
        ctx.process_dict(serde_json::Value::Object(result))
    }

    /// Serialize Tool 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 Tool 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 `Vec<Binding>` 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_bindings(&self) -> Option<Vec<Binding>> {
        match &self.bindings {
            serde_json::Value::Array(arr) => Some(
                arr.iter()
                    .map(|v| Binding::load_from_value(v, &LoadContext::default()))
                    .collect(),
            ),
            serde_json::Value::Object(obj) => {
                let result: Vec<Binding> = obj
                    .iter()
                    .filter_map(|(name, value)| {
                        if value.is_array() {
                            // Invalid format: skip entries with array values.
                            // 'bindings' 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(Binding::load_from_value(&v, &LoadContext::default()))
                    })
                    .collect();
                Some(result)
            }
            _ => None,
        }
    }
}