magi-code 0.63.1

Repository-aware CLI coding agent for terminal work
Documentation
#[cfg(test)]
use super::AgentOutputSink;
#[cfg(test)]
use super::AssistantTextSink;
use crate::sessions::{Session, SessionEventKind, try_record_session_event_batch};
use serde_json::json;
use std::path::Path;

#[cfg(test)]
pub(super) struct AssistantOnlySink<'a> {
    pub(super) on_text_delta: &'a mut AssistantTextSink<'a>,
}

#[cfg(test)]
impl AgentOutputSink for AssistantOnlySink<'_> {
    fn assistant_delta(&mut self, text: &str) -> anyhow::Result<()> {
        (self.on_text_delta)(text)
    }

    fn tool_block(&mut self, _block: &str) -> anyhow::Result<()> {
        Ok(())
    }
}

pub(super) fn normalize_agent_identifier(value: String, fallback: &str) -> String {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        fallback.to_string()
    } else {
        trimmed.to_string()
    }
}

pub(super) fn safe_error_message(error: &anyhow::Error) -> String {
    error
        .chain()
        .next()
        .map(ToString::to_string)
        .unwrap_or_else(|| "unknown error".to_string())
}

const ASSISTANT_CHUNK_BATCH_BYTES: usize = 4 * 1024;

pub(super) struct AssistantChunkBatch<'a> {
    session: Option<&'a Session>,
    cwd: &'a Path,
    chunks: Vec<String>,
    bytes: usize,
}

impl<'a> AssistantChunkBatch<'a> {
    pub(super) fn new(session: Option<&'a Session>, cwd: &'a Path) -> Self {
        Self {
            session,
            cwd,
            chunks: Vec::new(),
            bytes: 0,
        }
    }

    pub(super) fn push(&mut self, delta: &str) {
        self.bytes = self.bytes.saturating_add(delta.len());
        self.chunks.push(delta.to_string());
    }

    pub(super) fn flush_if_full(&mut self) -> anyhow::Result<()> {
        if self.bytes >= ASSISTANT_CHUNK_BATCH_BYTES {
            self.flush()?;
        }
        Ok(())
    }

    pub(super) fn flush(&mut self) -> anyhow::Result<()> {
        if self.chunks.iter().all(|chunk| chunk.trim().is_empty()) {
            self.chunks.clear();
            self.bytes = 0;
            return Ok(());
        }
        let chunks = std::mem::take(&mut self.chunks);
        self.bytes = 0;
        let text = chunks.concat();
        try_record_session_event_batch(
            self.session,
            self.cwd,
            SessionEventKind::AssistantChunk,
            std::iter::once(json!({"text": text})),
        )
        .map(|_| ())
    }
}