malvin 0.2.9

Non-interactive research and coding agent
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::{Child, ChildStderr, ChildStdin, ChildStdout};

use crate::acp::AgentError;

pub(crate) fn start_warning_forward(stderr: ChildStderr) {
    start_warning_forward_filtered(stderr, |_| false);
}

pub(crate) fn start_warning_forward_filtered(
    stderr: ChildStderr,
    drop_line: impl Fn(&str) -> bool + Send + 'static,
) {
    tokio::spawn(async move {
        forward_stderr_as_warnings(stderr, drop_line).await;
    });
}

pub(crate) fn take_stdio_forward_stderr(
    child: &mut Child,
    label: &str,
) -> Result<(ChildStdin, ChildStdout), AgentError> {
    let stdin = child
        .stdin
        .take()
        .ok_or_else(|| AgentError(format!("{label} stdin missing")))?;
    let stdout = child
        .stdout
        .take()
        .ok_or_else(|| AgentError(format!("{label} stdout missing")))?;
    let stderr = child
        .stderr
        .take()
        .ok_or_else(|| AgentError(format!("{label} stderr missing")))?;
    start_warning_forward(stderr);
    Ok((stdin, stdout))
}

async fn forward_stderr_as_warnings(stderr: ChildStderr, drop_line: impl Fn(&str) -> bool) {
    let mut reader = BufReader::new(stderr);
    let mut line = String::new();
    loop {
        line.clear();
        match reader.read_line(&mut line).await {
            Ok(0) => break,
            Ok(_) => emit_backend_stderr_warning(&line, &drop_line),
            Err(_) => break,
        }
    }
}

pub(crate) fn emit_backend_stderr_warning(line: &str, drop_line: &impl Fn(&str) -> bool) {
    let Some(payload) = backend_stderr_warning_payload(line) else {
        return;
    };
    if drop_line(payload) {
        return;
    }
    crate::output::print_log_warning(payload);
}

#[must_use]
pub(crate) fn backend_stderr_warning_payload(line: &str) -> Option<&str> {
    let payload = line.trim_end_matches(['\r', '\n']);
    if payload.is_empty() {
        None
    } else {
        Some(payload)
    }
}