opi_coding_agent/
image.rs1use std::path::PathBuf;
4
5use opi_ai::message::{ImageSource, InputContent, MediaType};
6
7pub const DEFAULT_MAX_IMAGE_BYTES: u64 = 20 * 1024 * 1024;
9
10pub fn detect_media_type(path: PathBuf) -> Option<MediaType> {
12 let ext = path.extension()?.to_str()?.to_ascii_lowercase();
13 match ext.as_str() {
14 "png" => Some(MediaType::Png),
15 "jpg" | "jpeg" => Some(MediaType::Jpeg),
16 "gif" => Some(MediaType::Gif),
17 "webp" => Some(MediaType::WebP),
18 _ => None,
19 }
20}
21
22pub fn load_image(path: &PathBuf) -> Result<InputContent, ImageLoadError> {
24 load_image_with_limit(path, DEFAULT_MAX_IMAGE_BYTES)
25}
26
27pub fn load_image_with_limit(
29 path: &PathBuf,
30 max_image_bytes: u64,
31) -> Result<InputContent, ImageLoadError> {
32 let media_type = detect_media_type(path.clone()).ok_or_else(|| ImageLoadError {
33 path: path.clone(),
34 reason: "unsupported image format (accepted: png, jpg/jpeg, gif, webp)".into(),
35 })?;
36 let size = std::fs::metadata(path).map_err(|e| ImageLoadError {
37 path: path.clone(),
38 reason: format!("failed to read file metadata: {e}"),
39 })?;
40 if size.len() > max_image_bytes {
41 return Err(ImageLoadError {
42 path: path.clone(),
43 reason: format!(
44 "image is {} bytes, exceeding max_image_bytes limit of {} bytes",
45 size.len(),
46 max_image_bytes
47 ),
48 });
49 }
50 let data = std::fs::read(path).map_err(|e| ImageLoadError {
51 path: path.clone(),
52 reason: format!("failed to read file: {e}"),
53 })?;
54 Ok(InputContent::Image {
55 source: ImageSource::Bytes { data },
56 media_type,
57 })
58}
59
60#[derive(Debug)]
62pub struct ImageLoadError {
63 pub path: PathBuf,
64 pub reason: String,
65}
66
67impl std::fmt::Display for ImageLoadError {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 write!(
70 f,
71 "image load error for {}: {}",
72 self.path.display(),
73 self.reason
74 )
75 }
76}
77
78impl std::error::Error for ImageLoadError {}