* 🧠 feat: Configurable Reasoning Replay for Custom Endpoints
Adds customParams.includeReasoningContent so OpenAI-compatible custom endpoints (e.g. Xiaomi MiMo, Kimi) can replay reasoning_content on tool-call turns natively, without impersonating the moonshot provider.
* 🔁 feat: Replay reasoning_content across turns for opted-in custom endpoints
Extends the DeepSeek reasoning-content format spoof to honor customParams.includeReasoningContent, so custom OpenAI-compatible endpoints (Xiaomi MiMo, Kimi) reconstruct reasoning_content from persisted history on later turns, matching DeepSeek thinking-mode parity. Adds shouldReplayReasoningContent predicate (tested) and surfaces the flag on the initialized agent.
* 🪢 refactor: Split within-run vs cross-turn reasoning replay flags
moonshot only replays reasoning_content within a run's tool calls, not across turns. Decouples the two: includeReasoningContent = within-run replay (exact moonshot parity), new includeReasoningHistory = cross-turn reconstruction from persisted history (implies includeReasoningContent, since reconstruction is a no-op without the within-run replay flag).
* 🩹 fix: Apply reasoning replay across all param-format branches
Move the within-run includeReasoningContent application out of the OpenAI-only branch in getOpenAIConfig to after the branch dispatch, so custom endpoints using anthropic/google defaultParamsEndpoint gateway modes also honor includeReasoningContent/includeReasoningHistory. Addresses Codex finding.
* chore: Update @librechat/agents to v3.2.46
* 🧽 refactor: De-spoof reasoning replay via explicit preserveReasoningContent
Now that @librechat/agents 3.2.46 exposes an explicit preserveReasoningContent option on formatAgentMessages, pass it directly instead of impersonating provider: deepseek. Behavior is unchanged (shouldReplayReasoningContent still gates DeepSeek + the custom includeReasoningHistory flag); also corrects the comment to reference includeReasoningHistory.
* 🌳 fix: Walk subagents in the reasoning-history replay gate
The gate only checked the primary agent and top-level handoff/parallel configs, so an opted-in custom endpoint used solely as a nested subagent had its persisted reasoning dropped on later turns. New exported anyAgentReplaysReasoningContent walks subagentAgentConfigs (cycle-safe, mirrors anyAgentHasCodeEnv); client.js uses it. Addresses Codex finding.
* 🔧 chore: Update dependencies in package-lock.json and package.json
Bump `form-data` to version 4.0.6 and update `hasown` and `mime-types` dependencies in package-lock.json. Add an `overrides` section in package.json to ensure compatibility with the new `form-data` version.
* 📦 chore: Bump `@librechat/agents` to v3.2.42
* fix(agents): use `path` for read/write/edit/create file tools
Pairs with @librechat/agents renaming the read_file/write_file/edit_file tool
parameter from `file_path` to `path` (models — esp. Kimi K2 — emit `path` far
more reliably, and it matches grep/glob/list_directory which already use `path`).
- tools.ts: LibreChat's own code/skill file-tool schemas use `path`
(the skill read_file tool inherits the SDK definition, which is already renamed)
- handlers.ts: read `args.path` for the model-facing tool arg + error messages
- the internal host `readSandboxFile`/`writeSandboxFile` contract is unchanged
- tests updated
Requires @librechat/agents with the param rename (danny-avila/agents#250). All
agents unit suites green (175).
* chore: update @librechat/agents to v3.2.41 and bump related dependencies in package-lock.json and package.json files
* fix(api): Refactor header merging in MCPConnection to use Object.assign for clarity
* test(e2e): mock emits `path` for create/edit file-authoring tools
The mock LLM still sent `file_path` for the create_file/edit_file calls, which the
renamed handlers no longer read -> the skill-file-authoring e2e failed with
'Expected skill to be persisted'. Switch the fixture to `path` to match the tools.
(The internal readSandboxFile/writeSandboxFile contract stays on `file_path`, so
api/server/services/Files/Code/process.js and its spec are unchanged.)
* 🔧 chore: Update `@librechat/agents` to v3.2.38 and bump related dependencies in package-lock.json and package.json files
* 🔧 chore: Upgrade `multer` dependency to version 2.2.0 in package-lock.json and package.json
* 🔧 chore: Upgrade `nodemailer` dependency to version 9.0.1 in package-lock.json and package.json
* 🔧 chore: Upgrade `@aws-sdk/client-bedrock-agent-runtime` and `@aws-sdk/client-bedrock-runtime` to versions 3.1071.0, update related dependencies in package-lock.json and package.json
* 🔧 chore: Upgrade `form-data` to version 4.0.6 and `hono` to version 4.12.25, update related dependencies in package-lock.json and package.json
* 🔧 chore: npm audit fix
* 🔧 chore: Remove unused Babel dependencies from package-lock.json and package.json
* 🔧 chore: Add '@mistralai/mistralai' to esModules in Jest configuration files
* 🛡️ fix: Bound object-traverse against DAG fan-out and shared refs
Detect cycles via the ancestor chain (so shared, non-circular references in sibling branches / DAGs are traversed correctly) and add defensive maxNodes (100k) / maxDepth (100) caps. The removed global visited set was implicitly bounding work at O(distinct nodes); ancestor-chain-only detection is O(root-to-node paths), exponential on DAGs (a depth-24 diamond went from 26 to 50M visits / 1.6s of synchronous work). The caps bound it to ~9ms while leaving normal traversal untouched. Adds a spec covering shared refs, cycles, DAGs, and both bounds. The lone consumer, debugTraverse, inherits the defaults with no change.
* 🪵 refactor: Remove legacy api/config logger duplicate
The api/config winston logger was a stale parallel implementation of the canonical @librechat/data-schemas logger, with unbounded redaction (regex-only redactFormat, npm traverse-based debugTraverse). Its winston instance and the logger export from api/config/index.js had zero consumers — every ~/config importer uses the MCP/flow-manager exports. The only live tie was ToolService's use of redactMessage.
Re-export redactMessage from @librechat/data-schemas (behaviorally identical, a superset of the regex set), point ToolService at it, delete api/config/winston.js and api/config/parsers.js, drop the dead logger export, and remove the orphaned ~/config/parsers mock from the global test setup.
* 🧹 chore: Drop orphaned traverse dep and stale legacy logger tests
Deleting api/config/{winston,parsers}.js left the npm 'traverse' package unused in api/package.json (flagged by the detect-unused-packages CI check) and orphaned two tests that imported the deleted modules. Remove the traverse dependency (sync package-lock), and delete api/config/__tests__/{parsers,logToFile}.spec.js — the canonical logger's behavior is covered by packages/data-schemas/src/config/parsers.spec.ts.
* 🩹 fix: Make object-traverse caps bound work and survive update()
Address Codex review: (1) break the child loops as soon as the node budget is spent and iterate objects via for...in instead of materializing Object.entries/Object.keys, so maxNodes actually bounds work for wide arrays/objects; (2) detect ancestor cycles against an immutable original-node stack rather than context.node, which a callback's update() can reassign (the debug formatter rewrites array nodes in place). Adds tests for the wide-array bound and the update()-cycle case.
* 🎚️ fix: Tighten object-traverse defaults to a ~1ms log budget
Lower maxNodes 100000 -> 2500 and maxDepth 100 -> 5. Measured cost is ~140ns/node with the debug formatter callback, so 2500 nodes keeps a single log under ~1ms even on slower prod hardware; real log objects are ~25-30 nodes at depth 3-4, leaving ample headroom. maxNodes is the fan-out/cost lever; maxDepth bounds recursion and output readability (depth-5 covers typical logs, deeper renders compactly).
* 🪙 feat: Context-usage projection — data-provider + client wiring
Consumer side of the SDK-aligned context projection (agents
`projectAgentContextUsage`). Adds the `/api/endpoints/context-projection`
data-provider plumbing (endpoint, service, query key, `TContextProjectionRequest`)
and a `useContextProjectionQuery` gated to fire only when no fresh snapshot
covers the viewed branch.
Wires `useTokenUsage` precedence to: live snapshot → fresh persisted snapshot
(window matches the resolved one) → server projection → per-message estimate.
A model/window switch marks the baked snapshot stale (its `maxContextTokens`
no longer matches) and falls to the projection — closing the gauge's
window-switch (G1) and snapshot-less-branch (G2) gaps. Snapshot and projection
share the render-relevant fields, so they render uniformly.
Backend endpoint + agents version bump land in follow-up commits. Includes the
design spec (CONTEXT_PROJECTION_SPEC.md).
* 🪙 feat: Context-projection backend endpoint
POST /api/endpoints/context-projection → resolveContextProjection (packages/api):
reconstructs the viewed branch (parent-chain walk from messageId), resolves the
agent config (instructions/provider/model/maxContextTokens), reuses LibreChat's
stored per-message tokenCounts as the index map (no re-tokenizing), and calls
the agents SDK projectAgentContextUsage — no model call. Thin controller injects
db.getMessages/db.getAgent; route mirrors /token-config.
First cut targets message-windowing accuracy; tool-schema tokens are deferred to
a follow-up that reuses the full initializeAgent path.
* 🩹 fix: Codex review on context projection (G1 guard, IDOR, recount, summary)
- Guard `currentActive` against a stale window: a model/window switch on the
current branch left the live snapshot outranking the projection (G1 didn't
fire). Now defers to the projection unless streaming or the window matches.
- Scope branch lookups to the authenticated user (`getMessages` filter +
injected `userId`) — was loading any conversation by id (IDOR).
- Recount messages with no stored `tokenCount` via the tokenizer instead of
charging 0, so snapshot-less/imported histories don't under-report.
- Fall back (null) for already-summarized branches rather than projecting from
the full raw parent chain (the next call would send summary + tail); the
client's summary-baseline-aware estimate handles them until a follow-up
replays the summary boundary.
* 🩹 fix: Codex round 2 — drop agent load, summary marker, edit-invalidation
- Stop loading agent/model-spec config server-side (closes the agent-access
IDOR and the spec-prompt special-casing). Provider/model/window now come from
the client-resolved request (`limits.endpoint`/model — the agent's real
provider, not the `agents` endpoint, so the tokenizer is right). Agent/spec/
promptPrefix instructions are uniformly deferred to the full-fidelity follow-up.
- Detect summarized branches via the live path's `metadata.summaryUsedTokens`
marker (was the wrong `summaryTokenCount` field) and fall back to the
summary-aware estimate.
- Invalidate the projection query on in-place message edits via a branch
content `revision` in the cache key (the tail id is unchanged on edit).
Deferred (valid, not a regression): same-window endpoint/model switch keeps a
window-matched snapshot — needs endpoint/model persisted on the snapshot, which
lands with the fidelity follow-up. Smoke-tested: fits / prunes / summarized→null
/ no-window→null.
* 🛡️ fix: make context projection strictly additive (no-regression)
Revert the G1 window-match guard on the live/branch snapshot. When no explicit
maxContextTokens is set (the common default), the SDK's snapshot window is
reserve-derived (~0.9·(modelContext − maxOutputTokens)) while useTokenLimits
resolves the raw model context — so `snapshot.maxContextTokens === resolvedMax`
is false for the SAME model, and the guard would wrongly drop a valid
current-branch snapshot to projection/estimate post-stream (a regression in the
default case, per initialize.ts:1240-1243).
The projection now activates ONLY for snapshot-less branches (G2): the
precedence is live snapshot → persisted branch snapshot → projection → estimate,
where the first two are byte-for-byte the prior behavior and the projection just
slots ahead of the estimate. Window/model-switch (G1) detection needs the
snapshot to carry its model/window and defers to the fidelity follow-up.
* 🩹 fix: surface projections as estimates, not authoritative snapshots
A first-cut projection carries the SDK's windowing but omits instruction/tool
overhead, so rendering it as `isEstimate: false` showed a confident under-count
for snapshot-less branches. Mark projection-sourced views `isEstimate: true` +
`snapshotActive: false` (and drop the snapshot field) so they present as a
better estimate than sumBranch — improved used/window number, estimate framing,
no misleading granular breakdown with ~0 tools. Real snapshots stay
authoritative. (Codex round 3, projection.ts:139.)
* 🧹 chore: drop CONTEXT_PROJECTION_SPEC.md from the PR
* 🎨 style: fix import-sort order in projection.ts (CI sort-imports check)
* 🔧 chore: update @librechat/agents dependency to version 3.2.36 in package-lock.json and related package.json files
* chore: npm audit fix
* 🎨 style: fix import-sort order in data-service.ts (CI sort-imports check)
* 🩹 fix: drop dead calibrationRatio in projectionParams (tsc never error)
Inside the ternary, branchSnapshot is narrowed to null (the gate is
), so accessed a
property on (frontend typecheck failure). It was also dead — there is
never a snapshot to seed from in this branch — so just remove it.
* Revert "chore: npm audit fix"
This reverts commit 4cdb862d0c.
* 🧾 fix: Bill Subagent Child-Run Model Usage in Parent Transactions
* 🩹 fix: Type Subagent Usage Sink Structurally Until SDK Release
* 🔧 chore: Update @librechat/agents dependency to version 3.2.35 in package-lock.json and related package.json files
* 📬 feat: Report Tool Results Per Call via onResult Channel
Tool batches already execute in parallel here, but results were only
delivered to the agent graph through the single resolve(results[])
call — so a fast tool's completion event waited on the slowest call
in the batch. Report each result through the optional onResult channel
(agents SDK > 3.2.33) as it settles, letting the graph emit that
call's completion immediately. resolve remains the authoritative batch
outcome; the callback is optional-chained, so this is a no-op until
the SDK release lands and remains backward compatible after.
* 🧹 chore: Prettier Formatting in onResult Spec
* 🧹 chore: Sort Imports in handlers.ts
* 🔧 chore: Update @librechat/agents dependency to version 3.2.34 in package-lock.json and related package.json files
Keep the Google Gen AI SDK aligned with the latest 2.x release. Updates the
declared range in both backend manifests (api, packages/api) and regenerates
the lockfile to resolve @google/genai to 2.8.0.
No application code changes: the sole consumer
(api/app/clients/tools/structured/GeminiImageGen.js) uses the stable
`GoogleGenAI` constructor and `models.generateContent` API, and the upstream
changelog records no breaking changes to those between 2.0 and 2.8.
Closes#13551
The tsdown migration (#13595) externalizes all third-party imports
(Rollup inlined them), so several modules the api source imports must be
present at runtime. Six were not, causing production (`npm ci --omit=dev`)
to crash on boot with `Cannot find module 'get-stream'` (then the next).
Fixed following the package's existing convention — packages/api declares
runtime libs as `peerDependencies`, and the `/api` app provides them as
real `dependencies` (how express/mongoose/sharp already resolve):
- `api/package.json` (the prod app, the provider): add the 3 that were
missing — `get-stream`, `jszip`, `mongodb`. (`dedent`/`lodash`/`nanoid`
were already provided by /api.)
- `packages/api/package.json`: add all 6 to `peerDependencies` (the
contract) and to `devDependencies` (workspace build/tests), matching
the existing `mammoth`/`pdfjs-dist`/`sanitize-html` dev+peer pattern.
`jszip`/`mongodb` move out of dev-only (were pruned in production).
Pinned to CJS-compatible majors (get-stream@6, nanoid@3). Verified the
built bundle has zero undeclared externals and the 3 newly-provided deps
are production (non-dev) in the lockfile, so they survive `--omit=dev`.
* 🔧 chore: Update ESLint config, add import sorting script, Test Sharding, Bump `@librechat/agents`
* Change 'no-nested-ternary' rule from 'warn' to 'error' in ESLint config
* Add new scripts for sorting imports in the project
* Update lint-staged configuration to include import sorting
* Modify GitHub Actions workflows to support sharding for unit tests
* chore: remove nested ternary expressions
* refactor: Extract scale multiplier logic into a separate function in CircleRender component
* refactor: Simplify auto-refill rendering logic in Balance component for better readability
* refactor: Improve width style handling in DataTable components for clarity and maintainability
* chore: remove CircleRender component
* delete: Remove CircleRender component as it is no longer needed in the project
* chore: Bump @librechat/agents to version 3.2.31 and update Node.js engine requirement
* Update @librechat/agents dependency from 3.2.2 to 3.2.31 in package-lock.json, api/package.json, and packages/api/package.json
* Change Node.js engine requirement from >=20.0.0 to >=24.0.0 in @librechat/agents
* chore: Add import sorting check to ESLint CI workflow
* Implement a new job in the GitHub Actions workflow to verify import ordering on changed files.
* The job checks for changes in specific file types and reports any import order drift, providing instructions for local fixes.
* 🎭 test: Run Mock E2E Suite Through createRun With In-Process Fake Model
Replace the standalone HTTP mock LLM server with an in-process fake model
injected into the real createRun -> Run.create pipeline via
run.Graph.overrideTestModel, so the mock suite exercises the agents
integration end-to-end without a live provider or a separate server.
- Bump @librechat/agents to 3.2.2 for the FakeChatModel/createFakeStreamingLLM exports
- Add an env-gated applyTestRunHook seam in packages/api createRun (no /api changes)
- Add e2e/setup/fake-model.js to drive default replies + the skill-authoring tool-call flow
- Drop the mock-llm webServer from playwright.config.mock.ts and set LIBRECHAT_TEST_RUN_HOOK
* 🧹 test: Retire Standalone Mock LLM Server From E2E Recorder
Migrate the `--profile=mock` recorder onto the same in-process fake model
as the Playwright mock suite, then delete the now-unused HTTP mock server
so the fake-LLM logic lives in a single place.
- Point record.js mock profile at the fake model via LIBRECHAT_TEST_RUN_HOOK
- Remove the mock-llm-server spawn/wait and MOCK_LLM_PORT plumbing from record.js
- Delete e2e/setup/mock-llm-server.js (e2e/setup/fake-model.js is now the only source)
- Update e2e/README.md to describe the in-process fake LLM
* 🏷️ ci: Rename Playwright Mock E2E Check to Playwright E2E Tests
* 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
* 📦 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
* 📦 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
* 🧱 refactor: typed CodeEnvRef + kind discriminator + tenant-aware sandbox cache
Final cutover for the LibreChat ↔ codeapi sandbox file identity. Replaces
the magic string `${session_id}/${file_id}?entity_id=...` with a typed,
discriminated `CodeEnvRef`. Pre-release lockstep deploy with codeapi
#1455 and agents #148; no legacy aliases retained.
## Final shape
```ts
type CodeEnvRef =
| { kind: 'skill'; id: string; storage_session_id: string; file_id: string; version: number }
| { kind: 'agent'; id: string; storage_session_id: string; file_id: string }
| { kind: 'user'; id: string; storage_session_id: string; file_id: string };
```
`kind` drives codeapi's sessionKey: `<tenant>:<kind>:<id>[✌️<version>]`
for shared kinds, `<tenant>:user:<userId>` for user-private (auth context
provides `userId`). `version` is statically required for `kind: 'skill'`
and forbidden otherwise via discriminated union — constraint holds at
compile time on every consumer, not just codeapi's runtime validator.
`id` is sessionKey-meaningful for `'skill'` / `'agent'`; informational
only for `'user'` (codeapi resolves user identity from auth context).
## What changed
- `packages/data-provider/src/codeEnvRef.ts` — discriminated union +
`CODE_ENV_KINDS` const-tuple keeps the runtime list and TS union
locked together.
- Schemas: `metadata.codeEnvRef` and `SkillFile.codeEnvRef` enums
tightened to `['skill', 'agent', 'user']`.
- `primeSkillFiles` writes `kind: 'skill'`, `id: skill._id`,
`version: skill.version`. Cache-hit path reads `codeEnvRef`
directly. Bumping `skill.version` on edit naturally invalidates
the prior cache entry under the new sessionKey.
- `processCodeOutput` writes `kind: 'user'`, `id: req.user.id`. Output
bucket is always user-scoped, regardless of which skill the
execution invoked. New regression test pins the asymmetry.
- `primeFiles` reupload preserves `kind`/`id`/`version?` from the
existing ref so a skill-cache-miss reupload doesn't silently demote
to user bucket.
- `crud.js` upload functions (`uploadCodeEnvFile` /
`batchUploadCodeEnvFiles`) thread `kind`/`id`/`version?` to the
multipart form (codeapi #1455 option α). Without these on the wire,
codeapi falls back to user bucketing and skill-cache invalidation
never fires. Client-side validation mirrors codeapi's validator.
- `Files/process.js` — chat attachments use `kind: 'user'`; agent
setup files use `kind: 'agent'`.
- Drops `entity_id` everywhere (struct, schema sub-docs, write paths,
upload form fields). Drops `'system'` from the kind enum (no emitter
ever existed).
## Test plan
- [x] `cd packages/data-provider && npx jest src/codeEnvRef.spec` — 4 / 4
- [x] `cd packages/data-schemas && npx jest` — 1447 / 1447
- [x] `cd packages/api && npx jest src/agents` — 81 / 81 in skillFiles +
handlers + resources
- [x] `cd api && npx jest server/services/Files server/controllers/agents` —
436 / 436
- [x] `cd api && npx jest server/services/Files/Code` — 98 / 98 (incl.
new "outputs are user-scoped regardless of which skill the execution
invoked" regression and "reupload forwards kind/id/version from
existing ref")
- [x] `npx tsc --noEmit -p packages/data-{provider,schemas}/tsconfig.json
&& npx tsc --noEmit -p packages/api/tsconfig.json` — clean (only
pre-existing unrelated dev errors in storage/balance, untouched here)
## Deploy notes
- **24h cache-miss burst** on first deploy. Inputs (skill caches re-prime
under new sessionKey shape) and outputs (any pre-Phase C skill-output
cached files become unreadable). Bounded by codeapi's 24h TTL.
- **Lockstep with codeapi #1455 and agents #148.** Either repo can land
first since no aliases to drain, but the three deploys must overlap
within the same maintenance window.
- **`@librechat/agents` bump to `3.1.79-dev.0`** required after agents
#148 lands and is published.
## What this enables
Auth bridge work (JWT-based tenant/user identity between LC and codeapi)
— codeapi now derives sessionKey purely from `req.codeApiAuthContext.{
tenantId, userId}`, so the next chapter is replacing the header-asserted
user identity with a verified-claim path.
* 🩹 fix: persist execute_code uploads under codeEnvRef metadata key
Codex review P1 (chatgpt-codex-connector). `Files/process.js` was
storing the upload result under `metadata.fileIdentifier` even though:
- `uploadCodeEnvFile` now returns `{ storage_session_id, file_id }`,
not the legacy magic string.
- The post-cutover schema (`File.metadata.codeEnvRef`) only declares
`codeEnvRef` — mongoose strict mode silently strips unknown keys.
- All readers (`primeFiles`, `getCodeFilesByIds`,
`categorizeFileForToolResources`, controller filtering) check
`metadata.codeEnvRef`.
Net effect of the bug: chat-attached and agent-setup execute_code files
would lose their sandbox reference on save, and primeFiles would skip
them on subsequent code-execution turns — the file blob would still be
available locally but never re-mounted in the sandbox.
Fix: construct the full `CodeEnvRef` (`{ kind, id, storage_session_id,
file_id }`) at the write site and persist under `metadata.codeEnvRef`.
`BaseClient`'s "is this a code-env file" presence check accepts the new
shape alongside the legacy `fileIdentifier` for back-compat with any
pre-cutover records still in the database. Mirrors the same change in
`processAttachments.spec.ts` (which re-implements the BaseClient logic
for testability).
New regression tests in `process.spec.js` cover three cases:
- chat attachments (`messageAttachment=true`) → `kind: 'user'`
- agent setup (`messageAttachment=false`) → `kind: 'agent'`
- legacy `fileIdentifier` key is NOT persisted (would be schema-stripped)
* 🩹 fix: read storage_session_id on primed file refs (Codex P1)
Codex review (chatgpt-codex-connector). After Phase B's per-file
`session_id` → `storage_session_id` rename, `primeFiles` emits the
new field — but `seedCodeFilesIntoSessions` was still reading
`files[0].session_id` for the representative session and `f.session_id`
for the dedupe key. In runs with only primed attachments (no skill
seed), `representativeSessionId` was `undefined`, the function
returned the unchanged map, and `seedCodeFilesIntoSessions` silently
dropped the entire batch. The first `execute_code` call then started
without `_injected_files` and the agent couldn't see prior-turn
artifacts.
Fix:
- `codeFilesSession.ts`: read `f.storage_session_id` for both the
dedupe key and the representative session id. JSDoc updated to
match the new field name.
- `callbacks.js`: the two output-file persistence paths read
`file.session_id` to pass to `processCodeOutput` — switch to
`file.storage_session_id`. The original comment explicitly says
this should be the STORAGE session, which is exactly the field
Phase B renamed.
- `codeFilesSession.spec.ts`: fixture builder uses `storage_session_id`
and `kind: 'user'` to match the post-cutover `CodeEnvFile` shape.
Lockstep coordination: this matches the post-bump shape of
`@librechat/agents` 3.1.79+. CI tsc errors against the currently-pinned
3.1.78 are expected and resolve when the dep bumps in this PR before
merge.
* 📦 chore: Bump `@librechat/agents` to version 3.1.80-dev.0 in package-lock and package.json files
* 🪪 fix: thread kind/id/version through codeapi /download URLs (Phase C α)
Symmetric fix for the upload-side wire change in 537725a. Codeapi's
`sessionAuth` middleware now requires `kind`/`id`/`version?` on every
download/freshness URL — without them it 400s with "kind must be one
of: skill, agent, user" before serving the file.
Three sites construct codeapi-side URLs that go through `sessionAuth`:
- `processCodeOutput` (`Files/Code/process.js`): `/download/<sess>/<id>`
for freshly-generated sandbox outputs. Always `kind: 'user'` +
`id: req.user.id` — code-output files are always user-private,
regardless of which skill the run invoked.
- `getSessionInfo` (`Files/Code/process.js`): `/sessions/<sess>/objects/<id>`
for the 23h freshness check. Pulls kind/id/version straight off the
`codeEnvRef` already in scope — skill files stay skill-bucketed,
user files stay user-bucketed.
- `/code/download/:session_id/:fileId` LC route (`routes/files/files.js`):
proxies to codeapi for manual downloads. Code-output files only on
this route, so `kind: 'user'` + `id: req.user.id`.
The `getCodeOutputDownloadStream` helper in `crud.js` now takes an
`identity` param, validated by a `buildCodeEnvDownloadQuery` helper
that mirrors `appendCodeEnvFileIdentity`'s shape rules: kind required
from the closed `{skill, agent, user}` set, version required for
'skill' and forbidden otherwise. Bad callers fail fast on the client
instead of round-tripping a 400.
Also cleans up two log-noise sources reported alongside the 400:
- `logAxiosError` in `packages/api/src/utils/axios.ts` was dumping
`error.response.data` raw. With `responseType: 'arraybuffer'` that's
a `Buffer` (~4 chars per byte after JSON-serialization); with
`responseType: 'stream'` it's a `Readable` whose internal state
serializes the entire ring buffer + socket. New `renderResponseData`
decodes small buffers as UTF-8 (truncated past 2KB) and stubs streams
as `'[stream]'`. Diagnostics stay useful, log lines stop being
megabytes.
- `/code/download` route's catch was bare `logger.error('...', error)`,
bypassing the redactor. Switched to `logAxiosError` so it benefits
from the same buffer/stream handling.
Tests updated to match the new contract:
- crud.spec: `getCodeOutputDownloadStream` fixtures pass `userIdentity`;
new cases cover skill identity (with version), bad kind rejection,
skill-without-version rejection.
- process.spec: `getSessionInfo` test passes a full `codeEnvRef` object.
* ♻️ refactor: extract codeEnv identity helpers into packages/api
Per the project convention that new backend code lives in TypeScript
under `packages/api`, moves `appendCodeEnvFileIdentity` and
`buildCodeEnvDownloadQuery` from `api/server/services/Files/Code/crud.js`
into a new `packages/api/src/files/code/identity.ts` module.
Both helpers are pure validators that mirror codeapi's
`parseUploadSessionKeyInput` server-side rules (closed kind set,
`version` required for `'skill'` and forbidden otherwise) — they
deserve TS support and a dedicated spec rather than living as
JSDoc-typed helpers in the legacy `/api` workspace. The new module:
- Exports a `CodeEnvIdentity` interface using the
`librechat-data-provider` `CodeEnvKind` discriminated union.
- Adds 13 unit tests in `identity.spec.ts` covering the validation
matrix (skill+version, agent, user, and every rejection path) plus
URL encoding for the download query.
- Re-exported from `packages/api/src/files/code/index.ts` alongside
`classify`, `extract`, and `form`.
Consumer updates:
- `api/server/services/Files/Code/crud.js`: drops the local helpers
and imports them from `@librechat/api`. Net -64 lines.
- `api/server/services/Files/Code/process.js`: same.
- Test mocks for `@librechat/api` in three spec files now stub the
helpers' validation behavior locally rather than pulling them
through `requireActual` (which would drag in provider-config
init-time side effects). The package's `exports` field only
surfaces the root barrel, so leaf imports aren't reachable from
legacy `/api` test setup.
No runtime behavior change. Identity validation rules and emitted
form/query shapes are byte-for-byte identical pre/post.
* 🪪 fix: emit resource_id alongside id on _injected_files (skill 403 fix)
Companion to codeapi #1455 fix and agents 3.1.80-dev.1 — the wire
shape for shared-kind files now requires `resource_id` distinct from
the storage `id`. Without this LC change, codeapi's sessionKey
re-derivation on every shared-kind /exec rejects with 403
session_key_mismatch:
cached: legacy:skill:69dcf561...✌️59 (signed at upload, skill _id)
derived: legacy:skill:ysPwEURuPk-...✌️59 (storage nanoid)
Emit sites updated:
- `primeInvokedSkills` cache-hit path: `resource_id: ref.id` (the
persisted skill `_id` from `codeEnvRef.id`); `id: ref.file_id`
unchanged (storage uuid).
- `primeInvokedSkills` fresh-upload path: `resource_id: skill._id.toString()`
on every primed file (the `allPrimedFiles` builder type now carries
the field).
- `processCodeOutput`'s `pushFile` (Code/process.js): `resource_id: ref.id`
— for `kind: 'user'` this is informational (codeapi derives
sessionKey from auth context) but emitted for shape uniformity
with shared kinds.
Bumps `@librechat/agents` to `^3.1.80-dev.1` (the version that
ships the matching `CodeEnvFile.resource_id` field).
## Test plan
- [x] `cd packages/api && npx jest src/agents` — 67 / 67 pass
(skillFiles fixtures updated to assert `resource_id` on the
emitted CodeSessionContext.files).
- [x] `cd api && npx jest server/services/Files server/controllers/agents` —
445 / 445 pass (process.spec fixtures updated for the reupload
+ cache-hit emission).
- [x] `npx tsc --noEmit -p packages/api/tsconfig.json` — clean.
* fix(skill-tool-call): carry resource_id through primeSkillFiles → artifact
Codeapi was 400ing every /exec following a `handle_skill` tool call
with `resource_id is invalid` (`type: 'undefined'`). Both code paths
in `primeSkillFiles` (cache-hit + fresh-upload) returned files
without `resource_id`/`kind`/`version`, and the artifact in
`handlers.ts` forwarded the stripped shape into
`tc.codeSessionContext.files` → `_injected_files`.
`primeInvokedSkills` (the NL-detected loader) had already been fixed
end-to-end; this commit aligns the tool-invoked path with the same
contract: `resource_id` = `skill._id.toString()`, `kind: 'skill'`,
`version` = the skill's monotonic counter.
Tests added to `skillFiles.spec.ts` lock the contract on
`primeSkillFiles` directly so future refactors can't silently drop
the resource identity again.
* fix(handlers.spec): align session_id → storage_session_id rename + kind discriminator
Pre-existing TS errors against the post-rename `CodeEnvFile` shape:
the test file still used `session_id` on per-file objects (renamed to
`storage_session_id` in agents Phase B/C) and was missing the `kind`
discriminator the discriminated union requires. Both inputs and the
matching `expect.toEqual(...)` mirrors updated together so the
runtime equality check still holds.
Lines 723-732 stay as-is — they sit behind `as unknown as
ToolCallRequest` and TS already skipped them.
* chore: fix `@librechat/agents`, correct version to 3.1.80-dev.0 in package.json files
* chore: bump `@librechat/agents` to version 3.1.80-dev.1 in package.json and package-lock.json
* chore: bump `@librechat/agents` to version 3.1.80-dev.2
* feat(observability): trace file priming chain from primeCodeFiles to _injected_files
Diagnosing the user-upload "files=[] on first /exec" bug requires
seeing where in the LC chain a file ref disappears. Prior to this
patch the chain (primeCodeFiles → primedCodeFiles → initialSessions
→ CodeSessionContext → _injected_files) was opaque end-to-end:
- primeCodeFiles silently dropped files without `metadata.codeEnvRef`
- reuploadFile catches all errors and continues with no signal
- the handlers.ts handoff to codeapi never logged what it was sending
After this patch, a single grep on `[primeCodeFiles]` plus
`[code-env:inject]` shows the full per-file path:
[primeCodeFiles] in: file_ids=N resourceFiles=M
[primeCodeFiles] file=<id> path=skip reason=no-codeenvref filename=...
[primeCodeFiles] file=<id> path=cache-hit-by-session storage_session_id=...
[primeCodeFiles] file=<id> path=reupload reason=no-uploadtime ...
[primeCodeFiles] file=<id> path=reupload reason=stale ...
[primeCodeFiles] file=<id> path=reupload-success oldSession=... newSession=... newFileId=...
[primeCodeFiles] file=<id> path=reupload-failed session=...
[primeCodeFiles] file=<id> path=fresh-active storage_session_id=...
[primeCodeFiles] out: returned=N skippedNoRef=M reuploadFailures=K
[code-env:inject] tool=<name> files=N missingResourceId=K (debug)
[code-env:inject] M/N files missing resource_id ... (warn)
[code-env:inject] tool=<name> _injected_files=0 ... (warn)
The boundary log warns when LC sends zero injected files on a
code-execution tool call — that's the user's actual symptom showing
up at the LC side instead of having to correlate against codeapi's
`Request received { files: [] }`.
Tag chosen as `[code-env:inject]` rather than `[handoff:exec]` to
avoid collision with the app-level "handoff" semantic (subagent
handoff workflow).
Structural cleanup in primeFiles: replaced the `if (ref) { ... }`
nesting with an early `if (!ref) continue` so the per-path
instrumentation hooks land at top-level scope instead of indented
inside a conditional. Behavior unchanged; pushFile / reuploadFile
identical.
Spec fixtures (handlers.spec.ts, codeFilesSession.spec.ts) updated
to include `resource_id` on `CodeEnvFile` literals — required by
the post-3.1.80-dev.2 type now installed.
## Test plan
- [x] `cd packages/api && npx jest src/agents/handlers.spec.ts src/agents/codeFilesSession.spec.ts src/agents/skillFiles.spec.ts` — 69/69 pass
- [x] `cd api && npx jest server/services/Files/Code/process.spec.js` — 84/84 pass
- [x] `npx tsc --noEmit -p packages/api` — clean
- [x] `npx eslint` on all four touched files — clean
* chore: add CONSOLE_JSON_STRING_LENGTH to .env.example for JSON log string length configuration
* fix(files): align codeapi upload filename with LC's sanitized DB filename
User-attached files for code execution were uploading to codeapi
under `file.originalname` (raw upload filename, may contain spaces /
special chars) while LC's DB record stored the sanitized form
(`sanitizeFilename(file.originalname)`, underscores). Codeapi
preserves whatever filename the upload sent, so the sandbox saw
`/mnt/data/<originalname>` while LC's `primeFiles` toolContext text
+ `_injected_files.name` referenced `file.filename` (sanitized).
Visible failure: agent gets system prompt saying
/mnt/data/librechat_code_api_-_active_customer_-_2025-11-05.xlsx
…tries that path, hits `FileNotFoundError`, then notices the
sandbox's actual `Available files` line says
/mnt/data/librechat code api - active customer - 2025-11-05.xlsx
…retries with spaces, succeeds. Wastes a tool call per upload and
leaks raw filenames into model context.
Fix: sanitize once and use the sanitized form in both the codeapi
upload AND the LC DB record. Sandbox path = LC toolContext text =
in-memory ref name. No drift.
Reupload path (`Code/process.js` line 867 `filename: file.filename`)
already uses the sanitized DB name, so it stays consistent with the
fresh-upload path after this change.
## Test plan
- [x] `cd api && npx jest server/services/Files/process` — 32/32 pass
- [x] `npx eslint` on the touched file — clean
* chore: bump `@librechat/agents` to version 3.1.80-dev.3 in package.json and package-lock.json
* chore: Update axios dependency to version 1.16.0 across multiple package files
* chore: Update express-rate-limit and ip-address dependencies to versions 8.5.1 and 10.2.0 in package-lock.json and package.json
* chore: Update mongoose and hono dependencies to versions 8.23.1 and 4.12.18 across multiple package files
* fix: Add type parameters to mongoose lean queries in accessRole and aclEntry methods
* fix: Add type parameters to mongoose lean queries in action, agent, and agentCategory methods
* chore: Update moduleResolution to 'bundler' in tsconfig.json for api and data-schemas packages
* fix: Update mongoose lean queries to include type parameters across various methods for improved type safety
* 📦 chore: bump `@librechat/agents` to v3.1.78
v3.1.78 ships [danny-avila/agents#147](https://github.com/danny-avila/agents/pull/147),
which makes `SubagentExecutor` inherit the parent invocation's
`configurable` (with `thread_id`/`run_id`/`parent_run_id` scrubbed)
into the child workflow. Subagent tool dispatches through the parent's
`ON_TOOL_EXECUTE` handler now arrive with parent's `requestBody`,
`user`, `userMCPAuthMap`, etc. — so `{{LIBRECHAT_BODY_*}}` placeholder
substitution and per-user MCP connection lookup work for subagent
tool calls the same way they do for the parent agent.
Note: `package-lock.json` will need an `npm install` refresh once
v3.1.78 lands on the registry. The user/user_id injection added in
PR #12950 stays as defense-in-depth.
* 🗑️ refactor: drop redundant user/user_id injection from `loadToolsForExecution`
`@librechat/agents@3.1.78` (via danny-avila/agents#147) makes
`SubagentExecutor` forward the parent's `configurable` verbatim into
the child workflow. Subagent `ON_TOOL_EXECUTE` dispatches now arrive
with parent's `user` / `user_id` already in `data.configurable` —
making the host-side injection added in #12950 a no-op.
Removes:
- The conditional `user: createSafeUser(req.user); user_id: req.user.id`
block in `loadToolsForExecution` (req.user.id-guarded so the
`'api-user'` fallback in Responses/OpenAI controllers is preserved).
- The unused `createSafeUser` import.
- The 4 unit tests covering the now-deleted behavior.
The merge in `handlers.ts` (`{ ...configurable, ...toolConfigurable }`)
still produces a `mergedConfigurable` with the right user identity for
both parent and subagent paths — the values just come from
`configurable` (forwarded by the SDK) rather than `toolConfigurable`.
Other fixes from #12950 stay (IUser.id narrowing, the env.ts /
google/initialize.ts / remoteAgentAuth.ts TS-warning fixes) — they
were independent of the subagent identity propagation issue.
* 📦 chore: update `@librechat/agents` to v3.1.78
This update reflects the transition from the development version `3.1.78-dev.0` to the stable release `3.1.78`. The package-lock.json has been refreshed to ensure consistency with the new version, including updated integrity checks and resolved URLs for the package. This change is part of ongoing improvements to enhance the functionality and stability of the agents module.
* 🔐 fix: Forward per-file `entity_id` through code-env priming
Skill files and persisted code-env files now carry their `entity_id` on
the in-memory file refs that seed `Graph.sessions`. Without this, an
execute call that mixes a skill file (uploaded with `entity_id=skillId`)
and a user attachment (uploaded with no `entity_id`) collapses onto a
single request-level entity at the codeapi authorization step and one
side 403s. With per-file `entity_id`, codeapi resolves sessionKey per
file and both authorize.
- `primeSkillFiles` / `primeInvokedSkills`: thread `entity_id` through
fresh-upload, cache-hit, and per-skill-batch paths in
`packages/api/src/agents/skillFiles.ts`.
- `primeFiles` (Code/process.js): parse `entity_id` from the persisted
`codeEnvIdentifier` query string once per iteration; forward through
`pushFile`, including the reupload path which re-parses the fresh
identifier returned by codeapi.
- Tests: extend `skillFiles.spec.ts` with two cases — fresh-upload
propagation and cached-hot-path parsing.
Companion PRs in flight on `@librechat/agents` (forward `entity_id`
through `_injected_files`) and codeapi (per-file authorization). All
three are wire-back-compat: an absent `entity_id` falls back to the
existing request-level resolution.
* 🔧 chore: Update dependencies in package-lock.json and package.json
- Bump `@librechat/agents` to version `3.1.78-dev.0` across multiple package files.
- Upgrade `@langchain/langgraph-checkpoint` to version `1.0.2` and update its peer dependency for `@langchain/core` to `^1.1.44`.
- Update `axios` to version `1.16.0` and `follow-redirects` to version `1.16.0`.
- Add `@types/diff` as a new dependency at version `7.0.2` and include `diff` at version `9.0.0` in the `@librechat/agents` module.
- Introduce optional peer dependency `@anthropic-ai/sandbox-runtime` for `@librechat/agents` with metadata indicating it is optional.
* 🐛 fix: Make skill code-env cache persistence observable
Two changes to surface the skill-bundle re-upload issue without
behavioral changes to tenant scoping (root cause to be confirmed via
the new warn log):
1. `primeSkillFiles` now awaits `updateSkillFileCodeEnvIds` instead of
firing-and-forgetting it. The prior shape could race with the next
prime (read-before-write) even when the bulkWrite itself succeeds,
producing a silent cache miss. Latency cost: ~10–50ms on first
prime; in exchange every subsequent prime can rely on the
identifier being persisted by the time it reads.
2. `updateSkillFileCodeEnvIds` now returns `{matchedCount, modifiedCount}`
from the underlying bulkWrite. `primeSkillFiles` warn-logs when
`modifiedCount < updates.length`, making any silent drop visible —
whether the cause is tenant filtering, a `relativePath` mismatch,
schema-plugin scoping, or something else. Prior shape returned
`Promise<void>` so any zero-modification result was invisible.
Tests:
- `skill.spec.ts`: real-MongoDB happy path (counts match), no-match
case (modifiedCount=0), and empty-input contract.
- `skillFiles.spec.ts`: deferred-promise harness proving the call
site awaits the persist (prime stays pending until the persist
resolves) and forwards partial-write counts.
Deliberately narrower than the original draft of this commit, which
also bypassed `tenantSafeBulkWrite` for the codeEnvIdentifier write
on the speculative diagnosis that tenant filtering was the cause.
That change was a behavior shift on tenant scoping without
confirmation; reverted pending real-world signal from the new warn
log.
* 🐛 fix: Justify await for skill code-env persistence under concurrency
The await on `updateSkillFileCodeEnvIds` isn't a defensive nicety —
it's load-bearing for cache effectiveness under concurrent priming.
Verified with an out-of-tree harness (`config/test-skill-cache.ts`,
not committed) that wires `primeSkillFiles` against a real codeapi
stack:
- With fire-and-forget (prior shape after this branch's revert):
back-to-back primes for the same skill miss the cache. Call N+1
reads SkillFile docs before Call N's write commits, sees no
`codeEnvIdentifier`, re-uploads, fires its own forget that Call N+2
also races. Steady-state stays in cache miss for the full burst.
- With await: the prime that does the upload commits its persist
before resolving, so the next concurrent prime observes the cache
pointer instead of racing the read. Latency cost ~10–50ms on the
upload prime; subsequent concurrent primes save an entire batch
upload.
In production with primes seconds apart this race is rare; at scale
with many users hitting the same skill in the same second it's the
difference between M and N×M uploads.
Updates the regression test to assert the await contract (deferred
persist promise → prime stays pending until persist resolves).
Comment in `skillFiles.ts` rewritten to document the concurrency
rationale rather than the weaker "race-with-next-prime" framing the
prior commit used.
* 🌩️ feat: CloudFront CDN File Strategy + signed cookies
Squashed from PR #12193:
- feat(storage): add CloudFront CDN file strategy
- feat(auth): add CloudFront signed cookie support
Note: package.json/package-lock.json dependency additions are intentionally
omitted from this commit and will be re-added via `npm install` after rebase
to avoid lock-file merge conflicts. The two new peer deps that need to be
re-installed are:
- @aws-sdk/client-cloudfront@^3.1032.0
- @aws-sdk/cloudfront-signer@^3.1012.0
Also fixes 4 missing destructured names in AuthService.spec.js
(getUserById, generateToken, generateRefreshToken, createSession) that
were referenced in tests but not imported from the mocked '~/models'.
* 📦 chore: install CloudFront SDK deps for PR #12193
Adds the two AWS CloudFront packages required by the rebased
CloudFront CDN strategy:
- @aws-sdk/client-cloudfront
- @aws-sdk/cloudfront-signer
Following the @aws-sdk/client-s3 pattern:
- api/package.json: regular dependency (runtime resolution)
- packages/api/package.json: peerDependency
Generated by `npm install` against the freshly rebased lock file
to avoid the merge conflicts that came from the original PR's
lock-file edits being made against an older base of dev.
* 🐛 fix: CI failures + review findings on CloudFront PR #12193
CI fixes
- Rename packages/data-provider/src/__tests__/cloudfront-config.test.ts
→ src/cloudfront-config.spec.ts. Jest's default testMatch picks up
__tests__/ directories even inside dist/, so the compiled .d.ts shell
was being executed as an empty test suite. Moving to .spec.ts (matching
the rest of the package) avoids the dist/ pickup.
- Add cookieExpiry: 1800 to CloudFront crud.test makeConfig: the schema
applies a default so CloudFrontFullConfig requires it.
Review findings addressed
- #1 (Codex + comprehensive): Normalize CloudFront domain with /\/+$/
regex (and key with /^\/+/ regex) in buildCloudFrontUrl, matching the
cookie code so resource policy and file URLs stay aligned even when
the configured domain has multiple trailing slashes. Added tests.
- #2: Move DEFAULT_BASE_PATH out of s3Config into shared
packages/api/src/storage/constants.ts. ImageService no longer imports
S3-specific config.
- #3: getCloudFrontConfig() returns Readonly<CloudFrontFullConfig> | null
to discourage mutation of the cached signing config.
- #4: Add cross-field refinement tests for cloudfrontConfigSchema
(invalidateOnDelete-without-distributionId,
imageSigning="cookies"-without-cookieDomain).
- #6: Revert unrelated MCP comment re-indentation in
librechat.example.yaml.
- #7: Add azure_blob to the strategy list comment.
Skipped
- #5 (extractKeyFromS3Url with CloudFront URLs): existing
deleteFileFromCloudFront tests already cover the path-equivalence
assumption; renaming the helper is real refactor work beyond this
PR's scope.
- #8, #9 (NIT, low confidence): leaving for author judgement.
* 🧹 chore: drop dead DEFAULT_BASE_PATH from s3Config test mock
After moving DEFAULT_BASE_PATH to ~/storage/constants, crud.ts no longer
reads it from s3Config — so the entry in the s3Config jest mock was
misleading dead config. The tests still pass because the unmocked real
constants module provides the value.
---------
Co-authored-by: Danny Avila <danny@librechat.ai>