claux 20260908.0.0

Terminal AI coding assistant with tool execution
use crate::api::types::ImageSource;
use anyhow::{bail, Context, Result};
use base64::Engine;
use std::path::{Path, PathBuf};

const MAX_IMAGE_BYTES: u64 = 20 * 1024 * 1024;

pub fn load_images(paths: &[PathBuf]) -> Result<Vec<ImageSource>> {
    paths.iter().map(|path| load_image(path)).collect()
}

pub fn load_image(path: &Path) -> Result<ImageSource> {
    let metadata = std::fs::metadata(path)
        .with_context(|| format!("failed to inspect image {}", path.display()))?;
    if !metadata.is_file() {
        bail!("image is not a regular file: {}", path.display());
    }
    if metadata.len() > MAX_IMAGE_BYTES {
        bail!(
            "image exceeds the 20 MiB attachment limit: {}",
            path.display()
        );
    }

    let bytes =
        std::fs::read(path).with_context(|| format!("failed to read image {}", path.display()))?;
    let media_type = detect_media_type(&bytes).ok_or_else(|| {
        anyhow::anyhow!(
            "unsupported image format for {}; expected PNG, JPEG, GIF, or WebP",
            path.display()
        )
    })?;

    Ok(ImageSource {
        source_type: "base64".to_string(),
        media_type: media_type.to_string(),
        data: base64::engine::general_purpose::STANDARD.encode(bytes),
    })
}

fn detect_media_type(bytes: &[u8]) -> Option<&'static str> {
    if bytes.starts_with(b"\x89PNG\r\n\x1a\n") {
        Some("image/png")
    } else if bytes.starts_with(b"\xff\xd8\xff") {
        Some("image/jpeg")
    } else if bytes.starts_with(b"GIF87a") || bytes.starts_with(b"GIF89a") {
        Some("image/gif")
    } else if bytes.len() >= 12 && &bytes[..4] == b"RIFF" && &bytes[8..12] == b"WEBP" {
        Some("image/webp")
    } else {
        None
    }
}

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

    #[test]
    fn detects_supported_image_signatures() {
        assert_eq!(
            detect_media_type(b"\x89PNG\r\n\x1a\nrest"),
            Some("image/png")
        );
        assert_eq!(detect_media_type(b"\xff\xd8\xffrest"), Some("image/jpeg"));
        assert_eq!(detect_media_type(b"GIF89arest"), Some("image/gif"));
        assert_eq!(detect_media_type(b"RIFFxxxxWEBPrest"), Some("image/webp"));
        assert_eq!(detect_media_type(b"not an image"), None);
    }

    #[test]
    fn loads_and_encodes_an_image() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("sample.png");
        std::fs::write(&path, b"\x89PNG\r\n\x1a\ncontents").unwrap();
        let image = load_image(&path).unwrap();
        assert_eq!(image.media_type, "image/png");
        assert_eq!(image.source_type, "base64");
        assert!(!image.data.is_empty());
    }
}