Skip to main content

lash_core/llm/
transport.rs

1//! Transport-level failure type shared by provider transport components.
2
3pub use lash_http_transport::retry_after_from_headers;
4/// Canonical provider-failure classification.
5pub use lash_sansio::llm::types::ProviderFailureKind;
6pub type ProviderFailure = lash_http_transport::HttpTransportError;
7pub type LlmTransportError = lash_http_transport::HttpTransportError;
8
9/// Validate that every image attachment in `req` carries a MIME type accepted
10/// by the provider. Returns a `Validation`-kind `LlmTransportError` with code
11/// `unsupported_image_format` and a descriptive message on the first
12/// unsupported attachment, naming the provider and the offending MIME.
13///
14/// Provider adapters call this at the top of their request-building pipeline
15/// to fail fast with a clear runtime-side error rather than relying on the
16/// upstream API to reject the request with a less actionable message.
17#[expect(
18    clippy::result_large_err,
19    reason = "provider transport errors are a public typed API and carry request/response diagnostics"
20)]
21pub fn validate_image_attachments(
22    req: &lash_sansio::llm::types::LlmRequest,
23    accepted_mimes: &[&str],
24    provider_name: &str,
25) -> Result<(), LlmTransportError> {
26    for (idx, att) in req.attachments.iter().enumerate() {
27        let mime = att.mime.trim().to_ascii_lowercase();
28        let normalized = if mime == "image/jpg" {
29            "image/jpeg"
30        } else {
31            mime.as_str()
32        };
33        if !accepted_mimes.contains(&normalized) {
34            return Err(ProviderFailure::new(format!(
35                "{provider_name} does not accept image attachments of type `{}` (attachment index {idx}); accepted: {}",
36                att.mime,
37                accepted_mimes.join(", "),
38            ))
39            .with_kind(ProviderFailureKind::Validation)
40            .with_code("unsupported_image_format"));
41        }
42    }
43    Ok(())
44}