vv-agent 0.8.0

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use std::time::{SystemTime, UNIX_EPOCH};

use serde_json::Value;

use crate::budget::MAX_WIRE_INTEGER;
use crate::checkpoint::RUN_DEFINITION_SCHEMA;

pub(crate) fn validate_current_discriminator_fields(payload: &Value) -> Result<(), String> {
    if payload.get("run_definition_schema").and_then(Value::as_str) != Some(RUN_DEFINITION_SCHEMA) {
        return Err("checkpoint_definition_schema_unsupported".to_string());
    }
    if payload.get("checkpoint_config").is_none_or(Value::is_null) {
        return Err("distributed run requires checkpoint_config".to_string());
    }
    if !matches!(
        payload.get("claim_mode").and_then(Value::as_str),
        Some("continue" | "recovery")
    ) {
        return Err("checkpoint_claim_mode_invalid".to_string());
    }
    Ok(())
}

pub(crate) fn normalize_integral_float(object: &mut serde_json::Map<String, Value>, field: &str) {
    let Some(value) = object.get(field).and_then(Value::as_f64) else {
        return;
    };
    if value.is_finite() && value >= 0.0 && value.fract() == 0.0 && value <= MAX_WIRE_INTEGER as f64
    {
        object.insert(field.to_string(), Value::from(value as u64));
    }
}

pub(crate) fn now_unix_ms() -> Result<u64, String> {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|error| error.to_string())?
        .as_millis()
        .try_into()
        .map_err(|_| "system clock milliseconds exceed u64".to_string())
}

pub(crate) fn require_non_empty(value: &str, field_name: &str) -> Result<(), String> {
    if value.trim().is_empty() {
        Err(format!("{field_name} must be a non-empty string"))
    } else {
        Ok(())
    }
}

pub(crate) fn validate_sorted_unique(
    values: &[String],
    field_name: &str,
    validate: impl Fn(&str) -> Result<(), String>,
) -> Result<(), String> {
    for value in values {
        validate(value)?;
    }
    if values.windows(2).any(|window| window[0] >= window[1]) {
        return Err(format!("{field_name} must be sorted and unique"));
    }
    Ok(())
}

pub(crate) fn validate_json_pointer(pointer: &str) -> Result<(), String> {
    if pointer.is_empty() {
        return Ok(());
    }
    if !pointer.starts_with('/') {
        return Err("credential_slots must contain RFC 6901 JSON pointers".to_string());
    }
    let bytes = pointer.as_bytes();
    let mut index = 0;
    while index < bytes.len() {
        if bytes[index] == b'~' {
            let Some(next) = bytes.get(index + 1) else {
                return Err("credential_slots must contain RFC 6901 JSON pointers".to_string());
            };
            if !matches!(*next, b'0' | b'1') {
                return Err("credential_slots must contain RFC 6901 JSON pointers".to_string());
            }
            index += 1;
        }
        index += 1;
    }
    Ok(())
}

pub(crate) fn utf16_cmp(left: &str, right: &str) -> std::cmp::Ordering {
    left.encode_utf16().cmp(right.encode_utf16())
}