rho-coding-agent 1.26.0

A lightweight agent harness inspired by Pi
Documentation
use std::collections::BTreeMap;

use super::{
    CommandExit, Condition, ExitCodePredicate, NodeId, NodeTerminalState, TruthValue,
    ValuePredicate, WorkflowValue,
};

pub(crate) struct ConditionContext<'a> {
    pub(crate) statuses: &'a BTreeMap<NodeId, NodeTerminalState>,
    pub(crate) command_exits: &'a BTreeMap<NodeId, CommandExit>,
    pub(crate) outputs: &'a BTreeMap<NodeId, WorkflowValue>,
}

pub(crate) fn evaluate_condition(
    condition: &Condition,
    context: &ConditionContext<'_>,
) -> TruthValue {
    match condition {
        Condition::NodeStatus { node, matches } => context
            .statuses
            .get(node)
            .map_or(TruthValue::Unavailable, |status| {
                truth(matches.contains(status))
            }),
        Condition::CommandExit { node, predicate } => context
            .command_exits
            .get(node)
            .map_or(TruthValue::Unavailable, |exit| {
                truth(exit_matches(exit, predicate))
            }),
        Condition::Output {
            node,
            path,
            predicate,
        } => context
            .outputs
            .get(node)
            .and_then(|value| value.at_path(&path.0))
            .map_or(TruthValue::Unavailable, |value| {
                truth(value_matches(value, predicate))
            }),
        Condition::All { conditions } => combine_all(
            conditions
                .iter()
                .map(|condition| evaluate_condition(condition, context)),
        ),
        Condition::Any { conditions } => combine_any(
            conditions
                .iter()
                .map(|condition| evaluate_condition(condition, context)),
        ),
        Condition::Not { condition } => match evaluate_condition(condition, context) {
            TruthValue::True => TruthValue::False,
            TruthValue::False => TruthValue::True,
            TruthValue::Unavailable => TruthValue::Unavailable,
        },
    }
}

fn truth(value: bool) -> TruthValue {
    if value {
        TruthValue::True
    } else {
        TruthValue::False
    }
}

fn combine_all(values: impl Iterator<Item = TruthValue>) -> TruthValue {
    let mut unavailable = false;
    for value in values {
        match value {
            TruthValue::False => return TruthValue::False,
            TruthValue::Unavailable => unavailable = true,
            TruthValue::True => {}
        }
    }
    if unavailable {
        TruthValue::Unavailable
    } else {
        TruthValue::True
    }
}

fn combine_any(values: impl Iterator<Item = TruthValue>) -> TruthValue {
    let mut unavailable = false;
    for value in values {
        match value {
            TruthValue::True => return TruthValue::True,
            TruthValue::Unavailable => unavailable = true,
            TruthValue::False => {}
        }
    }
    if unavailable {
        TruthValue::Unavailable
    } else {
        TruthValue::False
    }
}

fn exit_matches(exit: &CommandExit, predicate: &ExitCodePredicate) -> bool {
    match (exit, predicate) {
        (CommandExit::Code { code }, ExitCodePredicate::Equals(expected)) => code == expected,
        (CommandExit::Code { code }, ExitCodePredicate::IsOneOf(expected)) => {
            expected.contains(code)
        }
        (CommandExit::Code { code }, ExitCodePredicate::Succeeded) => *code == 0,
        (CommandExit::Code { code }, ExitCodePredicate::Failed) => *code != 0,
        (
            CommandExit::Signal { .. }
            | CommandExit::Timeout
            | CommandExit::Cancellation
            | CommandExit::Abnormal,
            ExitCodePredicate::Failed,
        ) => true,
        (
            CommandExit::Signal { .. }
            | CommandExit::Timeout
            | CommandExit::Cancellation
            | CommandExit::Abnormal,
            ExitCodePredicate::Equals(_)
            | ExitCodePredicate::IsOneOf(_)
            | ExitCodePredicate::Succeeded,
        ) => false,
    }
}

fn value_matches(value: &WorkflowValue, predicate: &ValuePredicate) -> bool {
    match predicate {
        ValuePredicate::Equals(expected) => value == expected,
        ValuePredicate::NotEquals(expected) => value != expected,
        ValuePredicate::IsOneOf(expected) => expected.contains(value),
    }
}

#[cfg(test)]
#[path = "condition_tests.rs"]
mod tests;