harn-vm 0.10.180

Async bytecode virtual machine for the Harn programming language
use crate::orchestration::{CrystallizationTrace, CrystallizeOptions};
use crate::value::VmError;

pub(super) fn parse_skill_induce_traces(
    payload: &serde_json::Map<String, serde_json::Value>,
) -> Result<Vec<CrystallizationTrace>, VmError> {
    let value = payload
        .get("traces")
        .or_else(|| payload.get("trajectory"))
        .or_else(|| payload.get("trace"))
        .ok_or_else(|| {
            VmError::Runtime(
                "skill_induce: payload requires `traces`, `trajectory`, or `trace`".to_string(),
            )
        })?;
    let traces = match value {
        serde_json::Value::Array(_) => {
            serde_json::from_value::<Vec<CrystallizationTrace>>(value.clone())
        }
        _ => serde_json::from_value::<CrystallizationTrace>(value.clone()).map(|trace| vec![trace]),
    }
    .map_err(|error| VmError::Runtime(format!("skill_induce: invalid trace payload: {error}")))?;
    if traces.is_empty() {
        return Err(VmError::Runtime(
            "skill_induce: at least one trace is required".to_string(),
        ));
    }
    Ok(traces)
}

pub(super) fn parse_skill_induce_options(
    payload: &serde_json::Map<String, serde_json::Value>,
) -> Result<CrystallizeOptions, VmError> {
    let mut options = CrystallizeOptions::default();
    let empty = serde_json::Map::new();
    let opts = payload
        .get("options")
        .and_then(serde_json::Value::as_object)
        .unwrap_or(&empty);
    options.min_examples = json_usize(
        payload
            .get("min_examples")
            .or_else(|| opts.get("min_examples")),
    )
    .unwrap_or_default();
    options.promotion_min_confidence = json_f64(
        payload
            .get("promotion_min_confidence")
            .or_else(|| opts.get("promotion_min_confidence")),
    )
    .unwrap_or_default();
    options.workflow_name = json_string(
        payload
            .get("workflow_name")
            .or_else(|| opts.get("workflow_name")),
    );
    options.package_name = json_string(
        payload
            .get("package_name")
            .or_else(|| opts.get("package_name")),
    );
    options.author = json_string(payload.get("author").or_else(|| opts.get("author")));
    options.approver = json_string(payload.get("approver").or_else(|| opts.get("approver")));
    options.eval_pack_link = json_string(
        payload
            .get("eval_pack_link")
            .or_else(|| opts.get("eval_pack_link")),
    );
    let heldout = payload
        .get("heldout_traces")
        .or_else(|| payload.get("shadow_traces"))
        .or_else(|| opts.get("heldout_traces"))
        .or_else(|| opts.get("shadow_traces"));
    if let Some(value) = heldout {
        options.shadow_traces = serde_json::from_value::<Vec<CrystallizationTrace>>(value.clone())
            .map_err(|error| {
                VmError::Runtime(format!("skill_induce: invalid heldout_traces: {error}"))
            })?;
    }
    Ok(options)
}

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

fn json_usize(value: Option<&serde_json::Value>) -> Option<usize> {
    value
        .and_then(serde_json::Value::as_u64)
        .map(|value| value as usize)
}

fn json_f64(value: Option<&serde_json::Value>) -> Option<f64> {
    value.and_then(serde_json::Value::as_f64)
}