malvin 0.2.6

Non-interactive research and coding agent
pub const MAX_CONSECUTIVE_SAME_BACKEND_ERRORS: u32 = 3;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BackendErrorTracker {
    consecutive_count: u32,
    last_error: Option<String>,
    max_consecutive: u32,
}

impl Default for BackendErrorTracker {
    fn default() -> Self {
        Self::new()
    }
}

impl BackendErrorTracker {
    #[must_use]
    pub const fn new() -> Self {
        Self::with_max_consecutive(MAX_CONSECUTIVE_SAME_BACKEND_ERRORS)
    }

    #[must_use]
    pub const fn empty() -> Self {
        Self::new()
    }

    #[must_use]
    pub const fn with_max_consecutive(max_consecutive: u32) -> Self {
        Self {
            consecutive_count: 0,
            last_error: None,
            max_consecutive: if max_consecutive == 0 {
                MAX_CONSECUTIVE_SAME_BACKEND_ERRORS
            } else {
                max_consecutive
            },
        }
    }

    pub const fn set_max_consecutive(&mut self, max_consecutive: u32) {
        self.max_consecutive = if max_consecutive == 0 {
            MAX_CONSECUTIVE_SAME_BACKEND_ERRORS
        } else {
            max_consecutive
        };
    }

    #[must_use]
    pub const fn max_consecutive(&self) -> u32 {
        self.max_consecutive
    }

    #[must_use]
    pub const fn consecutive_count(&self) -> u32 {
        self.consecutive_count
    }

    #[must_use]
    pub fn last_error(&self) -> Option<&str> {
        self.last_error.as_deref()
    }

    pub fn record_success(&mut self) {
        self.consecutive_count = 0;
        self.last_error = None;
    }

    pub fn record_error(&mut self, error: &str) -> bool {
        let is_same = self
            .last_error
            .as_ref()
            .is_some_and(|prev| prev == error || prev.trim() == error.trim());
        if is_same {
            self.consecutive_count = self.consecutive_count.saturating_add(1);
        } else {
            self.consecutive_count = 1;
            self.last_error = Some(error.to_string());
        }
        self.consecutive_count >= self.max_consecutive
    }

    #[must_use]
    pub const fn should_stop_and_exit(&self) -> bool {
        self.consecutive_count >= self.max_consecutive
    }
}

#[must_use]
pub fn format_backend_consecutive_error_message(
    backend_label: &str,
    error: &str,
    limit: u32,
) -> String {
    let times = if limit == 1 { "time" } else { "times" };
    format!(
        "{backend_label} backend error repeated {limit} {times} in a row; stopping and exiting. Last error:\n{error}"
    )
}