harn-vm 0.10.162

Async bytecode virtual machine for the Harn programming language
Documentation
use super::{
    optional_reminder_spec_bool, optional_reminder_spec_propagate, optional_reminder_spec_string,
    optional_reminder_spec_ttl, reminder_code_error, reminder_error, reminder_fields,
    reminder_spec_tags, required_reminder_spec_string, ReminderSpec,
};
use crate::llm::helpers::{ReminderPropagate, ReminderRoleHint, ReminderSource, SystemReminder};
use crate::value::{VmError, VmValue};
use harn_parser::diagnostic_codes::Code;

pub(super) fn parse_reminder_spec(value: &VmValue, context: &str) -> Result<ReminderSpec, VmError> {
    let Some(options) = value.as_dict() else {
        return Err(reminder_error(
            context,
            format!("reminder spec must be a dict, got {}", value.type_name()),
        ));
    };
    const ALLOWED: &[&str] = &[
        "body",
        "tags",
        "dedupe_key",
        "ttl_turns",
        "preserve_on_compact",
        "goal_pin",
        "propagate",
        "role_hint",
        "authority",
    ];
    let unknown = options
        .keys()
        .filter(|key| !ALLOWED.contains(&key.as_str()))
        .map(|key| key.as_str())
        .collect::<Vec<_>>();
    if !unknown.is_empty() {
        return Err(reminder_code_error(
            context,
            Code::ReminderUnknownOption,
            format!("unknown reminder option(s): {}", unknown.join(", ")),
        ));
    }
    let role_hint = optional_reminder_spec_string(options, "role_hint", context)?;
    let role_hint = reminder_fields::role_hint(role_hint.as_deref())
        .map_err(|message| reminder_error(context, message))?;
    let authority = optional_reminder_spec_string(options, "authority", context)?;
    let authority = reminder_fields::authority(authority.as_deref())
        .map_err(|message| reminder_error(context, message))?;
    Ok(SystemReminder {
        goal_pin: options
            .get("goal_pin")
            .filter(|value| !matches!(value, VmValue::Nil))
            .map(|value| {
                crate::llm::helpers::GoalPinProjection::from_json(crate::llm::vm_value_to_json(
                    value,
                ))
                .map_err(|error| reminder_error(context, error))
            })
            .transpose()?,
        id: uuid::Uuid::now_v7().to_string(),
        tags: reminder_spec_tags(options, context)?,
        dedupe_key: optional_reminder_spec_string(options, "dedupe_key", context)?,
        ttl_turns: optional_reminder_spec_ttl(options, context)?,
        preserve_on_compact: optional_reminder_spec_bool(options, "preserve_on_compact", context)?
            .unwrap_or(false),
        propagate: optional_reminder_spec_propagate(options, context)?
            .unwrap_or(ReminderPropagate::Session),
        role_hint: role_hint.unwrap_or(ReminderRoleHint::System),
        authority: authority.unwrap_or_default(),
        source: ReminderSource::Hook,
        body: required_reminder_spec_string(options, "body", context)?,
        fired_at_turn: 0,
        originating_agent_id: None,
    })
}