harn-vm 0.8.24

Async bytecode virtual machine for the Harn programming language
Documentation
use std::rc::Rc;

use crate::stdlib::json_to_vm_value;
use crate::stdlib::registration::SyncBuiltin;
use crate::value::{VmError, VmValue};
use crate::vm::VmBuiltinArity;

use super::{helpers, trace};

pub(super) const LLM_TRACE_SYNC_PRIMITIVES: &[SyncBuiltin] = &[
    SyncBuiltin::new("agent_trace", agent_trace_builtin)
        .signature("agent_trace()")
        .arity(VmBuiltinArity::Exact(0))
        .doc("Return captured agent trace events for the current process."),
    SyncBuiltin::new("agent_trace_summary", agent_trace_summary_builtin)
        .signature("agent_trace_summary()")
        .arity(VmBuiltinArity::Exact(0))
        .doc("Return a summarized view of captured agent trace events."),
    SyncBuiltin::new(
        "__host_typed_checkpoint_trace",
        host_typed_checkpoint_trace_builtin,
    )
    .signature("__host_typed_checkpoint_trace(checkpoint)")
    .arity(VmBuiltinArity::Exact(1))
    .doc("Record a typed-output checkpoint event in the current agent trace."),
];

fn agent_trace_builtin(_args: &[VmValue], _out: &mut String) -> Result<VmValue, VmError> {
    let events = trace::peek_agent_trace();
    let list: Vec<VmValue> = events
        .iter()
        .filter_map(|e| serde_json::to_value(e).ok())
        .map(|v| json_to_vm_value(&v))
        .collect();
    Ok(VmValue::List(Rc::new(list)))
}

fn agent_trace_summary_builtin(_args: &[VmValue], _out: &mut String) -> Result<VmValue, VmError> {
    let summary = trace::agent_trace_summary();
    Ok(json_to_vm_value(&summary))
}

fn host_typed_checkpoint_trace_builtin(
    args: &[VmValue],
    _out: &mut String,
) -> Result<VmValue, VmError> {
    let checkpoint = args.first().cloned().unwrap_or(VmValue::Nil);
    let checkpoint_json = helpers::vm_value_to_json(&checkpoint);
    let object = checkpoint_json.as_object();
    let string_field = |key: &str| -> String {
        object
            .and_then(|obj| obj.get(key))
            .and_then(|value| value.as_str())
            .unwrap_or("")
            .to_string()
    };
    let opt_string_field = |key: &str| -> Option<String> {
        object
            .and_then(|obj| obj.get(key))
            .and_then(|value| value.as_str())
            .map(str::to_string)
            .filter(|value| !value.is_empty())
    };
    let usize_field = |key: &str| -> usize {
        object
            .and_then(|obj| obj.get(key))
            .and_then(|value| value.as_u64())
            .unwrap_or(0) as usize
    };
    let bool_field = |key: &str| -> bool {
        object
            .and_then(|obj| obj.get(key))
            .and_then(|value| value.as_bool())
            .unwrap_or(false)
    };
    let list_strings = |key: &str| -> Vec<String> {
        object
            .and_then(|obj| obj.get(key))
            .and_then(|value| value.as_array())
            .map(|items| {
                items
                    .iter()
                    .map(|item| {
                        item.as_str()
                            .map(str::to_string)
                            .unwrap_or_else(|| item.to_string())
                    })
                    .collect()
            })
            .unwrap_or_default()
    };
    let mut errors = list_strings("validation_errors");
    if errors.is_empty() {
        errors = list_strings("validator_failures");
    }
    if errors.is_empty() {
        errors = list_strings("schema_failures");
    }
    if errors.is_empty() {
        errors = list_strings("parse_failures");
    }

    trace::emit_agent_event(trace::AgentTraceEvent::TypedCheckpoint {
        name: string_field("name"),
        status: string_field("status"),
        checkpoint_attempts: usize_field("checkpoint_attempts"),
        llm_attempts: usize_field("attempts"),
        error_category: opt_string_field("error_category"),
        errors,
        repaired: bool_field("repaired"),
        final_accepted: bool_field("final_accepted"),
        raw_text: string_field("raw_text"),
    });
    Ok(VmValue::Nil)
}