harn-cli 0.10.146

CLI for the Harn programming language — run, test, REPL, format, and lint
//! Argument and rendering projection of the VM-owned decision evaluator.
use crate::cli::LlmEvaluateArgs;
use harn_vm::llm::decision::identity::{verify_receipt, EvaluationRequest as Request};
use harn_vm::llm::decision::receipt::EvaluationReceipt;
use serde_json::{json, Value};
use std::path::Path;

fn read(path: &Path) -> Result<String, String> {
    std::fs::read_to_string(path)
        .map_err(|error| format!("cannot read {}: {error}", path.display()))
}

fn read_json(path: &Path) -> Result<Value, String> {
    serde_json::from_str(&read(path)?)
        .map_err(|error| format!("invalid JSON in {}: {error}", path.display()))
}

fn request(args: &LlmEvaluateArgs) -> Result<Request, String> {
    if let Some(path) = &args.request {
        return serde_json::from_value(read_json(path)?).map_err(|error| error.to_string());
    }
    let model = args.model.as_deref().ok_or("--model is required")?;
    let resolved = harn_vm::llm_config::resolve_model_info(model);
    let state = read(
        args.state_file
            .as_deref()
            .ok_or("--state-file is required")?,
    )?;
    let policy = if let Some(path) = &args.policy {
        let policy = read_json(path)?;
        if policy.get("model").and_then(Value::as_str) != Some(model) {
            return Err("--policy model must match --model".into());
        }
        policy
    } else {
        json!({"backend":"native_decision", "provider":resolved.provider, "model":model,
            "threshold":0.5, "evaluation_cost_limit":0.01, "run_cost_limit":0.01})
    };
    Ok(Request {
        site_id: args.site_id.clone(),
        state: serde_json::from_str(&state).unwrap_or(Value::String(state)),
        questions: read_json(args.questions.as_deref().ok_or("--questions is required")?)?,
        policy,
    })
}

pub(crate) async fn run(args: LlmEvaluateArgs) -> i32 {
    let request = match request(&args) {
        Ok(request) => request,
        Err(error) => {
            eprintln!("error: {error}");
            return 2;
        }
    };
    if let Some(path) = &args.verify_receipt {
        return verify(&request, path, args.json);
    }
    let mut vm = harn_vm::Vm::new();
    let tape_session = match (super::evaluation_tape::EvaluationReplayOptions {
        tape: args.tape.clone(),
        cache: false,
    })
    .install(&mut vm)
    {
        Ok(session) => session,
        Err(error) => {
            eprintln!("error: {error}");
            return 2;
        }
    };
    let evaluation = harn_vm::orchestration::scope_fresh_run_runtime(vm.evaluate_decision(
        &request.site_id,
        request.state,
        request.questions,
        request.policy,
    ));
    let result = evaluation.await;
    if let Some(session) = tape_session {
        if let Err(error) = session.finish(result.is_ok()) {
            eprintln!("error: {error}");
            return 2;
        }
    }
    match result {
        Ok(result) => {
            if args.json {
                println!(
                    "{}",
                    serde_json::to_string(&result).expect("evaluation result is serializable")
                );
            } else {
                println!("{}", result.outcome);
                println!(
                    "model={} requests={} input_tokens={} output_tokens={} cost_usd={} elapsed_ms={} receipt={}",
                    result.receipt.served_model.as_deref().unwrap_or("unknown"),
                    result.receipt.physical_attempts,
                    result.receipt.input_tokens.map_or_else(|| "unknown".into(), |value| value.to_string()),
                    result.receipt.output_tokens.map_or_else(|| "unknown".into(), |value| value.to_string()),
                    result
                        .receipt
                        .cost_usd
                        .map_or_else(|| "unknown".into(), |value| value.to_string()),
                    result.receipt.elapsed_ms,
                    result.receipt.reference()
                );
            }
            0
        }
        Err(error) => {
            eprintln!("error: {error}");
            2
        }
    }
}

fn verify(request: &Request, path: &Path, json: bool) -> i32 {
    let result = read_json(path)
        .and_then(|value| {
            serde_json::from_value::<EvaluationReceipt>(value)
                .map_err(|error| format!("invalid evaluation receipt: {error}"))
        })
        .and_then(|receipt| verify_receipt(request, &receipt).map_err(|error| error.to_string()));
    match result {
        Ok(report) => {
            if json {
                println!(
                    "{}",
                    serde_json::to_string(&report).expect("verification serializes")
                );
            } else if report.verified {
                println!("Request identity verified.");
            } else {
                println!(
                    "Request identity refused: {}",
                    serde_json::to_string(&report.refusals)
                        .expect("verification refusals serialize")
                );
            }
            i32::from(!report.verified)
        }
        Err(error) => {
            eprintln!("error: {error}");
            2
        }
    }
}