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

  1. Enable the media/allow_media option for this fetch call so the content can be embedded as a base64 data URI
  2. Verify the URL points to a text/document resource rather than a raw media asset
  3. Check the response Content-Type with curl -sI before fetching and convert the media separately if unsupported
  4. 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

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)