@librechat/agents 3.3.8 → 3.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/cjs/graphs/Graph.cjs +47 -13
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/graphs/MultiAgentGraph.cjs +35 -2
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  5. package/dist/cjs/hitl/askUserQuestion.cjs +3 -2
  6. package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -1
  7. package/dist/cjs/instrumentation.cjs +18 -48
  8. package/dist/cjs/instrumentation.cjs.map +1 -1
  9. package/dist/cjs/langfuse.cjs +174 -29
  10. package/dist/cjs/langfuse.cjs.map +1 -1
  11. package/dist/cjs/langfuseConfig.cjs +12 -0
  12. package/dist/cjs/langfuseConfig.cjs.map +1 -1
  13. package/dist/cjs/langfuseRuntimeContext.cjs +23 -2
  14. package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -1
  15. package/dist/cjs/langfuseRuntimeScope.cjs +39 -8
  16. package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -1
  17. package/dist/cjs/langfuseSpanRegistry.cjs +95 -0
  18. package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -0
  19. package/dist/cjs/langfuseTraceShaping.cjs +121 -4
  20. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  21. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +39 -15
  22. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  23. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +25 -5
  24. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  25. package/dist/cjs/llm/init.cjs +3 -3
  26. package/dist/cjs/llm/invoke.cjs +5 -5
  27. package/dist/cjs/llm/openai/index.cjs +1 -1
  28. package/dist/cjs/main.cjs +10 -10
  29. package/dist/cjs/messages/prune.cjs +13 -1
  30. package/dist/cjs/messages/prune.cjs.map +1 -1
  31. package/dist/cjs/prompts/activityLabel.cjs +24 -12
  32. package/dist/cjs/prompts/activityLabel.cjs.map +1 -1
  33. package/dist/cjs/run.cjs +47 -22
  34. package/dist/cjs/run.cjs.map +1 -1
  35. package/dist/cjs/session/messageSerialization.cjs +6 -0
  36. package/dist/cjs/session/messageSerialization.cjs.map +1 -1
  37. package/dist/cjs/stream.cjs +21 -10
  38. package/dist/cjs/stream.cjs.map +1 -1
  39. package/dist/cjs/summarization/node.cjs +5 -0
  40. package/dist/cjs/summarization/node.cjs.map +1 -1
  41. package/dist/cjs/tools/ToolNode.cjs +253 -24
  42. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  43. package/dist/cjs/tools/handlers.cjs +1 -1
  44. package/dist/cjs/tools/search/tool.cjs +1 -1
  45. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
  46. package/dist/cjs/utils/index.cjs +2 -2
  47. package/dist/esm/graphs/Graph.mjs +48 -14
  48. package/dist/esm/graphs/Graph.mjs.map +1 -1
  49. package/dist/esm/graphs/MultiAgentGraph.mjs +35 -2
  50. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  51. package/dist/esm/hitl/askUserQuestion.mjs +3 -2
  52. package/dist/esm/hitl/askUserQuestion.mjs.map +1 -1
  53. package/dist/esm/instrumentation.mjs +18 -48
  54. package/dist/esm/instrumentation.mjs.map +1 -1
  55. package/dist/esm/langfuse.mjs +176 -28
  56. package/dist/esm/langfuse.mjs.map +1 -1
  57. package/dist/esm/langfuseConfig.mjs +10 -1
  58. package/dist/esm/langfuseConfig.mjs.map +1 -1
  59. package/dist/esm/langfuseRuntimeContext.mjs +21 -3
  60. package/dist/esm/langfuseRuntimeContext.mjs.map +1 -1
  61. package/dist/esm/langfuseRuntimeScope.mjs +39 -10
  62. package/dist/esm/langfuseRuntimeScope.mjs.map +1 -1
  63. package/dist/esm/langfuseSpanRegistry.mjs +91 -0
  64. package/dist/esm/langfuseSpanRegistry.mjs.map +1 -0
  65. package/dist/esm/langfuseTraceShaping.mjs +121 -4
  66. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  67. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +39 -15
  68. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  69. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +25 -5
  70. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  71. package/dist/esm/llm/init.mjs +2 -2
  72. package/dist/esm/llm/invoke.mjs +5 -5
  73. package/dist/esm/llm/openai/index.mjs +1 -1
  74. package/dist/esm/main.mjs +8 -8
  75. package/dist/esm/messages/prune.mjs +13 -1
  76. package/dist/esm/messages/prune.mjs.map +1 -1
  77. package/dist/esm/prompts/activityLabel.mjs +24 -12
  78. package/dist/esm/prompts/activityLabel.mjs.map +1 -1
  79. package/dist/esm/run.mjs +47 -22
  80. package/dist/esm/run.mjs.map +1 -1
  81. package/dist/esm/session/messageSerialization.mjs +6 -0
  82. package/dist/esm/session/messageSerialization.mjs.map +1 -1
  83. package/dist/esm/stream.mjs +21 -10
  84. package/dist/esm/stream.mjs.map +1 -1
  85. package/dist/esm/summarization/node.mjs +5 -0
  86. package/dist/esm/summarization/node.mjs.map +1 -1
  87. package/dist/esm/tools/ToolNode.mjs +254 -25
  88. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  89. package/dist/esm/tools/handlers.mjs +1 -1
  90. package/dist/esm/tools/search/tool.mjs +1 -1
  91. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
  92. package/dist/esm/utils/index.mjs +2 -2
  93. package/dist/types/graphs/Graph.d.ts +19 -0
  94. package/dist/types/hitl/askUserQuestion.d.ts +11 -1
  95. package/dist/types/langfuse.d.ts +16 -8
  96. package/dist/types/langfuseConfig.d.ts +6 -0
  97. package/dist/types/langfuseRuntimeContext.d.ts +27 -1
  98. package/dist/types/langfuseRuntimeScope.d.ts +17 -2
  99. package/dist/types/langfuseSpanRegistry.d.ts +17 -0
  100. package/dist/types/langfuseTraceShaping.d.ts +2 -1
  101. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -0
  102. package/dist/types/session/types.d.ts +1 -0
  103. package/dist/types/tools/ToolNode.d.ts +7 -1
  104. package/dist/types/types/hitl.d.ts +8 -0
  105. package/dist/types/types/tools.d.ts +30 -0
  106. package/package.json +7 -4
  107. package/src/__tests__/stream.eagerArgsDivergence.test.ts +753 -0
  108. package/src/graphs/Graph.ts +69 -20
  109. package/src/graphs/MultiAgentGraph.ts +56 -2
  110. package/src/graphs/__tests__/composition.smoke.test.ts +4 -0
  111. package/src/hitl/askUserQuestion.ts +14 -1
  112. package/src/instrumentation.ts +35 -77
  113. package/src/langfuse.ts +320 -43
  114. package/src/langfuseConfig.ts +24 -0
  115. package/src/langfuseRuntimeContext.ts +43 -1
  116. package/src/langfuseRuntimeScope.ts +94 -21
  117. package/src/langfuseSpanRegistry.ts +131 -0
  118. package/src/langfuseTraceShaping.ts +194 -7
  119. package/src/llm/anthropic/utils/message_inputs.ts +70 -19
  120. package/src/llm/anthropic/utils/streaming-tool-input.test.ts +186 -11
  121. package/src/llm/bedrock/utils/message_inputs.test.ts +120 -4
  122. package/src/llm/bedrock/utils/message_inputs.ts +32 -7
  123. package/src/messages/prune.ts +12 -1
  124. package/src/prompts/activityLabel.ts +23 -6
  125. package/src/run.ts +76 -45
  126. package/src/scripts/activity-labels/captured.json +56 -0
  127. package/src/scripts/activity-labels/checks.cjs +205 -0
  128. package/src/scripts/activity-labels/corpus.cjs +473 -0
  129. package/src/scripts/activity-labels/report.cjs +203 -0
  130. package/src/scripts/activity-labels/rescore.cjs +102 -0
  131. package/src/scripts/activity-labels/run.ts +705 -0
  132. package/src/scripts/activity-labels/variants.ts +71 -0
  133. package/src/session/messageSerialization.ts +12 -1
  134. package/src/session/types.ts +1 -0
  135. package/src/specs/activity-label-prompt.test.ts +26 -10
  136. package/src/specs/agent-handoffs.test.ts +306 -0
  137. package/src/specs/langfuse-callbacks.test.ts +456 -0
  138. package/src/specs/langfuse-routing.integration.test.ts +138 -1
  139. package/src/specs/langfuse-span-registry.test.ts +70 -0
  140. package/src/specs/langfuse-trace-shaping.test.ts +294 -0
  141. package/src/specs/prune.test.ts +38 -1
  142. package/src/stream.ts +70 -6
  143. package/src/summarization/node.ts +5 -0
  144. package/src/tools/ToolNode.ts +400 -9
  145. package/src/tools/__tests__/ToolNode.invalidToolCalls.test.ts +757 -0
  146. package/src/tools/__tests__/hitl.test.ts +58 -0
  147. package/src/types/hitl.ts +8 -0
  148. package/src/types/tools.ts +35 -1
@@ -1,9 +1,12 @@
1
1
  import { isPresent, parseBooleanEnv } from "./utils/misc.mjs";
2
- import { resolveLangfuseConfigForSpan, resolveTraceIdSeedForSpan, withLangfuseRuntimeScope } from "./langfuseRuntimeScope.mjs";
2
+ import { hasLangfuseConfigCredentials, hasLangfuseEnvConfig, hasLangfuseEnvCredentials, resolveToolOutputTracingConfig } from "./langfuseConfig.mjs";
3
+ import { resolveLangfuseConfigForSpan, resolveLangfuseScopeAgentId, resolveLangfuseScopeRunId, resolveTraceIdSeedForSpan, withLangfuseRuntimeScope } from "./langfuseRuntimeScope.mjs";
4
+ import { getLangfuseManagedSpanDestination, resolveLangfuseDestinationKey } from "./langfuseSpanRegistry.mjs";
3
5
  import { AIMessage, AIMessageChunk } from "@langchain/core/messages";
4
6
  import { isGraphInterrupt, isParentCommand } from "@langchain/langgraph";
5
7
  import { CallbackHandler } from "@langfuse/langchain";
6
- import { context } from "@opentelemetry/api";
8
+ import { LangfuseOtelContextKeys } from "@langfuse/core";
9
+ import { context, trace } from "@opentelemetry/api";
7
10
  import { getLangfuseTracerProvider, propagateAttributes } from "@langfuse/tracing";
8
11
  //#region src/langfuse.ts
9
12
  const TRACE_METADATA_MAX_LENGTH = 200;
@@ -67,29 +70,180 @@ function normalizeBedrockUsageForLangfuse(output) {
67
70
  generations
68
71
  };
69
72
  }
73
+ const LANGGRAPH_NODE_METADATA_KEY = "langgraph_node";
74
+ /** Explicit agent identity in invoke metadata. Every identity-stamping
75
+ * component (graph model path, ToolNode, summarization node) overwrites the
76
+ * canonical `agentId` at its own invoke, so spread order guarantees the
77
+ * closest stamper wins — key priority alone could not (an inherited key of
78
+ * either casing can name the wrong agent). `agent_id` remains a fallback
79
+ * for third-party graphs that only stamp the snake-case form. */
80
+ const AGENT_ID_METADATA_KEYS = ["agentId", "agent_id"];
81
+ const LANGGRAPH_NODE_AGENT_PREFIXES = [
82
+ "agent=",
83
+ "tools=",
84
+ "summarize="
85
+ ];
86
+ /** The LangGraph node a callback executes under, from its inherited
87
+ * `langgraph_node` run metadata. `undefined` when the callback carries no
88
+ * node identity. */
89
+ function getCallbackNode(metadata) {
90
+ const node = metadata?.[LANGGRAPH_NODE_METADATA_KEY];
91
+ return typeof node === "string" && node !== "" ? node : void 0;
92
+ }
93
+ /** Whether a callback's node identifies the given agent. The outer workflow
94
+ * node carries the agent id VERBATIM — including ids that themselves begin
95
+ * with an internal prefix (an agent literally named `agent=research`) — so
96
+ * an exact match is checked before decoding the inner subgraph prefixes
97
+ * (`agent=` / `tools=` / `summarize=`). */
98
+ function callbackNodeMatchesAgent(node, agentId) {
99
+ if (node === agentId) return true;
100
+ for (const prefix of LANGGRAPH_NODE_AGENT_PREFIXES) if (node.startsWith(prefix) && node.slice(prefix.length) === agentId) return true;
101
+ return false;
102
+ }
103
+ /**
104
+ * Hosts often execute agent code inside their own OpenTelemetry spans (HTTP
105
+ * server auto-instrumentation on the global provider). Root observations must
106
+ * not inherit that ambient identity: the foreign parent is never exported to
107
+ * Langfuse, which orphans the trace root (root/trace input-output shaping is
108
+ * skipped because the span no longer looks like a root), collapses concurrent
109
+ * runs inside one request context — an agent run and the previous turn's
110
+ * title run — into a single merged trace with racing names and unioned tags,
111
+ * and bypasses the seeded deterministic trace id generator. Only a
112
+ * Langfuse-managed span bound to the same export destination as the starting
113
+ * run is a safe parent — that is the sanctioned way for hosts to group runs
114
+ * under their own Langfuse observations; a managed span from a different
115
+ * destination (another tenant's project) would leave this run's trace
116
+ * dangling in its own destination while inheriting the other trace's id.
117
+ */
118
+ function detachForeignAmbientSpan(activeContext, destinationKey) {
119
+ const activeSpan = trace.getSpan(activeContext);
120
+ if (activeSpan == null) return activeContext;
121
+ const parentDestination = getLangfuseManagedSpanDestination(activeSpan);
122
+ if (parentDestination != null && parentDestination === destinationKey) return activeContext;
123
+ return trace.deleteSpan(activeContext);
124
+ }
70
125
  var ScopedLangfuseCallbackHandler = class extends CallbackHandler {
71
126
  langfuse;
72
127
  traceIdSeed;
128
+ runId;
129
+ identity;
130
+ toolOutputTracing;
131
+ trackedRunIds = /* @__PURE__ */ new Set();
73
132
  constructor(params) {
74
- const { langfuse, traceIdSeed, ...handlerParams } = params ?? {};
133
+ const { langfuse, traceIdSeed, runId, toolOutputTracing, traceName, ...handlerParams } = params ?? {};
75
134
  super(handlerParams);
76
135
  this.langfuse = langfuse;
77
136
  this.traceIdSeed = traceIdSeed;
137
+ this.runId = runId;
138
+ this.toolOutputTracing = toolOutputTracing;
139
+ this.identity = {
140
+ userId: handlerParams.userId,
141
+ sessionId: handlerParams.sessionId,
142
+ tags: handlerParams.tags,
143
+ metadata: handlerParams.traceMetadata,
144
+ traceName
145
+ };
78
146
  }
79
147
  getDeterministicTraceSeed() {
80
148
  return this.langfuse?.deterministicTraceId === true ? this.traceIdSeed : void 0;
81
149
  }
82
- withRuntimeContext(action) {
83
- const activeContext = context.active();
84
- const langfuse = resolveLangfuseConfigForSpan(activeContext) ?? this.langfuse;
85
- const seed = this.getDeterministicTraceSeed();
86
- return withLangfuseRuntimeScope({
150
+ /**
151
+ * Mirrors the base handler's `runMap`: a start callback whose `parentRunId`
152
+ * this handler never observed gets no explicit parent span and falls back
153
+ * to the ambient OTEL context (`startAndRegisterOtelSpan`), so it needs the
154
+ * same foreign-span detachment as a true root. This happens whenever a
155
+ * handler is attached mid-graph — e.g. the per-agent handler created for a
156
+ * detached subagent's model invocations, whose surrounding graph runs were
157
+ * never traced.
158
+ */
159
+ startsDetachedRun(runId, parentRunId) {
160
+ const detached = parentRunId == null || !this.trackedRunIds.has(parentRunId);
161
+ this.trackedRunIds.add(runId);
162
+ return detached;
163
+ }
164
+ /**
165
+ * Whether the ambient runtime scope belongs to a different run — or, for
166
+ * per-agent overlay scopes, to a different concurrently executing agent of
167
+ * the same run than the one this callback reports via its inherited
168
+ * `langgraph_node` metadata. Unstamped scopes on unstamped handlers are
169
+ * never foreign (host-managed handler semantics).
170
+ */
171
+ isForeignScope(scopeRunId, scopeAgentId, callbackMetadata) {
172
+ if (this.runId == null || scopeRunId == null) return false;
173
+ if (scopeRunId !== this.runId) return true;
174
+ if (scopeAgentId == null) return false;
175
+ for (const key of AGENT_ID_METADATA_KEYS) {
176
+ const explicitAgentId = callbackMetadata?.[key];
177
+ if (typeof explicitAgentId === "string" && explicitAgentId !== "") return explicitAgentId !== scopeAgentId;
178
+ }
179
+ const callbackNode = getCallbackNode(callbackMetadata);
180
+ return callbackNode != null && !callbackNodeMatchesAgent(callbackNode, scopeAgentId);
181
+ }
182
+ /**
183
+ * LangChain executes non-awaited callbacks on a process-wide background
184
+ * queue (`consumeCallback`), so this callback may be running inside a
185
+ * DIFFERENT concurrent run's async context. The ambient runtime scope is
186
+ * therefore only adopted when it belongs to this handler's run (and, for
187
+ * agent sub-scopes, to this callback's agent): scopes are stamped at their
188
+ * call sites, and a foreign stamp means the scope's config would route
189
+ * spans to the wrong destination, its seed would collapse this run's spans
190
+ * into the foreign trace, and its tool-output policy could leak output the
191
+ * foreign run permits but this run redacts — so config, seed, AND redaction
192
+ * policy all fall back to this handler's own run. Unstamped scopes on
193
+ * unstamped handlers keep scope-first semantics (agent overlays and
194
+ * per-path seeds like the title/label scopes, host-managed handlers).
195
+ *
196
+ * Detached runs (roots, or starts whose parent this handler never tracked)
197
+ * take their span parent from the ambient OTEL context, so drop any
198
+ * foreign ambient span first — a run launched from a host's instrumented
199
+ * request context must start its own trace, not join an unexported
200
+ * foreign one.
201
+ */
202
+ withRuntimeContext(action, isDetachedRun = false, callbackMetadata) {
203
+ const currentContext = context.active();
204
+ const scopeRunId = resolveLangfuseScopeRunId(currentContext);
205
+ const scopeAgentId = resolveLangfuseScopeAgentId(currentContext);
206
+ if (this.isForeignScope(scopeRunId, scopeAgentId, callbackMetadata)) return this.withForeignScopeRejected(action);
207
+ const langfuse = resolveLangfuseConfigForSpan(currentContext) ?? this.langfuse;
208
+ const activeContext = isDetachedRun ? detachForeignAmbientSpan(currentContext, resolveLangfuseDestinationKey(langfuse)) : currentContext;
209
+ const scoped = () => withLangfuseRuntimeScope({
87
210
  langfuse,
88
- traceIdSeed: resolveTraceIdSeedForSpan(activeContext) ?? seed
211
+ traceIdSeed: resolveTraceIdSeedForSpan(activeContext) ?? this.getDeterministicTraceSeed(),
212
+ runId: scopeRunId ?? this.runId
89
213
  }, action);
214
+ return activeContext === currentContext ? scoped() : context.with(activeContext, scoped);
215
+ }
216
+ /**
217
+ * A foreign concurrent run's context must be replaced wholesale, not
218
+ * merged: this library's scope keys (config, seed, tool-output policy,
219
+ * identity stamps) via the replace-mode runtime scope, `@langfuse/tracing`'s
220
+ * propagated trace attributes (userId, sessionId, tags, metadata, …) by
221
+ * deleting their context keys and re-propagating this handler's own
222
+ * identity, and the foreign active span — removed both so detached runs
223
+ * root their own trace and so `propagateAttributes` cannot stamp this
224
+ * run's identity onto the foreign run's still-recording span.
225
+ */
226
+ withForeignScopeRejected(action) {
227
+ let cleanContext = trace.deleteSpan(context.active());
228
+ for (const key of Object.values(LangfuseOtelContextKeys)) cleanContext = cleanContext.deleteValue(key);
229
+ const scoped = () => withLangfuseRuntimeScope({
230
+ langfuse: this.langfuse,
231
+ traceIdSeed: this.getDeterministicTraceSeed(),
232
+ runId: this.runId,
233
+ toolOutputTracing: this.toolOutputTracing ?? resolveToolOutputTracingConfig(this.langfuse)
234
+ }, action, { replace: true });
235
+ const { userId, sessionId, tags, metadata, traceName } = this.identity;
236
+ const hasIdentity = userId != null || sessionId != null || metadata != null || traceName != null || (tags?.length ?? 0) > 0;
237
+ return context.with(cleanContext, () => hasIdentity ? propagateAttributes({
238
+ userId,
239
+ sessionId,
240
+ tags,
241
+ metadata,
242
+ traceName
243
+ }, scoped) : scoped());
90
244
  }
91
245
  handleChainStart(...args) {
92
- return this.withRuntimeContext(() => super.handleChainStart(...args));
246
+ return this.withRuntimeContext(() => super.handleChainStart(...args), this.startsDetachedRun(args[2], args[3]), args[5]);
93
247
  }
94
248
  handleChainError(...args) {
95
249
  const [error, runId, parentRunId] = args;
@@ -98,22 +252,22 @@ var ScopedLangfuseCallbackHandler = class extends CallbackHandler {
98
252
  return super.handleChainError(...args);
99
253
  }
100
254
  handleAgentAction(...args) {
101
- return this.withRuntimeContext(() => super.handleAgentAction(...args));
255
+ return this.withRuntimeContext(() => super.handleAgentAction(...args), this.startsDetachedRun(args[1], args[2]));
102
256
  }
103
257
  handleGenerationStart(...args) {
104
- return this.withRuntimeContext(() => super.handleGenerationStart(...args));
258
+ return this.withRuntimeContext(() => super.handleGenerationStart(...args), this.startsDetachedRun(args[2], args[3]), args[6]);
105
259
  }
106
260
  handleChatModelStart(...args) {
107
- return this.withRuntimeContext(() => super.handleChatModelStart(...args));
261
+ return this.withRuntimeContext(() => super.handleChatModelStart(...args), this.startsDetachedRun(args[2], args[3]), args[6]);
108
262
  }
109
263
  handleLLMStart(...args) {
110
- return this.withRuntimeContext(() => super.handleLLMStart(...args));
264
+ return this.withRuntimeContext(() => super.handleLLMStart(...args), this.startsDetachedRun(args[2], args[3]), args[6]);
111
265
  }
112
266
  handleLLMEnd(output, runId, parentRunId) {
113
267
  return super.handleLLMEnd(normalizeBedrockUsageForLangfuse(output), runId, parentRunId);
114
268
  }
115
269
  handleToolStart(...args) {
116
- return this.withRuntimeContext(() => super.handleToolStart(...args));
270
+ return this.withRuntimeContext(() => super.handleToolStart(...args), this.startsDetachedRun(args[2], args[3]), args[5]);
117
271
  }
118
272
  handleToolError(...args) {
119
273
  const [error, runId, parentRunId] = args;
@@ -121,12 +275,9 @@ var ScopedLangfuseCallbackHandler = class extends CallbackHandler {
121
275
  return super.handleToolError(...args);
122
276
  }
123
277
  handleRetrieverStart(...args) {
124
- return this.withRuntimeContext(() => super.handleRetrieverStart(...args));
278
+ return this.withRuntimeContext(() => super.handleRetrieverStart(...args), this.startsDetachedRun(args[2], args[3]), args[5]);
125
279
  }
126
280
  };
127
- function hasLangfuseConfigCredentials(langfuse) {
128
- return langfuse != null && isPresent(langfuse.publicKey) && isPresent(langfuse.secretKey);
129
- }
130
281
  function hasLangfuseConfigBaseUrl(langfuse) {
131
282
  return isPresent(langfuse?.baseUrl);
132
283
  }
@@ -176,17 +327,11 @@ function getLangfuseTraceName(traceMetadata, fallback = "LibreChat Agent") {
176
327
  const agentName = traceMetadata?.agentName;
177
328
  return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;
178
329
  }
179
- function hasLangfuseEnvConfig() {
180
- return hasLangfuseEnvCredentials();
181
- }
182
- function hasLangfuseEnvCredentials() {
183
- return isPresent(process.env.LANGFUSE_SECRET_KEY) && isPresent(process.env.LANGFUSE_PUBLIC_KEY);
184
- }
185
330
  function shouldCreateLangfuseHandler(langfuse) {
186
331
  if (langfuse?.enabled === false) return false;
187
332
  return hasLangfuseEnvConfig() || hasLangfuseConfigCredentials(langfuse) || hasLangfuseConfigBaseUrl(langfuse) && hasLangfuseEnvCredentials();
188
333
  }
189
- function createLangfuseHandler({ langfuse, userId, sessionId, traceMetadata, tags, traceIdSeed }) {
334
+ function createLangfuseHandler({ langfuse, userId, sessionId, traceMetadata, tags, traceIdSeed, runId, toolOutputTracing, traceName }) {
190
335
  if (!shouldCreateLangfuseHandler(langfuse)) return;
191
336
  return new ScopedLangfuseCallbackHandler({
192
337
  userId,
@@ -194,7 +339,10 @@ function createLangfuseHandler({ langfuse, userId, sessionId, traceMetadata, tag
194
339
  traceMetadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata),
195
340
  tags: mergeLangfuseTags(tags, langfuse?.tags),
196
341
  langfuse,
197
- traceIdSeed
342
+ traceIdSeed,
343
+ runId,
344
+ toolOutputTracing,
345
+ traceName
198
346
  });
199
347
  }
200
348
  function createPropagateAttributeParams({ langfuse, userId, sessionId, traceMetadata, traceName, tags }) {
@@ -218,6 +366,6 @@ async function disposeLangfuseHandler(value) {
218
366
  await getLangfuseTracerProvider().forceFlush?.();
219
367
  }
220
368
  //#endregion
221
- export { createLangfuseHandler, createLangfuseTraceMetadata, createLibreChatTraceAttributes, disposeLangfuseHandler, getLangfuseTraceName, hasLangfuseConfigCredentials, hasLangfuseEnvConfig, hasLangfuseEnvCredentials, isLangfuseCallbackHandler, withLangfuseAttributes };
369
+ export { createLangfuseHandler, createLangfuseTraceMetadata, createLibreChatTraceAttributes, disposeLangfuseHandler, getLangfuseTraceName, isLangfuseCallbackHandler, withLangfuseAttributes };
222
370
 
223
371
  //# sourceMappingURL=langfuse.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse.mjs","names":["otelContext"],"sources":["../../src/langfuse.ts"],"sourcesContent":["import { CallbackHandler } from '@langfuse/langchain';\nimport { context as otelContext } from '@opentelemetry/api';\nimport { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport { isGraphInterrupt, isParentCommand } from '@langchain/langgraph';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type {\n AIMessageChunkFields,\n AIMessageFields,\n UsageMetadata,\n} from '@langchain/core/messages';\nimport type {\n ChatGeneration,\n Generation,\n LLMResult,\n} from '@langchain/core/outputs';\nimport type { PropagateAttributesParams } from '@langfuse/tracing';\nimport type * as t from '@/types';\nimport {\n resolveLangfuseConfigForSpan,\n resolveTraceIdSeedForSpan,\n withLangfuseRuntimeScope,\n} from '@/langfuseRuntimeScope';\nimport { isPresent, parseBooleanEnv } from '@/utils/misc';\n\nconst TRACE_METADATA_MAX_LENGTH = 200;\nconst LANGFUSE_FORCE_FLUSH_ON_DISPOSE = 'LANGFUSE_FORCE_FLUSH_ON_DISPOSE';\nconst GRAPH_INTERRUPT_CONTROL_FLOW = { controlFlow: 'GraphInterrupt' } as const;\nconst GRAPH_INTERRUPT_TOOL_OUTPUT = JSON.stringify(\n GRAPH_INTERRUPT_CONTROL_FLOW\n);\n\nexport type LangfuseTraceMetadata = Record<string, string>;\nexport type LangfuseTraceAttributes = Record<string, string | number | boolean>;\ntype LangfuseMetadata = NonNullable<t.LangfuseConfig['metadata']>;\ntype LangfuseConfigTraceAttributes = NonNullable<\n t.LangfuseConfig['librechatTraceAttributes']\n>;\n\ntype LangfuseHandlerParams = {\n userId?: string;\n sessionId?: string;\n traceMetadata?: LangfuseTraceMetadata;\n tags?: string[];\n traceIdSeed?: string;\n};\n\ntype AgentLangfuseHandlerParams = LangfuseHandlerParams & {\n langfuse?: t.LangfuseConfig;\n};\n\ntype LangfuseAttributeParams = AgentLangfuseHandlerParams & {\n traceName?: string;\n};\n\ntype FlushableTracerProvider = {\n forceFlush?: () => Promise<void> | void;\n};\n\ntype BedrockResponseUsage = {\n inputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n};\n\ntype BedrockResponseMetadata = {\n metadata?: {\n usage?: BedrockResponseUsage;\n };\n};\n\nfunction getLangfuseBedrockUsage(\n message: AIMessage | AIMessageChunk\n): UsageMetadata | undefined {\n const usageMetadata = message.usage_metadata;\n const bedrockUsage = (message.response_metadata as BedrockResponseMetadata)\n .metadata?.usage;\n if (\n usageMetadata == null ||\n bedrockUsage == null ||\n usageMetadata.input_tokens !== bedrockUsage.inputTokens\n ) {\n return usageMetadata;\n }\n\n const cacheRead = bedrockUsage.cacheReadInputTokens ?? 0;\n const cacheCreation = bedrockUsage.cacheWriteInputTokens ?? 0;\n if (cacheRead === 0 && cacheCreation === 0) {\n return usageMetadata;\n }\n\n return {\n ...usageMetadata,\n input_tokens: usageMetadata.input_tokens + cacheRead + cacheCreation,\n };\n}\n\nfunction cloneMessageWithUsage(\n message: AIMessage | AIMessageChunk,\n usageMetadata: UsageMetadata\n): AIMessage | AIMessageChunk {\n const fields: AIMessageFields = {\n content: message.content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n id: message.id,\n name: message.name,\n tool_calls: message.tool_calls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: usageMetadata,\n };\n\n if (message instanceof AIMessageChunk) {\n const chunkFields: AIMessageChunkFields = {\n ...fields,\n tool_call_chunks: message.tool_call_chunks,\n };\n return new AIMessageChunk(chunkFields);\n }\n\n return new AIMessage(fields);\n}\n\nfunction normalizeGenerationForLangfuse(generation: Generation): Generation {\n if (!('message' in generation)) {\n return generation;\n }\n\n const message = (generation as ChatGeneration).message;\n if (!(message instanceof AIMessage || message instanceof AIMessageChunk)) {\n return generation;\n }\n\n const usageMetadata = getLangfuseBedrockUsage(message);\n if (usageMetadata == null || usageMetadata === message.usage_metadata) {\n return generation;\n }\n\n const chatGeneration: ChatGeneration = {\n ...(generation as ChatGeneration),\n message: cloneMessageWithUsage(message, usageMetadata),\n };\n return chatGeneration;\n}\n\nfunction normalizeBedrockUsageForLangfuse(output: LLMResult): LLMResult {\n if (output.generations.length === 0) {\n return output;\n }\n\n const listIndex = output.generations.length - 1;\n const generationList = output.generations[listIndex];\n if (generationList.length === 0) {\n return output;\n }\n\n const generationIndex = generationList.length - 1;\n const generation = generationList[generationIndex];\n const normalized = normalizeGenerationForLangfuse(generation);\n if (normalized === generation) {\n return output;\n }\n\n const generations = [...output.generations];\n generations[listIndex] = [...generationList];\n generations[listIndex][generationIndex] = normalized;\n return { ...output, generations };\n}\n\nclass ScopedLangfuseCallbackHandler extends CallbackHandler {\n private readonly langfuse?: t.LangfuseConfig;\n private readonly traceIdSeed?: string;\n\n constructor(params?: AgentLangfuseHandlerParams) {\n const { langfuse, traceIdSeed, ...handlerParams } = params ?? {};\n super(handlerParams);\n this.langfuse = langfuse;\n this.traceIdSeed = traceIdSeed;\n }\n\n private getDeterministicTraceSeed(): string | undefined {\n return this.langfuse?.deterministicTraceId === true\n ? this.traceIdSeed\n : undefined;\n }\n\n private withRuntimeContext<T>(action: () => T): T {\n const activeContext = otelContext.active();\n const langfuse =\n resolveLangfuseConfigForSpan(activeContext) ?? this.langfuse;\n const seed = this.getDeterministicTraceSeed();\n return withLangfuseRuntimeScope(\n {\n langfuse,\n traceIdSeed: resolveTraceIdSeedForSpan(activeContext) ?? seed,\n },\n action\n );\n }\n\n // LangChain may invoke callback handlers outside the caller's OTEL context.\n // Re-enter tenant scope only for callbacks that start Langfuse observations;\n // end/error/token callbacks use spans already bound to a processor at start.\n override handleChainStart(\n ...args: Parameters<CallbackHandler['handleChainStart']>\n ): ReturnType<CallbackHandler['handleChainStart']> {\n return this.withRuntimeContext(() => super.handleChainStart(...args));\n }\n\n override handleChainError(\n ...args: Parameters<CallbackHandler['handleChainError']>\n ): ReturnType<CallbackHandler['handleChainError']> {\n const [error, runId, parentRunId] = args;\n if (error != null && parentRunId != null && isGraphInterrupt(error)) {\n return super.handleChainEnd(\n GRAPH_INTERRUPT_CONTROL_FLOW,\n runId,\n parentRunId\n );\n }\n if (error != null && parentRunId != null && isParentCommand(error)) {\n return super.handleChainEnd(\n { controlFlow: 'ParentCommand' },\n runId,\n parentRunId\n );\n }\n return super.handleChainError(...args);\n }\n\n override handleAgentAction(\n ...args: Parameters<CallbackHandler['handleAgentAction']>\n ): ReturnType<CallbackHandler['handleAgentAction']> {\n return this.withRuntimeContext(() => super.handleAgentAction(...args));\n }\n\n override handleGenerationStart(\n ...args: Parameters<CallbackHandler['handleGenerationStart']>\n ): ReturnType<CallbackHandler['handleGenerationStart']> {\n return this.withRuntimeContext(() => super.handleGenerationStart(...args));\n }\n\n override handleChatModelStart(\n ...args: Parameters<CallbackHandler['handleChatModelStart']>\n ): ReturnType<CallbackHandler['handleChatModelStart']> {\n return this.withRuntimeContext(() => super.handleChatModelStart(...args));\n }\n\n override handleLLMStart(\n ...args: Parameters<CallbackHandler['handleLLMStart']>\n ): ReturnType<CallbackHandler['handleLLMStart']> {\n return this.withRuntimeContext(() => super.handleLLMStart(...args));\n }\n\n override handleLLMEnd(\n output: LLMResult,\n runId: string,\n parentRunId?: string\n ): Promise<void> {\n return super.handleLLMEnd(\n normalizeBedrockUsageForLangfuse(output),\n runId,\n parentRunId\n );\n }\n\n override handleToolStart(\n ...args: Parameters<CallbackHandler['handleToolStart']>\n ): ReturnType<CallbackHandler['handleToolStart']> {\n return this.withRuntimeContext(() => super.handleToolStart(...args));\n }\n\n override handleToolError(\n ...args: Parameters<CallbackHandler['handleToolError']>\n ): ReturnType<CallbackHandler['handleToolError']> {\n const [error, runId, parentRunId] = args;\n if (error != null && parentRunId != null && isGraphInterrupt(error)) {\n return super.handleToolEnd(\n GRAPH_INTERRUPT_TOOL_OUTPUT,\n runId,\n parentRunId\n );\n }\n return super.handleToolError(...args);\n }\n\n override handleRetrieverStart(\n ...args: Parameters<CallbackHandler['handleRetrieverStart']>\n ): ReturnType<CallbackHandler['handleRetrieverStart']> {\n return this.withRuntimeContext(() => super.handleRetrieverStart(...args));\n }\n}\n\nfunction hasLangfuseTracingConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.toolNodeTracing != null || langfuse?.toolOutputTracing != null\n );\n}\n\nfunction hasLangfuseTraceAttributes(langfuse?: t.LangfuseConfig): boolean {\n return (\n Object.keys(createTraceMetadata(langfuse?.metadata ?? {})).length > 0 ||\n Object.keys(\n createLibreChatTraceAttributes(langfuse?.librechatTraceAttributes ?? {})\n ).length > 0 ||\n (mergeLangfuseTags(undefined, langfuse?.tags)?.length ?? 0) > 0\n );\n}\n\nexport function hasLangfuseConfigCredentials(\n langfuse?: t.LangfuseConfig\n): langfuse is t.LangfuseConfig & {\n publicKey: string;\n secretKey: string;\n} {\n return (\n langfuse != null &&\n isPresent(langfuse.publicKey) &&\n isPresent(langfuse.secretKey)\n );\n}\n\nfunction hasLangfuseConfigBaseUrl(langfuse?: t.LangfuseConfig): boolean {\n return isPresent(langfuse?.baseUrl);\n}\n\nexport function isExplicitLangfuseConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.enabled != null ||\n isPresent(langfuse?.publicKey) ||\n isPresent(langfuse?.secretKey) ||\n isPresent(langfuse?.baseUrl) ||\n hasLangfuseTraceAttributes(langfuse) ||\n hasLangfuseTracingConfig(langfuse)\n );\n}\n\nfunction createTraceMetadata(\n metadata: Record<string, unknown>\n): LangfuseTraceMetadata {\n const traceMetadata: LangfuseTraceMetadata = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (value == null) {\n continue;\n }\n const stringValue = typeof value === 'string' ? value : String(value);\n if (\n stringValue.trim() === '' ||\n stringValue.length > TRACE_METADATA_MAX_LENGTH\n ) {\n continue;\n }\n traceMetadata[key] = stringValue;\n }\n return traceMetadata;\n}\n\nexport function createLibreChatTraceAttributes(\n attributes: LangfuseConfigTraceAttributes\n): LangfuseTraceAttributes {\n const librechatTraceAttributes: LangfuseTraceAttributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value == null || key.trim() === '') {\n continue;\n }\n if (typeof value === 'string') {\n if (value.trim() === '' || value.length > TRACE_METADATA_MAX_LENGTH) {\n continue;\n }\n librechatTraceAttributes[key] = value;\n continue;\n }\n librechatTraceAttributes[key] = value;\n }\n return librechatTraceAttributes;\n}\n\nexport function createLangfuseTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n}: {\n messageId?: unknown;\n parentMessageId?: unknown;\n agentId?: unknown;\n agentName?: unknown;\n}): LangfuseTraceMetadata {\n return createTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n });\n}\n\nfunction mergeLangfuseTraceMetadata(\n traceMetadata?: LangfuseTraceMetadata,\n metadata?: LangfuseMetadata\n): LangfuseTraceMetadata | undefined {\n const merged = createTraceMetadata({\n ...(metadata ?? {}),\n ...(traceMetadata ?? {}),\n });\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\nfunction mergeLangfuseTags(\n tags?: string[],\n configTags?: string[]\n): string[] | undefined {\n const merged = [...(tags ?? []), ...(configTags ?? [])].filter(\n (tag) => tag.trim() !== ''\n );\n return merged.length > 0 ? [...new Set(merged)] : undefined;\n}\n\nexport function getLangfuseTraceName(\n traceMetadata?: LangfuseTraceMetadata,\n fallback: string = 'LibreChat Agent'\n): string {\n const agentName = traceMetadata?.agentName;\n return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;\n}\n\nexport function hasLangfuseEnvConfig(): boolean {\n return hasLangfuseEnvCredentials();\n}\n\nexport function hasLangfuseEnvCredentials(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nexport function shouldCreateLangfuseHandler(\n langfuse?: t.LangfuseConfig\n): boolean {\n if (langfuse?.enabled === false) {\n return false;\n }\n return (\n hasLangfuseEnvConfig() ||\n hasLangfuseConfigCredentials(langfuse) ||\n (hasLangfuseConfigBaseUrl(langfuse) && hasLangfuseEnvCredentials())\n );\n}\n\nexport function createLegacyLangfuseHandler(\n params: LangfuseHandlerParams\n): CallbackHandler {\n return new ScopedLangfuseCallbackHandler(params);\n}\n\nexport function createLangfuseHandler({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n tags,\n traceIdSeed,\n}: AgentLangfuseHandlerParams): CallbackHandler | undefined {\n if (!shouldCreateLangfuseHandler(langfuse)) {\n return undefined;\n }\n return new ScopedLangfuseCallbackHandler({\n userId,\n sessionId,\n traceMetadata: mergeLangfuseTraceMetadata(\n traceMetadata,\n langfuse?.metadata\n ),\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n langfuse,\n traceIdSeed,\n });\n}\n\nfunction createPropagateAttributeParams({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n traceName,\n tags,\n}: LangfuseAttributeParams): PropagateAttributesParams {\n return {\n userId,\n sessionId,\n traceName,\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n metadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata),\n };\n}\n\nexport function withLangfuseAttributes<T>(\n params: LangfuseAttributeParams,\n action: () => T\n): T {\n if (!shouldCreateLangfuseHandler(params.langfuse)) {\n return action();\n }\n return propagateAttributes(createPropagateAttributeParams(params), action);\n}\n\nexport function hasExplicitLangfuseConfig(\n contexts: Iterable<{ langfuse?: t.LangfuseConfig }>\n): boolean {\n for (const context of contexts) {\n if (isExplicitLangfuseConfig(context.langfuse)) {\n return true;\n }\n }\n return false;\n}\n\nexport function isLangfuseCallbackHandler(value: unknown): boolean {\n return value instanceof CallbackHandler;\n}\n\nexport async function disposeLangfuseHandler(value: unknown): Promise<void> {\n if (\n value == null ||\n parseBooleanEnv(process.env[LANGFUSE_FORCE_FLUSH_ON_DISPOSE]) !== true\n ) {\n return;\n }\n const provider = getLangfuseTracerProvider() as FlushableTracerProvider;\n await provider.forceFlush?.();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuse.mjs","names":["otelTrace","otelContext"],"sources":["../../src/langfuse.ts"],"sourcesContent":["import { CallbackHandler } from '@langfuse/langchain';\nimport { LangfuseOtelContextKeys } from '@langfuse/core';\nimport { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport { isGraphInterrupt, isParentCommand } from '@langchain/langgraph';\nimport { context as otelContext, trace as otelTrace } from '@opentelemetry/api';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type {\n AIMessageChunkFields,\n AIMessageFields,\n UsageMetadata,\n} from '@langchain/core/messages';\nimport type {\n ChatGeneration,\n Generation,\n LLMResult,\n} from '@langchain/core/outputs';\nimport type { PropagateAttributesParams } from '@langfuse/tracing';\nimport type { Context } from '@opentelemetry/api';\nimport type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';\nimport type * as t from '@/types';\nimport {\n resolveLangfuseConfigForSpan,\n resolveLangfuseScopeAgentId,\n resolveLangfuseScopeRunId,\n resolveTraceIdSeedForSpan,\n withLangfuseRuntimeScope,\n} from '@/langfuseRuntimeScope';\nimport {\n hasLangfuseConfigCredentials,\n hasLangfuseEnvCredentials,\n resolveToolOutputTracingConfig,\n hasLangfuseEnvConfig,\n} from '@/langfuseConfig';\nimport {\n getLangfuseManagedSpanDestination,\n resolveLangfuseDestinationKey,\n} from '@/langfuseSpanRegistry';\nimport { isPresent, parseBooleanEnv } from '@/utils/misc';\n\nexport {\n hasLangfuseConfigCredentials,\n hasLangfuseEnvCredentials,\n hasLangfuseEnvConfig,\n};\n\nconst TRACE_METADATA_MAX_LENGTH = 200;\nconst LANGFUSE_FORCE_FLUSH_ON_DISPOSE = 'LANGFUSE_FORCE_FLUSH_ON_DISPOSE';\nconst GRAPH_INTERRUPT_CONTROL_FLOW = { controlFlow: 'GraphInterrupt' } as const;\nconst GRAPH_INTERRUPT_TOOL_OUTPUT = JSON.stringify(\n GRAPH_INTERRUPT_CONTROL_FLOW\n);\n\nexport type LangfuseTraceMetadata = Record<string, string>;\nexport type LangfuseTraceAttributes = Record<string, string | number | boolean>;\ntype LangfuseMetadata = NonNullable<t.LangfuseConfig['metadata']>;\ntype LangfuseConfigTraceAttributes = NonNullable<\n t.LangfuseConfig['librechatTraceAttributes']\n>;\n\ntype LangfuseHandlerParams = {\n userId?: string;\n sessionId?: string;\n traceMetadata?: LangfuseTraceMetadata;\n tags?: string[];\n traceIdSeed?: string;\n /** Identity of the run this handler traces; ambient runtime scopes are\n * only adopted when stamped with the same run (see\n * `LangfuseRuntimeContext.runId`). */\n runId?: string;\n /** The run's resolved tool-output policy — for multi-agent streams the\n * conservative aggregate across agents, which `this.langfuse` (the\n * primary agent's config) cannot reproduce. Applied when a foreign\n * scope's policy is rejected. */\n toolOutputTracing?: ResolvedLangfuseToolOutputTracingConfig;\n /** The run's propagated trace name, re-propagated when a foreign scope's\n * attributes are cleared. */\n traceName?: string;\n};\n\ntype AgentLangfuseHandlerParams = LangfuseHandlerParams & {\n langfuse?: t.LangfuseConfig;\n};\n\ntype HandlerIdentity = {\n userId?: string;\n sessionId?: string;\n tags?: string[];\n metadata?: LangfuseTraceMetadata;\n traceName?: string;\n};\n\ntype LangfuseAttributeParams = AgentLangfuseHandlerParams & {\n traceName?: string;\n};\n\ntype FlushableTracerProvider = {\n forceFlush?: () => Promise<void> | void;\n};\n\ntype BedrockResponseUsage = {\n inputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n};\n\ntype BedrockResponseMetadata = {\n metadata?: {\n usage?: BedrockResponseUsage;\n };\n};\n\nfunction getLangfuseBedrockUsage(\n message: AIMessage | AIMessageChunk\n): UsageMetadata | undefined {\n const usageMetadata = message.usage_metadata;\n const bedrockUsage = (message.response_metadata as BedrockResponseMetadata)\n .metadata?.usage;\n if (\n usageMetadata == null ||\n bedrockUsage == null ||\n usageMetadata.input_tokens !== bedrockUsage.inputTokens\n ) {\n return usageMetadata;\n }\n\n const cacheRead = bedrockUsage.cacheReadInputTokens ?? 0;\n const cacheCreation = bedrockUsage.cacheWriteInputTokens ?? 0;\n if (cacheRead === 0 && cacheCreation === 0) {\n return usageMetadata;\n }\n\n return {\n ...usageMetadata,\n input_tokens: usageMetadata.input_tokens + cacheRead + cacheCreation,\n };\n}\n\nfunction cloneMessageWithUsage(\n message: AIMessage | AIMessageChunk,\n usageMetadata: UsageMetadata\n): AIMessage | AIMessageChunk {\n const fields: AIMessageFields = {\n content: message.content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n id: message.id,\n name: message.name,\n tool_calls: message.tool_calls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: usageMetadata,\n };\n\n if (message instanceof AIMessageChunk) {\n const chunkFields: AIMessageChunkFields = {\n ...fields,\n tool_call_chunks: message.tool_call_chunks,\n };\n return new AIMessageChunk(chunkFields);\n }\n\n return new AIMessage(fields);\n}\n\nfunction normalizeGenerationForLangfuse(generation: Generation): Generation {\n if (!('message' in generation)) {\n return generation;\n }\n\n const message = (generation as ChatGeneration).message;\n if (!(message instanceof AIMessage || message instanceof AIMessageChunk)) {\n return generation;\n }\n\n const usageMetadata = getLangfuseBedrockUsage(message);\n if (usageMetadata == null || usageMetadata === message.usage_metadata) {\n return generation;\n }\n\n const chatGeneration: ChatGeneration = {\n ...(generation as ChatGeneration),\n message: cloneMessageWithUsage(message, usageMetadata),\n };\n return chatGeneration;\n}\n\nfunction normalizeBedrockUsageForLangfuse(output: LLMResult): LLMResult {\n if (output.generations.length === 0) {\n return output;\n }\n\n const listIndex = output.generations.length - 1;\n const generationList = output.generations[listIndex];\n if (generationList.length === 0) {\n return output;\n }\n\n const generationIndex = generationList.length - 1;\n const generation = generationList[generationIndex];\n const normalized = normalizeGenerationForLangfuse(generation);\n if (normalized === generation) {\n return output;\n }\n\n const generations = [...output.generations];\n generations[listIndex] = [...generationList];\n generations[listIndex][generationIndex] = normalized;\n return { ...output, generations };\n}\n\nconst LANGGRAPH_NODE_METADATA_KEY = 'langgraph_node';\n/** Explicit agent identity in invoke metadata. Every identity-stamping\n * component (graph model path, ToolNode, summarization node) overwrites the\n * canonical `agentId` at its own invoke, so spread order guarantees the\n * closest stamper wins — key priority alone could not (an inherited key of\n * either casing can name the wrong agent). `agent_id` remains a fallback\n * for third-party graphs that only stamp the snake-case form. */\nconst AGENT_ID_METADATA_KEYS = ['agentId', 'agent_id'];\nconst LANGGRAPH_NODE_AGENT_PREFIXES = ['agent=', 'tools=', 'summarize='];\n\n/** The LangGraph node a callback executes under, from its inherited\n * `langgraph_node` run metadata. `undefined` when the callback carries no\n * node identity. */\nfunction getCallbackNode(\n metadata?: Record<string, unknown>\n): string | undefined {\n const node = metadata?.[LANGGRAPH_NODE_METADATA_KEY];\n return typeof node === 'string' && node !== '' ? node : undefined;\n}\n\n/** Whether a callback's node identifies the given agent. The outer workflow\n * node carries the agent id VERBATIM — including ids that themselves begin\n * with an internal prefix (an agent literally named `agent=research`) — so\n * an exact match is checked before decoding the inner subgraph prefixes\n * (`agent=` / `tools=` / `summarize=`). */\nfunction callbackNodeMatchesAgent(node: string, agentId: string): boolean {\n if (node === agentId) {\n return true;\n }\n for (const prefix of LANGGRAPH_NODE_AGENT_PREFIXES) {\n if (node.startsWith(prefix) && node.slice(prefix.length) === agentId) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Hosts often execute agent code inside their own OpenTelemetry spans (HTTP\n * server auto-instrumentation on the global provider). Root observations must\n * not inherit that ambient identity: the foreign parent is never exported to\n * Langfuse, which orphans the trace root (root/trace input-output shaping is\n * skipped because the span no longer looks like a root), collapses concurrent\n * runs inside one request context — an agent run and the previous turn's\n * title run — into a single merged trace with racing names and unioned tags,\n * and bypasses the seeded deterministic trace id generator. Only a\n * Langfuse-managed span bound to the same export destination as the starting\n * run is a safe parent — that is the sanctioned way for hosts to group runs\n * under their own Langfuse observations; a managed span from a different\n * destination (another tenant's project) would leave this run's trace\n * dangling in its own destination while inheriting the other trace's id.\n */\nfunction detachForeignAmbientSpan(\n activeContext: Context,\n destinationKey?: string\n): Context {\n const activeSpan = otelTrace.getSpan(activeContext);\n if (activeSpan == null) {\n return activeContext;\n }\n const parentDestination = getLangfuseManagedSpanDestination(activeSpan);\n if (parentDestination != null && parentDestination === destinationKey) {\n return activeContext;\n }\n return otelTrace.deleteSpan(activeContext);\n}\n\nclass ScopedLangfuseCallbackHandler extends CallbackHandler {\n private readonly langfuse?: t.LangfuseConfig;\n private readonly traceIdSeed?: string;\n private readonly runId?: string;\n private readonly identity: HandlerIdentity;\n private readonly toolOutputTracing?: ResolvedLangfuseToolOutputTracingConfig;\n private readonly trackedRunIds = new Set<string>();\n\n constructor(params?: AgentLangfuseHandlerParams) {\n const {\n langfuse,\n traceIdSeed,\n runId,\n toolOutputTracing,\n traceName,\n ...handlerParams\n } = params ?? {};\n super(handlerParams);\n this.langfuse = langfuse;\n this.traceIdSeed = traceIdSeed;\n this.runId = runId;\n this.toolOutputTracing = toolOutputTracing;\n this.identity = {\n userId: handlerParams.userId,\n sessionId: handlerParams.sessionId,\n tags: handlerParams.tags,\n metadata: handlerParams.traceMetadata,\n traceName,\n };\n }\n\n private getDeterministicTraceSeed(): string | undefined {\n return this.langfuse?.deterministicTraceId === true\n ? this.traceIdSeed\n : undefined;\n }\n\n /**\n * Mirrors the base handler's `runMap`: a start callback whose `parentRunId`\n * this handler never observed gets no explicit parent span and falls back\n * to the ambient OTEL context (`startAndRegisterOtelSpan`), so it needs the\n * same foreign-span detachment as a true root. This happens whenever a\n * handler is attached mid-graph — e.g. the per-agent handler created for a\n * detached subagent's model invocations, whose surrounding graph runs were\n * never traced.\n */\n private startsDetachedRun(runId: string, parentRunId?: string): boolean {\n const detached =\n parentRunId == null || !this.trackedRunIds.has(parentRunId);\n this.trackedRunIds.add(runId);\n return detached;\n }\n\n /**\n * Whether the ambient runtime scope belongs to a different run — or, for\n * per-agent overlay scopes, to a different concurrently executing agent of\n * the same run than the one this callback reports via its inherited\n * `langgraph_node` metadata. Unstamped scopes on unstamped handlers are\n * never foreign (host-managed handler semantics).\n */\n private isForeignScope(\n scopeRunId: string | undefined,\n scopeAgentId: string | undefined,\n callbackMetadata?: Record<string, unknown>\n ): boolean {\n if (this.runId == null || scopeRunId == null) {\n return false;\n }\n if (scopeRunId !== this.runId) {\n return true;\n }\n if (scopeAgentId == null) {\n return false;\n }\n // Explicit agent identity (stamped into invoke metadata by the graph's\n // model path and ToolNode) is unambiguous; node names are a fallback —\n // an agent literally named `agent=research` makes its outer node\n // indistinguishable from agent `research`'s inner model node.\n for (const key of AGENT_ID_METADATA_KEYS) {\n const explicitAgentId = callbackMetadata?.[key];\n if (typeof explicitAgentId === 'string' && explicitAgentId !== '') {\n return explicitAgentId !== scopeAgentId;\n }\n }\n const callbackNode = getCallbackNode(callbackMetadata);\n return (\n callbackNode != null &&\n !callbackNodeMatchesAgent(callbackNode, scopeAgentId)\n );\n }\n\n /**\n * LangChain executes non-awaited callbacks on a process-wide background\n * queue (`consumeCallback`), so this callback may be running inside a\n * DIFFERENT concurrent run's async context. The ambient runtime scope is\n * therefore only adopted when it belongs to this handler's run (and, for\n * agent sub-scopes, to this callback's agent): scopes are stamped at their\n * call sites, and a foreign stamp means the scope's config would route\n * spans to the wrong destination, its seed would collapse this run's spans\n * into the foreign trace, and its tool-output policy could leak output the\n * foreign run permits but this run redacts — so config, seed, AND redaction\n * policy all fall back to this handler's own run. Unstamped scopes on\n * unstamped handlers keep scope-first semantics (agent overlays and\n * per-path seeds like the title/label scopes, host-managed handlers).\n *\n * Detached runs (roots, or starts whose parent this handler never tracked)\n * take their span parent from the ambient OTEL context, so drop any\n * foreign ambient span first — a run launched from a host's instrumented\n * request context must start its own trace, not join an unexported\n * foreign one.\n */\n private withRuntimeContext<T>(\n action: () => T,\n isDetachedRun = false,\n callbackMetadata?: Record<string, unknown>\n ): T {\n const currentContext = otelContext.active();\n const scopeRunId = resolveLangfuseScopeRunId(currentContext);\n const scopeAgentId = resolveLangfuseScopeAgentId(currentContext);\n if (this.isForeignScope(scopeRunId, scopeAgentId, callbackMetadata)) {\n return this.withForeignScopeRejected(action);\n }\n const langfuse =\n resolveLangfuseConfigForSpan(currentContext) ?? this.langfuse;\n const activeContext = isDetachedRun\n ? detachForeignAmbientSpan(\n currentContext,\n resolveLangfuseDestinationKey(langfuse)\n )\n : currentContext;\n const scoped = (): T =>\n withLangfuseRuntimeScope(\n {\n langfuse,\n traceIdSeed:\n resolveTraceIdSeedForSpan(activeContext) ??\n this.getDeterministicTraceSeed(),\n runId: scopeRunId ?? this.runId,\n },\n action\n );\n return activeContext === currentContext\n ? scoped()\n : otelContext.with(activeContext, scoped);\n }\n\n /**\n * A foreign concurrent run's context must be replaced wholesale, not\n * merged: this library's scope keys (config, seed, tool-output policy,\n * identity stamps) via the replace-mode runtime scope, `@langfuse/tracing`'s\n * propagated trace attributes (userId, sessionId, tags, metadata, …) by\n * deleting their context keys and re-propagating this handler's own\n * identity, and the foreign active span — removed both so detached runs\n * root their own trace and so `propagateAttributes` cannot stamp this\n * run's identity onto the foreign run's still-recording span.\n */\n private withForeignScopeRejected<T>(action: () => T): T {\n let cleanContext = otelTrace.deleteSpan(otelContext.active());\n for (const key of Object.values(LangfuseOtelContextKeys)) {\n cleanContext = cleanContext.deleteValue(key);\n }\n const scoped = (): T =>\n withLangfuseRuntimeScope(\n {\n langfuse: this.langfuse,\n traceIdSeed: this.getDeterministicTraceSeed(),\n runId: this.runId,\n toolOutputTracing:\n this.toolOutputTracing ??\n resolveToolOutputTracingConfig(this.langfuse),\n },\n action,\n { replace: true }\n );\n const { userId, sessionId, tags, metadata, traceName } = this.identity;\n const hasIdentity =\n userId != null ||\n sessionId != null ||\n metadata != null ||\n traceName != null ||\n (tags?.length ?? 0) > 0;\n return otelContext.with(cleanContext, () =>\n hasIdentity\n ? propagateAttributes(\n { userId, sessionId, tags, metadata, traceName },\n scoped\n )\n : scoped()\n );\n }\n\n // LangChain may invoke callback handlers outside the caller's OTEL context.\n // Re-enter tenant scope only for callbacks that start Langfuse observations;\n // end/error/token callbacks use spans already bound to a processor at start.\n override handleChainStart(\n ...args: Parameters<CallbackHandler['handleChainStart']>\n ): ReturnType<CallbackHandler['handleChainStart']> {\n return this.withRuntimeContext(\n () => super.handleChainStart(...args),\n this.startsDetachedRun(args[2], args[3]),\n args[5]\n );\n }\n\n override handleChainError(\n ...args: Parameters<CallbackHandler['handleChainError']>\n ): ReturnType<CallbackHandler['handleChainError']> {\n const [error, runId, parentRunId] = args;\n if (error != null && parentRunId != null && isGraphInterrupt(error)) {\n return super.handleChainEnd(\n GRAPH_INTERRUPT_CONTROL_FLOW,\n runId,\n parentRunId\n );\n }\n if (error != null && parentRunId != null && isParentCommand(error)) {\n return super.handleChainEnd(\n { controlFlow: 'ParentCommand' },\n runId,\n parentRunId\n );\n }\n return super.handleChainError(...args);\n }\n\n override handleAgentAction(\n ...args: Parameters<CallbackHandler['handleAgentAction']>\n ): ReturnType<CallbackHandler['handleAgentAction']> {\n return this.withRuntimeContext(\n () => super.handleAgentAction(...args),\n this.startsDetachedRun(args[1], args[2])\n );\n }\n\n override handleGenerationStart(\n ...args: Parameters<CallbackHandler['handleGenerationStart']>\n ): ReturnType<CallbackHandler['handleGenerationStart']> {\n return this.withRuntimeContext(\n () => super.handleGenerationStart(...args),\n this.startsDetachedRun(args[2], args[3]),\n args[6]\n );\n }\n\n override handleChatModelStart(\n ...args: Parameters<CallbackHandler['handleChatModelStart']>\n ): ReturnType<CallbackHandler['handleChatModelStart']> {\n return this.withRuntimeContext(\n () => super.handleChatModelStart(...args),\n this.startsDetachedRun(args[2], args[3]),\n args[6]\n );\n }\n\n override handleLLMStart(\n ...args: Parameters<CallbackHandler['handleLLMStart']>\n ): ReturnType<CallbackHandler['handleLLMStart']> {\n return this.withRuntimeContext(\n () => super.handleLLMStart(...args),\n this.startsDetachedRun(args[2], args[3]),\n args[6]\n );\n }\n\n override handleLLMEnd(\n output: LLMResult,\n runId: string,\n parentRunId?: string\n ): Promise<void> {\n return super.handleLLMEnd(\n normalizeBedrockUsageForLangfuse(output),\n runId,\n parentRunId\n );\n }\n\n override handleToolStart(\n ...args: Parameters<CallbackHandler['handleToolStart']>\n ): ReturnType<CallbackHandler['handleToolStart']> {\n return this.withRuntimeContext(\n () => super.handleToolStart(...args),\n this.startsDetachedRun(args[2], args[3]),\n args[5]\n );\n }\n\n override handleToolError(\n ...args: Parameters<CallbackHandler['handleToolError']>\n ): ReturnType<CallbackHandler['handleToolError']> {\n const [error, runId, parentRunId] = args;\n if (error != null && parentRunId != null && isGraphInterrupt(error)) {\n return super.handleToolEnd(\n GRAPH_INTERRUPT_TOOL_OUTPUT,\n runId,\n parentRunId\n );\n }\n return super.handleToolError(...args);\n }\n\n override handleRetrieverStart(\n ...args: Parameters<CallbackHandler['handleRetrieverStart']>\n ): ReturnType<CallbackHandler['handleRetrieverStart']> {\n return this.withRuntimeContext(\n () => super.handleRetrieverStart(...args),\n this.startsDetachedRun(args[2], args[3]),\n args[5]\n );\n }\n}\n\nfunction hasLangfuseTracingConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.toolNodeTracing != null || langfuse?.toolOutputTracing != null\n );\n}\n\nfunction hasLangfuseTraceAttributes(langfuse?: t.LangfuseConfig): boolean {\n return (\n Object.keys(createTraceMetadata(langfuse?.metadata ?? {})).length > 0 ||\n Object.keys(\n createLibreChatTraceAttributes(langfuse?.librechatTraceAttributes ?? {})\n ).length > 0 ||\n (mergeLangfuseTags(undefined, langfuse?.tags)?.length ?? 0) > 0\n );\n}\n\nfunction hasLangfuseConfigBaseUrl(langfuse?: t.LangfuseConfig): boolean {\n return isPresent(langfuse?.baseUrl);\n}\n\nexport function isExplicitLangfuseConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.enabled != null ||\n isPresent(langfuse?.publicKey) ||\n isPresent(langfuse?.secretKey) ||\n isPresent(langfuse?.baseUrl) ||\n hasLangfuseTraceAttributes(langfuse) ||\n hasLangfuseTracingConfig(langfuse)\n );\n}\n\nfunction createTraceMetadata(\n metadata: Record<string, unknown>\n): LangfuseTraceMetadata {\n const traceMetadata: LangfuseTraceMetadata = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (value == null) {\n continue;\n }\n const stringValue = typeof value === 'string' ? value : String(value);\n if (\n stringValue.trim() === '' ||\n stringValue.length > TRACE_METADATA_MAX_LENGTH\n ) {\n continue;\n }\n traceMetadata[key] = stringValue;\n }\n return traceMetadata;\n}\n\nexport function createLibreChatTraceAttributes(\n attributes: LangfuseConfigTraceAttributes\n): LangfuseTraceAttributes {\n const librechatTraceAttributes: LangfuseTraceAttributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value == null || key.trim() === '') {\n continue;\n }\n if (typeof value === 'string') {\n if (value.trim() === '' || value.length > TRACE_METADATA_MAX_LENGTH) {\n continue;\n }\n librechatTraceAttributes[key] = value;\n continue;\n }\n librechatTraceAttributes[key] = value;\n }\n return librechatTraceAttributes;\n}\n\nexport function createLangfuseTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n}: {\n messageId?: unknown;\n parentMessageId?: unknown;\n agentId?: unknown;\n agentName?: unknown;\n}): LangfuseTraceMetadata {\n return createTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n });\n}\n\nfunction mergeLangfuseTraceMetadata(\n traceMetadata?: LangfuseTraceMetadata,\n metadata?: LangfuseMetadata\n): LangfuseTraceMetadata | undefined {\n const merged = createTraceMetadata({\n ...(metadata ?? {}),\n ...(traceMetadata ?? {}),\n });\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\nfunction mergeLangfuseTags(\n tags?: string[],\n configTags?: string[]\n): string[] | undefined {\n const merged = [...(tags ?? []), ...(configTags ?? [])].filter(\n (tag) => tag.trim() !== ''\n );\n return merged.length > 0 ? [...new Set(merged)] : undefined;\n}\n\nexport function getLangfuseTraceName(\n traceMetadata?: LangfuseTraceMetadata,\n fallback: string = 'LibreChat Agent'\n): string {\n const agentName = traceMetadata?.agentName;\n return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;\n}\n\nexport function shouldCreateLangfuseHandler(\n langfuse?: t.LangfuseConfig\n): boolean {\n if (langfuse?.enabled === false) {\n return false;\n }\n return (\n hasLangfuseEnvConfig() ||\n hasLangfuseConfigCredentials(langfuse) ||\n (hasLangfuseConfigBaseUrl(langfuse) && hasLangfuseEnvCredentials())\n );\n}\n\nexport function createLegacyLangfuseHandler(\n params: LangfuseHandlerParams\n): CallbackHandler {\n return new ScopedLangfuseCallbackHandler(params);\n}\n\nexport function createLangfuseHandler({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n tags,\n traceIdSeed,\n runId,\n toolOutputTracing,\n traceName,\n}: AgentLangfuseHandlerParams): CallbackHandler | undefined {\n if (!shouldCreateLangfuseHandler(langfuse)) {\n return undefined;\n }\n return new ScopedLangfuseCallbackHandler({\n userId,\n sessionId,\n traceMetadata: mergeLangfuseTraceMetadata(\n traceMetadata,\n langfuse?.metadata\n ),\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n langfuse,\n traceIdSeed,\n runId,\n toolOutputTracing,\n traceName,\n });\n}\n\nfunction createPropagateAttributeParams({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n traceName,\n tags,\n}: LangfuseAttributeParams): PropagateAttributesParams {\n return {\n userId,\n sessionId,\n traceName,\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n metadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata),\n };\n}\n\nexport function withLangfuseAttributes<T>(\n params: LangfuseAttributeParams,\n action: () => T\n): T {\n if (!shouldCreateLangfuseHandler(params.langfuse)) {\n return action();\n }\n return propagateAttributes(createPropagateAttributeParams(params), action);\n}\n\nexport function hasExplicitLangfuseConfig(\n contexts: Iterable<{ langfuse?: t.LangfuseConfig }>\n): boolean {\n for (const context of contexts) {\n if (isExplicitLangfuseConfig(context.langfuse)) {\n return true;\n }\n }\n return false;\n}\n\nexport function isLangfuseCallbackHandler(value: unknown): boolean {\n return value instanceof CallbackHandler;\n}\n\nexport async function disposeLangfuseHandler(value: unknown): Promise<void> {\n if (\n value == null ||\n parseBooleanEnv(process.env[LANGFUSE_FORCE_FLUSH_ON_DISPOSE]) !== true\n ) {\n return;\n }\n const provider = getLangfuseTracerProvider() as FlushableTracerProvider;\n await provider.forceFlush?.();\n}\n"],"mappings":"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"}
@@ -7,6 +7,15 @@ function isPresent(value) {
7
7
  function normalizeToolName(name) {
8
8
  return name.trim().toLowerCase();
9
9
  }
10
+ function hasLangfuseConfigCredentials(langfuse) {
11
+ return langfuse != null && isPresent(langfuse.publicKey) && isPresent(langfuse.secretKey);
12
+ }
13
+ function hasLangfuseEnvCredentials() {
14
+ return isPresent(process.env.LANGFUSE_SECRET_KEY) && isPresent(process.env.LANGFUSE_PUBLIC_KEY);
15
+ }
16
+ function hasLangfuseEnvConfig() {
17
+ return hasLangfuseEnvCredentials();
18
+ }
10
19
  function normalizeToolNames(names) {
11
20
  const normalized = /* @__PURE__ */ new Set();
12
21
  for (const name of names ?? []) if (isPresent(name)) normalized.add(normalizeToolName(name));
@@ -97,6 +106,6 @@ function resolveLangfuseConfig(runLangfuse, agentLangfuse) {
97
106
  };
98
107
  }
99
108
  //#endregion
100
- export { LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT, hasToolOutputTracingConfig, normalizeToolName, resolveLangfuseConfig, resolveToolOutputTracingConfig };
109
+ export { LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT, hasLangfuseConfigCredentials, hasLangfuseEnvConfig, hasLangfuseEnvCredentials, hasToolOutputTracingConfig, normalizeToolName, resolveLangfuseConfig, resolveToolOutputTracingConfig };
101
110
 
102
111
  //# sourceMappingURL=langfuseConfig.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"langfuseConfig.mjs","names":[],"sources":["../../src/langfuseConfig.ts"],"sourcesContent":["import type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';\nimport type * as t from '@/types';\nimport { parseBooleanEnv } from '@/utils/misc';\n\nexport const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = '[tool output redacted]';\n\nfunction isPresent(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nexport function normalizeToolName(name: string): string {\n return name.trim().toLowerCase();\n}\n\nfunction normalizeToolNames(names: string[] | undefined): Set<string> {\n const normalized = new Set<string>();\n for (const name of names ?? []) {\n if (isPresent(name)) {\n normalized.add(normalizeToolName(name));\n }\n }\n return normalized;\n}\n\nfunction parseToolNames(value: string | undefined): string[] | undefined {\n if (!isPresent(value)) {\n return undefined;\n }\n\n return value\n .split(',')\n .map((name) => name.trim())\n .filter((name) => name !== '');\n}\n\nfunction getEnvToolOutputTracingEnabled(): boolean | undefined {\n const traceToolOutputs = parseBooleanEnv(\n process.env.LANGFUSE_TRACE_TOOL_OUTPUTS\n );\n if (traceToolOutputs != null) {\n return traceToolOutputs;\n }\n\n const redactToolOutputs = parseBooleanEnv(\n process.env.LANGFUSE_REDACT_TOOL_OUTPUTS\n );\n if (redactToolOutputs != null) {\n return !redactToolOutputs;\n }\n\n return parseBooleanEnv(process.env.LANGFUSE_TOOL_OUTPUT_TRACING_ENABLED);\n}\n\nfunction getEnvRedactedToolNames(): string[] | undefined {\n return (\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAMES) ??\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_NAMES)\n );\n}\n\nfunction getEnvRedactionText(): string | undefined {\n return isPresent(process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT)\n ? process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT\n : undefined;\n}\n\nfunction getEnvToolNameMatchMode(): 'exact' | 'partial' | undefined {\n const mode = (\n process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAME_MATCH_MODE ??\n process.env.LANGFUSE_REDACT_TOOL_NAME_MATCH_MODE\n )\n ?.trim()\n .toLowerCase();\n if (mode === 'exact' || mode === 'partial') {\n return mode;\n }\n return undefined;\n}\n\nfunction hasEnvToolOutputTracingConfig(): boolean {\n return (\n getEnvToolOutputTracingEnabled() != null ||\n getEnvRedactedToolNames() != null ||\n getEnvRedactionText() != null ||\n getEnvToolNameMatchMode() != null\n );\n}\n\nfunction resolveToolOutputTracingEnabled(\n runConfig?: t.LangfuseToolOutputTracingConfig,\n agentConfig?: t.LangfuseToolOutputTracingConfig\n): boolean {\n return (\n agentConfig?.enabled ??\n runConfig?.enabled ??\n getEnvToolOutputTracingEnabled() ??\n true\n );\n}\n\nfunction resolveRedactedToolNames(\n runConfig?: t.LangfuseToolOutputTracingConfig,\n agentConfig?: t.LangfuseToolOutputTracingConfig\n): Set<string> {\n return normalizeToolNames([\n ...(getEnvRedactedToolNames() ?? []),\n ...(runConfig?.redactedToolNames ?? []),\n ...(agentConfig?.redactedToolNames ?? []),\n ]);\n}\n\nfunction resolveToolNameMatchMode(\n runConfig?: t.LangfuseToolOutputTracingConfig,\n agentConfig?: t.LangfuseToolOutputTracingConfig\n): 'exact' | 'partial' {\n const modes = [\n getEnvToolNameMatchMode(),\n runConfig?.redactedToolNameMatchMode,\n agentConfig?.redactedToolNameMatchMode,\n ];\n return modes.includes('partial') ? 'partial' : 'exact';\n}\n\nexport function hasToolOutputTracingConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): boolean {\n return (\n runLangfuse?.toolOutputTracing != null ||\n agentLangfuse?.toolOutputTracing != null ||\n hasEnvToolOutputTracingConfig()\n );\n}\n\nexport function resolveToolOutputTracingConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): ResolvedLangfuseToolOutputTracingConfig {\n const runConfig = runLangfuse?.toolOutputTracing;\n const agentConfig = agentLangfuse?.toolOutputTracing;\n\n return {\n enabled: resolveToolOutputTracingEnabled(runConfig, agentConfig),\n redactedToolNames: resolveRedactedToolNames(runConfig, agentConfig),\n redactedToolNameMatchMode: resolveToolNameMatchMode(runConfig, agentConfig),\n redactionText:\n agentConfig?.redactionText ??\n runConfig?.redactionText ??\n getEnvRedactionText() ??\n LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,\n };\n}\n\nexport function resolveLangfuseConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): t.LangfuseConfig | undefined {\n if (runLangfuse == null) {\n return agentLangfuse;\n }\n if (agentLangfuse == null) {\n return runLangfuse;\n }\n\n const toolNodeTracing =\n runLangfuse.toolNodeTracing != null || agentLangfuse.toolNodeTracing != null\n ? {\n ...runLangfuse.toolNodeTracing,\n ...agentLangfuse.toolNodeTracing,\n }\n : undefined;\n const toolOutputTracing =\n runLangfuse.toolOutputTracing != null ||\n agentLangfuse.toolOutputTracing != null\n ? {\n ...runLangfuse.toolOutputTracing,\n ...agentLangfuse.toolOutputTracing,\n }\n : undefined;\n const metadata =\n runLangfuse.metadata != null || agentLangfuse.metadata != null\n ? {\n ...runLangfuse.metadata,\n ...agentLangfuse.metadata,\n }\n : undefined;\n const librechatTraceAttributes =\n runLangfuse.librechatTraceAttributes != null ||\n agentLangfuse.librechatTraceAttributes != null\n ? {\n ...runLangfuse.librechatTraceAttributes,\n ...agentLangfuse.librechatTraceAttributes,\n }\n : undefined;\n const tags =\n runLangfuse.tags != null || agentLangfuse.tags != null\n ? [\n ...new Set([\n ...(runLangfuse.tags ?? []),\n ...(agentLangfuse.tags ?? []),\n ]),\n ]\n : undefined;\n\n return {\n ...runLangfuse,\n ...agentLangfuse,\n ...(metadata != null ? { metadata } : {}),\n ...(librechatTraceAttributes != null ? { librechatTraceAttributes } : {}),\n ...(tags != null ? { tags } : {}),\n ...(toolNodeTracing != null ? { toolNodeTracing } : {}),\n ...(toolOutputTracing != null ? { toolOutputTracing } : {}),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuseConfig.mjs","names":[],"sources":["../../src/langfuseConfig.ts"],"sourcesContent":["import type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';\nimport type * as t from '@/types';\nimport { parseBooleanEnv } from '@/utils/misc';\n\nexport const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = '[tool output redacted]';\n\nfunction isPresent(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nexport function normalizeToolName(name: string): string {\n return name.trim().toLowerCase();\n}\n\nexport function hasLangfuseConfigCredentials(\n langfuse?: t.LangfuseConfig\n): langfuse is t.LangfuseConfig & {\n publicKey: string;\n secretKey: string;\n} {\n return (\n langfuse != null &&\n isPresent(langfuse.publicKey) &&\n isPresent(langfuse.secretKey)\n );\n}\n\nexport function hasLangfuseEnvCredentials(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nexport function hasLangfuseEnvConfig(): boolean {\n return hasLangfuseEnvCredentials();\n}\n\nfunction normalizeToolNames(names: string[] | undefined): Set<string> {\n const normalized = new Set<string>();\n for (const name of names ?? []) {\n if (isPresent(name)) {\n normalized.add(normalizeToolName(name));\n }\n }\n return normalized;\n}\n\nfunction parseToolNames(value: string | undefined): string[] | undefined {\n if (!isPresent(value)) {\n return undefined;\n }\n\n return value\n .split(',')\n .map((name) => name.trim())\n .filter((name) => name !== '');\n}\n\nfunction getEnvToolOutputTracingEnabled(): boolean | undefined {\n const traceToolOutputs = parseBooleanEnv(\n process.env.LANGFUSE_TRACE_TOOL_OUTPUTS\n );\n if (traceToolOutputs != null) {\n return traceToolOutputs;\n }\n\n const redactToolOutputs = parseBooleanEnv(\n process.env.LANGFUSE_REDACT_TOOL_OUTPUTS\n );\n if (redactToolOutputs != null) {\n return !redactToolOutputs;\n }\n\n return parseBooleanEnv(process.env.LANGFUSE_TOOL_OUTPUT_TRACING_ENABLED);\n}\n\nfunction getEnvRedactedToolNames(): string[] | undefined {\n return (\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAMES) ??\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_NAMES)\n );\n}\n\nfunction getEnvRedactionText(): string | undefined {\n return isPresent(process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT)\n ? process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT\n : undefined;\n}\n\nfunction getEnvToolNameMatchMode(): 'exact' | 'partial' | undefined {\n const mode = (\n process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAME_MATCH_MODE ??\n process.env.LANGFUSE_REDACT_TOOL_NAME_MATCH_MODE\n )\n ?.trim()\n .toLowerCase();\n if (mode === 'exact' || mode === 'partial') {\n return mode;\n }\n return undefined;\n}\n\nfunction hasEnvToolOutputTracingConfig(): boolean {\n return (\n getEnvToolOutputTracingEnabled() != null ||\n getEnvRedactedToolNames() != null ||\n getEnvRedactionText() != null ||\n getEnvToolNameMatchMode() != null\n );\n}\n\nfunction resolveToolOutputTracingEnabled(\n runConfig?: t.LangfuseToolOutputTracingConfig,\n agentConfig?: t.LangfuseToolOutputTracingConfig\n): boolean {\n return (\n agentConfig?.enabled ??\n runConfig?.enabled ??\n getEnvToolOutputTracingEnabled() ??\n true\n );\n}\n\nfunction resolveRedactedToolNames(\n runConfig?: t.LangfuseToolOutputTracingConfig,\n agentConfig?: t.LangfuseToolOutputTracingConfig\n): Set<string> {\n return normalizeToolNames([\n ...(getEnvRedactedToolNames() ?? []),\n ...(runConfig?.redactedToolNames ?? []),\n ...(agentConfig?.redactedToolNames ?? []),\n ]);\n}\n\nfunction resolveToolNameMatchMode(\n runConfig?: t.LangfuseToolOutputTracingConfig,\n agentConfig?: t.LangfuseToolOutputTracingConfig\n): 'exact' | 'partial' {\n const modes = [\n getEnvToolNameMatchMode(),\n runConfig?.redactedToolNameMatchMode,\n agentConfig?.redactedToolNameMatchMode,\n ];\n return modes.includes('partial') ? 'partial' : 'exact';\n}\n\nexport function hasToolOutputTracingConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): boolean {\n return (\n runLangfuse?.toolOutputTracing != null ||\n agentLangfuse?.toolOutputTracing != null ||\n hasEnvToolOutputTracingConfig()\n );\n}\n\nexport function resolveToolOutputTracingConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): ResolvedLangfuseToolOutputTracingConfig {\n const runConfig = runLangfuse?.toolOutputTracing;\n const agentConfig = agentLangfuse?.toolOutputTracing;\n\n return {\n enabled: resolveToolOutputTracingEnabled(runConfig, agentConfig),\n redactedToolNames: resolveRedactedToolNames(runConfig, agentConfig),\n redactedToolNameMatchMode: resolveToolNameMatchMode(runConfig, agentConfig),\n redactionText:\n agentConfig?.redactionText ??\n runConfig?.redactionText ??\n getEnvRedactionText() ??\n LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,\n };\n}\n\nexport function resolveLangfuseConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): t.LangfuseConfig | undefined {\n if (runLangfuse == null) {\n return agentLangfuse;\n }\n if (agentLangfuse == null) {\n return runLangfuse;\n }\n\n const toolNodeTracing =\n runLangfuse.toolNodeTracing != null || agentLangfuse.toolNodeTracing != null\n ? {\n ...runLangfuse.toolNodeTracing,\n ...agentLangfuse.toolNodeTracing,\n }\n : undefined;\n const toolOutputTracing =\n runLangfuse.toolOutputTracing != null ||\n agentLangfuse.toolOutputTracing != null\n ? {\n ...runLangfuse.toolOutputTracing,\n ...agentLangfuse.toolOutputTracing,\n }\n : undefined;\n const metadata =\n runLangfuse.metadata != null || agentLangfuse.metadata != null\n ? {\n ...runLangfuse.metadata,\n ...agentLangfuse.metadata,\n }\n : undefined;\n const librechatTraceAttributes =\n runLangfuse.librechatTraceAttributes != null ||\n agentLangfuse.librechatTraceAttributes != null\n ? {\n ...runLangfuse.librechatTraceAttributes,\n ...agentLangfuse.librechatTraceAttributes,\n }\n : undefined;\n const tags =\n runLangfuse.tags != null || agentLangfuse.tags != null\n ? [\n ...new Set([\n ...(runLangfuse.tags ?? []),\n ...(agentLangfuse.tags ?? []),\n ]),\n ]\n : undefined;\n\n return {\n ...runLangfuse,\n ...agentLangfuse,\n ...(metadata != null ? { metadata } : {}),\n ...(librechatTraceAttributes != null ? { librechatTraceAttributes } : {}),\n ...(tags != null ? { tags } : {}),\n ...(toolNodeTracing != null ? { toolNodeTracing } : {}),\n ...(toolOutputTracing != null ? { toolOutputTracing } : {}),\n };\n}\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ function hasText(value) {
6
6
  return value != null && value.trim() !== "";
7
7
  }
8
8
  function hasLangfuseRuntimeContextValue(context) {
9
- return context.langfuse != null || hasText(context.traceIdSeed) || context.toolOutputTracing != null;
9
+ return context.langfuse != null || hasText(context.traceIdSeed) || hasText(context.runId) || hasText(context.agentId) || context.toolOutputTracing != null;
10
10
  }
11
11
  /** sha256(seed) -> first 32 hex chars; matches `@langfuse/tracing` `createTraceId`. */
12
12
  function traceIdFromSeed(seed) {
@@ -21,24 +21,42 @@ function getLangfuseRuntimeConfig() {
21
21
  function getTraceIdSeed() {
22
22
  return getLangfuseRuntimeContext()?.traceIdSeed;
23
23
  }
24
+ function getLangfuseScopeRunId() {
25
+ return getLangfuseRuntimeContext()?.runId;
26
+ }
27
+ function getLangfuseScopeAgentId() {
28
+ return getLangfuseRuntimeContext()?.agentId;
29
+ }
24
30
  function getLangfuseRuntimeToolOutputTracingConfig() {
25
31
  return getLangfuseRuntimeContext()?.toolOutputTracing;
26
32
  }
27
33
  /**
28
34
  * Runs `fn` with a merged Langfuse runtime context. Undefined fields inherit
29
35
  * from the parent scope; callers intentionally cannot clear parent values by
30
- * passing `undefined`.
36
+ * passing `undefined` — use `replaceLangfuseRuntimeContext` when the parent
37
+ * scope must NOT leak through (a foreign concurrent run's scope).
31
38
  */
32
39
  function runWithLangfuseRuntimeContext(context, fn) {
33
40
  const next = {
34
41
  ...getLangfuseRuntimeContext() ?? {},
35
42
  ...context.langfuse !== void 0 ? { langfuse: context.langfuse } : {},
36
43
  ...hasText(context.traceIdSeed) ? { traceIdSeed: context.traceIdSeed } : {},
44
+ ...hasText(context.runId) ? { runId: context.runId } : {},
45
+ ...hasText(context.agentId) ? { agentId: context.agentId } : {},
37
46
  ...context.toolOutputTracing !== void 0 ? { toolOutputTracing: context.toolOutputTracing } : {}
38
47
  };
39
48
  return hasLangfuseRuntimeContextValue(next) ? langfuseRuntimeContextStore.run(next, fn) : fn();
40
49
  }
50
+ /**
51
+ * Runs `fn` with EXACTLY the provided context — the surrounding scope's
52
+ * fields do not leak through. A rejected foreign run's explicit destination
53
+ * or seed must not survive via merge inheritance when this run has none of
54
+ * its own (env-credential runs, non-deterministic runs).
55
+ */
56
+ function replaceLangfuseRuntimeContext(context, fn) {
57
+ return langfuseRuntimeContextStore.run({ ...context }, fn);
58
+ }
41
59
  //#endregion
42
- export { getLangfuseRuntimeConfig, getLangfuseRuntimeToolOutputTracingConfig, getTraceIdSeed, hasLangfuseRuntimeContextValue, runWithLangfuseRuntimeContext, traceIdFromSeed };
60
+ export { getLangfuseRuntimeConfig, getLangfuseRuntimeToolOutputTracingConfig, getLangfuseScopeAgentId, getLangfuseScopeRunId, getTraceIdSeed, hasLangfuseRuntimeContextValue, replaceLangfuseRuntimeContext, runWithLangfuseRuntimeContext, traceIdFromSeed };
43
61
 
44
62
  //# sourceMappingURL=langfuseRuntimeContext.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"langfuseRuntimeContext.mjs","names":[],"sources":["../../src/langfuseRuntimeContext.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type * as t from '@/types';\n\nexport type ResolvedLangfuseToolOutputTracingConfig = {\n enabled: boolean;\n redactedToolNames: Set<string>;\n redactedToolNameMatchMode: 'exact' | 'partial';\n redactionText: string;\n};\n\nexport type LangfuseRuntimeContext = {\n langfuse?: t.LangfuseConfig;\n traceIdSeed?: string;\n toolOutputTracing?: ResolvedLangfuseToolOutputTracingConfig;\n};\n\nconst langfuseRuntimeContextStore =\n new AsyncLocalStorage<LangfuseRuntimeContext>();\n\nfunction hasText(value: string | undefined): value is string {\n return value != null && value.trim() !== '';\n}\n\nexport function hasLangfuseRuntimeContextValue(\n context: LangfuseRuntimeContext\n): boolean {\n return (\n context.langfuse != null ||\n hasText(context.traceIdSeed) ||\n context.toolOutputTracing != null\n );\n}\n\n/** sha256(seed) -> first 32 hex chars; matches `@langfuse/tracing` `createTraceId`. */\nexport function traceIdFromSeed(seed: string): string {\n return createHash('sha256').update(seed, 'utf8').digest('hex').slice(0, 32);\n}\n\nexport function getLangfuseRuntimeContext():\n | LangfuseRuntimeContext\n | undefined {\n return langfuseRuntimeContextStore.getStore();\n}\n\nexport function getLangfuseRuntimeConfig(): t.LangfuseConfig | undefined {\n return getLangfuseRuntimeContext()?.langfuse;\n}\n\nexport function getTraceIdSeed(): string | undefined {\n return getLangfuseRuntimeContext()?.traceIdSeed;\n}\n\nexport function getLangfuseRuntimeToolOutputTracingConfig():\n | ResolvedLangfuseToolOutputTracingConfig\n | undefined {\n return getLangfuseRuntimeContext()?.toolOutputTracing;\n}\n\n/**\n * Runs `fn` with a merged Langfuse runtime context. Undefined fields inherit\n * from the parent scope; callers intentionally cannot clear parent values by\n * passing `undefined`.\n */\nexport function runWithLangfuseRuntimeContext<T>(\n context: LangfuseRuntimeContext,\n fn: () => T\n): T {\n const current = getLangfuseRuntimeContext();\n const next = {\n ...(current ?? {}),\n ...(context.langfuse !== undefined ? { langfuse: context.langfuse } : {}),\n ...(hasText(context.traceIdSeed)\n ? { traceIdSeed: context.traceIdSeed }\n : {}),\n ...(context.toolOutputTracing !== undefined\n ? { toolOutputTracing: context.toolOutputTracing }\n : {}),\n };\n\n return hasLangfuseRuntimeContextValue(next)\n ? langfuseRuntimeContextStore.run(next, fn)\n : fn();\n}\n\nexport function runWithTraceIdSeed<T>(\n seed: string | undefined,\n fn: () => T\n): T {\n return hasText(seed)\n ? runWithLangfuseRuntimeContext({ traceIdSeed: seed }, fn)\n : fn();\n}\n"],"mappings":";;;AAiBA,MAAM,8BACJ,IAAI,kBAA0C;AAEhD,SAAS,QAAQ,OAA4C;CAC3D,OAAO,SAAS,QAAQ,MAAM,KAAK,MAAM;AAC3C;AAEA,SAAgB,+BACd,SACS;CACT,OACE,QAAQ,YAAY,QACpB,QAAQ,QAAQ,WAAW,KAC3B,QAAQ,qBAAqB;AAEjC;;AAGA,SAAgB,gBAAgB,MAAsB;CACpD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;AAC5E;AAEA,SAAgB,4BAEF;CACZ,OAAO,4BAA4B,SAAS;AAC9C;AAEA,SAAgB,2BAAyD;CACvE,OAAO,0BAA0B,CAAC,EAAE;AACtC;AAEA,SAAgB,iBAAqC;CACnD,OAAO,0BAA0B,CAAC,EAAE;AACtC;AAEA,SAAgB,4CAEF;CACZ,OAAO,0BAA0B,CAAC,EAAE;AACtC;;;;;;AAOA,SAAgB,8BACd,SACA,IACG;CAEH,MAAM,OAAO;EACX,GAFc,0BAEJ,KAAK,CAAC;EAChB,GAAI,QAAQ,aAAa,KAAA,IAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;EACvE,GAAI,QAAQ,QAAQ,WAAW,IAC3B,EAAE,aAAa,QAAQ,YAAY,IACnC,CAAC;EACL,GAAI,QAAQ,sBAAsB,KAAA,IAC9B,EAAE,mBAAmB,QAAQ,kBAAkB,IAC/C,CAAC;CACP;CAEA,OAAO,+BAA+B,IAAI,IACtC,4BAA4B,IAAI,MAAM,EAAE,IACxC,GAAG;AACT"}
1
+ {"version":3,"file":"langfuseRuntimeContext.mjs","names":[],"sources":["../../src/langfuseRuntimeContext.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type * as t from '@/types';\n\nexport type ResolvedLangfuseToolOutputTracingConfig = {\n enabled: boolean;\n redactedToolNames: Set<string>;\n redactedToolNameMatchMode: 'exact' | 'partial';\n redactionText: string;\n};\n\nexport type LangfuseRuntimeContext = {\n langfuse?: t.LangfuseConfig;\n traceIdSeed?: string;\n toolOutputTracing?: ResolvedLangfuseToolOutputTracingConfig;\n /**\n * Identity of the run this scope belongs to. LangChain executes\n * non-awaited callbacks on a shared background queue\n * (`@langchain/core` `consumeCallback`, a process-wide `p-queue` with\n * concurrency 1), so a callback can run inside a DIFFERENT concurrent\n * run's async context. Handlers compare this id against their own run to\n * decide whether an ambient scope is theirs to adopt.\n */\n runId?: string;\n /**\n * Identity of the agent whose overlay this scope carries, for per-agent\n * scopes inside a run. Fan-out agents execute concurrently with distinct\n * Langfuse overlays, so handlers additionally compare this against the\n * agent a callback reports via its inherited `langgraph_node` metadata.\n */\n agentId?: string;\n};\n\nconst langfuseRuntimeContextStore =\n new AsyncLocalStorage<LangfuseRuntimeContext>();\n\nfunction hasText(value: string | undefined): value is string {\n return value != null && value.trim() !== '';\n}\n\nexport function hasLangfuseRuntimeContextValue(\n context: LangfuseRuntimeContext\n): boolean {\n return (\n context.langfuse != null ||\n hasText(context.traceIdSeed) ||\n hasText(context.runId) ||\n hasText(context.agentId) ||\n context.toolOutputTracing != null\n );\n}\n\n/** sha256(seed) -> first 32 hex chars; matches `@langfuse/tracing` `createTraceId`. */\nexport function traceIdFromSeed(seed: string): string {\n return createHash('sha256').update(seed, 'utf8').digest('hex').slice(0, 32);\n}\n\nexport function getLangfuseRuntimeContext():\n | LangfuseRuntimeContext\n | undefined {\n return langfuseRuntimeContextStore.getStore();\n}\n\nexport function getLangfuseRuntimeConfig(): t.LangfuseConfig | undefined {\n return getLangfuseRuntimeContext()?.langfuse;\n}\n\nexport function getTraceIdSeed(): string | undefined {\n return getLangfuseRuntimeContext()?.traceIdSeed;\n}\n\nexport function getLangfuseScopeRunId(): string | undefined {\n return getLangfuseRuntimeContext()?.runId;\n}\n\nexport function getLangfuseScopeAgentId(): string | undefined {\n return getLangfuseRuntimeContext()?.agentId;\n}\n\nexport function getLangfuseRuntimeToolOutputTracingConfig():\n | ResolvedLangfuseToolOutputTracingConfig\n | undefined {\n return getLangfuseRuntimeContext()?.toolOutputTracing;\n}\n\n/**\n * Runs `fn` with a merged Langfuse runtime context. Undefined fields inherit\n * from the parent scope; callers intentionally cannot clear parent values by\n * passing `undefined` — use `replaceLangfuseRuntimeContext` when the parent\n * scope must NOT leak through (a foreign concurrent run's scope).\n */\nexport function runWithLangfuseRuntimeContext<T>(\n context: LangfuseRuntimeContext,\n fn: () => T\n): T {\n const current = getLangfuseRuntimeContext();\n const next = {\n ...(current ?? {}),\n ...(context.langfuse !== undefined ? { langfuse: context.langfuse } : {}),\n ...(hasText(context.traceIdSeed)\n ? { traceIdSeed: context.traceIdSeed }\n : {}),\n ...(hasText(context.runId) ? { runId: context.runId } : {}),\n ...(hasText(context.agentId) ? { agentId: context.agentId } : {}),\n ...(context.toolOutputTracing !== undefined\n ? { toolOutputTracing: context.toolOutputTracing }\n : {}),\n };\n\n return hasLangfuseRuntimeContextValue(next)\n ? langfuseRuntimeContextStore.run(next, fn)\n : fn();\n}\n\n/**\n * Runs `fn` with EXACTLY the provided context — the surrounding scope's\n * fields do not leak through. A rejected foreign run's explicit destination\n * or seed must not survive via merge inheritance when this run has none of\n * its own (env-credential runs, non-deterministic runs).\n */\nexport function replaceLangfuseRuntimeContext<T>(\n context: LangfuseRuntimeContext,\n fn: () => T\n): T {\n return langfuseRuntimeContextStore.run({ ...context }, fn);\n}\n\nexport function runWithTraceIdSeed<T>(\n seed: string | undefined,\n fn: () => T\n): T {\n return hasText(seed)\n ? runWithLangfuseRuntimeContext({ traceIdSeed: seed }, fn)\n : fn();\n}\n"],"mappings":";;;AAiCA,MAAM,8BACJ,IAAI,kBAA0C;AAEhD,SAAS,QAAQ,OAA4C;CAC3D,OAAO,SAAS,QAAQ,MAAM,KAAK,MAAM;AAC3C;AAEA,SAAgB,+BACd,SACS;CACT,OACE,QAAQ,YAAY,QACpB,QAAQ,QAAQ,WAAW,KAC3B,QAAQ,QAAQ,KAAK,KACrB,QAAQ,QAAQ,OAAO,KACvB,QAAQ,qBAAqB;AAEjC;;AAGA,SAAgB,gBAAgB,MAAsB;CACpD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;AAC5E;AAEA,SAAgB,4BAEF;CACZ,OAAO,4BAA4B,SAAS;AAC9C;AAEA,SAAgB,2BAAyD;CACvE,OAAO,0BAA0B,CAAC,EAAE;AACtC;AAEA,SAAgB,iBAAqC;CACnD,OAAO,0BAA0B,CAAC,EAAE;AACtC;AAEA,SAAgB,wBAA4C;CAC1D,OAAO,0BAA0B,CAAC,EAAE;AACtC;AAEA,SAAgB,0BAA8C;CAC5D,OAAO,0BAA0B,CAAC,EAAE;AACtC;AAEA,SAAgB,4CAEF;CACZ,OAAO,0BAA0B,CAAC,EAAE;AACtC;;;;;;;AAQA,SAAgB,8BACd,SACA,IACG;CAEH,MAAM,OAAO;EACX,GAFc,0BAEJ,KAAK,CAAC;EAChB,GAAI,QAAQ,aAAa,KAAA,IAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;EACvE,GAAI,QAAQ,QAAQ,WAAW,IAC3B,EAAE,aAAa,QAAQ,YAAY,IACnC,CAAC;EACL,GAAI,QAAQ,QAAQ,KAAK,IAAI,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;EACzD,GAAI,QAAQ,QAAQ,OAAO,IAAI,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;EAC/D,GAAI,QAAQ,sBAAsB,KAAA,IAC9B,EAAE,mBAAmB,QAAQ,kBAAkB,IAC/C,CAAC;CACP;CAEA,OAAO,+BAA+B,IAAI,IACtC,4BAA4B,IAAI,MAAM,EAAE,IACxC,GAAG;AACT;;;;;;;AAQA,SAAgB,8BACd,SACA,IACG;CACH,OAAO,4BAA4B,IAAI,EAAE,GAAG,QAAQ,GAAG,EAAE;AAC3D"}