sigoden/aichat · error · anyhow::Error
Unexpected media type
Error message
Unexpected media type
What it means
fetch_with_loaders downloads a URL and, if the response Content-Type is an image/video/audio type (e.g. image/png), requires the caller to have explicitly enabled media handling via `allow_media`. When such a media type is received but `allow_media` is false, it aborts with 'Unexpected media type' instead of embedding the binary as a base64 data URI.
Solutions
- Enable the media/allow_media option for this fetch call so the content can be embedded as a base64 data URI
- Verify the URL points to a text/document resource rather than a raw media asset
- Check the response Content-Type with curl -sI before fetching and convert the media separately if unsupported
- Convert or host the media content in a supported format (pdf/html/text) that the loaders can process
Example fix
// before load_documents(&["https://example.com/logo.png"], loaders, false)?; // after load_documents(&["https://example.com/logo.png"], loaders, true)?; // allow_media = true
Defensive patterns
Strategy: validation
Validate before calling
async fn content_type_is_media(url: &str) -> anyhow::Result<bool> {
let resp = reqwest::get(url).await?;
let ct = resp.headers().get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok()).unwrap_or("");
Ok(["image", "video", "audio"].iter().any(|t| ct.starts_with(&format!("{t}/"))))
}
// if content_type_is_media(url).await? { /* enable allow_media or skip */ } Try / catch
match load_url(url, loaders, allow_media).await {
Ok((contents, ext)) => { /* use contents */ }
Err(e) if e.to_string() == "Unexpected media type" => {
// re-fetch with allow_media=true or skip this resource
}
Err(e) => return Err(e),
} Prevention
- HEAD-request the URL and inspect Content-Type before fetching
- Enable allow_media when crawling pages known to embed binary assets
- Filter or sanitize URL lists to exclude direct media links
When it happens
Trigger: Calling load_documents/load_url on a URL that serves image, video, or audio content (Content-Type like image/*, video/*, audio/*) while media loading is disabled.
Common situations: Fetching a link that turns out to be a direct image/binary asset; the URL resolves to an avatar, icon, or media file instead of a text/PDF/document; server redirects a doc URL to a media CDN.
Related errors
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/17d49a26e5e2a7c1.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/request.rs:130
"application/vnd.oasis.opendocument.presentation" => "odp".into(),
"application/rtf" => "rtf".into(),
"text/javascript" => "js".into(),
"text/html" => "html".into(),
_ => content_type
.rsplit_once('/')
.map(|(first, last)| {
if ["image", "video", "audio"].contains(&first) {
is_media = true;
MEDIA_URL_EXTENSION.into()
} else {
last.to_lowercase()
}
})
.unwrap_or_else(|| DEFAULT_EXTENSION.into()),
};
let result = if is_media {
if !allow_media {
bail!("Unexpected media type")
}
let image_bytes = res.bytes().await?;
let image_base64 = base64_encode(&image_bytes);
let contents = format!("data:{content_type};base64,{image_base64}");
(contents, extension)
} else {
match loaders.get(&extension) {
Some(loader_command) => {
let save_path = temp_file("-download-", &format!(".{extension}"))
.display()
.to_string();
let mut save_file = tokio::fs::File::create(&save_path).await?;
let mut size = 0;
while let Some(chunk) = res.chunk().await? {
size += chunk.len();
save_file.write_all(&chunk).await?;
}
let contents = if size == 0 {View on GitHub (pinned to 82976d349a)