orchestral-runtime 0.2.0

Thread Runtime with concurrency, interruption, scheduling, LLM planners, actions, and API for Orchestral
Documentation
use std::collections::HashSet;
use std::sync::{Mutex, OnceLock};

fn once_failures() -> &'static Mutex<HashSet<String>> {
    static ONCE_FAILURES: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
    ONCE_FAILURES.get_or_init(|| Mutex::new(HashSet::new()))
}

fn matches_csv_env(key: &str, action_name: &str) -> bool {
    std::env::var(key)
        .ok()
        .map(|value| {
            value
                .split(',')
                .map(str::trim)
                .filter(|item| !item.is_empty())
                .any(|item| item == action_name)
        })
        .unwrap_or(false)
}

pub(crate) fn forced_action_failure_reason(action_name: &str) -> Option<String> {
    if matches_csv_env("ORCHESTRAL_TEST_FAIL_ACTION_ALWAYS", action_name) {
        return Some(format!(
            "forced action failure for testing: {}",
            action_name
        ));
    }

    if !matches_csv_env("ORCHESTRAL_TEST_FAIL_ACTION_ONCE", action_name) {
        return None;
    }

    let mut fired = once_failures().lock().ok()?;
    if fired.insert(format!("action:{action_name}")) {
        Some(format!(
            "forced action failure-once for testing: {}",
            action_name
        ))
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::forced_action_failure_reason;

    #[test]
    fn test_forced_action_failure_once_consumes_single_hit() {
        std::env::set_var("ORCHESTRAL_TEST_FAIL_ACTION_ONCE", "file_read");
        let first = forced_action_failure_reason("file_read");
        let second = forced_action_failure_reason("file_read");
        std::env::remove_var("ORCHESTRAL_TEST_FAIL_ACTION_ONCE");

        assert!(first.is_some());
        assert!(second.is_none());
    }
}