harn-vm 0.7.30

Async bytecode virtual machine for the Harn programming language
Documentation
use crate::value::VmValue;

/// Convert a VmValue returned by a prompt handler into MCP messages.
pub(super) fn prompt_value_to_messages(value: &VmValue) -> Vec<serde_json::Value> {
    match value {
        VmValue::String(s) => {
            vec![serde_json::json!({
                "role": "user",
                "content": { "type": "text", "text": &**s }
            })]
        }
        VmValue::List(items) => items
            .iter()
            .map(|item| {
                if let VmValue::Dict(d) = item {
                    let role = d
                        .get("role")
                        .map(|v| v.display())
                        .unwrap_or_else(|| "user".into());
                    let content = d.get("content").map(|v| v.display()).unwrap_or_default();
                    serde_json::json!({
                        "role": role,
                        "content": { "type": "text", "text": content }
                    })
                } else {
                    serde_json::json!({
                        "role": "user",
                        "content": { "type": "text", "text": item.display() }
                    })
                }
            })
            .collect(),
        _ => {
            vec![serde_json::json!({
                "role": "user",
                "content": { "type": "text", "text": value.display() }
            })]
        }
    }
}

/// Convert a tool result VmValue into MCP content items.
///
/// Supports text, embedded resource, and resource_link content types.
/// If the value is a list of dicts with a `type` field, each is treated as a
/// content item. Otherwise, the whole value is serialized as a single text item.
pub(super) fn vm_value_to_content(value: &VmValue) -> Vec<serde_json::Value> {
    if let VmValue::List(items) = value {
        let mut content = Vec::new();
        for item in items.iter() {
            if let VmValue::Dict(d) = item {
                let item_type = d.get("type").map(|v| v.display()).unwrap_or_default();
                match item_type.as_str() {
                    "resource" => {
                        let mut entry = serde_json::json!({ "type": "resource" });
                        if let Some(resource) = d.get("resource") {
                            entry["resource"] = vm_value_to_json(resource);
                        }
                        content.push(entry);
                    }
                    "resource_link" => {
                        let mut entry = serde_json::json!({ "type": "resource_link" });
                        if let Some(uri) = d.get("uri") {
                            entry["uri"] = serde_json::json!(uri.display());
                        }
                        if let Some(name) = d.get("name") {
                            entry["name"] = serde_json::json!(name.display());
                        }
                        if let Some(desc) = d.get("description") {
                            entry["description"] = serde_json::json!(desc.display());
                        }
                        if let Some(mime) = d.get("mimeType") {
                            entry["mimeType"] = serde_json::json!(mime.display());
                        }
                        content.push(entry);
                    }
                    _ => {
                        let text = d
                            .get("text")
                            .map(|v| v.display())
                            .unwrap_or_else(|| item.display());
                        content.push(serde_json::json!({ "type": "text", "text": text }));
                    }
                }
            } else {
                content.push(serde_json::json!({ "type": "text", "text": item.display() }));
            }
        }
        if content.is_empty() {
            vec![serde_json::json!({ "type": "text", "text": value.display() })]
        } else {
            content
        }
    } else {
        vec![serde_json::json!({ "type": "text", "text": value.display() })]
    }
}

/// Convert a VmValue to a serde_json::Value.
pub(super) fn vm_value_to_json(value: &VmValue) -> serde_json::Value {
    match value {
        VmValue::Nil => serde_json::Value::Null,
        VmValue::Bool(b) => serde_json::json!(b),
        VmValue::Int(n) => serde_json::json!(n),
        VmValue::Float(f) => serde_json::json!(f),
        VmValue::String(s) => serde_json::json!(&**s),
        VmValue::List(items) => {
            serde_json::Value::Array(items.iter().map(vm_value_to_json).collect())
        }
        VmValue::Dict(d) => {
            let mut map = serde_json::Map::new();
            for (k, v) in d.iter() {
                map.insert(k.clone(), vm_value_to_json(v));
            }
            serde_json::Value::Object(map)
        }
        VmValue::StructInstance { .. } => {
            let mut map = serde_json::Map::new();
            for (k, v) in value.struct_fields_map().unwrap_or_default().iter() {
                map.insert(k.clone(), vm_value_to_json(v));
            }
            serde_json::Value::Object(map)
        }
        _ => serde_json::json!(value.display()),
    }
}

/// Convert a VmValue annotations dict to a serde_json::Value with only the
/// recognized MCP annotation fields.
pub(super) fn annotations_to_json(annotations: &VmValue) -> Option<serde_json::Value> {
    let dict = match annotations {
        VmValue::Dict(d) => d,
        _ => return None,
    };

    let mut out = serde_json::Map::new();
    let str_keys = ["title"];
    let bool_keys = [
        "readOnlyHint",
        "destructiveHint",
        "idempotentHint",
        "openWorldHint",
    ];

    for key in str_keys {
        if let Some(VmValue::String(s)) = dict.get(key) {
            out.insert(key.into(), serde_json::json!(&**s));
        }
    }
    for key in bool_keys {
        if let Some(VmValue::Bool(b)) = dict.get(key) {
            out.insert(key.into(), serde_json::json!(b));
        }
    }

    if out.is_empty() {
        None
    } else {
        Some(serde_json::Value::Object(out))
    }
}