Skip to content

🎞️ fix: Deliver Opted-In Custom Endpoint Media Safely - #15937

Merged
danny-avila merged 6 commits into
devfrom
danny-avila/custom-provider-media-clean
Sep 14, 2026
Merged

danny-avila merged 6 commits into
devfrom
danny-avila/custom-provider-media-clean

Conversation

@danny-avila

Copy link
Copy Markdown
Collaborator

Summary

I completed custom-endpoint audio/video delivery and added upload-time rejection for unsupported direct-audio formats. Previously, explicitly allowed media could be hidden by the picker, routed away from the provider, or discarded by the encoder; unsupported audio could upload successfully and then abort message submission.

  • Enable picker, drag-and-drop, unified routing, and encoding for custom OpenAI-compatible endpoints with an explicit matching supportedMimeTypes allowlist.
  • Preserve inherited defaults and built-in provider behavior; keep explicit transcription, OCR, and code destinations unchanged.
  • Canonicalize audio formats and reject unrepresentable direct audio with HTTP 415 before processing, with localized conversion/retry guidance and pending-attachment cleanup.
  • Reuse the request-cached agent lookup instead of adding database reads.

Replaces #15709 and addresses #15708. Preserves the original contributor's commits and authorship. Documentation: LibreChat-AI/docs#761.

How it works

The existing endpoint MIME allowlist is the opt-in; no new configuration key is required.

fileConfig:
  endpoints:
    MyGateway:
      supportedMimeTypes:
        - "image/.*"
        - "application/pdf"
        - "video/.*"
        - "audio/.*"
upload route
  resolveEffectiveToolResource
    resolveUploadEndpoint (request-cached)
    resolveUploadLLMDeliveryPath
    getAudioFormat → reject incompatible direct audio with 415
  processAgentFileUpload (only after preflight passes)

The encoder emits OpenAI-format video_url / input_audio parts only for compatible, opted-in custom endpoints. Google/Vertex retain their media-block behavior.

Configuration limitations

Do not allow audio/video on a custom endpoint declared provider: anthropic: the unified route rejects this incompatible combination, but the legacy explicit-provider destination can still accept and discard it. Remove those MIME types from that endpoint's allowlist. Custom endpoint names matching built-in provider identifiers retain the resolver's existing built-in-provider interpretation.

Change Type

  • Bug fix (non-breaking change which fixes an issue)
  • This change requires a documentation update

Testing

I verified that this branch's complete Git tree is identical to 87bd5cffb2fead9e4d9121c26af208943f955b45, the tested head of #15709. Only commit ancestry changed: six scoped commits on current dev, with no integration merge commit.

  • Passed 593 focused tests on that identical source tree: 150 API helper/encoder tests, 299 data-provider tests, 103 client picker/upload tests, and 41 HTTP file-route tests.
  • Passed npx tsc --noEmit in packages/api and packages/data-provider, scoped ESLint, Prettier, import ordering, and git diff --check.
  • Covered JSON and SSE-requested 415 responses, no processing on rejection, temporary-file cleanup, localized client failure, pending removal, and replacement upload.
  • Covered accepted aliases and filename fallback through the real upload resolver and encoder, plus preserved transcription/code and Google/Vertex paths.
  • Attempted local client typechecking and Lighthouse; stale local frontend dependencies and unavailable @tsdown/css prevented completion. Both passed in the original head's clean-install CI, alongside all 31 executed checks; five optional checks were skipped.

The original contributor reported a successful configured vLLM video upload and unchanged behavior without opt-in. I did not repeat that live-provider manual test.

New PR CI is pending. The original head received a clean Codex result; that is not a review of this new SHA. No additional review is requested, per maintainer instruction.

Test Configuration:

Node 24.16.0. Base: b88d70bf63598af19e9f599a0b16d6fe9f4aef8f (origin/dev). Configure a custom OpenAI-compatible endpoint using the example allowlist; verify supported media reaches the model, incompatible direct audio returns 415, and a converted replacement can be uploaded.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes
  • A pull request for updating the documentation has been submitted: 🎞️ docs: Video/Audio Opt-In for Custom Endpoints via supportedMimeTypes docs#761

Renato Garita Figueiredo and others added 6 commits September 14, 2026 16:19
…imeTypes

The video and audio encoders only emitted OpenAI-format parts (video_url,
input_audio) for OpenRouter, and Google's media part for Google/Vertex. For
every other provider the file was validated and then silently dropped, so a
custom OpenAI-compatible endpoint (vLLM, LiteLLM, ...) never received the
attachment even though the wire format is identical to OpenRouter's.

Emit the OpenAI-format part for OpenAI-like providers when the endpoint's
fileConfig supportedMimeTypes explicitly allows the file's type. The inherited
default list does not count as opting in (isExplicitMimeConfig, keyed on
referential identity like the client's picker), so endpoints whose gateway
cannot handle media are unaffected.
The attach menu's Upload to Provider filter and the drag-drop viability check
only opened video/audio for Google and OpenRouter, or for a custom endpoint
with a fully permissive (.*) supportedMimeTypes. Honor any admin-configured
allowlist on a custom endpoint instead, so a finite list that includes
video/.* or audio/.* is enough, matching the server-side encoders.
The `input_audio.format` value was derived from the filename extension via
`filename.split('.').pop()`, which produces values providers reject:

- `clip.wave` (audio/wave) emitted `wave`, but the accepted format is `wav`
- a file with no extension (`recording`) emitted the whole filename, since
  splitting a dotless string yields a single-element array, so the existing
  non-empty guard never fired
- `clip.mpeg` (audio/mpeg) emitted `mpeg` rather than `mp3`

All of these pass MIME validation and then fail at the provider. Derive the
format from the MIME type instead, mirroring the canonicalization already in
STTService, and fall back to the filename extension only when it is itself a
supported format. Throws when neither source yields one rather than sending an
unsupported value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
In unified upload mode an upload carries no tool resource, so its delivery path is
inferred by resolveDefaultLLMDeliveryPath. That resolver judged audio and video for any
named endpoint with isMediaSupportedProvider, which lists only Google, Vertex and
OpenRouter, so a custom endpoint that opted into media through supportedMimeTypes still
recorded video as none and audio as text, and the encoder branch was never reached.

The upload resolver already holds the endpoint's file config, so it now passes the
endpoint's supportedMimeTypes through, and an explicit match on an OpenAI-compatible or
custom endpoint counts as provider-capable, mirroring isConfiguredProviderMediaType on
the encoder side. The inherited default list is still not an opt-in, and known providers
whose encoders emit nothing for media are unchanged. BaseClient re-resolves per turn with
the same function, so the turn path follows.
The opt-in applied to the built-in openAI and azureOpenAI endpoints too, but the picker
and drag-drop only open media for custom endpoints, so that was a route the client could
not send to. It is now limited to endpoints that are not a known provider.

A custom endpoint may also declare provider: anthropic, in which case initialization runs
it as Anthropic and the encoders emit no media part for it. getCustomEndpointProvider
reads that declaration off the app config, the upload callers pass it as endpointProvider,
the agent encoder and BaseClient pass the agent's resolved provider, and an endpoint known
to run as something other than OpenAI keeps its previous route: video stays off the model
path and audio still reaches transcription.
@danny-avila
danny-avila merged commit 2f8bc3d into dev Sep 14, 2026
40 checks passed
@danny-avila
danny-avila deleted the danny-avila/custom-provider-media-clean branch September 14, 2026 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Upload to Provider silently drops video/audio for custom OpenAI-compatible endpoints

1 participant