Repository navigation
Conversation
Adds an opt-in `llmDeliveryPolicy: automatic` under `fileConfig` (global, `endpoints.default`, or per endpoint); the default `classic` keeps today's routing. On a turn that loads Run Code or File Search, the policy decides each attachment's reader from its category, the current provider's existing validators and limits, and the tools actually loaded: spreadsheets (xlsx, xls, ods, csv, tsv, parquet) go to Run Code with no upload-time extraction; other documents go to the provider within its limits, then complete extracted text when it fits, then File Search, then Run Code; a document too large to send directly tries File Search and Run Code before text. Without a file tool, and under the classic policy, every attachment keeps its classic route and limits unchanged. Explicit destinations, `legacyFileUploadUX` and `defaultLLMDeliveryPath` still win. Code access is independent of the primary reader: a compatible original the model reads natively or as text is also advertised to Run Code at a stable path and copied into the sandbox only when code first runs. The model receives a per-turn inventory of how each attachment can be read and which it cannot. Text is derived from the retained original only when a reader needs it, under the deployment's text allowlist, and cached on the record (`metadata.textDerivation`). A pending File Search file is advertised and indexed before the first search; a failed preparation fails the turn with the stable `file_search_preparation_failed` error. Native size limits resolve in one place, a provider's capacity rejection moves the file to the next reader instead of dropping it, and a resumed turn charges checkpoint files by the resuming agent's decision. The UI is unchanged except that the preview dialog no longer treats Office Open XML types as XML text, which rendered a code-kept workbook as raw ZIP bytes; such a file shows the existing unavailable state. The e2e mock lane gains fake-model, code and RAG fixtures that record what the model, the sandbox and the vector store received, and an automatic-upload spec that proves the policy end to end.
The capability-gate invariant pins every read of the Run Code and File Search capabilities per file. Upload processing gained one: the read that decides whether a spreadsheet is left to Run Code, paired with the Run Code role grant inside resolveUploadCodePossible. The count for process.js moves from five to six with that pairing recorded.
This branch has not been deployed
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
With unified uploads a user attaches a file once, but LibreChat still decides what happens to it from the file type alone. A spreadsheet is text-extracted into the prompt at upload even when Run Code is enabled, a document the provider rejects for size is filtered out of the turn before File Search or Run Code could take it, and the model is never told how an attachment was actually read. #16618 reports the spreadsheet case; the broader request is #10941.
It triggers whenever an agent conversation with Run Code or File Search enabled receives a workbook, a CSV, or a PDF above the provider's effective size limit, through the attach button, drag and drop, file paste, or an existing file.
This adds an opt-in
llmDeliveryPolicy: automaticunderfileConfig(global,endpoints.default, or a provider or custom endpoint name); the defaultclassickeeps today's routing. The policy decides only on turns where a file tool is loaded, Run Code or File Search, whether from the agent builder or the message bar; a conversation that attaches no tool keeps today's classic routing and limits unchanged, truncated text included. On such a turn each attachment's reader comes from the file category, the current provider's existing validators and limits, and the tools actually loaded. Spreadsheets (xlsx, xls, ods, csv, tsv, parquet) go to Run Code when it is available, with no upload-time extraction. Other documents go to the provider within its limits, then complete extracted text when it fits, then File Search, then Run Code; a document too large to send directly tries File Search and Run Code before text. Explicit destinations,legacyFileUploadUXanddefaultLLMDeliveryPathoverrides still win. Code access is independent of the primary reader: a PDF the model reads natively is also listed for Run Code at a stable path, so the model can edit it in code without reading it again, and the sandbox copy is made only when code first runs.After the change the model receives a compact per-turn inventory stating how each attachment can be read on that turn and which it cannot read, text is derived from the retained original only when a reader needs it and cached on the record, and a pending File Search file is advertised and indexed before the first search. Nothing changes in the UI: file chips, previews and messages render as they do today, with one exception called out under screenshots.
Related to #16618
Related to #16560
Related to #15420
How it works
Runtime flow:
Ownership:
Type of change
Testing
fileConfig.llmDeliveryPolicy: automaticinlibrechat.yaml(globally or for one endpoint) and start LibreChat with Agents, Run Code and File Search enabled.fileSizeLimit(uploaded where it fits, then switch to the smaller endpoint) with File Search enabled. The system message lists it for File Search as too large to send directly, and the file is indexed before the first search.classic, every upload behaves as it does on dev, including the truncated text a long CSV gets.Tested environments/configuration:
Mock Auto ProviderandMock Auto Small Provider(llmDeliveryPolicy: automatic, the latter withfileSizeLimit: 1),Mock Provider C(classic)Automated tests:
npx playwright test --config=e2e/playwright.config.mock.ts automatic-upload.spec.ts unified-upload.spec.ts file-provisioning.spec.ts run-files-delivery.spec.ts— 21 passed (10 automatic-upload scenarios, including the no-tool classic case and the entry-point equivalence check, plus the unified-upload, file-provisioning and run-files-delivery lanes)packages/api:npx jest src/files src/agents— 7,418 passed, 13 skipped (the fullsrc/filesandsrc/agentstrees)api: Jest over BaseClient, the agent controllers and their specs, file processing, ToolService, the files route and agent initialization — 1,683 passed, 1 skipped (pre-existing)packages/data-schemas:npx jest src/methods/file.spec.ts— 115 passedpackages/data-provider:npx jest src/resolve-llm-delivery-path.spec.ts src/reading.spec.ts src/messages.spec.ts src/file-config.spec.ts— 566 passedclient: Jest over the message-content, preview, thread, regenerate, equivalence and error suites — 1,243 passednpx tsc --noEmitinpackages/api,packages/data-provider,packages/data-schemasandclient— cleannpm run static-checks -- --against origin/dev— ESLint and Prettier cleandevat e1dfc10. A headless browser pass confirmed the file chips render identically on automatic and classic endpoints and that a code-kept workbook's preview shows the unavailable state.devat that commit carries three lane-recording tests inToolService.spec.jsthat still stub a function 🏝️ fix: Apply the No-Workspace Decision Wherever an Agent's Code Route Resolves #16798 removed from the execution-time loader; since this branch touches that spec, they are adapted here to the shared code rule so the file lints and passes.npm run lighthouse(the earlier run on this branch, with 250 ms per query, measured median LCP 3,962 ms, CLS 0.0166 and TBT 221 ms, all within budget). No live provider or real code sandbox was used; every code receipt comes from the fake code server.Screenshots / recordings
No user-facing change, with one exception. The file preview dialog treated every MIME type containing
xmlas text, so a workbook kept for Run Code, which has no extracted text on its record, opened as its raw ZIP bytes. Only XML proper and+xmltypes preview as text now, and such a workbook shows the existing "Preview not available" state. Dev masks this today because classic routing always extracts workbook text; the fix is one line inpreview.ts. Both captures are the same dialog for the same upload, with and without that line.Risk / compatibility
metadata.textDerivationsubdocument on file records (deferred,completeorfailed), written only under the automatic policy; no migration. Before downgrading the server, set the policy back toclassicso no new deferred records are written; workbooks already deferred stay readable only by Run Code on the older version until re-uploaded (seelibrechat.example.yaml).llmDeliveryPolicydefaults toclassic; enable it only after every replica runs this version.legacyFileUploadUXforcesclassic. The policy engages only on turns that load Run Code or File Search, so a deployment that enables it sees no change in conversations without a file tool; there, a long file follows dev's behavior exactly, including the context-window error a small-context model gives when extracted text does not fit.fileSizeLimitis treated as native delivery capacity rather than admission, so a file admitted under another endpoint can reach Run Code or File Search preparation here where classic dropped it from the turn (onlyserverFileSizeLimitbounds those preparations); and a resumed run charges checkpoint files by the resuming agent's own decision, so a tool set that changed while paused can under-count text already in the checkpoint.Checklist
librechat.example.yaml)