@librechat/agents 3.1.95 → 3.1.97

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 (67) hide show
  1. package/dist/cjs/graphs/Graph.cjs +54 -21
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/instrumentation.cjs +120 -9
  4. package/dist/cjs/instrumentation.cjs.map +1 -1
  5. package/dist/cjs/langfuse.cjs +30 -226
  6. package/dist/cjs/langfuse.cjs.map +1 -1
  7. package/dist/cjs/langfuseToolOutputTracing.cjs +465 -0
  8. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -0
  9. package/dist/cjs/main.cjs +1 -0
  10. package/dist/cjs/main.cjs.map +1 -1
  11. package/dist/cjs/run.cjs +142 -69
  12. package/dist/cjs/run.cjs.map +1 -1
  13. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +29 -2
  14. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  15. package/dist/cjs/tools/ToolNode.cjs +20 -8
  16. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  17. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +10 -6
  18. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  19. package/dist/esm/graphs/Graph.mjs +56 -23
  20. package/dist/esm/graphs/Graph.mjs.map +1 -1
  21. package/dist/esm/instrumentation.mjs +118 -9
  22. package/dist/esm/instrumentation.mjs.map +1 -1
  23. package/dist/esm/langfuse.mjs +28 -224
  24. package/dist/esm/langfuse.mjs.map +1 -1
  25. package/dist/esm/langfuseToolOutputTracing.mjs +457 -0
  26. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -0
  27. package/dist/esm/main.mjs +1 -1
  28. package/dist/esm/run.mjs +144 -71
  29. package/dist/esm/run.mjs.map +1 -1
  30. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +29 -3
  31. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  32. package/dist/esm/tools/ToolNode.mjs +20 -8
  33. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  34. package/dist/esm/tools/subagent/SubagentExecutor.mjs +10 -6
  35. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  36. package/dist/types/graphs/Graph.d.ts +5 -1
  37. package/dist/types/instrumentation.d.ts +5 -1
  38. package/dist/types/langfuse.d.ts +6 -28
  39. package/dist/types/langfuseToolOutputTracing.d.ts +20 -0
  40. package/dist/types/run.d.ts +5 -1
  41. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +1 -0
  42. package/dist/types/tools/ToolNode.d.ts +4 -1
  43. package/dist/types/tools/subagent/SubagentExecutor.d.ts +2 -0
  44. package/dist/types/types/graph.d.ts +30 -0
  45. package/dist/types/types/run.d.ts +6 -0
  46. package/dist/types/types/tools.d.ts +7 -0
  47. package/package.json +2 -1
  48. package/src/graphs/Graph.ts +90 -34
  49. package/src/instrumentation.ts +172 -11
  50. package/src/langfuse.ts +59 -324
  51. package/src/langfuseToolOutputTracing.ts +683 -0
  52. package/src/run.ts +190 -87
  53. package/src/specs/langfuse-callbacks.test.ts +178 -1
  54. package/src/specs/langfuse-config.test.ts +112 -76
  55. package/src/specs/langfuse-instrumentation.test.ts +283 -0
  56. package/src/specs/langfuse-metadata.test.ts +54 -1
  57. package/src/specs/langfuse-tool-output-tracing.test.ts +588 -0
  58. package/src/tools/BashProgrammaticToolCalling.ts +39 -5
  59. package/src/tools/ToolNode.ts +28 -7
  60. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +54 -0
  61. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +72 -4
  62. package/src/tools/__tests__/SubagentExecutor.test.ts +32 -0
  63. package/src/tools/__tests__/ToolNode.langfuse.test.ts +41 -0
  64. package/src/tools/subagent/SubagentExecutor.ts +11 -6
  65. package/src/types/graph.ts +32 -0
  66. package/src/types/run.ts +6 -0
  67. package/src/types/tools.ts +7 -0
package/dist/cjs/run.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- require('./instrumentation.cjs');
3
+ var instrumentation = require('./instrumentation.cjs');
4
4
  var prompts = require('@langchain/core/prompts');
5
5
  var runnables = require('@langchain/core/runnables');
6
6
  var openai = require('@langchain/openai');
@@ -19,6 +19,7 @@ require('./hooks/createWorkspacePolicyHook.cjs');
19
19
  var llm = require('./utils/llm.cjs');
20
20
  var callbacks = require('./utils/callbacks.cjs');
21
21
  var langfuse = require('./langfuse.cjs');
22
+ var langfuseToolOutputTracing = require('./langfuseToolOutputTracing.cjs');
22
23
 
23
24
  // src/run.ts
24
25
  const defaultOmitOptions = new Set([
@@ -73,6 +74,7 @@ class Run {
73
74
  handlerRegistry;
74
75
  hookRegistry;
75
76
  humanInTheLoop;
77
+ langfuse;
76
78
  toolOutputReferences;
77
79
  eagerEventToolExecution;
78
80
  toolExecution;
@@ -112,6 +114,7 @@ class Run {
112
114
  this.handlerRegistry = handlerRegistry;
113
115
  this.hookRegistry = config.hooks;
114
116
  this.humanInTheLoop = config.humanInTheLoop;
117
+ this.langfuse = config.langfuse;
115
118
  this.toolOutputReferences = config.toolOutputReferences;
116
119
  this.eagerEventToolExecution = config.eagerEventToolExecution;
117
120
  this.toolExecution = config.toolExecution;
@@ -170,6 +173,7 @@ class Run {
170
173
  signal,
171
174
  runId: this.id,
172
175
  agents: [agentConfig],
176
+ langfuse: this.langfuse,
173
177
  tokenCounter: this.tokenCounter,
174
178
  indexTokenCountMap: this.indexTokenCountMap,
175
179
  calibrationRatio: this.calibrationRatio,
@@ -190,6 +194,7 @@ class Run {
190
194
  runId: this.id,
191
195
  agents,
192
196
  edges,
197
+ langfuse: this.langfuse,
193
198
  tokenCounter: this.tokenCounter,
194
199
  indexTokenCountMap: this.indexTokenCountMap,
195
200
  calibrationRatio: this.calibrationRatio,
@@ -423,6 +428,77 @@ class Run {
423
428
  }
424
429
  };
425
430
  }
431
+ shouldClearHookSession(streamThrew) {
432
+ return (this._interrupt == null || this._haltedReason != null || streamThrew);
433
+ }
434
+ isAwaitingResume(streamThrew) {
435
+ return (this._interrupt != null && this._haltedReason == null && !streamThrew);
436
+ }
437
+ getStreamLangfuseConfig(graph) {
438
+ const primaryContext = graph.agentContexts.get(graph.defaultAgentId);
439
+ if (primaryContext != null) {
440
+ return langfuseToolOutputTracing.resolveLangfuseConfig(this.langfuse, primaryContext.langfuse);
441
+ }
442
+ for (const context of graph.agentContexts.values()) {
443
+ const langfuse = langfuseToolOutputTracing.resolveLangfuseConfig(this.langfuse, context.langfuse);
444
+ if (langfuse != null) {
445
+ return langfuse;
446
+ }
447
+ }
448
+ return this.langfuse;
449
+ }
450
+ getStreamToolOutputTracingLangfuseConfig(graph) {
451
+ const toolOutputTracingConfigs = Array.from(graph.agentContexts.values())
452
+ .map((context) => {
453
+ return langfuseToolOutputTracing.resolveLangfuseConfig(this.langfuse, context.langfuse)
454
+ ?.toolOutputTracing;
455
+ })
456
+ .filter((config) => {
457
+ return config != null;
458
+ });
459
+ if (toolOutputTracingConfigs.length === 0) {
460
+ return this.langfuse?.toolOutputTracing != null
461
+ ? { toolOutputTracing: this.langfuse.toolOutputTracing }
462
+ : undefined;
463
+ }
464
+ if (toolOutputTracingConfigs.length === 1) {
465
+ return { toolOutputTracing: toolOutputTracingConfigs[0] };
466
+ }
467
+ let enabled;
468
+ let redactionText;
469
+ let redactedToolNameMatchMode;
470
+ const redactedToolNames = new Set();
471
+ for (const config of toolOutputTracingConfigs) {
472
+ if (config.enabled === false) {
473
+ enabled = false;
474
+ }
475
+ else if (enabled !== false && config.enabled != null) {
476
+ enabled = config.enabled;
477
+ }
478
+ redactionText ??= config.redactionText;
479
+ if (config.redactedToolNameMatchMode === 'partial') {
480
+ redactedToolNameMatchMode = 'partial';
481
+ }
482
+ else {
483
+ redactedToolNameMatchMode ??= config.redactedToolNameMatchMode;
484
+ }
485
+ for (const toolName of config.redactedToolNames ?? []) {
486
+ redactedToolNames.add(toolName);
487
+ }
488
+ }
489
+ return {
490
+ toolOutputTracing: {
491
+ ...(enabled != null ? { enabled } : {}),
492
+ ...(redactedToolNames.size > 0
493
+ ? { redactedToolNames: Array.from(redactedToolNames) }
494
+ : {}),
495
+ ...(redactedToolNameMatchMode != null
496
+ ? { redactedToolNameMatchMode }
497
+ : {}),
498
+ ...(redactionText != null ? { redactionText } : {}),
499
+ },
500
+ };
501
+ }
426
502
  async processStream(inputs, callerConfig, streamOptions) {
427
503
  if (this.graphRunnable == null) {
428
504
  throw new Error('Run not initialized. Make sure to use Run.create() to instantiate the Run.');
@@ -430,6 +506,8 @@ class Run {
430
506
  if (!this.Graph) {
431
507
  throw new Error('Graph not initialized. Make sure to use Run.create() to instantiate the Run.');
432
508
  }
509
+ const graphRunnable = this.graphRunnable;
510
+ const graph = this.Graph;
433
511
  /**
434
512
  * `Command` inputs (currently only `Command({ resume })`) are
435
513
  * resume-mode invocations: LangGraph rebuilds graph state from the
@@ -456,7 +534,7 @@ class Run {
456
534
  * boundary.
457
535
  */
458
536
  if (!isResume) {
459
- this.Graph.resetValues(streamOptions?.keepContent);
537
+ graph.resetValues(streamOptions?.keepContent);
460
538
  }
461
539
  this._interrupt = undefined;
462
540
  this._haltedReason = undefined;
@@ -471,28 +549,31 @@ class Run {
471
549
  });
472
550
  customHandler.awaitHandlers = true;
473
551
  config.callbacks = callbacks.appendCallbacks(config.callbacks, streamCallbacks ? [streamCallbacks, customHandler] : [customHandler]);
474
- if (langfuse.hasLangfuseEnvConfig() &&
475
- !langfuse.hasExplicitLangfuseConfig(this.Graph.agentContexts.values())) {
476
- const userId = typeof config.configurable?.user_id === 'string'
477
- ? config.configurable.user_id
478
- : undefined;
479
- const sessionId = typeof config.configurable?.thread_id === 'string'
480
- ? config.configurable.thread_id
481
- : undefined;
482
- const primaryContext = this.Graph.agentContexts.get(this.Graph.defaultAgentId);
483
- const traceMetadata = langfuse.createLangfuseTraceMetadata({
484
- messageId: this.id,
485
- parentMessageId: config.configurable?.requestBody?.parentMessageId,
486
- agentName: primaryContext?.name,
487
- });
488
- const handler = langfuse.createLegacyLangfuseHandler({
489
- userId,
490
- sessionId,
491
- traceMetadata,
492
- tags: ['librechat', 'agent'],
493
- });
552
+ const primaryContext = graph.agentContexts.get(graph.defaultAgentId);
553
+ const userId = typeof config.configurable?.user_id === 'string'
554
+ ? config.configurable.user_id
555
+ : undefined;
556
+ const sessionId = typeof config.configurable?.thread_id === 'string'
557
+ ? config.configurable.thread_id
558
+ : undefined;
559
+ const traceMetadata = langfuse.createLangfuseTraceMetadata({
560
+ messageId: this.id,
561
+ parentMessageId: config.configurable?.requestBody?.parentMessageId,
562
+ agentId: graph.defaultAgentId,
563
+ agentName: primaryContext?.name,
564
+ });
565
+ const streamLangfuseConfig = this.getStreamLangfuseConfig(graph);
566
+ instrumentation.initializeLangfuseTracing(streamLangfuseConfig);
567
+ const langfuseHandler = langfuse.createLangfuseHandler({
568
+ langfuse: streamLangfuseConfig,
569
+ userId,
570
+ sessionId,
571
+ traceMetadata,
572
+ tags: ['librechat', 'agent'],
573
+ });
574
+ if (langfuseHandler != null) {
494
575
  config.runName = config.runName ?? langfuse.getLangfuseTraceName(traceMetadata);
495
- config.callbacks = callbacks.appendCallbacks(config.callbacks, [handler]);
576
+ config.callbacks = callbacks.appendCallbacks(config.callbacks, [langfuseHandler]);
496
577
  }
497
578
  if (!this.id) {
498
579
  throw new Error('Run ID not provided');
@@ -508,24 +589,6 @@ class Run {
508
589
  return undefined;
509
590
  }
510
591
  }
511
- /**
512
- * `streamEvents` accepts both state inputs and `Command` (resume) at
513
- * runtime, but our `CompiledStateWorkflow` type narrows the first
514
- * arg to `BaseGraphState`. Cast on the call so the resume path
515
- * type-checks without widening the wrapper for every caller.
516
- */
517
- const stream = this.graphRunnable.streamEvents(inputs, config, {
518
- raiseError: true,
519
- /**
520
- * Prevent EventStreamCallbackHandler from processing custom events.
521
- * Custom events are already handled via our createCustomEventCallback()
522
- * which routes them through the handlerRegistry.
523
- * Without this flag, EventStreamCallbackHandler throws errors when
524
- * custom events are dispatched for run IDs not in its internal map
525
- * (due to timing issues in parallel execution or after run cleanup).
526
- */
527
- ignoreCustomEvent: true,
528
- });
529
592
  /**
530
593
  * Tracks whether the stream loop threw. Used by the `finally`
531
594
  * block to decide whether to honor the interrupt-preservation
@@ -536,7 +599,25 @@ class Run {
536
599
  * preserving session hooks would leak them into the next run.
537
600
  */
538
601
  let streamThrew = false;
539
- try {
602
+ const consumeStream = async () => {
603
+ /**
604
+ * `streamEvents` accepts both state inputs and `Command` (resume) at
605
+ * runtime, but our `CompiledStateWorkflow` type narrows the first
606
+ * arg to `BaseGraphState`. Cast on the call so the resume path
607
+ * type-checks without widening the wrapper for every caller.
608
+ */
609
+ const stream = graphRunnable.streamEvents(inputs, config, {
610
+ raiseError: true,
611
+ /**
612
+ * Prevent EventStreamCallbackHandler from processing custom events.
613
+ * Custom events are already handled via our createCustomEventCallback()
614
+ * which routes them through the handlerRegistry.
615
+ * Without this flag, EventStreamCallbackHandler throws errors when
616
+ * custom events are dispatched for run IDs not in its internal map
617
+ * (due to timing issues in parallel execution or after run cleanup).
618
+ */
619
+ ignoreCustomEvent: true,
620
+ });
540
621
  for await (const event of stream) {
541
622
  const { data, metadata, ...info } = event;
542
623
  const eventName = info.event;
@@ -621,8 +702,8 @@ class Run {
621
702
  hook_event_name: 'Stop',
622
703
  runId: this.id,
623
704
  threadId,
624
- agentId: this.Graph.defaultAgentId,
625
- messages: this.Graph.getRunMessages() ?? stateInputs?.messages ?? [],
705
+ agentId: graph.defaultAgentId,
706
+ messages: graph.getRunMessages() ?? stateInputs?.messages ?? [],
626
707
  stopHookActive: false, // will be true when stop is triggered by a hook (Phase 2)
627
708
  },
628
709
  sessionId: this.id,
@@ -630,6 +711,9 @@ class Run {
630
711
  /* Stop hook errors must not masquerade as stream failures */
631
712
  });
632
713
  }
714
+ };
715
+ try {
716
+ await langfuseToolOutputTracing.withLangfuseToolOutputTracingConfig(streamLangfuseConfig, consumeStream, this.getStreamToolOutputTracingLangfuseConfig(graph));
633
717
  }
634
718
  catch (err) {
635
719
  streamThrew = true;
@@ -667,9 +751,7 @@ class Run {
667
751
  * expected, sessions must drop). Every state where no resume
668
752
  * is expected clears.
669
753
  */
670
- if (this._interrupt == null ||
671
- this._haltedReason != null ||
672
- streamThrew) {
754
+ if (this.shouldClearHookSession(streamThrew)) {
673
755
  this.hookRegistry?.clearSession(this.id);
674
756
  }
675
757
  /**
@@ -681,6 +763,7 @@ class Run {
681
763
  * unaffected — their entries live under their own session ids.
682
764
  */
683
765
  this.hookRegistry?.clearHaltSignal(this.id);
766
+ await langfuse.disposeLangfuseHandler(langfuseHandler);
684
767
  /**
685
768
  * Break the reference chain that keeps heavy data alive via
686
769
  * LangGraph's internal `__pregel_scratchpad.currentTaskInput` →
@@ -728,7 +811,7 @@ class Run {
728
811
  * Run from scratch) is a separate concern; see
729
812
  * `HumanInTheLoopConfig` JSDoc.
730
813
  */
731
- const awaitingResume = this._interrupt != null && this._haltedReason == null && !streamThrew;
814
+ const awaitingResume = this.isAwaitingResume(streamThrew);
732
815
  if (!this.skipCleanup && !awaitingResume) {
733
816
  this.Graph.clearHeavyState();
734
817
  }
@@ -907,25 +990,15 @@ class Run {
907
990
  const sessionId = typeof chainOptions.configurable?.thread_id === 'string'
908
991
  ? chainOptions.configurable.thread_id
909
992
  : undefined;
910
- const hasExplicitLangfuse = this.Graph != null &&
911
- langfuse.hasExplicitLangfuseConfig(this.Graph.agentContexts.values());
912
- if (titleContext?.langfuse != null) {
913
- titleLangfuseHandler = langfuse.createLangfuseHandler({
914
- langfuse: titleContext.langfuse,
915
- userId,
916
- sessionId,
917
- traceMetadata,
918
- tags: ['librechat', 'title'],
919
- });
920
- }
921
- else if (langfuse.hasLangfuseEnvConfig() && !hasExplicitLangfuse) {
922
- titleLangfuseHandler = langfuse.createLegacyLangfuseHandler({
923
- userId,
924
- sessionId,
925
- traceMetadata,
926
- tags: ['librechat', 'title'],
927
- });
928
- }
993
+ const titleLangfuseConfig = langfuseToolOutputTracing.resolveLangfuseConfig(this.langfuse, titleContext?.langfuse);
994
+ instrumentation.initializeLangfuseTracing(titleLangfuseConfig);
995
+ titleLangfuseHandler = langfuse.createLangfuseHandler({
996
+ langfuse: titleLangfuseConfig,
997
+ userId,
998
+ sessionId,
999
+ traceMetadata,
1000
+ tags: ['librechat', 'title'],
1001
+ });
929
1002
  if (titleLangfuseHandler != null) {
930
1003
  chainOptions.callbacks = callbacks.appendCallbacks(chainOptions.callbacks, [
931
1004
  titleLangfuseHandler,
@@ -978,7 +1051,7 @@ class Run {
978
1051
  });
979
1052
  try {
980
1053
  try {
981
- return await fullChain.invoke({ input: inputText, output: response }, invokeConfig);
1054
+ return await langfuseToolOutputTracing.withLangfuseToolOutputTracingConfig(this.langfuse, () => fullChain.invoke({ input: inputText, output: response }, invokeConfig), titleContext?.langfuse);
982
1055
  }
983
1056
  catch (_e) {
984
1057
  // Fallback: strip callbacks to avoid EventStream tracer errors in certain environments
@@ -988,7 +1061,7 @@ class Run {
988
1061
  const safeConfig = Object.assign({}, rest, {
989
1062
  callbacks: langfuseHandler ? [langfuseHandler] : [],
990
1063
  });
991
- return await fullChain.invoke({ input: inputText, output: response }, safeConfig);
1064
+ return await langfuseToolOutputTracing.withLangfuseToolOutputTracingConfig(this.langfuse, () => fullChain.invoke({ input: inputText, output: response }, safeConfig), titleContext?.langfuse);
992
1065
  }
993
1066
  }
994
1067
  finally {