@librechat/agents 3.1.39 → 3.1.41

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.
@@ -7,10 +7,11 @@ describe('AgentContext', () => {
7
7
  type ContextOptions = {
8
8
  agentConfig?: Partial<t.AgentInputs>;
9
9
  tokenCounter?: t.TokenCounter;
10
+ indexTokenCountMap?: Record<string, number>;
10
11
  };
11
12
 
12
13
  const createBasicContext = (options: ContextOptions = {}): AgentContext => {
13
- const { agentConfig = {}, tokenCounter } = options;
14
+ const { agentConfig = {}, tokenCounter, indexTokenCountMap } = options;
14
15
  return AgentContext.fromConfig(
15
16
  {
16
17
  agentId: 'test-agent',
@@ -18,7 +19,8 @@ describe('AgentContext', () => {
18
19
  instructions: 'Test instructions',
19
20
  ...agentConfig,
20
21
  },
21
- tokenCounter
22
+ tokenCounter,
23
+ indexTokenCountMap
22
24
  );
23
25
  };
24
26
 
@@ -393,6 +395,23 @@ describe('AgentContext', () => {
393
395
  expect(ctx.currentUsage).toBeUndefined();
394
396
  });
395
397
 
398
+ it('rebuilds indexTokenCountMap from base map after reset', async () => {
399
+ const tokenCounter = jest.fn(() => 5);
400
+ const ctx = createBasicContext({
401
+ tokenCounter,
402
+ indexTokenCountMap: { '0': 10, '1': 20 },
403
+ });
404
+
405
+ await ctx.tokenCalculationPromise;
406
+ ctx.indexTokenCountMap = {};
407
+
408
+ ctx.reset();
409
+ await ctx.tokenCalculationPromise;
410
+
411
+ expect(ctx.indexTokenCountMap['1']).toBe(20);
412
+ expect(ctx.indexTokenCountMap['0'] ?? 0).toBeGreaterThanOrEqual(10);
413
+ });
414
+
396
415
  it('forces rebuild on next systemRunnable access', () => {
397
416
  const ctx = createBasicContext({ agentConfig: { instructions: 'Test' } });
398
417
 
@@ -780,8 +799,9 @@ describe('AgentContext', () => {
780
799
 
781
800
  // Verify state is cleared
782
801
  expect(ctx.discoveredToolNames.size).toBe(0);
783
- expect(ctx.instructionTokens).toBe(0);
784
- expect(ctx.indexTokenCountMap).toEqual({});
802
+ const resetTokens = ctx.instructionTokens;
803
+ expect(resetTokens).toBeGreaterThan(0);
804
+ expect(resetTokens).toBeLessThan(run1Tokens);
785
805
 
786
806
  // ========== RUN 2 ==========
787
807
  // Re-initialize (as fromConfig would do)
@@ -790,6 +810,7 @@ describe('AgentContext', () => {
790
810
  // System runnable should NOT include the previously discovered tool
791
811
  // (because discoveredToolNames was cleared)
792
812
  const run2Tokens = ctx.instructionTokens;
813
+ expect(run2Tokens).toBe(resetTokens);
793
814
 
794
815
  // Token count should be lower than run 1 (no discovered tool in system message)
795
816
  expect(run2Tokens).toBeLessThan(run1Tokens);
@@ -15,6 +15,7 @@ import {
15
15
  getCurrentTaskInput,
16
16
  messagesStateReducer,
17
17
  } from '@langchain/langgraph';
18
+ import type { LangGraphRunnableConfig } from '@langchain/langgraph';
18
19
  import type { BaseMessage, AIMessageChunk } from '@langchain/core/messages';
19
20
  import type { ToolRunnableConfig } from '@langchain/core/tools';
20
21
  import type * as t from '@/types';
@@ -745,7 +746,8 @@ export class MultiAgentGraph extends StandardGraph {
745
746
 
746
747
  /** Wrapper function that handles agentMessages channel, handoff reception, and conditional routing */
747
748
  const agentWrapper = async (
748
- state: t.MultiAgentGraphState
749
+ state: t.MultiAgentGraphState,
750
+ config?: LangGraphRunnableConfig
749
751
  ): Promise<t.MultiAgentGraphState | Command> => {
750
752
  let result: t.MultiAgentGraphState;
751
753
 
@@ -817,7 +819,7 @@ export class MultiAgentGraph extends StandardGraph {
817
819
  ...state,
818
820
  messages: messagesForAgent,
819
821
  };
820
- result = await agentSubgraph.invoke(transformedState);
822
+ result = await agentSubgraph.invoke(transformedState, config);
821
823
  result = {
822
824
  ...result,
823
825
  agentMessages: [],
@@ -863,14 +865,14 @@ export class MultiAgentGraph extends StandardGraph {
863
865
  ...state,
864
866
  messages: state.agentMessages,
865
867
  };
866
- result = await agentSubgraph.invoke(transformedState);
868
+ result = await agentSubgraph.invoke(transformedState, config);
867
869
  result = {
868
870
  ...result,
869
871
  /** Clear agentMessages for next agent */
870
872
  agentMessages: [],
871
873
  };
872
874
  } else {
873
- result = await agentSubgraph.invoke(state);
875
+ result = await agentSubgraph.invoke(state, config);
874
876
  }
875
877
 
876
878
  /** If agent has both handoff and direct edges, use Command for exclusive routing */
@@ -288,7 +288,12 @@ function formatAssistantMessage(
288
288
  let hasReasoning = false;
289
289
 
290
290
  if (Array.isArray(message.content)) {
291
- for (const part of message.content) {
291
+ for (const part of message.content as Array<
292
+ MessageContentComplex | undefined | null
293
+ >) {
294
+ if (part == null) {
295
+ continue;
296
+ }
292
297
  if (part.type === ContentTypes.TEXT && part.tool_call_ids) {
293
298
  /*
294
299
  If there's pending content, it needs to be aggregated as a single string to prepare for tool calls.
@@ -865,7 +870,6 @@ export const formatAgentMessages = (
865
870
  indexMapping[i] = resultIndices;
866
871
  }
867
872
 
868
- // Update the token count map if it was provided
869
873
  if (indexTokenCountMap) {
870
874
  for (
871
875
  let originalIndex = 0;
@@ -875,24 +879,77 @@ export const formatAgentMessages = (
875
879
  const resultIndices = indexMapping[originalIndex] || [];
876
880
  const tokenCount = indexTokenCountMap[originalIndex];
877
881
 
878
- if (tokenCount !== undefined) {
879
- if (resultIndices.length === 1) {
880
- // Simple 1:1 mapping
881
- updatedIndexTokenCountMap[resultIndices[0]] = tokenCount;
882
- } else if (resultIndices.length > 1) {
883
- // If one message was split into multiple, distribute the token count
884
- // This is a simplification - in reality, you might want a more sophisticated distribution
885
- const countPerMessage = Math.floor(tokenCount / resultIndices.length);
886
- resultIndices.forEach((resultIndex, idx) => {
887
- if (idx === resultIndices.length - 1) {
888
- // Give any remainder to the last message
889
- updatedIndexTokenCountMap[resultIndex] =
890
- tokenCount - countPerMessage * (resultIndices.length - 1);
891
- } else {
892
- updatedIndexTokenCountMap[resultIndex] = countPerMessage;
882
+ if (tokenCount === undefined) {
883
+ continue;
884
+ }
885
+
886
+ const msgCount = resultIndices.length;
887
+ if (msgCount === 1) {
888
+ updatedIndexTokenCountMap[resultIndices[0]] = tokenCount;
889
+ continue;
890
+ }
891
+
892
+ if (msgCount < 2) {
893
+ continue;
894
+ }
895
+
896
+ let totalLength = 0;
897
+ const lastIdx = msgCount - 1;
898
+ const lengths = new Array<number>(msgCount);
899
+ for (let k = 0; k < msgCount; k++) {
900
+ const msg = messages[resultIndices[k]];
901
+ const { content } = msg;
902
+ let len = 0;
903
+ if (typeof content === 'string') {
904
+ len = content.length;
905
+ } else if (Array.isArray(content)) {
906
+ for (const part of content as Array<
907
+ Record<string, unknown> | string | undefined
908
+ >) {
909
+ if (typeof part === 'string') {
910
+ len += part.length;
911
+ } else if (part != null && typeof part === 'object') {
912
+ const val = part.text ?? part.content;
913
+ if (typeof val === 'string') {
914
+ len += val.length;
915
+ }
916
+ }
917
+ }
918
+ }
919
+ const toolCalls = (msg as AIMessage).tool_calls;
920
+ if (Array.isArray(toolCalls)) {
921
+ for (const tc of toolCalls as Array<Record<string, unknown>>) {
922
+ if (typeof tc.name === 'string') {
923
+ len += tc.name.length;
924
+ }
925
+ const { args } = tc;
926
+ if (typeof args === 'string') {
927
+ len += args.length;
928
+ } else if (args != null) {
929
+ len += JSON.stringify(args).length;
893
930
  }
894
- });
931
+ }
932
+ }
933
+ lengths[k] = len;
934
+ totalLength += len;
935
+ }
936
+
937
+ if (totalLength === 0) {
938
+ const countPerMessage = Math.floor(tokenCount / msgCount);
939
+ for (let k = 0; k < lastIdx; k++) {
940
+ updatedIndexTokenCountMap[resultIndices[k]] = countPerMessage;
941
+ }
942
+ updatedIndexTokenCountMap[resultIndices[lastIdx]] =
943
+ tokenCount - countPerMessage * lastIdx;
944
+ } else {
945
+ let distributed = 0;
946
+ for (let k = 0; k < lastIdx; k++) {
947
+ const share = Math.floor((lengths[k] / totalLength) * tokenCount);
948
+ updatedIndexTokenCountMap[resultIndices[k]] = share;
949
+ distributed += share;
895
950
  }
951
+ updatedIndexTokenCountMap[resultIndices[lastIdx]] =
952
+ tokenCount - distributed;
896
953
  }
897
954
  }
898
955
  }