🎞️ fix: Deliver Opted-In Custom Endpoint Media Safely - #15937
Merged
Merged
Conversation
…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.
10 tasks done
9 tasks done
1 task done
Closed
1 task done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
supportedMimeTypesallowlist.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.
The encoder emits OpenAI-format
video_url/input_audioparts 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
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 currentdev, with no integration merge commit.npx tsc --noEmitinpackages/apiandpackages/data-provider, scoped ESLint, Prettier, import ordering, andgit diff --check.@tsdown/cssprevented 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
supportedMimeTypesdocs#761