@librechat/agents 3.1.57 → 3.1.60

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 (214) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +326 -62
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +13 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/events.cjs +7 -27
  6. package/dist/cjs/events.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +303 -222
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +4 -4
  10. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +6 -2
  12. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  13. package/dist/cjs/llm/init.cjs +60 -0
  14. package/dist/cjs/llm/init.cjs.map +1 -0
  15. package/dist/cjs/llm/invoke.cjs +90 -0
  16. package/dist/cjs/llm/invoke.cjs.map +1 -0
  17. package/dist/cjs/llm/openai/index.cjs +2 -0
  18. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  19. package/dist/cjs/llm/request.cjs +41 -0
  20. package/dist/cjs/llm/request.cjs.map +1 -0
  21. package/dist/cjs/main.cjs +40 -0
  22. package/dist/cjs/main.cjs.map +1 -1
  23. package/dist/cjs/messages/cache.cjs +76 -89
  24. package/dist/cjs/messages/cache.cjs.map +1 -1
  25. package/dist/cjs/messages/contextPruning.cjs +156 -0
  26. package/dist/cjs/messages/contextPruning.cjs.map +1 -0
  27. package/dist/cjs/messages/contextPruningSettings.cjs +53 -0
  28. package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -0
  29. package/dist/cjs/messages/core.cjs +23 -37
  30. package/dist/cjs/messages/core.cjs.map +1 -1
  31. package/dist/cjs/messages/format.cjs +156 -11
  32. package/dist/cjs/messages/format.cjs.map +1 -1
  33. package/dist/cjs/messages/prune.cjs +1161 -49
  34. package/dist/cjs/messages/prune.cjs.map +1 -1
  35. package/dist/cjs/messages/reducer.cjs +87 -0
  36. package/dist/cjs/messages/reducer.cjs.map +1 -0
  37. package/dist/cjs/run.cjs +81 -42
  38. package/dist/cjs/run.cjs.map +1 -1
  39. package/dist/cjs/stream.cjs +54 -7
  40. package/dist/cjs/stream.cjs.map +1 -1
  41. package/dist/cjs/summarization/index.cjs +75 -0
  42. package/dist/cjs/summarization/index.cjs.map +1 -0
  43. package/dist/cjs/summarization/node.cjs +663 -0
  44. package/dist/cjs/summarization/node.cjs.map +1 -0
  45. package/dist/cjs/tools/ToolNode.cjs +16 -8
  46. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  47. package/dist/cjs/tools/handlers.cjs +2 -0
  48. package/dist/cjs/tools/handlers.cjs.map +1 -1
  49. package/dist/cjs/utils/errors.cjs +115 -0
  50. package/dist/cjs/utils/errors.cjs.map +1 -0
  51. package/dist/cjs/utils/events.cjs +17 -0
  52. package/dist/cjs/utils/events.cjs.map +1 -1
  53. package/dist/cjs/utils/handlers.cjs +16 -0
  54. package/dist/cjs/utils/handlers.cjs.map +1 -1
  55. package/dist/cjs/utils/llm.cjs +10 -0
  56. package/dist/cjs/utils/llm.cjs.map +1 -1
  57. package/dist/cjs/utils/tokens.cjs +247 -14
  58. package/dist/cjs/utils/tokens.cjs.map +1 -1
  59. package/dist/cjs/utils/truncation.cjs +107 -0
  60. package/dist/cjs/utils/truncation.cjs.map +1 -0
  61. package/dist/esm/agents/AgentContext.mjs +325 -61
  62. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  63. package/dist/esm/common/enum.mjs +13 -0
  64. package/dist/esm/common/enum.mjs.map +1 -1
  65. package/dist/esm/events.mjs +8 -28
  66. package/dist/esm/events.mjs.map +1 -1
  67. package/dist/esm/graphs/Graph.mjs +307 -226
  68. package/dist/esm/graphs/Graph.mjs.map +1 -1
  69. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +4 -4
  70. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  71. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +6 -2
  72. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  73. package/dist/esm/llm/init.mjs +58 -0
  74. package/dist/esm/llm/init.mjs.map +1 -0
  75. package/dist/esm/llm/invoke.mjs +87 -0
  76. package/dist/esm/llm/invoke.mjs.map +1 -0
  77. package/dist/esm/llm/openai/index.mjs +2 -0
  78. package/dist/esm/llm/openai/index.mjs.map +1 -1
  79. package/dist/esm/llm/request.mjs +38 -0
  80. package/dist/esm/llm/request.mjs.map +1 -0
  81. package/dist/esm/main.mjs +13 -3
  82. package/dist/esm/main.mjs.map +1 -1
  83. package/dist/esm/messages/cache.mjs +76 -89
  84. package/dist/esm/messages/cache.mjs.map +1 -1
  85. package/dist/esm/messages/contextPruning.mjs +154 -0
  86. package/dist/esm/messages/contextPruning.mjs.map +1 -0
  87. package/dist/esm/messages/contextPruningSettings.mjs +50 -0
  88. package/dist/esm/messages/contextPruningSettings.mjs.map +1 -0
  89. package/dist/esm/messages/core.mjs +23 -37
  90. package/dist/esm/messages/core.mjs.map +1 -1
  91. package/dist/esm/messages/format.mjs +156 -11
  92. package/dist/esm/messages/format.mjs.map +1 -1
  93. package/dist/esm/messages/prune.mjs +1158 -52
  94. package/dist/esm/messages/prune.mjs.map +1 -1
  95. package/dist/esm/messages/reducer.mjs +83 -0
  96. package/dist/esm/messages/reducer.mjs.map +1 -0
  97. package/dist/esm/run.mjs +82 -43
  98. package/dist/esm/run.mjs.map +1 -1
  99. package/dist/esm/stream.mjs +54 -7
  100. package/dist/esm/stream.mjs.map +1 -1
  101. package/dist/esm/summarization/index.mjs +73 -0
  102. package/dist/esm/summarization/index.mjs.map +1 -0
  103. package/dist/esm/summarization/node.mjs +659 -0
  104. package/dist/esm/summarization/node.mjs.map +1 -0
  105. package/dist/esm/tools/ToolNode.mjs +16 -8
  106. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  107. package/dist/esm/tools/handlers.mjs +2 -0
  108. package/dist/esm/tools/handlers.mjs.map +1 -1
  109. package/dist/esm/utils/errors.mjs +111 -0
  110. package/dist/esm/utils/errors.mjs.map +1 -0
  111. package/dist/esm/utils/events.mjs +17 -1
  112. package/dist/esm/utils/events.mjs.map +1 -1
  113. package/dist/esm/utils/handlers.mjs +16 -0
  114. package/dist/esm/utils/handlers.mjs.map +1 -1
  115. package/dist/esm/utils/llm.mjs +10 -1
  116. package/dist/esm/utils/llm.mjs.map +1 -1
  117. package/dist/esm/utils/tokens.mjs +245 -15
  118. package/dist/esm/utils/tokens.mjs.map +1 -1
  119. package/dist/esm/utils/truncation.mjs +102 -0
  120. package/dist/esm/utils/truncation.mjs.map +1 -0
  121. package/dist/types/agents/AgentContext.d.ts +124 -6
  122. package/dist/types/common/enum.d.ts +14 -1
  123. package/dist/types/graphs/Graph.d.ts +22 -27
  124. package/dist/types/index.d.ts +5 -0
  125. package/dist/types/llm/init.d.ts +18 -0
  126. package/dist/types/llm/invoke.d.ts +48 -0
  127. package/dist/types/llm/request.d.ts +14 -0
  128. package/dist/types/messages/contextPruning.d.ts +42 -0
  129. package/dist/types/messages/contextPruningSettings.d.ts +44 -0
  130. package/dist/types/messages/core.d.ts +1 -1
  131. package/dist/types/messages/format.d.ts +17 -1
  132. package/dist/types/messages/index.d.ts +3 -0
  133. package/dist/types/messages/prune.d.ts +162 -1
  134. package/dist/types/messages/reducer.d.ts +18 -0
  135. package/dist/types/run.d.ts +12 -1
  136. package/dist/types/summarization/index.d.ts +20 -0
  137. package/dist/types/summarization/node.d.ts +29 -0
  138. package/dist/types/tools/ToolNode.d.ts +3 -1
  139. package/dist/types/types/graph.d.ts +44 -6
  140. package/dist/types/types/index.d.ts +1 -0
  141. package/dist/types/types/run.d.ts +30 -0
  142. package/dist/types/types/stream.d.ts +31 -4
  143. package/dist/types/types/summarize.d.ts +47 -0
  144. package/dist/types/types/tools.d.ts +7 -0
  145. package/dist/types/utils/errors.d.ts +28 -0
  146. package/dist/types/utils/events.d.ts +13 -0
  147. package/dist/types/utils/index.d.ts +2 -0
  148. package/dist/types/utils/llm.d.ts +4 -0
  149. package/dist/types/utils/tokens.d.ts +14 -1
  150. package/dist/types/utils/truncation.d.ts +49 -0
  151. package/package.json +1 -1
  152. package/src/agents/AgentContext.ts +388 -58
  153. package/src/agents/__tests__/AgentContext.test.ts +265 -5
  154. package/src/common/enum.ts +13 -0
  155. package/src/events.ts +9 -39
  156. package/src/graphs/Graph.ts +468 -331
  157. package/src/index.ts +7 -0
  158. package/src/llm/anthropic/llm.spec.ts +3 -3
  159. package/src/llm/anthropic/utils/message_inputs.ts +6 -4
  160. package/src/llm/bedrock/llm.spec.ts +1 -1
  161. package/src/llm/bedrock/utils/message_inputs.ts +6 -2
  162. package/src/llm/init.ts +63 -0
  163. package/src/llm/invoke.ts +144 -0
  164. package/src/llm/request.ts +55 -0
  165. package/src/messages/__tests__/observationMasking.test.ts +221 -0
  166. package/src/messages/cache.ts +77 -102
  167. package/src/messages/contextPruning.ts +191 -0
  168. package/src/messages/contextPruningSettings.ts +90 -0
  169. package/src/messages/core.ts +32 -53
  170. package/src/messages/ensureThinkingBlock.test.ts +39 -39
  171. package/src/messages/format.ts +227 -15
  172. package/src/messages/formatAgentMessages.test.ts +511 -1
  173. package/src/messages/index.ts +3 -0
  174. package/src/messages/prune.ts +1548 -62
  175. package/src/messages/reducer.ts +22 -0
  176. package/src/run.ts +104 -51
  177. package/src/scripts/bedrock-merge-test.ts +1 -1
  178. package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
  179. package/src/scripts/test-thinking-handoff.ts +1 -1
  180. package/src/scripts/thinking-bedrock.ts +1 -1
  181. package/src/scripts/thinking.ts +1 -1
  182. package/src/specs/anthropic.simple.test.ts +1 -1
  183. package/src/specs/multi-agent-summarization.test.ts +396 -0
  184. package/src/specs/prune.test.ts +1196 -23
  185. package/src/specs/summarization-unit.test.ts +868 -0
  186. package/src/specs/summarization.test.ts +3810 -0
  187. package/src/specs/summarize-prune.test.ts +376 -0
  188. package/src/specs/thinking-handoff.test.ts +10 -10
  189. package/src/specs/thinking-prune.test.ts +7 -4
  190. package/src/specs/token-accounting-e2e.test.ts +1034 -0
  191. package/src/specs/token-accounting-pipeline.test.ts +882 -0
  192. package/src/specs/token-distribution-edge-case.test.ts +25 -26
  193. package/src/splitStream.test.ts +42 -33
  194. package/src/stream.ts +64 -11
  195. package/src/summarization/__tests__/aggregator.test.ts +153 -0
  196. package/src/summarization/__tests__/node.test.ts +708 -0
  197. package/src/summarization/__tests__/trigger.test.ts +50 -0
  198. package/src/summarization/index.ts +102 -0
  199. package/src/summarization/node.ts +982 -0
  200. package/src/tools/ToolNode.ts +25 -3
  201. package/src/types/graph.ts +62 -7
  202. package/src/types/index.ts +1 -0
  203. package/src/types/run.ts +32 -0
  204. package/src/types/stream.ts +45 -5
  205. package/src/types/summarize.ts +58 -0
  206. package/src/types/tools.ts +7 -0
  207. package/src/utils/errors.ts +117 -0
  208. package/src/utils/events.ts +31 -0
  209. package/src/utils/handlers.ts +18 -0
  210. package/src/utils/index.ts +2 -0
  211. package/src/utils/llm.ts +12 -0
  212. package/src/utils/tokens.ts +336 -18
  213. package/src/utils/truncation.ts +124 -0
  214. package/src/scripts/image.ts +0 -180
@@ -383,7 +383,7 @@ describe('AgentContext', () => {
383
383
 
384
384
  ctx.markToolsAsDiscovered(['tool1']);
385
385
  void ctx.systemRunnable;
386
- ctx.instructionTokens = 100;
386
+ ctx.systemMessageTokens = 100;
387
387
  ctx.indexTokenCountMap = { '0': 50 };
388
388
  ctx.currentUsage = { input_tokens: 100 };
389
389
 
@@ -395,6 +395,70 @@ describe('AgentContext', () => {
395
395
  expect(ctx.currentUsage).toBeUndefined();
396
396
  });
397
397
 
398
+ it('preserves summarization settings across resets', () => {
399
+ const ctx = createBasicContext({
400
+ agentConfig: {
401
+ summarizationEnabled: true,
402
+ summarizationConfig: {
403
+ provider: Providers.ANTHROPIC,
404
+ model: 'claude-sonnet-4-5',
405
+ prompt: 'Keep decisions and next steps concise.',
406
+ trigger: {
407
+ type: 'token_ratio',
408
+ value: 0.8,
409
+ },
410
+ },
411
+ },
412
+ });
413
+
414
+ ctx.reset();
415
+
416
+ expect(ctx.summarizationEnabled).toBe(true);
417
+ expect(ctx.summarizationConfig).toEqual({
418
+ provider: 'anthropic',
419
+ model: 'claude-sonnet-4-5',
420
+ prompt: 'Keep decisions and next steps concise.',
421
+ trigger: {
422
+ type: 'token_ratio',
423
+ value: 0.8,
424
+ },
425
+ });
426
+ });
427
+
428
+ it('shouldSkipSummarization returns true when message count unchanged', () => {
429
+ const ctx = createBasicContext({
430
+ agentConfig: { summarizationEnabled: true },
431
+ });
432
+ ctx.markSummarizationTriggered(10);
433
+ expect(ctx.shouldSkipSummarization(10)).toBe(true);
434
+ });
435
+
436
+ it('shouldSkipSummarization returns false with any new messages', () => {
437
+ const ctx = createBasicContext({
438
+ agentConfig: { summarizationEnabled: true },
439
+ });
440
+ ctx.markSummarizationTriggered(10);
441
+ expect(ctx.shouldSkipSummarization(11)).toBe(false);
442
+ });
443
+
444
+ it('shouldSkipSummarization returns false when no prior summarization', () => {
445
+ const ctx = createBasicContext({
446
+ agentConfig: { summarizationEnabled: true },
447
+ });
448
+ expect(ctx.shouldSkipSummarization(5)).toBe(false);
449
+ });
450
+
451
+ it('shouldSkipSummarization allows unlimited summarizations per run', () => {
452
+ const ctx = createBasicContext({
453
+ agentConfig: { summarizationEnabled: true },
454
+ });
455
+ for (let i = 0; i < 10; i++) {
456
+ ctx.markSummarizationTriggered(i * 5);
457
+ }
458
+ // Even after 10 summarizations, new messages allow another
459
+ expect(ctx.shouldSkipSummarization(50)).toBe(false);
460
+ });
461
+
398
462
  it('rebuilds indexTokenCountMap from base map after reset', async () => {
399
463
  const tokenCounter = jest.fn(() => 5);
400
464
  const ctx = createBasicContext({
@@ -611,7 +675,7 @@ describe('AgentContext', () => {
611
675
 
612
676
  // Simulate token map update (as done in fromConfig flow)
613
677
  ctx.updateTokenMapWithInstructions({ '0': 10, '1': 20 });
614
- expect(ctx.indexTokenCountMap['0']).toBe(10 + turn1Tokens);
678
+ expect(ctx.indexTokenCountMap['0']).toBe(10);
615
679
  expect(ctx.indexTokenCountMap['1']).toBe(20);
616
680
 
617
681
  // ========== TURN 2: Tool search results come back ==========
@@ -674,8 +738,8 @@ describe('AgentContext', () => {
674
738
  const messageTokenCounts = { '0': 50, '1': 100, '2': 75 };
675
739
  ctx.updateTokenMapWithInstructions(messageTokenCounts);
676
740
 
677
- // Verify token map: first message gets instruction tokens added
678
- expect(ctx.indexTokenCountMap['0']).toBe(50 + initialSystemTokens);
741
+ // Verify token map: first message keeps its real token count (no inflation)
742
+ expect(ctx.indexTokenCountMap['0']).toBe(50);
679
743
  expect(ctx.indexTokenCountMap['1']).toBe(100);
680
744
  expect(ctx.indexTokenCountMap['2']).toBe(75);
681
745
 
@@ -708,7 +772,7 @@ describe('AgentContext', () => {
708
772
  const newMessageTokenCounts = { '0': 60, '1': 110 };
709
773
  ctx.updateTokenMapWithInstructions(newMessageTokenCounts);
710
774
 
711
- expect(ctx.indexTokenCountMap['0']).toBe(60 + newSystemTokens);
775
+ expect(ctx.indexTokenCountMap['0']).toBe(60);
712
776
  expect(ctx.indexTokenCountMap['1']).toBe(110);
713
777
  });
714
778
 
@@ -823,4 +887,200 @@ describe('AgentContext', () => {
823
887
  expect(ctx.instructionTokens).toBe(run1Tokens);
824
888
  });
825
889
  });
890
+
891
+ describe('Summary Token Accounting', () => {
892
+ const charTokenCounter: t.TokenCounter = (msg) => {
893
+ const raw = msg.content;
894
+ if (typeof raw === 'string') return raw.length;
895
+ if (Array.isArray(raw)) {
896
+ let total = 0;
897
+ for (let i = 0; i < raw.length; i++) {
898
+ const item = raw[i] as unknown;
899
+ if (typeof item === 'string') {
900
+ total += item.length;
901
+ } else if (
902
+ typeof item === 'object' &&
903
+ item != null &&
904
+ 'text' in item
905
+ ) {
906
+ const text = (item as Record<string, unknown>).text;
907
+ if (typeof text === 'string') total += text.length;
908
+ }
909
+ }
910
+ return total;
911
+ }
912
+ return 0;
913
+ };
914
+
915
+ it('mid-run setSummary increases instructionTokens by the summary token count', () => {
916
+ const ctx = createBasicContext({
917
+ agentConfig: { instructions: 'Be helpful.' },
918
+ tokenCounter: charTokenCounter,
919
+ });
920
+
921
+ void ctx.systemRunnable;
922
+ const baseInstructionTokens = ctx.instructionTokens;
923
+ expect(baseInstructionTokens).toBeGreaterThan(0);
924
+
925
+ // Mid-run summary is injected as HumanMessage but still counts as
926
+ // instruction overhead so the pruner reserves budget for it.
927
+ ctx.setSummary('User asked about math. Key results: 2+2=4, 3*5=15.', 50);
928
+ expect(ctx.hasSummary()).toBe(true);
929
+
930
+ void ctx.systemRunnable;
931
+ expect(ctx.instructionTokens).toBe(baseInstructionTokens + 50);
932
+ });
933
+
934
+ it('summary text appears in rebuilt system message', () => {
935
+ const ctx = createBasicContext({
936
+ agentConfig: { instructions: 'Be helpful.' },
937
+ tokenCounter: charTokenCounter,
938
+ });
939
+
940
+ void ctx.systemRunnable;
941
+ ctx.setSummary('Prior context: user computed factorials.', 40);
942
+
943
+ const runnable = ctx.systemRunnable;
944
+ expect(runnable).toBeDefined();
945
+ });
946
+
947
+ it('clearSummary removes summary overhead from instructionTokens', () => {
948
+ const ctx = createBasicContext({
949
+ agentConfig: { instructions: 'Be helpful.' },
950
+ tokenCounter: charTokenCounter,
951
+ });
952
+
953
+ void ctx.systemRunnable;
954
+ const baseTokens = ctx.instructionTokens;
955
+
956
+ ctx.setSummary('Summary of the conversation so far.', 35);
957
+ void ctx.systemRunnable;
958
+ expect(ctx.instructionTokens).toBe(baseTokens + 35);
959
+
960
+ ctx.clearSummary();
961
+ void ctx.systemRunnable;
962
+ expect(ctx.instructionTokens).toBe(baseTokens);
963
+ });
964
+
965
+ it('reset preserves durable summary and maintains token counts', () => {
966
+ const ctx = createBasicContext({
967
+ agentConfig: { instructions: 'Be helpful.' },
968
+ tokenCounter: charTokenCounter,
969
+ indexTokenCountMap: { '0': 10, '1': 20 },
970
+ });
971
+
972
+ void ctx.systemRunnable;
973
+ ctx.setSummary('Summary text.', 15);
974
+ void ctx.systemRunnable;
975
+ expect(ctx.hasSummary()).toBe(true);
976
+ const tokensWithSummary = ctx.instructionTokens;
977
+
978
+ ctx.reset();
979
+ // Summary should survive reset (durable cross-run state)
980
+ expect(ctx.hasSummary()).toBe(true);
981
+ expect(ctx.getSummaryText()).toBe('Summary text.');
982
+
983
+ void ctx.systemRunnable;
984
+ const postResetTokens = ctx.instructionTokens;
985
+ expect(postResetTokens).toBeGreaterThan(0);
986
+ // Token count should be the same since summary is preserved
987
+ expect(postResetTokens).toBe(tokensWithSummary);
988
+ });
989
+
990
+ it('updateTokenMapWithInstructions copies base map without inflating index 0', () => {
991
+ const ctx = createBasicContext({
992
+ agentConfig: { instructions: 'Be helpful.' },
993
+ tokenCounter: charTokenCounter,
994
+ });
995
+
996
+ void ctx.systemRunnable;
997
+ ctx.setSummary('Summary of prior context with key facts.', 40);
998
+ void ctx.systemRunnable;
999
+
1000
+ const instructionTokens = ctx.instructionTokens;
1001
+ expect(instructionTokens).toBeGreaterThan(0);
1002
+
1003
+ const baseMap: Record<string, number> = { '0': 5, '1': 10 };
1004
+ ctx.updateTokenMapWithInstructions(baseMap);
1005
+
1006
+ // Index 0 should contain the real message token count, NOT inflated
1007
+ // with instruction tokens. Instruction overhead is now handled by
1008
+ // getInstructionTokens() in the pruning factory.
1009
+ expect(ctx.indexTokenCountMap['0']).toBe(5);
1010
+ expect(ctx.indexTokenCountMap['1']).toBe(10);
1011
+ });
1012
+
1013
+ it('hasSummary returns false before setSummary and true after', () => {
1014
+ const ctx = createBasicContext({
1015
+ agentConfig: { instructions: 'Be helpful.' },
1016
+ });
1017
+
1018
+ expect(ctx.hasSummary()).toBe(false);
1019
+ ctx.setSummary('Some summary.', 10);
1020
+ expect(ctx.hasSummary()).toBe(true);
1021
+ ctx.clearSummary();
1022
+ expect(ctx.hasSummary()).toBe(false);
1023
+ });
1024
+ });
1025
+
1026
+ describe('shouldSkipSummarization — re-trigger after summary', () => {
1027
+ it('allows re-summarization after rebuildTokenMapAfterSummarization resets baseline', () => {
1028
+ const ctx = createBasicContext();
1029
+
1030
+ expect(ctx.shouldSkipSummarization(25)).toBe(false);
1031
+ ctx.markSummarizationTriggered(25);
1032
+
1033
+ // Same count — skip
1034
+ expect(ctx.shouldSkipSummarization(25)).toBe(true);
1035
+
1036
+ ctx.setSummary('Summary of conversation', 100);
1037
+ // Full compaction: empty state, baseline resets to 0
1038
+ ctx.rebuildTokenMapAfterSummarization({});
1039
+
1040
+ // Baseline is 0 after full compaction. Guard `_lastSummarizationMsgCount > 0`
1041
+ // is false, so all counts are allowed.
1042
+ expect(ctx.shouldSkipSummarization(0)).toBe(false);
1043
+ expect(ctx.shouldSkipSummarization(1)).toBe(false);
1044
+ });
1045
+
1046
+ it('allows summarization after full compaction resets to empty state', () => {
1047
+ const ctx = createBasicContext();
1048
+
1049
+ ctx.markSummarizationTriggered(20);
1050
+ ctx.setSummary('Summary', 50);
1051
+ ctx.rebuildTokenMapAfterSummarization({});
1052
+
1053
+ // Baseline is 0 after full compaction. The guard `_lastSummarizationMsgCount > 0`
1054
+ // is false, so summarization is always allowed — the model starts fresh.
1055
+ expect(ctx.shouldSkipSummarization(0)).toBe(false);
1056
+ expect(ctx.shouldSkipSummarization(1)).toBe(false);
1057
+ });
1058
+ });
1059
+
1060
+ describe('updateLastCallUsage', () => {
1061
+ it('records usage without modifying toolSchemaTokens', () => {
1062
+ const ctx = createBasicContext();
1063
+ ctx.toolSchemaTokens = 200;
1064
+
1065
+ ctx.updateLastCallUsage({ input_tokens: 500, output_tokens: 30 });
1066
+
1067
+ expect(ctx.lastCallUsage).toBeDefined();
1068
+ expect(ctx.lastCallUsage!.inputTokens).toBe(500);
1069
+ expect(ctx.lastCallUsage!.outputTokens).toBe(30);
1070
+ expect(ctx.toolSchemaTokens).toBe(200);
1071
+ });
1072
+
1073
+ it('handles additive cache tokens', () => {
1074
+ const ctx = createBasicContext();
1075
+
1076
+ ctx.updateLastCallUsage({
1077
+ input_tokens: 5,
1078
+ output_tokens: 100,
1079
+ input_token_details: { cache_creation: 8000, cache_read: 0 },
1080
+ });
1081
+
1082
+ // cache_creation (8000) > input_tokens (5) → additive
1083
+ expect(ctx.lastCallUsage!.inputTokens).toBe(8005);
1084
+ });
1085
+ });
826
1086
  });
@@ -21,6 +21,14 @@ export enum GraphEvents {
21
21
  ON_REASONING_DELTA = 'on_reasoning_delta',
22
22
  /** [Custom] Request to execute tools - dispatched by ToolNode, handled by host */
23
23
  ON_TOOL_EXECUTE = 'on_tool_execute',
24
+ /** [Custom] Emitted when the summarize node begins generating a summary */
25
+ ON_SUMMARIZE_START = 'on_summarize_start',
26
+ /** [Custom] Delta event carrying the completed summary content */
27
+ ON_SUMMARIZE_DELTA = 'on_summarize_delta',
28
+ /** [Custom] Emitted when the summarize node completes with the final summary */
29
+ ON_SUMMARIZE_COMPLETE = 'on_summarize_complete',
30
+ /** [Custom] Diagnostic logging event for context management observability */
31
+ ON_AGENT_LOG = 'on_agent_log',
24
32
 
25
33
  /* Official Events */
26
34
 
@@ -90,6 +98,7 @@ export enum Providers {
90
98
  export enum GraphNodeKeys {
91
99
  TOOLS = 'tools=',
92
100
  AGENT = 'agent=',
101
+ SUMMARIZE = 'summarize=',
93
102
  ROUTER = 'router',
94
103
  PRE_TOOLS = 'pre_tools',
95
104
  POST_TOOLS = 'post_tools',
@@ -123,6 +132,8 @@ export enum ContentTypes {
123
132
  REASONING = 'reasoning',
124
133
  /** Multi-Agent Switch */
125
134
  AGENT_UPDATE = 'agent_update',
135
+ /** Framework-level conversation summary block */
136
+ SUMMARY = 'summary',
126
137
  /** Bedrock */
127
138
  REASONING_CONTENT = 'reasoning_content',
128
139
  }
@@ -169,6 +180,8 @@ export enum Constants {
169
180
  LC_TRANSFER_TO_ = 'lc_transfer_to_',
170
181
  /** Delimiter for MCP tools: toolName_mcp_serverName */
171
182
  MCP_DELIMITER = '_mcp_',
183
+ /** Anthropic server tool ID prefix (web_search, code_execution, etc.) */
184
+ ANTHROPIC_SERVER_TOOL_PREFIX = 'srvtoolu_',
172
185
  }
173
186
 
174
187
  export enum TitleMethod {
package/src/events.ts CHANGED
@@ -7,8 +7,7 @@ import type {
7
7
  import type { MultiAgentGraph, StandardGraph } from '@/graphs';
8
8
  import type { Logger } from 'winston';
9
9
  import type * as t from '@/types';
10
- import { handleToolCalls } from '@/tools/handlers';
11
- import { Constants, Providers } from '@/common';
10
+ import { Constants } from '@/common';
12
11
 
13
12
  export class HandlerRegistry {
14
13
  private handlers: Map<string, t.EventHandler> = new Map();
@@ -46,29 +45,6 @@ export class ModelEndHandler implements t.EventHandler {
46
45
  if (usage != null && this.collectedUsage != null) {
47
46
  this.collectedUsage.push(usage);
48
47
  }
49
-
50
- if (metadata.ls_provider === 'FakeListChatModel') {
51
- return handleToolCalls(data?.output?.tool_calls, metadata, graph);
52
- }
53
-
54
- console.log(`====== ${event.toUpperCase()} ======`);
55
- console.dir(
56
- {
57
- usage,
58
- },
59
- { depth: null }
60
- );
61
-
62
- const agentContext = graph.getAgentContext(metadata);
63
-
64
- if (
65
- agentContext.provider !== Providers.GOOGLE &&
66
- agentContext.provider !== Providers.BEDROCK
67
- ) {
68
- return;
69
- }
70
-
71
- await handleToolCalls(data?.output?.tool_calls, metadata, graph);
72
48
  }
73
49
  }
74
50
 
@@ -138,10 +114,8 @@ export class TestLLMStreamHandler implements t.EventHandler {
138
114
  const msg = isMessageChunk ? chunk.message : undefined;
139
115
  if (msg && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) {
140
116
  console.log(msg.tool_call_chunks);
141
- } else if (msg && msg.content) {
142
- if (typeof msg.content === 'string') {
143
- process.stdout.write(msg.content);
144
- }
117
+ } else if (msg && typeof msg.content === 'string') {
118
+ process.stdout.write(msg.content);
145
119
  }
146
120
  }
147
121
  }
@@ -150,12 +124,12 @@ export class TestChatStreamHandler implements t.EventHandler {
150
124
  handle(event: string, data: t.StreamEventData | undefined): void {
151
125
  const chunk = data?.chunk;
152
126
  const isContentChunk = !!(chunk && 'content' in chunk);
153
- const content = isContentChunk && chunk.content;
154
-
155
- if (!content || !isContentChunk) {
127
+ if (!isContentChunk) {
156
128
  return;
157
129
  }
158
130
 
131
+ const content = chunk.content;
132
+
159
133
  if (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) {
160
134
  console.dir(chunk.tool_call_chunks, { depth: null });
161
135
  }
@@ -176,18 +150,14 @@ export class LLMStreamHandler implements t.EventHandler {
176
150
  ): void {
177
151
  const chunk = data?.chunk;
178
152
  const isMessageChunk = !!(chunk && 'message' in chunk);
179
- const msg = isMessageChunk && chunk.message;
153
+ const msg = isMessageChunk ? chunk.message : undefined;
180
154
  if (metadata) {
181
155
  console.log(metadata);
182
156
  }
183
157
  if (msg && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) {
184
158
  console.log(msg.tool_call_chunks);
185
- } else if (msg && msg.content) {
186
- if (typeof msg.content === 'string') {
187
- // const text_delta = msg.content;
188
- // dispatchCustomEvent(GraphEvents.CHAT_MODEL_STREAM, { chunk }, config);
189
- process.stdout.write(msg.content);
190
- }
159
+ } else if (msg && typeof msg.content === 'string') {
160
+ process.stdout.write(msg.content);
191
161
  }
192
162
  }
193
163
  }