use adk_core::{AdkError, ErrorCategory, ErrorComponent};
use async_openai::types::responses::{
EasyInputContent, EasyInputMessage, InputContent, InputFileArgs, InputItem, Role,
};
pub const SUPPORTED_FILE_MIME_TYPES: &[&str] = &[
"application/pdf",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"text/plain",
"text/csv",
"text/html",
"text/markdown",
"text/x-rust",
"text/x-python",
"text/javascript",
"text/x-typescript",
"text/x-java",
"text/x-c",
"text/x-cpp",
"text/x-go",
];
pub fn convert_file_input(mime_type: &str, file_uri: &str) -> Result<InputItem, AdkError> {
if !is_supported_mime_type(mime_type) {
return Err(AdkError::new(
ErrorComponent::Model,
ErrorCategory::InvalidInput,
"model.openai_responses.unsupported_file_type",
format!(
"Unsupported file type '{mime_type}'. Supported types: {}",
SUPPORTED_FILE_MIME_TYPES.join(", ")
),
));
}
let file_content = if file_uri.starts_with("file-") {
InputFileArgs::default()
.file_id(file_uri)
.build()
.expect("InputFileArgs build with file_id should not fail")
} else if file_uri.starts_with("data:") {
InputFileArgs::default()
.file_data(file_uri)
.build()
.expect("InputFileArgs build with file_data should not fail")
} else {
InputFileArgs::default()
.file_url(file_uri)
.build()
.expect("InputFileArgs build with file_url should not fail")
};
let content = InputContent::InputFile(file_content);
Ok(InputItem::EasyMessage(EasyInputMessage {
role: Role::User,
content: EasyInputContent::ContentList(vec![content]),
..Default::default()
}))
}
fn is_supported_mime_type(mime_type: &str) -> bool {
SUPPORTED_FILE_MIME_TYPES.contains(&mime_type)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_supported_pdf_with_file_id() {
let result = convert_file_input("application/pdf", "file-abc123");
assert!(result.is_ok());
}
#[test]
fn test_supported_text_with_data_uri() {
let result = convert_file_input("text/plain", "data:text/plain;base64,SGVsbG8=");
assert!(result.is_ok());
}
#[test]
fn test_supported_rust_with_url() {
let result = convert_file_input("text/x-rust", "https://example.com/main.rs");
assert!(result.is_ok());
}
#[test]
fn test_unsupported_mime_type_returns_error() {
let result = convert_file_input("application/zip", "file-abc123");
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.category, ErrorCategory::InvalidInput);
assert_eq!(err.code, "model.openai_responses.unsupported_file_type");
}
#[test]
fn test_all_supported_types_accepted() {
for mime_type in SUPPORTED_FILE_MIME_TYPES {
let result = convert_file_input(mime_type, "file-test123");
assert!(result.is_ok(), "Expected {mime_type} to be supported");
}
}
#[test]
fn test_file_id_mode_produces_file_id_content() {
let item = convert_file_input("application/pdf", "file-xyz789").unwrap();
let json = serde_json::to_value(&item).unwrap();
let content = &json["content"][0];
assert_eq!(content["type"], "input_file");
assert_eq!(content["file_id"], "file-xyz789");
}
#[test]
fn test_data_uri_mode_produces_file_data_content() {
let data_uri = "data:text/plain;base64,SGVsbG8gV29ybGQ=";
let item = convert_file_input("text/plain", data_uri).unwrap();
let json = serde_json::to_value(&item).unwrap();
let content = &json["content"][0];
assert_eq!(content["type"], "input_file");
assert_eq!(content["file_data"], data_uri);
}
#[test]
fn test_url_mode_produces_file_url_content() {
let url = "https://example.com/report.pdf";
let item = convert_file_input("application/pdf", url).unwrap();
let json = serde_json::to_value(&item).unwrap();
let content = &json["content"][0];
assert_eq!(content["type"], "input_file");
assert_eq!(content["file_url"], url);
}
}