Files
galaxy/app/src/ai/attachment_utils.rs
T
2026-04-29 10:43:20 -05:00

82 lines
2.5 KiB
Rust

//! Shared utilities for file-attachment handling (download, filename sanitization,
//! and building attachment maps).
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// Max file attachment size is 10 MB.
pub(crate) const MAX_ATTACHMENT_SIZE_BYTES: usize = 10 * 1024 * 1024;
use crate::ai::agent::AIAgentAttachment;
/// Returns the per-session directory for downloading file attachments,
/// based on the agent's working directory.
#[cfg_attr(target_family = "wasm", allow(dead_code))]
pub(crate) fn attachments_download_dir(working_dir: &Path) -> PathBuf {
working_dir.join(".warp-core").join("attachments")
}
/// Extracts the filename component from a path, stripping any directory prefixes.
pub(crate) fn sanitize_filename(raw: &str) -> &str {
Path::new(raw)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(raw)
}
/// A downloaded file attachment with its resolved path on disk.
pub(crate) struct DownloadedAttachment {
/// The UUID of the attachment.
pub file_id: String,
/// The sanitized display name.
pub file_name: String,
/// The full resolved path on disk where the file was downloaded.
pub file_path: String,
}
/// Builds a `HashMap<String, AIAgentAttachment>` keyed by (deduplicated) filename
/// from a list of successfully downloaded attachments.
pub(crate) fn build_file_attachment_map(
downloads: &[DownloadedAttachment],
) -> HashMap<String, AIAgentAttachment> {
let mut map = HashMap::new();
for download in downloads {
let mut key = download.file_name.clone();
if map.contains_key(&key) {
let mut suffix = 1;
loop {
key = format!("{} ({suffix})", download.file_name);
if !map.contains_key(&key) {
break;
}
suffix += 1;
}
}
map.insert(
key,
AIAgentAttachment::FilePathReference {
file_id: download.file_id.clone(),
file_name: download.file_name.clone(),
file_path: download.file_path.clone(),
},
);
}
map
}
/// Downloads a file from `url` and writes it to `dest`. Returns the number of bytes written.
pub(crate) async fn download_file(
client: &http_client::Client,
url: &str,
dest: &Path,
) -> anyhow::Result<usize> {
let bytes: bytes::Bytes = client
.get(url)
.send()
.await?
.error_for_status()?
.bytes()
.await?;
async_fs::write(dest, &bytes).await?;
Ok(bytes.len())
}