malvin 0.2.4

Non-interactive research and coding agent
use clap::ArgMatches;

use crate::reliability_tier::{ReliabilityTier, ReliabilityTierFlags};

use super::config_defaults::global_flag_from_command_line;
use super::config_loop::subcommand_flag_from_command_line;

pub const DEFAULT_TENACIOUS: bool = true;
pub const TENACIOUS_MAX_LOOPS: usize = 9999;
pub const TENACIOUS_MAX_ACP_RETRIES: u32 = 9999;

pub struct TenaciousBudgetGuard {
    pub max_loops_explicit: bool,
    pub max_acp_retries_explicit: bool,
}

#[must_use]
pub fn tenacious_budget_guard(matches: &ArgMatches, subcommand: &str) -> TenaciousBudgetGuard {
    TenaciousBudgetGuard {
        max_loops_explicit: subcommand_flag_from_command_line(matches, subcommand, "max_loops"),
        max_acp_retries_explicit: global_flag_from_command_line(matches, "max_acp_retries"),
    }
}

pub struct GateLoopTenaciousApply<'a> {
    pub subcommand: &'a str,
    pub max_loops: &'a mut usize,
    pub tenacious: bool,
    pub no_tenacious: bool,
    pub max_acp_retries: &'a mut u32,
    pub matches: &'a ArgMatches,
}

pub fn apply_gate_loop_tenacious(input: GateLoopTenaciousApply<'_>) {
    let tier = ReliabilityTier::resolve(ReliabilityTierFlags {
        tenacious: input.tenacious,
        no_tenacious: input.no_tenacious,
    });
    apply_tenacious(
        input.max_loops,
        input.max_acp_retries,
        tier,
        tenacious_budget_guard(input.matches, input.subcommand),
    );
}

pub fn apply_default_route_tenacious(
    max_loops: &mut usize,
    max_acp_retries: &mut u32,
    no_tenacious: bool,
    matches: &ArgMatches,
) {
    let tier = ReliabilityTier::resolve(ReliabilityTierFlags {
        tenacious: true,
        no_tenacious,
    });
    if tier == ReliabilityTier::Tenacious {
        if !global_flag_from_command_line(matches, "max_loops") {
            *max_loops = TENACIOUS_MAX_LOOPS;
        }
        if !global_flag_from_command_line(matches, "max_acp_retries") {
            *max_acp_retries = TENACIOUS_MAX_ACP_RETRIES;
        }
    }
}

#[allow(clippy::missing_const_for_fn)]
pub fn apply_tenacious(
    max_loops: &mut usize,
    max_acp_retries: &mut u32,
    tier: ReliabilityTier,
    guard: TenaciousBudgetGuard,
) {
    if tier == ReliabilityTier::Tenacious {
        if !guard.max_loops_explicit {
            *max_loops = TENACIOUS_MAX_LOOPS;
        }
        if !guard.max_acp_retries_explicit {
            *max_acp_retries = TENACIOUS_MAX_ACP_RETRIES;
        }
    }
}

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

    #[test]
    fn apply_tenacious_sets_large_budgets() {
        let mut loops = 1usize;
        let mut retries = 3u32;
        apply_tenacious(
            &mut loops,
            &mut retries,
            ReliabilityTier::Tenacious,
            TenaciousBudgetGuard {
                max_loops_explicit: false,
                max_acp_retries_explicit: false,
            },
        );
        assert_eq!(loops, TENACIOUS_MAX_LOOPS);
        assert_eq!(retries, TENACIOUS_MAX_ACP_RETRIES);
    }

    #[test]
    fn apply_tenacious_skips_explicit_max_loops() {
        let mut loops = 3usize;
        let mut retries = 3u32;
        apply_tenacious(
            &mut loops,
            &mut retries,
            ReliabilityTier::Tenacious,
            TenaciousBudgetGuard {
                max_loops_explicit: true,
                max_acp_retries_explicit: false,
            },
        );
        assert_eq!(loops, 3);
        assert_eq!(retries, TENACIOUS_MAX_ACP_RETRIES);
    }

    #[test]
    fn apply_gate_loop_tenacious_expands_unless_opted_out() {
        use crate::cli::Cli;
        use clap::CommandFactory;
        let matches = Cli::command().get_matches_from(["malvin", "write", "topic", "--tenacious"]);
        let mut loops = 1usize;
        let mut retries = 3u32;
        apply_gate_loop_tenacious(GateLoopTenaciousApply {
            subcommand: "write",
            max_loops: &mut loops,
            tenacious: true,
            no_tenacious: false,
            max_acp_retries: &mut retries,
            matches: &matches,
        });
        assert_eq!(loops, TENACIOUS_MAX_LOOPS);
        assert_eq!(retries, TENACIOUS_MAX_ACP_RETRIES);
    }

    #[test]
    fn apply_tenacious_conservative_leaves_budgets() {
        let mut loops = 3usize;
        let mut retries = 5u32;
        apply_tenacious(
            &mut loops,
            &mut retries,
            ReliabilityTier::Conservative,
            TenaciousBudgetGuard {
                max_loops_explicit: false,
                max_acp_retries_explicit: false,
            },
        );
        assert_eq!(loops, 3);
        assert_eq!(retries, 5);
    }
}