@librechat/agents 3.7.10 → 3.7.11

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.
@@ -101,6 +101,14 @@ function isTransferToolName(name: unknown): boolean {
101
101
  );
102
102
  }
103
103
 
104
+ function graphToolName(tool: unknown): string | undefined {
105
+ if (tool == null || typeof tool !== 'object' || !('name' in tool)) {
106
+ return undefined;
107
+ }
108
+ const { name } = tool;
109
+ return typeof name === 'string' ? name : undefined;
110
+ }
111
+
104
112
  /**
105
113
  * Drop transfer `tool_use` content blocks from an AI message's array content.
106
114
  * Companion to the reception's tool-call filtering: array-content providers
@@ -287,6 +295,21 @@ function withHandoffGroupMetadata(
287
295
  };
288
296
  }
289
297
 
298
+ function withActiveAgentMetadata(
299
+ config: LangGraphRunnableConfig | undefined,
300
+ agentId: string,
301
+ agentName: string | undefined
302
+ ): LangGraphRunnableConfig {
303
+ return {
304
+ ...config,
305
+ metadata: {
306
+ ...config?.metadata,
307
+ activeAgentId: agentId,
308
+ ...(agentName == null ? {} : { activeAgentName: agentName }),
309
+ },
310
+ };
311
+ }
312
+
290
313
  /**
291
314
  * MultiAgentGraph extends StandardGraph to support dynamic multi-agent workflows
292
315
  * with handoffs, fan-in/fan-out, and other composable patterns.
@@ -594,6 +617,24 @@ export class MultiAgentGraph extends StandardGraph {
594
617
  private createHandoffTools(): void {
595
618
  // Group handoff edges by source agent(s)
596
619
  const handoffsByAgent = new Map<string, t.GraphEdge[]>();
620
+ const tokenAccountingRefresh = new Set<string>();
621
+
622
+ /** Transfer tool names are SDK-reserved. Remove externally supplied or
623
+ * stale transfer tools before deriving the source agent's allowed set
624
+ * from the graph edges below. */
625
+ for (const agentContext of this.agentContexts.values()) {
626
+ const originalTools = agentContext.graphTools;
627
+ const retainedTools = agentContext.graphTools?.filter(
628
+ (graphTool) => !isTransferToolName(graphToolName(graphTool))
629
+ );
630
+ if (retainedTools?.length !== originalTools?.length) {
631
+ tokenAccountingRefresh.add(agentContext.agentId);
632
+ }
633
+ agentContext.graphTools =
634
+ retainedTools != null && retainedTools.length > 0
635
+ ? retainedTools
636
+ : undefined;
637
+ }
597
638
 
598
639
  // Only process handoff edges for tool creation
599
640
  for (const edge of this.handoffEdges) {
@@ -643,6 +684,29 @@ export class MultiAgentGraph extends StandardGraph {
643
684
  for (const handoffTool of handoffTools) {
644
685
  agentContext.graphTools.push(handoffTool);
645
686
  }
687
+ if (handoffTools.length > 0) {
688
+ tokenAccountingRefresh.add(agentId);
689
+ }
690
+ }
691
+
692
+ for (const agentId of tokenAccountingRefresh) {
693
+ const agentContext = this.agentContexts.get(agentId);
694
+ if (agentContext?.tokenCounter == null) {
695
+ continue;
696
+ }
697
+ const { tokenCounter, baseIndexTokenCountMap } = agentContext;
698
+ agentContext.tokenCalculationPromise = agentContext
699
+ .calculateInstructionTokens(tokenCounter)
700
+ .then(() => {
701
+ agentContext.updateTokenMapWithInstructions(baseIndexTokenCountMap);
702
+ })
703
+ .catch((err) => {
704
+ // eslint-disable-next-line no-console
705
+ console.error(
706
+ 'Error recalculating instruction tokens after handoff tool updates:',
707
+ err
708
+ );
709
+ });
646
710
  }
647
711
  }
648
712
 
@@ -1239,10 +1303,16 @@ export class MultiAgentGraph extends StandardGraph {
1239
1303
  ): Promise<t.MultiAgentGraphState | Command> => {
1240
1304
  let result: t.MultiAgentGraphState;
1241
1305
  let inputMessages = state.messages;
1242
- const memberConfig =
1306
+ const agentContext = this.agentContexts.get(agentId);
1307
+ const recursionLimitedConfig =
1243
1308
  this.memberRecursionLimit == null
1244
1309
  ? config
1245
1310
  : { ...config, recursionLimit: this.memberRecursionLimit };
1311
+ const memberConfig = withActiveAgentMetadata(
1312
+ recursionLimitedConfig,
1313
+ agentId,
1314
+ agentContext?.name
1315
+ );
1246
1316
 
1247
1317
  /**
1248
1318
  * Check if this agent is receiving a handoff.
@@ -1254,7 +1324,6 @@ export class MultiAgentGraph extends StandardGraph {
1254
1324
  state.messages,
1255
1325
  agentId
1256
1326
  );
1257
- const agentContext = this.agentContexts.get(agentId);
1258
1327
 
1259
1328
  if (
1260
1329
  handoffContext?.sourceAgentName != null &&
package/src/langfuse.ts CHANGED
@@ -783,17 +783,29 @@ export function createLangfuseTraceMetadata({
783
783
  parentMessageId,
784
784
  agentId,
785
785
  agentName,
786
+ rootAgentId,
787
+ rootAgentName,
788
+ activeAgentId,
789
+ activeAgentName,
786
790
  }: {
787
791
  messageId?: unknown;
788
792
  parentMessageId?: unknown;
789
793
  agentId?: unknown;
790
794
  agentName?: unknown;
795
+ rootAgentId?: unknown;
796
+ rootAgentName?: unknown;
797
+ activeAgentId?: unknown;
798
+ activeAgentName?: unknown;
791
799
  }): LangfuseTraceMetadata {
792
800
  return createTraceMetadata({
793
801
  messageId,
794
802
  parentMessageId,
795
803
  agentId,
796
804
  agentName,
805
+ rootAgentId,
806
+ rootAgentName,
807
+ activeAgentId,
808
+ activeAgentName,
797
809
  });
798
810
  }
799
811
 
package/src/run.ts CHANGED
@@ -1311,6 +1311,10 @@ export class Run<_T extends t.BaseGraphState> {
1311
1311
  parentMessageId: config.configurable?.requestBody?.parentMessageId,
1312
1312
  agentId: graph.defaultAgentId,
1313
1313
  agentName: primaryContext?.name,
1314
+ rootAgentId: graph.defaultAgentId,
1315
+ rootAgentName: primaryContext?.name,
1316
+ activeAgentId: graph.defaultAgentId,
1317
+ activeAgentName: primaryContext?.name,
1314
1318
  });
1315
1319
  const traceName = config.runName ?? getLangfuseTraceName(traceMetadata);
1316
1320
  const streamLangfuseConfig = this.getStreamLangfuseConfig(graph);
@@ -738,6 +738,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
738
738
  * even where `agentId` (the subagent-scope marker) is undefined.
739
739
  */
740
740
  private executingAgentId?: string;
741
+ private executingAgentName?: string;
742
+ private rootAgentId?: string;
743
+ private rootAgentName?: string;
741
744
  /** Tool names that bypass event dispatch and execute directly (e.g., graph-managed handoff tools) */
742
745
  private directToolNames?: Set<string>;
743
746
  /**
@@ -823,6 +826,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
823
826
  eagerEventToolSuppressions,
824
827
  agentId,
825
828
  executingAgentId,
829
+ executingAgentName,
830
+ rootAgentId,
831
+ rootAgentName,
826
832
  directToolNames,
827
833
  interruptingToolNames,
828
834
  codeSessionToolNames,
@@ -902,6 +908,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
902
908
  // Default to agentId so callers constructing ToolNode directly (who pass the
903
909
  // existing agentId option) still get attribution without knowing the new option.
904
910
  this.executingAgentId = executingAgentId ?? agentId;
911
+ this.executingAgentName = executingAgentName;
912
+ this.rootAgentId = rootAgentId;
913
+ this.rootAgentName = rootAgentName;
905
914
  this.directToolNames = directToolNames;
906
915
  this.interruptingToolNames =
907
916
  interruptingToolNames != null && interruptingToolNames.size > 0
@@ -963,6 +972,16 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
963
972
  metadata: {
964
973
  ...options?.metadata,
965
974
  agentId: this.executingAgentId,
975
+ activeAgentId: this.executingAgentId,
976
+ ...(this.executingAgentName == null
977
+ ? {}
978
+ : { activeAgentName: this.executingAgentName }),
979
+ ...(this.rootAgentId == null
980
+ ? {}
981
+ : { rootAgentId: this.rootAgentId }),
982
+ ...(this.rootAgentName == null
983
+ ? {}
984
+ : { rootAgentName: this.rootAgentName }),
966
985
  },
967
986
  };
968
987
  return withLangfuseRuntimeScope(
@@ -148,6 +148,11 @@ export type ToolNodeOptions = {
148
148
  /** ID of the agent that owns this tool node, surfaced to hooks as `executingAgentId`
149
149
  * so a batch can be attributed to a specific agent even where `agentId` is undefined. */
150
150
  executingAgentId?: string;
151
+ /** Name of the agent that owns this tool node, used for active Langfuse attribution. */
152
+ executingAgentName?: string;
153
+ /** Root graph-agent identity retained alongside the currently executing tool owner. */
154
+ rootAgentId?: string;
155
+ rootAgentName?: string;
151
156
  /** Tool names that must be executed directly (via runTool) even in event-driven mode (e.g., graph-managed handoff tools) */
152
157
  directToolNames?: Set<string>;
153
158
  /**