LibreChat/packages/api
Danny Avila cd4511038d
🚏 feat: Central Trace Destination Opt-Out for Langfuse (#14838)
* feat(langfuse): let callers opt out of the central trace destination

Adds `centralTraceExportEnabled` to the score-destination options and threads
it through `getScoreDestinations`, `getLangfuseTraceDestinationIds` and
`getLangfuseTraceMessageFields`.

Deployments that route traces per tenant may want a given turn's spans to reach
only the tenant destination — for example when central export is a per-tenant
setting rather than a deployment-wide one. Today the central project is included
whenever env credentials exist, with no way for a caller to decline it for a
single trace.

Defaults to `true` everywhere, so existing callers are unaffected: the option is
additive and every current call site resolves exactly as before. The three
public helpers gain an optional trailing parameter and nothing else.

While here, `getScoreDestinations` destructures its options with defaults
instead of repeating `options?.waitForCentralProjectId !== false` at both call
sites, which is what made adding a second flag awkward.

Verified: no new tsc errors (one pre-existing cacheFactory error is unchanged),
99 langfuse tests pass, ESLint and Prettier clean.

* fix(langfuse): keep the central opt-out intact when destinations resolve

Addresses two review findings on the new `centralTraceExportEnabled` option.
Both are cases where opting out of central export was silently discarded by
destination resolution, letting later feedback reach a project the trace never
went to.

1. Non-fanout deployments with no central env credentials still returned the
   configured connection, because only the central-credential branch was gated.
   `resolveLangfuseExportPlan` reports `disabled` for that shape — without a
   fanout route there is nowhere for a central-suppressed trace to go — so
   return no destinations and match it.

2. `getLangfuseTraceDestinationIds` returned `undefined` whenever any
   destination lacked an id, and `sendFeedbackScore` reads `undefined` as
   unrestricted. A tenant destination has no id when its optional `projectId`
   is unset, so a suppressed-central trace could resolve back to the central
   project at feedback time. Fail closed with an empty list, which stays
   restricted, instead.

Both paths now have regression tests, each verified to fail without the
corresponding change: the first returned 3 destination ids, the second returned
`undefined`.

* fix(langfuse): carry the central opt-out into the feedback path

`getLangfuseTraceDestinationIds` returns `undefined` when an eligible
destination has no stable id, which a tenant route hits whenever the
optional `langfuse.projectId` is unset. `sendFeedbackScore` read that as
"unrestricted" and re-resolved destinations with central export enabled,
so a suppressed-central trace still drew central feedback.

Persisting an empty list instead only traded the leak for a drop: the
destination filter rejects every id-less destination, discarding ratings
the tenant should receive. The id list restricts feedback to destinations
that survived reconfiguration; it cannot also encode deployment policy.

Thread `centralTraceExportEnabled` through `sendFeedbackScore` so the
policy is evaluated the same way at trace time and feedback time, and
restore `undefined` for unidentifiable destinations.
2026-08-15 08:41:18 -04:00
..
src 🚏 feat: Central Trace Destination Opt-Out for Langfuse (#14838) 2026-08-15 08:41:18 -04:00
types
.gitignore
babel.config.cjs
jest.config.mjs 📦 chore: update sanitize-html to latest (#14573) 2026-08-01 09:35:57 -04:00
jest.setup.cjs
package.json 📦 chore: bump @librechat/agents to v3.5.1 (#14830) 2026-08-14 11:14:07 -04:00
tsconfig-paths-bootstrap.mjs
tsconfig.build.json
tsconfig.json 📦 chore: npm audit fixes and Mongoose 8.23 TypeScript follow-ups (#12996) 2026-05-07 09:47:40 -04:00
tsconfig.spec.json
tsdown.config.mjs 🫆 chore: Remove Published Credential Defaults (#14680) 2026-08-07 07:25:05 -04:00