mod compose;
mod ingest;
mod shape;
use sha2::Digest;
use shuvarie_llm::Attachment;
use std::path::{Path, PathBuf};
pub use compose::{DirectiveProbe, PathCandidate, path_candidates, run_directive_probe};
pub use ingest::resolve_directives;
pub(crate) use ingest::{DocumentFailure, document_convert, fit_image, image_media_type};
pub use shape::{
FitReport, collect_blobs, prepare_for_send, resolve_stored_prepared, supports_images,
};
pub(crate) const DEFAULT_IMAGE_EDGE: u32 = 1568;
pub(crate) const DEFAULT_MAX_IMAGES: usize = 8;
pub(crate) const DEFAULT_REQUEST_IMAGE_BYTES: u64 = 16 * 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttachmentSettings {
pub max_images: usize,
pub image_budget: u64,
pub image_edge: u32,
pub office_converter: Option<String>,
}
impl Default for AttachmentSettings {
fn default() -> Self {
Self {
max_images: DEFAULT_MAX_IMAGES,
image_budget: DEFAULT_REQUEST_IMAGE_BYTES,
image_edge: DEFAULT_IMAGE_EDGE,
office_converter: None,
}
}
}
impl From<&shuvarie_config::AttachmentsConfig> for AttachmentSettings {
fn from(config: &shuvarie_config::AttachmentsConfig) -> Self {
Self {
max_images: config.max_images,
image_budget: config.image_budget as u64 * 1024 * 1024,
image_edge: config.image_edge,
office_converter: config.office_converter.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct Prepared {
pub meta: Attachment,
pub bytes: Option<Vec<u8>>,
}
impl Prepared {
pub fn metadata_only(meta: Attachment) -> Self {
Self { meta, bytes: None }
}
}
pub(super) fn normalize_path(workspace_root: &Path, path: &str) -> PathBuf {
normalize_path_in(workspace_root, dirs::home_dir().as_deref(), path)
}
pub(super) fn normalize_path_in(workspace_root: &Path, home: Option<&Path>, path: &str) -> PathBuf {
let path = path.trim().strip_prefix('@').unwrap_or(path.trim());
let path = crate::permissions::expand_home_in(path, home);
let path = PathBuf::from(path);
if path.is_absolute() {
path
} else {
workspace_root.join(path)
}
}
pub(super) fn sha256_hex(bytes: &[u8]) -> String {
let digest = sha2::Sha256::digest(bytes);
let mut out = String::with_capacity(digest.len() * 2);
for byte in digest {
out.push_str(&format!("{byte:02x}"));
}
out
}