@librechat/agents 3.7.6 → 3.7.8

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 (92) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +23 -3
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/constants.cjs +10 -0
  4. package/dist/cjs/common/constants.cjs.map +1 -1
  5. package/dist/cjs/eventActor/EventActorExecutor.cjs +305 -11
  6. package/dist/cjs/eventActor/EventActorExecutor.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +16 -5
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/langfuseTraceShaping.cjs +75 -0
  10. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/toolCache.cjs +1 -1
  12. package/dist/cjs/llm/fake.cjs +9 -5
  13. package/dist/cjs/llm/fake.cjs.map +1 -1
  14. package/dist/cjs/llm/invoke.cjs +1 -1
  15. package/dist/cjs/llm/openrouter/toolCache.cjs +1 -1
  16. package/dist/cjs/llm/prepareProviderRequest.cjs +2 -2
  17. package/dist/cjs/llm/providers.cjs +1 -1
  18. package/dist/cjs/llm/truncation.cjs +1 -0
  19. package/dist/cjs/main.cjs +3 -2
  20. package/dist/cjs/messages/format.cjs +298 -3
  21. package/dist/cjs/messages/format.cjs.map +1 -1
  22. package/dist/cjs/messages/prune.cjs +1 -1
  23. package/dist/cjs/run.cjs +25 -15
  24. package/dist/cjs/run.cjs.map +1 -1
  25. package/dist/cjs/stream.cjs +1 -1
  26. package/dist/cjs/summarization/node.cjs +43 -9
  27. package/dist/cjs/summarization/node.cjs.map +1 -1
  28. package/dist/cjs/summarization/semanticIndex.cjs +362 -0
  29. package/dist/cjs/summarization/semanticIndex.cjs.map +1 -0
  30. package/dist/cjs/tools/subagent/childGraphConfig.cjs +2 -1
  31. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
  32. package/dist/cjs/utils/index.cjs +1 -1
  33. package/dist/esm/agents/AgentContext.mjs +23 -3
  34. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  35. package/dist/esm/common/constants.mjs +10 -1
  36. package/dist/esm/common/constants.mjs.map +1 -1
  37. package/dist/esm/eventActor/EventActorExecutor.mjs +305 -11
  38. package/dist/esm/eventActor/EventActorExecutor.mjs.map +1 -1
  39. package/dist/esm/graphs/Graph.mjs +16 -5
  40. package/dist/esm/graphs/Graph.mjs.map +1 -1
  41. package/dist/esm/langfuseTraceShaping.mjs +75 -0
  42. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  43. package/dist/esm/llm/bedrock/toolCache.mjs +1 -1
  44. package/dist/esm/llm/fake.mjs +9 -5
  45. package/dist/esm/llm/fake.mjs.map +1 -1
  46. package/dist/esm/llm/invoke.mjs +1 -1
  47. package/dist/esm/llm/openrouter/toolCache.mjs +1 -1
  48. package/dist/esm/llm/prepareProviderRequest.mjs +2 -2
  49. package/dist/esm/llm/providers.mjs +1 -1
  50. package/dist/esm/llm/truncation.mjs +1 -1
  51. package/dist/esm/main.mjs +4 -4
  52. package/dist/esm/messages/format.mjs +298 -3
  53. package/dist/esm/messages/format.mjs.map +1 -1
  54. package/dist/esm/messages/prune.mjs +1 -1
  55. package/dist/esm/run.mjs +25 -15
  56. package/dist/esm/run.mjs.map +1 -1
  57. package/dist/esm/stream.mjs +1 -1
  58. package/dist/esm/summarization/node.mjs +43 -9
  59. package/dist/esm/summarization/node.mjs.map +1 -1
  60. package/dist/esm/summarization/semanticIndex.mjs +360 -0
  61. package/dist/esm/summarization/semanticIndex.mjs.map +1 -0
  62. package/dist/esm/tools/subagent/childGraphConfig.mjs +2 -1
  63. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
  64. package/dist/esm/utils/index.mjs +1 -1
  65. package/dist/types/agents/AgentContext.d.ts +27 -1
  66. package/dist/types/common/constants.d.ts +10 -0
  67. package/dist/types/eventActor/EventActorExecutor.d.ts +3 -1
  68. package/dist/types/eventActor/index.d.ts +1 -1
  69. package/dist/types/eventActor/types.d.ts +149 -5
  70. package/dist/types/graphs/Graph.d.ts +15 -0
  71. package/dist/types/llm/fake.d.ts +12 -2
  72. package/dist/types/messages/format.d.ts +10 -1
  73. package/dist/types/run.d.ts +17 -5
  74. package/dist/types/summarization/semanticIndex.d.ts +19 -0
  75. package/dist/types/types/graph.d.ts +9 -1
  76. package/dist/types/types/summarize.d.ts +38 -0
  77. package/package.json +2 -1
  78. package/src/agents/AgentContext.ts +61 -1
  79. package/src/common/constants.ts +11 -0
  80. package/src/eventActor/EventActorExecutor.ts +545 -16
  81. package/src/eventActor/index.ts +17 -0
  82. package/src/eventActor/types.ts +182 -3
  83. package/src/graphs/Graph.ts +41 -8
  84. package/src/langfuseTraceShaping.ts +94 -0
  85. package/src/llm/fake.ts +35 -4
  86. package/src/messages/format.ts +652 -4
  87. package/src/run.ts +36 -10
  88. package/src/summarization/node.ts +90 -33
  89. package/src/summarization/semanticIndex.ts +653 -0
  90. package/src/tools/subagent/childGraphConfig.ts +3 -0
  91. package/src/types/graph.ts +9 -0
  92. package/src/types/summarize.ts +50 -0
@@ -28,6 +28,8 @@ import type {
28
28
  TPayload,
29
29
  TMessage,
30
30
  ProviderName,
31
+ CompactionSemanticIndex,
32
+ CompactionSemanticIndexEntry,
31
33
  } from '@/types';
32
34
  import type {
33
35
  ProviderMessageAttribution,
@@ -56,11 +58,17 @@ import {
56
58
  isAtomicToolContentBlock,
57
59
  serializeStructuredValueBounded,
58
60
  } from '@/utils/toolContent';
61
+ import { setCompactionSemanticIndexProvidedEntryCount } from '@/summarization/semanticIndex';
59
62
  import { normalizeAnthropicToolCallId } from '@/llm/anthropic/utils/message_inputs';
60
63
  import { toLangChainContent, toLangChainMessageFields } from './langchain';
61
64
  import { flattenLegacyContent, isLegacyConvertible } from './content';
62
65
  import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
63
- import { Providers, ContentTypes, Constants } from '@/common';
66
+ import {
67
+ Providers,
68
+ ContentTypes,
69
+ Constants,
70
+ COMPACTION_SEMANTIC_INDEX_LIMITS,
71
+ } from '@/common';
64
72
  import { emitAgentLog } from '@/utils/events';
65
73
 
66
74
  interface MediaMessageParams {
@@ -367,9 +375,12 @@ export const formatFromLangChain = (
367
375
  };
368
376
 
369
377
  interface FormatAssistantMessageOptions {
378
+ compactionSemanticIndex?: DerivedCompactionSemanticIndexCollector;
379
+ intentToolNames?: ReadonlySet<string>;
370
380
  preserveUnpairedServerToolUses?: boolean;
371
381
  preserveReasoningContent?: boolean;
372
382
  provider?: ProviderName;
383
+ retainedSourceContentEnd?: number;
373
384
  sourceMessageId?: string;
374
385
  sourceContentPartOffset?: number;
375
386
  sourceContentPartIndices?: readonly SourceContentPartIndices[];
@@ -394,6 +405,13 @@ interface FormatAgentMessagesOptions {
394
405
  * historical `skill` tool_calls are not reconstructed into a HumanMessage,
395
406
  * so the same SKILL.md body is not injected twice in one request. */
396
407
  skipSkillBodyNames?: Set<string>;
408
+ /** Derive bounded compaction guidance during the formatter's existing
409
+ * persisted-content analysis. Tool intents are accepted only for names
410
+ * the host identifies as semantic-label fields; business `intent`
411
+ * parameters must remain ordinary tool input. */
412
+ compactionSemanticIndex?: {
413
+ intentToolNames?: ReadonlySet<string>;
414
+ };
397
415
  }
398
416
 
399
417
  function extractReasoningContent(
@@ -476,6 +494,542 @@ function getToolUseId(part: MessageContentComplex): string | undefined {
476
494
  return part.tool_use_id;
477
495
  }
478
496
 
497
+ type CompactionSemanticContentPart = MessageContentComplex & {
498
+ activity_label?: string;
499
+ activity_label_type?: string;
500
+ activity_label_revision?: number;
501
+ activity_start_index?: number;
502
+ pending?: boolean;
503
+ reasoning_label?: string;
504
+ reasoning_label_revision?: number;
505
+ reasoning_label_status?: string;
506
+ reasoning_label_step_id?: string;
507
+ };
508
+
509
+ type OrderedCompactionSemanticIndexEntry = {
510
+ entry: CompactionSemanticIndexEntry;
511
+ identityHash: number;
512
+ order: number;
513
+ retentionCount: number;
514
+ };
515
+
516
+ type CompactionSemanticEntryRing = {
517
+ entries: OrderedCompactionSemanticIndexEntry[];
518
+ cursor: number;
519
+ };
520
+
521
+ type CompactionSemanticTypeCoverage = {
522
+ head: OrderedCompactionSemanticIndexEntry[];
523
+ tail: CompactionSemanticEntryRing;
524
+ };
525
+
526
+ type CompactionSemanticCoverageState = {
527
+ head: OrderedCompactionSemanticIndexEntry[];
528
+ tail: CompactionSemanticEntryRing;
529
+ latestByIdentityHash: Map<number, OrderedCompactionSemanticIndexEntry[]>;
530
+ typeCoverage: Map<
531
+ CompactionSemanticIndexEntry['type'],
532
+ CompactionSemanticTypeCoverage
533
+ >;
534
+ };
535
+
536
+ type DerivedCompactionSemanticIndexCollector = {
537
+ entries: CompactionSemanticIndexEntry[];
538
+ entryCount: number;
539
+ coverage?: CompactionSemanticCoverageState;
540
+ };
541
+
542
+ const DERIVED_SEMANTIC_HEAD_LIMIT =
543
+ COMPACTION_SEMANTIC_INDEX_LIMITS.maxInputEntries / 4;
544
+ const DERIVED_SEMANTIC_TAIL_LIMIT =
545
+ COMPACTION_SEMANTIC_INDEX_LIMITS.maxInputEntries / 2;
546
+ const DERIVED_SEMANTIC_TYPE_EDGE_LIMIT =
547
+ COMPACTION_SEMANTIC_INDEX_LIMITS.maxInputEntries / 32;
548
+ const DERIVED_SEMANTIC_TYPE_HASH: Readonly<
549
+ Record<CompactionSemanticIndexEntry['type'], number>
550
+ > = Object.freeze({
551
+ tool_intent: 1,
552
+ tool_outcome: 2,
553
+ activity_phase: 3,
554
+ reasoning_label: 4,
555
+ });
556
+
557
+ function createDerivedCompactionSemanticIndexCollector(): DerivedCompactionSemanticIndexCollector {
558
+ return {
559
+ entries: [],
560
+ entryCount: 0,
561
+ };
562
+ }
563
+
564
+ function createCompactionSemanticCoverageState(): CompactionSemanticCoverageState {
565
+ return {
566
+ head: [],
567
+ tail: { entries: [], cursor: 0 },
568
+ latestByIdentityHash: new Map(),
569
+ typeCoverage: new Map(),
570
+ };
571
+ }
572
+
573
+ function getDerivedCompactionSemanticLocalId(
574
+ entry: CompactionSemanticIndexEntry
575
+ ): string {
576
+ if (entry.type === 'reasoning_label') {
577
+ return entry.reasoningStepId.trim();
578
+ }
579
+ if (entry.type !== 'activity_phase') {
580
+ return entry.toolCallId.trim();
581
+ }
582
+ return '';
583
+ }
584
+
585
+ function hashDerivedCompactionSemanticIdentityString(
586
+ initialHash: number,
587
+ value: string
588
+ ): number {
589
+ let hash = initialHash;
590
+ for (let index = 0; index < value.length; index++) {
591
+ hash ^= value.charCodeAt(index);
592
+ hash = Math.imul(hash, 16_777_619);
593
+ }
594
+ return hash;
595
+ }
596
+
597
+ function hashDerivedCompactionSemanticIdentity(
598
+ entry: CompactionSemanticIndexEntry
599
+ ): number {
600
+ let hash = 2_166_136_261 ^ DERIVED_SEMANTIC_TYPE_HASH[entry.type];
601
+ hash = Math.imul(hash, 16_777_619);
602
+ hash = hashDerivedCompactionSemanticIdentityString(
603
+ hash,
604
+ entry.sourceMessageId.trim()
605
+ );
606
+ hash ^= entry.sourceContentIndex;
607
+ hash = Math.imul(hash, 16_777_619);
608
+ hash = hashDerivedCompactionSemanticIdentityString(
609
+ hash,
610
+ getDerivedCompactionSemanticLocalId(entry)
611
+ );
612
+ return hash >>> 0;
613
+ }
614
+
615
+ function entriesShareDerivedCompactionSemanticIdentity(
616
+ left: CompactionSemanticIndexEntry,
617
+ right: CompactionSemanticIndexEntry
618
+ ): boolean {
619
+ return (
620
+ left.type === right.type &&
621
+ left.sourceMessageId.trim() === right.sourceMessageId.trim() &&
622
+ left.sourceContentIndex === right.sourceContentIndex &&
623
+ getDerivedCompactionSemanticLocalId(left) ===
624
+ getDerivedCompactionSemanticLocalId(right)
625
+ );
626
+ }
627
+
628
+ function entriesHaveConflictingSemanticState(
629
+ left: CompactionSemanticIndexEntry,
630
+ right: CompactionSemanticIndexEntry
631
+ ): boolean {
632
+ return (
633
+ left.status !== right.status ||
634
+ (left.redacted === true) !== (right.redacted === true) ||
635
+ left.text.replace(/\s+/g, ' ').trim() !==
636
+ right.text.replace(/\s+/g, ' ').trim()
637
+ );
638
+ }
639
+
640
+ function retainCompactionSemanticEntry(
641
+ entry: OrderedCompactionSemanticIndexEntry
642
+ ): void {
643
+ entry.retentionCount++;
644
+ }
645
+
646
+ function releaseCompactionSemanticEntry(
647
+ coverage: CompactionSemanticCoverageState,
648
+ entry: OrderedCompactionSemanticIndexEntry
649
+ ): void {
650
+ entry.retentionCount--;
651
+ if (
652
+ entry.retentionCount === 0 &&
653
+ coverage.latestByIdentityHash.has(entry.identityHash)
654
+ ) {
655
+ const bucket = coverage.latestByIdentityHash.get(entry.identityHash);
656
+ if (bucket == null) {
657
+ return;
658
+ }
659
+ const entryIndex = bucket.indexOf(entry);
660
+ if (entryIndex >= 0) {
661
+ bucket.splice(entryIndex, 1);
662
+ }
663
+ if (bucket.length === 0) {
664
+ coverage.latestByIdentityHash.delete(entry.identityHash);
665
+ }
666
+ }
667
+ }
668
+
669
+ function appendCompactionSemanticEntryRing(
670
+ coverage: CompactionSemanticCoverageState,
671
+ ring: CompactionSemanticEntryRing,
672
+ entry: OrderedCompactionSemanticIndexEntry,
673
+ limit: number
674
+ ): void {
675
+ if (ring.entries.length < limit) {
676
+ ring.entries.push(entry);
677
+ retainCompactionSemanticEntry(entry);
678
+ return;
679
+ }
680
+ releaseCompactionSemanticEntry(coverage, ring.entries[ring.cursor]);
681
+ ring.entries[ring.cursor] = entry;
682
+ retainCompactionSemanticEntry(entry);
683
+ ring.cursor = (ring.cursor + 1) % limit;
684
+ }
685
+
686
+ function renewCompactionSemanticEntryRing(
687
+ coverage: CompactionSemanticCoverageState,
688
+ ring: CompactionSemanticEntryRing,
689
+ entry: OrderedCompactionSemanticIndexEntry,
690
+ limit: number
691
+ ): void {
692
+ const existingIndex = ring.entries.indexOf(entry);
693
+ if (existingIndex < 0) {
694
+ appendCompactionSemanticEntryRing(coverage, ring, entry, limit);
695
+ return;
696
+ }
697
+ if (ring.entries.length < limit) {
698
+ ring.entries.splice(existingIndex, 1);
699
+ ring.entries.push(entry);
700
+ return;
701
+ }
702
+ const newestIndex =
703
+ (ring.cursor + ring.entries.length - 1) % ring.entries.length;
704
+ let currentIndex = existingIndex;
705
+ for (
706
+ let offset = 0;
707
+ currentIndex !== newestIndex && offset < ring.entries.length;
708
+ offset++
709
+ ) {
710
+ const nextIndex = (currentIndex + 1) % ring.entries.length;
711
+ ring.entries[currentIndex] = ring.entries[nextIndex];
712
+ currentIndex = nextIndex;
713
+ }
714
+ ring.entries[newestIndex] = entry;
715
+ }
716
+
717
+ function renewCoverageBalancedCompactionSemanticEntry(
718
+ coverage: CompactionSemanticCoverageState,
719
+ entry: OrderedCompactionSemanticIndexEntry
720
+ ): void {
721
+ renewCompactionSemanticEntryRing(
722
+ coverage,
723
+ coverage.tail,
724
+ entry,
725
+ DERIVED_SEMANTIC_TAIL_LIMIT
726
+ );
727
+ const typeCoverage = coverage.typeCoverage.get(entry.entry.type);
728
+ if (typeCoverage == null) {
729
+ return;
730
+ }
731
+ renewCompactionSemanticEntryRing(
732
+ coverage,
733
+ typeCoverage.tail,
734
+ entry,
735
+ DERIVED_SEMANTIC_TYPE_EDGE_LIMIT
736
+ );
737
+ }
738
+
739
+ function appendDerivedCompactionSemanticEntry(
740
+ collector: DerivedCompactionSemanticIndexCollector,
741
+ entry: CompactionSemanticIndexEntry
742
+ ): void {
743
+ let boundedEntry = entry;
744
+ if (entry.status === 'pending') {
745
+ boundedEntry = { ...entry, text: '' };
746
+ } else if (
747
+ entry.text.length > COMPACTION_SEMANTIC_INDEX_LIMITS.maxInputTextChars
748
+ ) {
749
+ boundedEntry = { ...entry, text: '', redacted: true };
750
+ }
751
+ const order = collector.entryCount;
752
+ collector.entryCount++;
753
+ if (
754
+ collector.coverage == null &&
755
+ collector.entries.length < COMPACTION_SEMANTIC_INDEX_LIMITS.maxInputEntries
756
+ ) {
757
+ collector.entries.push(boundedEntry);
758
+ return;
759
+ }
760
+ const orderedEntry = {
761
+ entry: boundedEntry,
762
+ identityHash: hashDerivedCompactionSemanticIdentity(boundedEntry),
763
+ order,
764
+ retentionCount: 0,
765
+ };
766
+ if (collector.coverage == null) {
767
+ collector.coverage = createCompactionSemanticCoverageState();
768
+ for (let order = 0; order < collector.entries.length; order++) {
769
+ appendCoverageBalancedCompactionSemanticEntry(collector.coverage, {
770
+ entry: collector.entries[order],
771
+ identityHash: hashDerivedCompactionSemanticIdentity(
772
+ collector.entries[order]
773
+ ),
774
+ order,
775
+ retentionCount: 0,
776
+ });
777
+ }
778
+ collector.entries = [];
779
+ }
780
+ appendCoverageBalancedCompactionSemanticEntry(
781
+ collector.coverage,
782
+ orderedEntry
783
+ );
784
+ }
785
+
786
+ function appendCoverageBalancedCompactionSemanticEntry(
787
+ coverage: CompactionSemanticCoverageState,
788
+ orderedEntry: OrderedCompactionSemanticIndexEntry
789
+ ): void {
790
+ const identityHash = orderedEntry.identityHash;
791
+ const identityBucket = coverage.latestByIdentityHash.get(identityHash);
792
+ const current = identityBucket?.find(({ entry }) =>
793
+ entriesShareDerivedCompactionSemanticIdentity(entry, orderedEntry.entry)
794
+ );
795
+ if (current != null) {
796
+ if (orderedEntry.entry.revision < current.entry.revision) {
797
+ return;
798
+ }
799
+ if (orderedEntry.entry.revision === current.entry.revision) {
800
+ if (
801
+ entriesHaveConflictingSemanticState(current.entry, orderedEntry.entry)
802
+ ) {
803
+ current.entry = { ...current.entry, text: '', redacted: true };
804
+ current.order = orderedEntry.order;
805
+ renewCoverageBalancedCompactionSemanticEntry(coverage, current);
806
+ }
807
+ return;
808
+ }
809
+ current.entry = orderedEntry.entry;
810
+ current.order = orderedEntry.order;
811
+ renewCoverageBalancedCompactionSemanticEntry(coverage, current);
812
+ return;
813
+ }
814
+ if (identityBucket == null) {
815
+ coverage.latestByIdentityHash.set(identityHash, [orderedEntry]);
816
+ } else {
817
+ identityBucket.push(orderedEntry);
818
+ }
819
+ if (coverage.head.length < DERIVED_SEMANTIC_HEAD_LIMIT) {
820
+ coverage.head.push(orderedEntry);
821
+ retainCompactionSemanticEntry(orderedEntry);
822
+ }
823
+ appendCompactionSemanticEntryRing(
824
+ coverage,
825
+ coverage.tail,
826
+ orderedEntry,
827
+ DERIVED_SEMANTIC_TAIL_LIMIT
828
+ );
829
+ let typeCoverage = coverage.typeCoverage.get(orderedEntry.entry.type);
830
+ if (typeCoverage == null) {
831
+ typeCoverage = { head: [], tail: { entries: [], cursor: 0 } };
832
+ coverage.typeCoverage.set(orderedEntry.entry.type, typeCoverage);
833
+ }
834
+ if (typeCoverage.head.length < DERIVED_SEMANTIC_TYPE_EDGE_LIMIT) {
835
+ typeCoverage.head.push(orderedEntry);
836
+ retainCompactionSemanticEntry(orderedEntry);
837
+ }
838
+ appendCompactionSemanticEntryRing(
839
+ coverage,
840
+ typeCoverage.tail,
841
+ orderedEntry,
842
+ DERIVED_SEMANTIC_TYPE_EDGE_LIMIT
843
+ );
844
+ }
845
+
846
+ function finalizeDerivedCompactionSemanticIndex(
847
+ collector: DerivedCompactionSemanticIndexCollector | undefined
848
+ ): CompactionSemanticIndex | undefined {
849
+ if (collector == null || collector.entryCount === 0) {
850
+ return undefined;
851
+ }
852
+ if (collector.coverage == null) {
853
+ setCompactionSemanticIndexProvidedEntryCount(
854
+ collector.entries,
855
+ collector.entryCount
856
+ );
857
+ return collector.entries;
858
+ }
859
+ const retained = new Map<number, CompactionSemanticIndexEntry>();
860
+ const retain = (
861
+ entries: readonly OrderedCompactionSemanticIndexEntry[]
862
+ ): void => {
863
+ for (const { entry, order } of entries) {
864
+ retained.set(order, entry);
865
+ }
866
+ };
867
+ retain(collector.coverage.head);
868
+ retain(collector.coverage.tail.entries);
869
+ for (const typeCoverage of collector.coverage.typeCoverage.values()) {
870
+ retain(typeCoverage.head);
871
+ retain(typeCoverage.tail.entries);
872
+ }
873
+ const result = [...retained.entries()]
874
+ .sort(([left], [right]) => left - right)
875
+ .map(([, entry]) => entry);
876
+ setCompactionSemanticIndexProvidedEntryCount(result, collector.entryCount);
877
+ return result;
878
+ }
879
+
880
+ function collectCompactionSemanticEntriesFromPart(
881
+ collector: DerivedCompactionSemanticIndexCollector,
882
+ part: CompactionSemanticContentPart,
883
+ sourceMessageId: string,
884
+ sourceContentIndex: number,
885
+ retainedSourceContentStart: number,
886
+ retainedSourceContentEnd: number,
887
+ intentToolNames?: ReadonlySet<string>,
888
+ parsedToolInput?: ToolCallPart['args']
889
+ ): void {
890
+ if (
891
+ sourceMessageId.length >
892
+ COMPACTION_SEMANTIC_INDEX_LIMITS.maxIdentityChars
893
+ ) {
894
+ return;
895
+ }
896
+
897
+ if (part.type === ContentTypes.TOOL_CALL) {
898
+ if (
899
+ sourceContentIndex < 0 ||
900
+ sourceContentIndex >
901
+ COMPACTION_SEMANTIC_INDEX_LIMITS.maxSourceContentIndex
902
+ ) {
903
+ return;
904
+ }
905
+ const toolCall = part.tool_call;
906
+ const toolCallId = toolCall?.id;
907
+ if (
908
+ typeof toolCallId !== 'string' ||
909
+ toolCallId === '' ||
910
+ toolCallId.length > COMPACTION_SEMANTIC_INDEX_LIMITS.maxIdentityChars
911
+ ) {
912
+ return;
913
+ }
914
+ if (
915
+ typeof toolCall.name === 'string' &&
916
+ intentToolNames?.has(toolCall.name) === true
917
+ ) {
918
+ const intent =
919
+ parsedToolInput != null &&
920
+ typeof parsedToolInput === 'object' &&
921
+ !Array.isArray(parsedToolInput)
922
+ ? parsedToolInput.intent
923
+ : undefined;
924
+ if (typeof intent === 'string' && intent !== '') {
925
+ appendDerivedCompactionSemanticEntry(collector, {
926
+ type: 'tool_intent',
927
+ sourceMessageId,
928
+ sourceContentIndex,
929
+ revision: 0,
930
+ status: 'committed',
931
+ text: intent,
932
+ toolCallId,
933
+ });
934
+ }
935
+ }
936
+ if (typeof toolCall.outcome === 'string' && toolCall.outcome !== '') {
937
+ appendDerivedCompactionSemanticEntry(collector, {
938
+ type: 'tool_outcome',
939
+ sourceMessageId,
940
+ sourceContentIndex,
941
+ revision: 0,
942
+ status: 'committed',
943
+ text: toolCall.outcome,
944
+ toolCallId,
945
+ });
946
+ }
947
+ return;
948
+ }
949
+
950
+ if (part.type === ContentTypes.THINK) {
951
+ if (
952
+ sourceContentIndex < 0 ||
953
+ sourceContentIndex >
954
+ COMPACTION_SEMANTIC_INDEX_LIMITS.maxSourceContentIndex
955
+ ) {
956
+ return;
957
+ }
958
+ const reasoningStepId = part.reasoning_label_step_id;
959
+ const revision = part.reasoning_label_revision;
960
+ const status = part.reasoning_label_status;
961
+ const text = part.reasoning_label;
962
+ if (
963
+ typeof reasoningStepId !== 'string' ||
964
+ reasoningStepId === '' ||
965
+ reasoningStepId.length >
966
+ COMPACTION_SEMANTIC_INDEX_LIMITS.maxIdentityChars ||
967
+ typeof revision !== 'number' ||
968
+ !Number.isSafeInteger(revision) ||
969
+ revision < 0
970
+ ) {
971
+ return;
972
+ }
973
+ const malformedState =
974
+ (status !== 'complete' && status !== 'streaming') ||
975
+ typeof text !== 'string';
976
+ appendDerivedCompactionSemanticEntry(collector, {
977
+ type: 'reasoning_label',
978
+ sourceMessageId,
979
+ sourceContentIndex,
980
+ revision,
981
+ status:
982
+ status === 'streaming' || malformedState ? 'pending' : 'committed',
983
+ text: typeof text === 'string' ? text : '',
984
+ reasoningStepId,
985
+ ...(malformedState ? { redacted: true } : {}),
986
+ });
987
+ return;
988
+ }
989
+
990
+ if (
991
+ part.type !== ContentTypes.ACTIVITY_LABEL ||
992
+ part.activity_label_type !== 'phase'
993
+ ) {
994
+ return;
995
+ }
996
+ const activitySourceContentIndex = part.activity_start_index;
997
+ if (
998
+ typeof activitySourceContentIndex !== 'number' ||
999
+ !Number.isSafeInteger(activitySourceContentIndex) ||
1000
+ activitySourceContentIndex < retainedSourceContentStart ||
1001
+ activitySourceContentIndex >= retainedSourceContentEnd ||
1002
+ activitySourceContentIndex >
1003
+ COMPACTION_SEMANTIC_INDEX_LIMITS.maxSourceContentIndex
1004
+ ) {
1005
+ return;
1006
+ }
1007
+ const revision = part.activity_label_revision;
1008
+ if (
1009
+ revision !== undefined &&
1010
+ (typeof revision !== 'number' ||
1011
+ !Number.isSafeInteger(revision) ||
1012
+ revision < 0)
1013
+ ) {
1014
+ return;
1015
+ }
1016
+ const malformedPending =
1017
+ part.pending !== undefined && typeof part.pending !== 'boolean';
1018
+ const malformedText = typeof part.activity_label !== 'string';
1019
+ const malformedState = malformedPending || malformedText;
1020
+ const text = typeof part.activity_label === 'string' ? part.activity_label : '';
1021
+ appendDerivedCompactionSemanticEntry(collector, {
1022
+ type: 'activity_phase',
1023
+ sourceMessageId,
1024
+ sourceContentIndex: activitySourceContentIndex,
1025
+ revision: revision ?? 0,
1026
+ status:
1027
+ part.pending === true || malformedPending ? 'pending' : 'committed',
1028
+ text,
1029
+ ...(malformedState ? { redacted: true } : {}),
1030
+ });
1031
+ }
1032
+
479
1033
  function collectTrustedToolResultSourceContentPartIndices(
480
1034
  content: readonly unknown[] | undefined,
481
1035
  sourceContentPartOffset: number
@@ -868,6 +1422,17 @@ function formatAssistantMessage(
868
1422
  >();
869
1423
  const shouldPreserveReasoningContent =
870
1424
  options?.preserveReasoningContent === true;
1425
+ const compactionSemanticIndex = options?.compactionSemanticIndex;
1426
+ const semanticSourceMessageId =
1427
+ compactionSemanticIndex != null &&
1428
+ options?.sourceMessageId != null &&
1429
+ options.sourceMessageId.length <=
1430
+ COMPACTION_SEMANTIC_INDEX_LIMITS.maxIdentityChars
1431
+ ? options.sourceMessageId
1432
+ : undefined;
1433
+ const retainedSourceContentStart = options?.sourceContentPartOffset ?? 0;
1434
+ const retainedSourceContentEnd =
1435
+ options?.retainedSourceContentEnd ?? retainedSourceContentStart;
871
1436
  const serverToolResultIds = new Set<string>();
872
1437
  const preferredToolCallParts = new Map<string, MessageContentComplex>();
873
1438
 
@@ -976,6 +1541,9 @@ function formatAssistantMessage(
976
1541
  if (part == null) {
977
1542
  continue;
978
1543
  }
1544
+ if (part.type === ContentTypes.ACTIVITY_LABEL) {
1545
+ continue;
1546
+ }
979
1547
  const isTrustedResult = isTrustedServerToolResult(
980
1548
  part,
981
1549
  getSourcePartIndices(partIndex),
@@ -1011,6 +1579,24 @@ function formatAssistantMessage(
1011
1579
  continue;
1012
1580
  }
1013
1581
  const sourcePartIndices = getSourcePartIndices(partIndex);
1582
+ if (part.type === ContentTypes.ACTIVITY_LABEL) {
1583
+ if (
1584
+ compactionSemanticIndex != null &&
1585
+ semanticSourceMessageId != null &&
1586
+ typeof sourcePartIndices === 'number'
1587
+ ) {
1588
+ collectCompactionSemanticEntriesFromPart(
1589
+ compactionSemanticIndex,
1590
+ part,
1591
+ semanticSourceMessageId,
1592
+ sourcePartIndices,
1593
+ retainedSourceContentStart,
1594
+ retainedSourceContentEnd,
1595
+ options?.intentToolNames
1596
+ );
1597
+ }
1598
+ continue;
1599
+ }
1014
1600
  const toolUseId = getToolUseId(part);
1015
1601
  if (toolUseId != null) {
1016
1602
  const isServerToolResult =
@@ -1117,12 +1703,29 @@ function formatAssistantMessage(
1117
1703
  ) {
1118
1704
  continue;
1119
1705
  }
1706
+ const serverToolInput = parseServerToolInput(_args);
1707
+ if (
1708
+ compactionSemanticIndex != null &&
1709
+ semanticSourceMessageId != null &&
1710
+ typeof sourcePartIndices === 'number'
1711
+ ) {
1712
+ collectCompactionSemanticEntriesFromPart(
1713
+ compactionSemanticIndex,
1714
+ part,
1715
+ semanticSourceMessageId,
1716
+ sourcePartIndices,
1717
+ retainedSourceContentStart,
1718
+ retainedSourceContentEnd,
1719
+ options.intentToolNames,
1720
+ serverToolInput
1721
+ );
1722
+ }
1120
1723
  pendingServerToolUses.set(_tool_call.id, {
1121
1724
  content: {
1122
1725
  type: 'server_tool_use',
1123
1726
  id: _tool_call.id,
1124
1727
  name: _tool_call.name,
1125
- input: parseServerToolInput(_args),
1728
+ input: serverToolInput,
1126
1729
  } as MessageContentComplex,
1127
1730
  sourceContentPartIndices: sourcePartIndices,
1128
1731
  });
@@ -1154,6 +1757,22 @@ function formatAssistantMessage(
1154
1757
  }
1155
1758
 
1156
1759
  tool_call.args = args;
1760
+ if (
1761
+ compactionSemanticIndex != null &&
1762
+ semanticSourceMessageId != null &&
1763
+ typeof sourcePartIndices === 'number'
1764
+ ) {
1765
+ collectCompactionSemanticEntriesFromPart(
1766
+ compactionSemanticIndex,
1767
+ part,
1768
+ semanticSourceMessageId,
1769
+ sourcePartIndices,
1770
+ retainedSourceContentStart,
1771
+ retainedSourceContentEnd,
1772
+ options?.intentToolNames,
1773
+ args
1774
+ );
1775
+ }
1157
1776
  if (
1158
1777
  options?.provider === Providers.ANTHROPIC &&
1159
1778
  Array.isArray(lastAIMessage.content)
@@ -1192,6 +1811,22 @@ function formatAssistantMessage(
1192
1811
  part.type === ContentTypes.REASONING_CONTENT ||
1193
1812
  part.type === 'redacted_thinking'
1194
1813
  ) {
1814
+ if (
1815
+ part.type === ContentTypes.THINK &&
1816
+ compactionSemanticIndex != null &&
1817
+ semanticSourceMessageId != null &&
1818
+ typeof sourcePartIndices === 'number'
1819
+ ) {
1820
+ collectCompactionSemanticEntriesFromPart(
1821
+ compactionSemanticIndex,
1822
+ part,
1823
+ semanticSourceMessageId,
1824
+ sourcePartIndices,
1825
+ retainedSourceContentStart,
1826
+ retainedSourceContentEnd,
1827
+ options?.intentToolNames
1828
+ );
1829
+ }
1195
1830
  hasReasoning = true;
1196
1831
  pendingReasoningContent += extractReasoningContent(part);
1197
1832
  appendSourceContentPartIndices(
@@ -1270,8 +1905,7 @@ function formatAssistantMessage(
1270
1905
  } else if (
1271
1906
  part.type === ContentTypes.ERROR ||
1272
1907
  part.type === ContentTypes.AGENT_UPDATE ||
1273
- part.type === ContentTypes.SUMMARY ||
1274
- part.type === ContentTypes.ACTIVITY_LABEL
1908
+ part.type === ContentTypes.SUMMARY
1275
1909
  ) {
1276
1910
  continue;
1277
1911
  } else {
@@ -1991,6 +2625,8 @@ export const formatAgentMessages = (
1991
2625
  /** When a positional summary boundary sliced content from a message, the token
1992
2626
  * count was proportionally reduced. Returned so the caller can log it. */
1993
2627
  boundaryTokenAdjustment?: SummaryTokenAdjustment;
2628
+ /** Bounded semantic guidance derived during persisted-content analysis. */
2629
+ compactionSemanticIndex?: CompactionSemanticIndex;
1994
2630
  } => {
1995
2631
  const messages: Array<
1996
2632
  | RoleBearingMessage<HumanMessage>
@@ -2005,6 +2641,10 @@ export const formatAgentMessages = (
2005
2641
  * nothing cannot leave the anchor stranded as the final turn.
2006
2642
  */
2007
2643
  const legacyContentEnabled = options?.legacyContent === true;
2644
+ const compactionSemanticIndexCollector =
2645
+ options?.compactionSemanticIndex != null
2646
+ ? createDerivedCompactionSemanticIndexCollector()
2647
+ : undefined;
2008
2648
  /** Emission choke point: every formatted message enters the result here, so
2009
2649
  * the legacy flatten happens once per message with no closing rescan. The
2010
2650
  * summary boundary slices payload entries before formatting, and nothing
@@ -2334,11 +2974,16 @@ export const formatAgentMessages = (
2334
2974
  }
2335
2975
 
2336
2976
  const formattedMessages = formatAssistantMessage(processedMessage, {
2977
+ compactionSemanticIndex: compactionSemanticIndexCollector,
2978
+ intentToolNames: options?.compactionSemanticIndex?.intentToolNames,
2337
2979
  preserveUnpairedServerToolUses: i === payload.length - 1,
2338
2980
  preserveReasoningContent:
2339
2981
  options?.preserveReasoningContent ??
2340
2982
  options?.provider === Providers.DEEPSEEK,
2341
2983
  provider: options?.provider,
2984
+ retainedSourceContentEnd:
2985
+ sourceContentPartOffset +
2986
+ (Array.isArray(message.content) ? message.content.length : 0),
2342
2987
  sourceMessageId,
2343
2988
  sourceContentPartOffset,
2344
2989
  sourceContentPartIndices: processedSourceContentPartIndices,
@@ -2610,6 +3255,9 @@ export const formatAgentMessages = (
2610
3255
  ? { text: summaryBoundary.text, tokenCount: summaryBoundary.tokenCount }
2611
3256
  : undefined,
2612
3257
  boundaryTokenAdjustment,
3258
+ compactionSemanticIndex: finalizeDerivedCompactionSemanticIndex(
3259
+ compactionSemanticIndexCollector
3260
+ ),
2613
3261
  };
2614
3262
  };
2615
3263