use base64::{engine::general_purpose, Engine};
use mime_guess::MimeGuess;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{debug, error, info, instrument};
use crate::errors::{Result, WinxError};
use crate::state::bash_state::BashState;
use crate::types::ReadImage;
use crate::utils::path::expand_user;
pub const SUPPORTED_MIME_TYPES: [&str; 4] = ["image/jpeg", "image/png", "image/gif", "image/webp"];
#[instrument(level = "debug", skip(file_path))]
fn read_image_from_path(file_path: &str, cwd: &Path) -> Result<(String, String)> {
debug!("Reading image: {}", file_path);
let file_path = expand_user(file_path);
let path = if Path::new(&file_path).is_absolute() {
PathBuf::from(&file_path)
} else {
cwd.join(&file_path)
};
if !path.exists() {
return Err(WinxError::FileAccessError {
path: path.clone(),
message: "File does not exist".to_string(),
});
}
if !path.is_file() {
return Err(WinxError::FileAccessError {
path: path.clone(),
message: "Path exists but is not a file".to_string(),
});
}
let image_bytes = std::fs::read(&path).map_err(|e| WinxError::FileAccessError {
path: path.clone(),
message: format!("Error reading file: {e}"),
})?;
let image_b64 = general_purpose::STANDARD.encode(&image_bytes);
let mime_type =
MimeGuess::from_path(&path).first_raw().unwrap_or("application/octet-stream").to_string();
if SUPPORTED_MIME_TYPES.contains(&mime_type.as_str()) {
Ok((mime_type, image_b64))
} else {
debug!("Detected MIME type '{}' is not in the supported list", mime_type);
let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("").to_lowercase();
let mime_type = match extension.as_str() {
"png" => "image/png",
"gif" => "image/gif",
"webp" => "image/webp",
_ => "image/jpeg", };
debug!("Using fallback MIME type: {}", mime_type);
Ok((mime_type.to_string(), image_b64))
}
}
#[instrument(level = "info", skip(bash_state_arc, read_image))]
pub async fn handle_tool_call(
bash_state_arc: &Arc<Mutex<Option<BashState>>>,
read_image: ReadImage,
) -> Result<(String, String)> {
info!("ReadImage tool called with: {:?}", read_image);
let cwd: PathBuf;
{
let bash_state_guard = bash_state_arc.lock().await;
let Some(bash_state) = &*bash_state_guard else {
error!("BashState not initialized");
return Err(WinxError::BashStateNotInitialized);
};
cwd = bash_state.cwd.clone();
}
read_image_from_path(&read_image.file_path, &cwd)
}