Skip to main content

openai_compat/types/
files.rs

1//! File types, mirroring `openai-python/src/openai/types/file_object.py`.
2
3use serde::{Deserialize, Serialize};
4
5use crate::pagination::HasId;
6
7/// A file stored with the provider.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[non_exhaustive]
10pub struct FileObject {
11    pub id: String,
12    #[serde(default)]
13    pub bytes: Option<u64>,
14    #[serde(default)]
15    pub created_at: Option<i64>,
16    #[serde(default)]
17    pub expires_at: Option<i64>,
18    #[serde(default)]
19    pub filename: Option<String>,
20    #[serde(default)]
21    pub object: String,
22    #[serde(default)]
23    pub purpose: Option<String>,
24    #[serde(default)]
25    pub status: Option<String>,
26    #[serde(default)]
27    pub status_details: Option<String>,
28}
29
30impl HasId for FileObject {
31    fn id(&self) -> Option<&str> {
32        Some(&self.id)
33    }
34}
35
36/// A file to upload: raw bytes plus a filename.
37#[derive(Debug, Clone)]
38pub struct FileUpload {
39    pub filename: String,
40    pub bytes: Vec<u8>,
41    pub content_type: Option<String>,
42}
43
44impl FileUpload {
45    pub fn new(filename: impl Into<String>, bytes: Vec<u8>) -> Self {
46        Self {
47            filename: filename.into(),
48            bytes,
49            content_type: None,
50        }
51    }
52
53    /// Read the upload from disk, using the file name from the path and
54    /// inferring the content type from the extension (like the Python SDK's
55    /// `mimetypes` guess).
56    pub async fn from_path(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
57        let path = path.as_ref();
58        let filename = path
59            .file_name()
60            .map(|n| n.to_string_lossy().into_owned())
61            .unwrap_or_else(|| "file".to_string());
62        let bytes = tokio::fs::read(path).await?;
63        let mut upload = Self::new(filename, bytes);
64        upload.content_type = path
65            .extension()
66            .and_then(|e| e.to_str())
67            .and_then(mime_for_extension)
68            .map(str::to_owned);
69        Ok(upload)
70    }
71}
72
73/// Content types for extensions commonly uploaded to LLM APIs.
74fn mime_for_extension(ext: &str) -> Option<&'static str> {
75    Some(match ext.to_ascii_lowercase().as_str() {
76        "mp3" | "mpga" => "audio/mpeg",
77        "wav" => "audio/wav",
78        "m4a" | "mp4" => "audio/mp4",
79        "flac" => "audio/flac",
80        "ogg" | "oga" => "audio/ogg",
81        "webm" => "audio/webm",
82        "json" => "application/json",
83        "jsonl" => "application/jsonl",
84        "txt" => "text/plain",
85        "csv" => "text/csv",
86        "pdf" => "application/pdf",
87        "png" => "image/png",
88        "jpg" | "jpeg" => "image/jpeg",
89        "gif" => "image/gif",
90        "webp" => "image/webp",
91        _ => return None,
92    })
93}