winx_code_agent/tools/
read_image.rs1use base64::{engine::general_purpose, Engine};
8use mime_guess::MimeGuess;
9use std::path::{Path, PathBuf};
10use std::sync::Arc;
11use tokio::sync::Mutex;
12use tracing::{debug, error, info, instrument};
13
14use crate::errors::{Result, WinxError};
15use crate::state::bash_state::BashState;
16use crate::types::ReadImage;
17use crate::utils::path::expand_user;
18
19pub const SUPPORTED_MIME_TYPES: [&str; 4] = ["image/jpeg", "image/png", "image/gif", "image/webp"];
21
22#[instrument(level = "debug", skip(file_path))]
42fn read_image_from_path(file_path: &str, cwd: &Path) -> Result<(String, String)> {
43 debug!("Reading image: {}", file_path);
44
45 let file_path = expand_user(file_path);
47
48 let path = if Path::new(&file_path).is_absolute() {
50 PathBuf::from(&file_path)
51 } else {
52 cwd.join(&file_path)
54 };
55
56 if !path.exists() {
58 return Err(WinxError::FileAccessError {
59 path: path.clone(),
60 message: "File does not exist".to_string(),
61 });
62 }
63
64 if !path.is_file() {
66 return Err(WinxError::FileAccessError {
67 path: path.clone(),
68 message: "Path exists but is not a file".to_string(),
69 });
70 }
71
72 let image_bytes = std::fs::read(&path).map_err(|e| WinxError::FileAccessError {
74 path: path.clone(),
75 message: format!("Error reading file: {e}"),
76 })?;
77
78 let image_b64 = general_purpose::STANDARD.encode(&image_bytes);
80
81 let mime_type =
83 MimeGuess::from_path(&path).first_raw().unwrap_or("application/octet-stream").to_string();
84
85 if SUPPORTED_MIME_TYPES.contains(&mime_type.as_str()) {
87 Ok((mime_type, image_b64))
88 } else {
89 debug!("Detected MIME type '{}' is not in the supported list", mime_type);
90 let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("").to_lowercase();
92
93 let mime_type = match extension.as_str() {
94 "png" => "image/png",
95 "gif" => "image/gif",
96 "webp" => "image/webp",
97 _ => "image/jpeg", };
99
100 debug!("Using fallback MIME type: {}", mime_type);
101 Ok((mime_type.to_string(), image_b64))
102 }
103}
104
105#[instrument(level = "info", skip(bash_state_arc, read_image))]
123pub async fn handle_tool_call(
124 bash_state_arc: &Arc<Mutex<Option<BashState>>>,
125 read_image: ReadImage,
126) -> Result<(String, String)> {
127 info!("ReadImage tool called with: {:?}", read_image);
128
129 let cwd: PathBuf;
132
133 {
135 let bash_state_guard = bash_state_arc.lock().await;
136
137 let Some(bash_state) = &*bash_state_guard else {
139 error!("BashState not initialized");
140 return Err(WinxError::BashStateNotInitialized);
141 };
142
143 cwd = bash_state.cwd.clone();
145 }
146
147 read_image_from_path(&read_image.file_path, &cwd)
149}