* 🧠 fix: Replay DeepSeek `reasoning_content` via OpenRouter
DeepSeek's thinking-mode API rejects multi-turn tool-calling requests
unless `reasoning_content` from each tool-bearing assistant message is
replayed verbatim, returning HTTP 400 "The `reasoning_content` in the
thinking mode must be passed back to the API." The agents SDK already
handles this for direct `Providers.DEEPSEEK`, but DeepSeek models routed
via OpenRouter use `Providers.OPENROUTER` — `formatAgentMessages` skipped
the reasoning-preservation branch, and `ChatOpenRouter` left
`includeReasoningContent` unset, so the field silently dropped on every
subsequent turn.
Add `isDeepSeekReasoningProvider(provider, model)` and use it in two
places: (1) `getOpenAILLMConfig` flips `includeReasoningContent: true`
when OpenRouter is dispatching a `deepseek/*` model so the LangChain
client emits the field on assistant turns that have non-empty
`additional_kwargs.reasoning_content`, and (2) `AgentClient` spoofs the
provider hint to `Providers.DEEPSEEK` when calling
`formatAgentMessages`, triggering the SDK's existing
`preserveReasoningContent` path that re-attaches the field to
reconstructed tool-bearing AIMessages. The downstream
`_convertMessagesToOpenAIParams` is already gated on non-empty
`reasoning_content`, so the flag is a no-op outside thinking mode.
Resolves#13366.
* fix: Harden DeepSeek detection against OpenRouter routing edges
Address three Codex review findings on #13368:
1. Strip OpenRouter's `~` latest-routing prefix before applying the
DeepSeek model regex. `~deepseek-chat` and `~deepseek/r1` were
previously left unmatched because the regex's start/`/` boundary
only saw the `~`. Mirror the SDK's `normalizeOpenRouterModel()`
here and in `getOpenAILLMConfig`.
2. Add a custom-endpoint fallback: when the model id carries the
unambiguous `deepseek/...` OpenRouter namespace, accept it
regardless of the resolved provider. Covers the case where a user
configures OpenRouter under a non-standard endpoint name and
`initializeAgent` normalizes the unknown provider to `openai`,
stranding the spoof. Bare `deepseek-*` ids still require an
explicit DeepSeek/OpenRouter provider so unrelated endpoints
labelling a model `deepseek-r1` don't trigger.
3. Inspect every agent in `this.agentConfigs` when deciding whether
to spoof the format provider. Multi-agent handoff runs feed all
agents' messages through one `formatAgentMessages` call, so a
DeepSeek handoff under a non-DeepSeek primary previously lost its
persisted reasoning_content too.
Also addresses Copilot's review note: only pass the options object
to `formatAgentMessages` when the DeepSeek spoof is actually needed,
preserving the pre-fix behavior for everyone else.
* fix: Extend DeepSeek reasoning_content fix to OpenAI-compat agent paths
Address two more Codex P2 findings on #13368:
1. `getOpenAILLMConfig` no longer gates `includeReasoningContent` on
`useOpenRouter`. Any DeepSeek-style model id (with `~` latest-routing
prefix stripped) is sufficient. This re-aligns the LLM gate with
`AgentClient`'s formatter spoof, which already treats a `deepseek/*`
id as authoritative — so a custom-named OpenRouter endpoint or a
DeepSeek-compatible proxy gets the field both attached to history AND
serialized to the wire. Direct `ChatDeepSeek` ignores the flag (its
own conversion path hardcodes `includeReasoningContent: true`), so
this is a harmless no-op there.
2. Thread the same `Providers.DEEPSEEK` formatter hint through
`api/server/controllers/agents/openai.js` and `responses.js` (the
OpenAI-/Responses-compatible serving paths). Without it those paths
restored `additional_kwargs.reasoning_content` only in `AgentClient`
while the LLM config flipped `includeReasoningContent` on for them
too — so DeepSeek tool turns served from those endpoints would still
ship requests with the flag set but no field present, hitting the
same second-turn 400. The `needsDeepSeekFormatHint` helper in
`openai.js` mirrors `AgentClient`'s per-agent check.
* fix: Tighten DeepSeek detection and cover handoff sub-agents
Address four more Codex P2 findings on #13368:
- Tighten the DeepSeek model regex to `^deepseek(?:[-/]|$)/i` (anchored
to start). Rejects cloned/distilled slugs like
`mistral/deepseek-distilled-foo` and `community/deepseek-r1` that
previously matched via the `(?:^|/)` alternation, which could attach
the DeepSeek-only `reasoning_content` field on proxies that don't
accept it.
- Anchoring also collapses the namespace-only fallback into the same
pattern, so bare `deepseek-chat` / `deepseek-reasoner` on a
custom OpenAI-compatible DeepSeek proxy are now recognized — fixing
the asymmetry where `getOpenAILLMConfig` would flip
`includeReasoningContent` for those bare ids but `AgentClient`
wouldn't pass the formatter hint.
- Extend `needsDeepSeekFormatHint` in `openai.js` (and the inline
check in `responses.js`) to walk `handoffAgentConfigs` too. In
multi-agent runs where the primary isn't DeepSeek but a connected
handoff agent is, the SDK's `formatAgentMessages` previously dropped
the handoff's persisted reasoning_content before the next tool turn,
preserving the 400 the PR was meant to prevent.
- Mirror the regex change in `getOpenAILLMConfig`.
Out of scope: the OpenAI-compatible serving paths still don't
preserve incoming `reasoning_content`/`reasoning` fields in
`convertMessages`, nor does the Responses API persist reasoning in
`saveResponseOutput`. Those are deeper persistence/conversion fixes
worth a separate PR.
* test: Allow includeReasoningContent for Azure-serverless DeepSeek
CI surfaced a backward-compat expectation that snapshotted the
pre-fix behavior. Azure-serverless DeepSeek deployments (e.g.
`DeepSeek-R1`) forward to the same DeepSeek thinking-mode tool-call
contract, so the LLM gate now correctly flips
`includeReasoningContent: true` for them too. The downstream
gate on a non-empty `additional_kwargs.reasoning_content` keeps
this a no-op outside thinking mode.
* chore: Trim noisy comments
Per CLAUDE.md ("self-documenting code; no inline comments narrating
what code does"), strip the multi-paragraph rationale that crept into
the DeepSeek reasoning_content fix. The commit history and PR
description carry the why; the code says the what.
Keeps one single-line JSDoc on `isDeepSeekReasoningProvider` (linking
to the DeepSeek docs) and a `(#13366)` tag on each opt-in site so
future readers can find the context.
* revert: Drop non-functional DeepSeek hint from OpenAI-compat serving paths
Codex's later review passes correctly flagged that threading the
DeepSeek formatter hint through openai.js (`/v1/chat/completions`) and
responses.js (`/v1/responses`) doesn't actually fix the second-turn
400 in those paths. Empirical check against the real SDK confirmed the
gap is deeper and pre-existing:
formatAgentMessages(payload, ..., { provider: DEEPSEEK })
where payload is the `convertMessages`/`convertInputToMessages` output
shape (string content + TOP-LEVEL `tool_calls`) produces NO tool-bearing
AIMessage at all — `formatAssistantMessage` only reconstructs tool calls
from `tool_call`-typed *content parts*, never a top-level `tool_calls`
field. So those serving paths don't reconstruct tool-call history (let
alone reasoning) regardless of the hint. The Responses persistence layer
likewise stores only output text, not tool calls or reasoning.
Making those paths work requires reworking the wire->internal message
conversion (and Responses persistence) to emit content-part arrays — a
broad, pre-existing concern beyond this issue and risky to land here.
Rather than ship a hint that looks like a fix but is inert, revert the
serving-path changes and scope this PR to the validated AgentClient
chat path (the actual surface in #13366).
Reverts the openai.js/responses.js threading and their spec mocks to
main. Keeps the AgentClient fix, `isDeepSeekReasoningProvider`, the
`getOpenAILLMConfig` flag, and the type.
* 🛡️ fix: Cap Default Limit on Agent List Queries (#13363)
`GET /api/agents` accepted unbounded requests: when the client omitted
`limit`, the value flowed straight into `getListAgentsByAccess`, which
set `isPaginated = false` and issued an uncapped MongoDB query. Combined
with the unindexed `findPubliclyAccessibleResources` AclEntry scan run
on every request, this produced 10-19s response times and stalled the
connection pool on instances with 100+ agents.
- Default `limit` to 100 in the route handler so client requests without
`?limit=` paginate by default.
- Default `limit` to 100 in `getListAgentsByAccess` itself as
defense-in-depth. The function already caps numeric limits at 100, so
there is no client-facing change.
- Pass `limit: null` explicitly in the actions route, which legitimately
needs the full editable-agent set, to preserve its existing behavior.
- Add regression tests covering the default cap and the explicit
unbounded opt-out.
* 🛡️ fix: Avoid agent-list regression for users with 100+ agents
Codex review pointed out that capping `getListAgentsByAccess` at 100
silently truncated agents past the first page for the four consumers
(`useAgentsMap`, `AgentSelect`, `ModelSelectorContext`, `useMentions`)
that read `res.data` without following `has_more`/`after`.
- Raise the function's hard cap from 100 to 1000 to match
`MAX_AVATAR_REFRESH_AGENTS`, the realistic upper bound the
avatar-refresh path already assumes. (Side effect: the avatar refresh
call site was silently being capped at 100 by the old normalize step.)
- In `useListAgentsQuery`, merge `limit: 1000` into params so the four
consumers above get the user's full accessible set in a single
round-trip instead of needing cursor pagination.
- Route handler default stays at 100 as defense-in-depth for any other
caller that omits `limit`.
- Add a regression test asserting an explicit `limit` above 100 now
returns the full set instead of being clipped.
* 🪢 fix: Keep agent-list cache key stable for mutations
Codex P2 review noted that folding `limit: 1000` into the cache key
broke `allAgentViewAndEditQueryKeys` in `Agents/mutations.ts`, which
references `[QueryKeys.agents, { requiredPermission }]` directly across
eight mutation handlers. After my prior change the cached entry lived
under `[QueryKeys.agents, { limit: 1000, requiredPermission }]`, so
create/update/delete/avatar/action mutations stopped updating the list
the four consumer hooks render — and with `refetchOnMount` and focus/
reconnect refetches disabled, the UI would stay stale until something
else triggered a fetch.
Split the merged limit out of the cache key: the request to
`dataService.listAgents` still uses `requestParams` (with the default
limit applied), but the React Query cache key uses the caller's `params`
as-is. The mutation cache updates land again, and the request still
returns the user's full accessible set in one round-trip.
* 🛡️ fix: Index AclEntry and paginate agent list internally (#13363)
Completes the perf fix for #13363 properly — resolves both the
unbounded ACL scans Copilot flagged and Codex's tension between "show
all agents" and "don't bypass the server cap".
Backend:
- Add a compound index on `{ principalType, resourceType, permBits,
resourceId }` to the AclEntry schema. This is the index missing for
`findPublicResourceIds` and the public branch of the `$or` in
`findAccessibleResources`, both of which previously fell back to a
collection scan on every `GET /api/agents`. Adds an `explain`-based
regression test asserting the public query no longer COLLSCANs.
Client:
- Rewrite `useListAgentsQuery` to follow the server's cursor
pagination internally and concatenate every page into a single flat
`AgentListResponse`. Consumers (`useAgentsMap`, `AgentSelect`,
`ModelSelectorContext`, `useMentions`) get the user's complete
accessible-agent set without any of them needing to learn about
cursors, and each individual request uses the server's default
page size (so the route's 100-default defense-in-depth fires for
real). Cache key shape is unchanged, so the eight mutation handlers
in `Agents/mutations.ts` keep matching `allAgentViewAndEditQueryKeys`
and update the cached list as before.
- Drop the `FULL_AGENT_LIST_LIMIT = 1000` injection added in the
previous commit — no longer needed once pagination handles the full
set, and removing it stops bypassing the route default.
* 🧹 fix: CI fallout from C-done-properly refactor
- Collapse multi-line `fetchAllAgentPages` signature in queries.ts so
prettier stops complaining.
- In the new public-principal index test, grant one ACL entry before
calling `.explain()` so the collection exists (otherwise mongo returns
`nonExistentNamespace` and there is no winning plan to inspect).
- Cast the `.explain('queryPlanner')` result to a typed shape — the
mongoose return type doesn't expose `queryPlanner` directly and was
failing the TypeScript check.
* 🧪 fix: Test the AclEntry public-principal index via hint, not planner choice
The previous test asserted the query planner did not pick COLLSCAN for
the public-principal lookup. That assertion fails on small collections
(under the planner's collection-size heuristic) — the index exists and
is usable, but with a single document in the test the planner correctly
chooses COLLSCAN as the cheaper plan.
Reshape the assertion:
1. Confirm the new compound index is actually declared by inspecting
`collection.indexes()` after `syncIndexes()`.
2. Force the planner to that index via `.hint()` and assert the winning
plan is `IXSCAN` — proves the index is real and serves this query
shape, without depending on collection-size heuristics.
* 🧹 chore: Slim down verbose comments
The JSDoc and inline comments added across the perf fix had drifted
into multi-paragraph rationale better suited to the PR description than
the source. Collapse to single-line JSDoc that just describes what each
piece does; drop the inline comment in `actions.js` entirely — the call
is self-evident.
* feat: add Claude Opus 4.8 support
* fix: omit sampling params for Claude Opus 4.8
* fix: flatten Bedrock beta header merge
* fix: strip Bedrock sampling params for Opus 4.8
* chore: Update @librechat/agents to version 3.1.93 and @langfuse packages to version 5.3.0 in package-lock.json and package.json files
* chore: Update browserify-sign to version 4.2.6 and qs to version 6.15.2 in package-lock.json
* feat: Add Bedrock API key support
* fix: Respect Bedrock credential mode
* fix: Support mixed Bedrock credential forms
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
* 🏗️ refactor: Derive App Version from Root package.json + Add buildInfo Schema
The hardcoded `Constants.VERSION` in `data-provider` is now replaced at
rollup build time via `@rollup/plugin-replace`, sourcing from the root
`package.json` so version bumps are a single-file change.
Adds the shape needed by the rest of the series:
- `interface.buildInfo` boolean flag (default `true`) — lets self-hosters
opt out of exposing commit/branch/date.
- `buildInfo` on `TStartupConfig` — commit/commitShort/branch/buildDate.
- `SettingsTabValues.ABOUT` — new settings tab enum value.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* 🛠️ feat: Add Build Metadata Resolver and Expose via /api/config
Adds `resolveBuildInfo()` in `@librechat/api` that surfaces commit SHA,
branch, and build date from (in order) `BUILD_*` env vars, then local git
metadata. Result is cached per-process.
`/api/config` includes a `buildInfo` field on both authenticated and
anonymous responses when `interface.buildInfo !== false` and at least one
resolver field is populated. Omitted entirely otherwise.
Designed so pre-built Docker images carry metadata via build-arg while
source installs pick it up from `.git` — no manual version tracking.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* ℹ️ feat: Add Settings → About Panel with Diagnostics Copy
New Settings tab that renders the running build's version, commit (short
SHA), branch, and build date in a monospaced block alongside a "Copy
diagnostics" button that emits a preformatted text blob for pasting into
support issues.
Tab is hidden when `interface.buildInfo` is set to `false`. Reads from
`startupConfig.buildInfo` provided by `/api/config`.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* 🐳 ci: Inject BUILD_COMMIT/BRANCH/DATE into Docker Images
Adds optional `BUILD_COMMIT`, `BUILD_BRANCH`, `BUILD_DATE` ARGs to both
`Dockerfile` and `Dockerfile.multi`, wired as `ENV` vars in the runtime
stage so the backend's `resolveBuildInfo` picks them up.
All image-publishing workflows (`tag`, `main`, `dev`, `dev-branch`,
`dev-staging`) now compute `${github.sha}`, `${github.ref_name}`, and a
UTC timestamp, then pass them to `docker/build-push-action` as
`build-args`.
Defaults are empty — non-CI builds (local `docker build`) still work,
and the backend falls back to local `.git` metadata if ARGs aren't set.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* 📝 docs: Direct Bug Reporters to Settings → About for Version Info
The previous instructions (`docker images | grep librechat`,
`git rev-parse HEAD`) only worked for a subset of deployments and
rarely produced a commit SHA for users pulling pre-built images.
Point users to the new in-app Settings → About panel's
"Copy diagnostics" button, which captures version, commit, branch,
build date, and user agent in a single preformatted block. Fallback
instructions preserved for older installs.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* 🐳 fix: Move BUILD_* ENV to End of Docker Stages to Preserve Layer Cache
Per-commit BUILD_COMMIT/BUILD_DATE changes were being promoted to ENV
before `npm ci` / `npm run frontend` (single-stage) and before
`npm ci --omit=dev` (multi-stage api-build), which invalidated the cache
for every subsequent layer on every CI run.
Move the ARG/ENV block below the heavy install and build steps in both
Dockerfiles. Metadata is still available in the runtime image but no
longer busts layer reuse.
Addresses codex review on #12756.
* 🔧 fix: Propagate interface.buildInfo=false to Unauthenticated /api/config
The unauthenticated branch of `/api/config` was emitting an `interface`
object only when `privacyPolicy` or `termsOfService` was set, which
meant an admin's explicit `interface.buildInfo: false` opt-out was never
visible to anonymous/guest clients. `Settings.tsx` gates the About tab
on `startupConfig?.interface?.buildInfo !== false`, so a missing field
fell through as "enabled" for those clients.
Include `interface.buildInfo: false` in the unauth payload whenever it's
explicitly disabled. Keep the implicit default (true) absent to preserve
the minimal-unauth-payload convention.
Addresses codex review on #12756.
* 🔀 ci: Trigger Dev Image Workflows on Root package.json + Dockerfile Changes
The baked `Constants.VERSION` now reads from the root `package.json` via
rollup-plugin-replace, but the `dev-images.yml` and `dev-branch-images.yml`
path filters only matched `api/**`, `client/**`, `packages/**`. A release
commit that only bumps root `package.json` would not trigger a rebuild,
leaving `latest` dev images with stale Footer/About version metadata.
Include `package.json`, `package-lock.json`, and both Dockerfiles in the
path filters so dependency changes (lockfile rebuilds) and image build
tweaks also rebuild dev images.
Addresses codex review on #12756.
* 🧽 fix: Harden About Panel Lifecycle, A11y, and Loading Gate
Review follow-ups on #12756:
- #1 timer leak: stash the copy-state `setTimeout` in a ref and clear it
from a `useEffect` cleanup so unmounting the Settings dialog mid-toast
doesn't fire `setCopied(false)` on an unmounted component.
- #3 flash of About tab: gate `aboutEnabled` on `startupConfig != null`
so the tab stays hidden until `/api/config` returns. For admins who
disabled `interface.buildInfo`, the tab no longer briefly appears and
vanishes on page load.
- #6 aria-live placement: move the live region off the interactive
button onto a dedicated `<span role="status" aria-live="polite">` so
screen readers announce the copied state, not the full button content
on every re-render.
- #2 missing coverage: add `About.spec.tsx` exercising populated/empty
buildInfo rendering, invalid-date handling, diagnostics clipboard
payload, copy-state toggling, unmount cleanup, and the live region.
* ⚡ perf: Eagerly Resolve Build Info at Module Load
Review follow-up #4 on #12756: `resolveBuildInfo()` calls `execFileSync`
with a 2s timeout on source installs without `BUILD_*` env vars. Paying
this cost on the first HTTP request blocks the event loop mid-flight.
Call `resolveBuildInfo()` once at config route module load so the
resolver's cache is warm before any request arrives. Docker images with
the BUILD_* env vars set sidestep the git path entirely, so this only
affects the edge case of source installs.
* 📝 docs: Document rollup Version Placeholder Contract
Review follow-ups #5 / #8 on #12756. The `__LIBRECHAT_VERSION__`
placeholder relies on a substring replacement rule that only works
because the token appears inside a string literal, and the substitution
only runs during `npm run build:data-provider`.
- Expand the `Constants.VERSION` JSDoc to spell out that consumers read
the placeholder through the built dist bundle; source-level test
imports would see the raw placeholder.
- Add a NOTE above the rollup `replace` config warning future
contributors not to repurpose the token as a bare identifier without
switching to a quoted replacement value.
Non-functional; prevents future contributors from stepping on a subtle
constraint.
* 🪪 fix: Only Toast "Copied" When Clipboard Copy Actually Succeeds
Codex R5 on #12756. `copy-to-clipboard` returns a boolean indicating
whether the underlying `execCommand('copy')` / fallback prompt actually
wrote to the clipboard. The previous handler flipped to the "Copied"
state unconditionally, which in hardened browsers or when the
permission prompt is dismissed would mislead users into filing bug
reports without the diagnostics blob attached.
Gate the state/timer/live-region on the boolean return; silently no-op
on failure rather than showing a false positive. Adds a test asserting
the button label stays at "Copy diagnostics" when the clipboard call
fails.
* 🐳 fix: Derive main image metadata from checkout
* 🪪 fix: Keep About enabled until disabled
* ✅ test: Avoid literal Settings mock text
* 🧱 refactor: Rename Build Info Module
* fix: allow OpenID PKCE authentication without client secret
* Linting
* Strategy fix
* fix(openid): trim secret gates and add PKCE client metadata tests
* chore(openid): normalize spec line endings
* ⚡ perf: Short-Circuit Config Override Resolution for Empty Principals (#12549)
Skip the getApplicableConfigs DB query when buildPrincipals returns
an empty array, since there are no principals to match against.
* ⚡ perf: Separate Error Handling for Principal Resolution vs Config Overrides (#12550)
Distinguish between buildPrincipals and getApplicableConfigs failures
so the uncached fallback to baseConfig is intentional and logged
separately from config override errors.
* Revert "⚡ perf: Separate Error Handling for Principal Resolution vs Config Overrides (#12550)"
This reverts commit 1729378a65.
* Revert "⚡ perf: Short-Circuit Config Override Resolution for Empty Principals (#12549)"
This reverts commit a100aa5738.
---------
Co-authored-by: CMF\e-leite <EduardoLeite@criticalmanufacturing.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
* feat: support data retention for normal chats
Add retentionMode config variable supporting "all" and "temporary" values.
When "all" is set, data retention applies to all chats, not just temporary ones.
Adds isTemporary field to conversations for proper filtering.
Adapted to new TS method files in packages/data-schemas since upstream
moved models out of api/models/.
Based on danny-avila/LibreChat#10532
Co-Authored-By: WhammyLeaf <233105313+WhammyLeaf@users.noreply.github.com>
(cherry picked from commit 30109e90b0)
* feat: extend data retention to files, tool calls, and shared links
Add expiredAt field and TTL indexes to file, toolCall, and share schemas.
Set expiredAt on tool calls, shared links, and file uploads when
retentionMode is "all" or chat is temporary.
(cherry picked from commit 48973752d3)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: lint/test
(cherry picked from commit 310c514e6a)
* fix: address code review feedback for data retention PR
Critical:
- Fix BookmarkMenu crash: restore optional chaining on conversation
- Fix migration hazard: backward-compatible sidebar filter that also
checks expiredAt for documents without isTemporary field
Major:
- Add logging to getRetentionExpiry error path, align with tools.js
- Add tests for retentionMode: ALL in saveConvo and saveMessage
- Fix share route: apply expiredAt for temporary chats too by
querying the conversation's isTemporary flag server-side
- Add assertions for getRetentionExpiry mocks in process tests
Minor:
- Fix ChatRoute isTemporaryChat to be strictly boolean via Boolean()
- Fix stale test description (expired -> temporary)
- Comment out retentionMode default in example yaml
- Simplify verbose if/else to isTemporary === true
- Add compound index on { user: 1, isTemporary: 1 }
- Remove narrating comment from process.spec.js
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6bad535f90)
* chore: fix typescript
(cherry picked from commit 826527a46b)
* fix: lint
(cherry picked from commit 77817e80ea)
* fix: use mockSanitizeArtifactPath in retention test
The 'getRetentionExpiry is called with the request object' test
referenced an undefined `mockSanitizeFilename` identifier, breaking
both lint (no-undef) and the test suite. Use the existing
`mockSanitizeArtifactPath` mock that the surrounding tests already
use, since `processCodeOutput` calls `sanitizeArtifactPath` (not
`sanitizeFilename`) before invoking `getRetentionExpiry`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit 52ea2da66d)
* fix: forward isTemporary from client for retention on file uploads and tool calls
Server-side `getRetentionExpiry` (file uploads) and the tool-call
controller both read `req.body.isTemporary`, but the file upload
multipart form and the tool-call payload did not include that field.
In `retentionMode: temporary` (default), files uploaded and tool
calls created from temporary chats were therefore retained
indefinitely.
Forward the Recoil `isTemporary` flag in both client paths so the
existing server checks can fire correctly. `ToolParams` gains an
optional `isTemporary` field.
Addresses Codex P1 review feedback on PR #29.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit 7e937df05a)
* test: stub store.isTemporary in useFileHandling test mocks
Previous commit added `useRecoilValue(store.isTemporary)` to the
hook. The test file mocks `~/store` with only `ephemeralAgentByConvoId`
and does not stub `useRecoilValue`, so all 7 cases threw
"Invalid argument to useRecoilValue: expected an atom or selector but
got undefined". Add a stub default export with `isTemporary` and a
`useRecoilValue` mock returning `false`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit eb1609537d)
* fix: harden data retention semantics
* fix: provide sweep request context for expired files
* fix: preserve temporary flags in all-retention updates
* fix: honor assistant versions in retention sweeps
* fix: retain non-temporary flags in all mode
* fix: hide expired retained records
* fix: propagate retained conversation expiry
* fix: refresh meili retention cutoff
* fix: prevent overlapping file sweeps
* fix: show legacy retained conversations
* fix: index legacy retained records
* fix: harden retention cleanup edge cases
* fix: count failed file storage sweeps
* fix: preserve legacy temporary retention
* fix: assign retention sweep worker deterministically
* fix: hide expired shared links on reads
* fix: prevent retention refresh after parent expiry
* fix: break code output retention import cycle
* fix: harden retention review findings
* fix: ignore expired share duplicates
* fix: reject expired retained share creation
* fix: harden retention review edge cases
* fix: address retention audit findings
* fix: enforce expired conversation shares in all retention
* fix: scope temporary upload flag to chat files
* fix: address retention review findings
* fix: address codex retention review findings
* fix: tighten missing storage detection
* test: remove unused file process spec bindings
---------
Co-authored-by: WhammyLeaf <233105313+WhammyLeaf@users.noreply.github.com>
Co-authored-by: Aron Gates <aron@muonspace.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* 📦 chore: npm audit fix 2026-05-18
- Added @js-sdsl/ordered-map version 4.4.2
- Updated @librechat/agents to version 3.1.87
- Upgraded @opentelemetry/sdk-node to version 0.218.0
- Added new dependencies for gRPC and OpenTelemetry exporters
* 🔧 chore: Update @librechat/agents to version 3.1.87 in package-lock.json and package.json files
* 🔧 chore: Upgrade @opentelemetry/sdk-node to version 0.218.0 in package.json and package-lock.json
Four jest mocks for `winston` in the test suite return the wrong shape:
api/test/__mocks__/logger.js (returns inner fn directly)
packages/api/src/agents/__tests__/memory.test.ts (`format` is a plain object)
packages/api/src/agents/__tests__/run-summarization.test.ts (same)
packages/api/src/agents/__tests__/initialize.test.ts (same)
Real `winston.format(fn)` returns a Format constructor whose instances
expose a `.transform(info, opts)` method that winston's pipeline calls
with the log info object. The current mocks collapse this:
- `(fn) => fn` returns the inner transform fn directly. When module-load
code in `@librechat/data-schemas/dist/config/parsers.cjs:52` does
`const redactFormat = winston.format((info) => ...)`, `redactFormat`
becomes the inner fn. The next line in `winston.cjs` calls
`parsers.redactFormat()` which invokes the inner fn with no `info`,
throwing `TypeError: Cannot read properties of undefined (reading 'level')`.
- `format: { combine, colorize, simple }` makes `winston.format` not
callable at all — `winston.format((info) => ...)` throws
`TypeError: winston.format is not a function`.
These currently pass in CI on GitHub Actions Ubuntu / Node 20.19, but
fail reproducibly on Node 24.x and on some Linux distros (verified on
WSL Ubuntu with Node 24.9.0). The CI passes appears to be environmental
luck around jest's mock-hoisting interaction with the workspace symlink
chain — the mocks are genuinely wrong against the data-schemas contract.
The fix: return a thunk that yields `{ transform: fn }` — matches real
winston's shape just enough that module-load completes; the inner fn is
only ever invoked by winston's pipeline (never at load time). Also adds
the full `winston.format.*` method surface (printf, timestamp, errors,
splat, json) plus `addColors` and the `DailyRotateFile`/`File` transports
that data-schemas's dist code references at module-load.
Verification (Node 24.9.0):
npm run build:data-provider && npm run build:data-schemas && npm run build:api
cd packages/api && npx jest src/agents/__tests__/{memory,run-summarization,initialize}.test.ts
→ 3 suites, 106 tests, all pass
No production code or behavior changes — test-only patch.
Co-authored-by: Jorge Costa <8352477+JorgeCosta87@users.noreply.github.com>
* 📦 chore: Bump `@librechat/agents` to v3.1.86 in package-lock.json and package.json files
* 📦 chore: Update dependencies in package-lock.json to latest versions, including @protobufjs/codegen, @protobufjs/inquire, @protobufjs/utf8, and protobufjs
* 📦 chore: Add `librechat-data-provider` dependency in package.json and package-lock.json, and update build dependencies in turbo.json
* 📦 chore: Update @librechat/agents to version 3.1.85 in package-lock.json and package.json files
* 📦 chore: Update mermaid to version 11.15.0 in package.json and package-lock.json