@librechat/agents 3.7.0 → 3.7.1

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 (87) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +11 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +7 -2
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/llm/invoke.cjs +56 -9
  6. package/dist/cjs/llm/invoke.cjs.map +1 -1
  7. package/dist/cjs/main.cjs +11 -1
  8. package/dist/cjs/messages/content.cjs +8 -5
  9. package/dist/cjs/messages/content.cjs.map +1 -1
  10. package/dist/cjs/messages/format.cjs +15 -5
  11. package/dist/cjs/messages/format.cjs.map +1 -1
  12. package/dist/cjs/messages/index.cjs +2 -1
  13. package/dist/cjs/messages/projectionInvariant.cjs +74 -0
  14. package/dist/cjs/messages/projectionInvariant.cjs.map +1 -0
  15. package/dist/cjs/messages/provenance.cjs +30 -8
  16. package/dist/cjs/messages/provenance.cjs.map +1 -1
  17. package/dist/cjs/messages/recency.cjs +202 -7
  18. package/dist/cjs/messages/recency.cjs.map +1 -1
  19. package/dist/cjs/messages/toolResultTypes.cjs +1 -0
  20. package/dist/cjs/session/AgentSession.cjs +4 -23
  21. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  22. package/dist/cjs/session/deriveMessages.cjs +25 -0
  23. package/dist/cjs/session/deriveMessages.cjs.map +1 -0
  24. package/dist/cjs/session/index.cjs +1 -0
  25. package/dist/cjs/summarization/node.cjs +13 -6
  26. package/dist/cjs/summarization/node.cjs.map +1 -1
  27. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +65 -8
  28. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -1
  29. package/dist/esm/agents/AgentContext.mjs +11 -2
  30. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  31. package/dist/esm/graphs/Graph.mjs +8 -3
  32. package/dist/esm/graphs/Graph.mjs.map +1 -1
  33. package/dist/esm/llm/invoke.mjs +56 -9
  34. package/dist/esm/llm/invoke.mjs.map +1 -1
  35. package/dist/esm/main.mjs +6 -4
  36. package/dist/esm/messages/content.mjs +8 -6
  37. package/dist/esm/messages/content.mjs.map +1 -1
  38. package/dist/esm/messages/format.mjs +16 -6
  39. package/dist/esm/messages/format.mjs.map +1 -1
  40. package/dist/esm/messages/index.mjs +2 -1
  41. package/dist/esm/messages/projectionInvariant.mjs +72 -0
  42. package/dist/esm/messages/projectionInvariant.mjs.map +1 -0
  43. package/dist/esm/messages/provenance.mjs +30 -9
  44. package/dist/esm/messages/provenance.mjs.map +1 -1
  45. package/dist/esm/messages/recency.mjs +201 -8
  46. package/dist/esm/messages/recency.mjs.map +1 -1
  47. package/dist/esm/messages/toolResultTypes.mjs +1 -1
  48. package/dist/esm/session/AgentSession.mjs +4 -23
  49. package/dist/esm/session/AgentSession.mjs.map +1 -1
  50. package/dist/esm/session/deriveMessages.mjs +25 -0
  51. package/dist/esm/session/deriveMessages.mjs.map +1 -0
  52. package/dist/esm/session/index.mjs +1 -0
  53. package/dist/esm/summarization/node.mjs +14 -7
  54. package/dist/esm/summarization/node.mjs.map +1 -1
  55. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +65 -8
  56. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -1
  57. package/dist/types/agents/AgentContext.d.ts +6 -1
  58. package/dist/types/messages/content.d.ts +4 -1
  59. package/dist/types/messages/format.d.ts +6 -0
  60. package/dist/types/messages/index.d.ts +1 -0
  61. package/dist/types/messages/projectionInvariant.d.ts +25 -0
  62. package/dist/types/messages/provenance.d.ts +10 -0
  63. package/dist/types/messages/recency.d.ts +30 -18
  64. package/dist/types/session/deriveMessages.d.ts +11 -0
  65. package/dist/types/session/index.d.ts +2 -0
  66. package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +13 -1
  67. package/dist/types/types/graph.d.ts +1 -1
  68. package/dist/types/types/subagentTasks.d.ts +22 -0
  69. package/dist/types/types/summarize.d.ts +15 -13
  70. package/package.json +3 -1
  71. package/src/agents/AgentContext.ts +19 -5
  72. package/src/graphs/Graph.ts +9 -1
  73. package/src/llm/invoke.ts +102 -23
  74. package/src/messages/content.ts +20 -10
  75. package/src/messages/format.ts +29 -5
  76. package/src/messages/index.ts +1 -0
  77. package/src/messages/projectionInvariant.ts +134 -0
  78. package/src/messages/provenance.ts +60 -18
  79. package/src/messages/recency.ts +429 -27
  80. package/src/session/AgentSession.ts +4 -30
  81. package/src/session/deriveMessages.ts +37 -0
  82. package/src/session/index.ts +2 -0
  83. package/src/summarization/node.ts +37 -15
  84. package/src/tools/subagent/InMemorySubagentTaskStore.ts +139 -6
  85. package/src/types/graph.ts +1 -0
  86. package/src/types/subagentTasks.ts +28 -0
  87. package/src/types/summarize.ts +15 -13
@@ -46,6 +46,7 @@ import {
46
46
  hasBijectiveProviderContentPartMapping,
47
47
  inspectProviderMessageProvenance,
48
48
  inspectProviderSourceMessageIds,
49
+ setFreshProviderMessageProvenance,
49
50
  setInvalidProviderMessageProvenance,
50
51
  setProviderMessageProvenance,
51
52
  } from './provenance';
@@ -57,6 +58,7 @@ import {
57
58
  } from '@/utils/toolContent';
58
59
  import { normalizeAnthropicToolCallId } from '@/llm/anthropic/utils/message_inputs';
59
60
  import { toLangChainContent, toLangChainMessageFields } from './langchain';
61
+ import { flattenLegacyContent, isLegacyConvertible } from './content';
60
62
  import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
61
63
  import { Providers, ContentTypes, Constants } from '@/common';
62
64
  import { emitAgentLog } from '@/utils/events';
@@ -378,6 +380,12 @@ type SourceContentPartIndices = number | readonly number[];
378
380
 
379
381
  interface FormatAgentMessagesOptions {
380
382
  provider?: ProviderName;
383
+ /** Emit flattenable text content as the joined string the legacy-content
384
+ * projection would produce, so the per-request `formatContentStrings` pass
385
+ * finds nothing to convert and every history message keeps its identity —
386
+ * which is what lets exact-count reuse skip re-tokenizing it. Set this if
387
+ * and only if the run's provider uses legacy string content. */
388
+ legacyContent?: boolean;
381
389
  /** Reconstruct hidden `reasoning_content` from `THINK` parts onto prior
382
390
  * tool-call messages. Explicit opt-in for OpenAI-compatible endpoints that
383
391
  * replay reasoning across turns; defaults to on for DeepSeek thinking-mode. */
@@ -1380,7 +1388,7 @@ function stampSourceMessageIdentity(
1380
1388
  },
1381
1389
  ];
1382
1390
  }
1383
- setProviderMessageProvenance(message, partsToStamp);
1391
+ setFreshProviderMessageProvenance(message, partsToStamp);
1384
1392
  if (sourceMessageId == null || derivedIndex !== 0) {
1385
1393
  return;
1386
1394
  }
@@ -1996,6 +2004,22 @@ export const formatAgentMessages = (
1996
2004
  * assistant turn. Held rather than emitted so an entry that produces
1997
2005
  * nothing cannot leave the anchor stranded as the final turn.
1998
2006
  */
2007
+ const legacyContentEnabled = options?.legacyContent === true;
2008
+ /** Emission choke point: every formatted message enters the result here, so
2009
+ * the legacy flatten happens once per message with no closing rescan. The
2010
+ * summary boundary slices payload entries before formatting, and nothing
2011
+ * mutates an emitted message's content afterwards, so flattening at
2012
+ * emission and flattening at return are equivalent. */
2013
+ const emitFormattedMessage = (message: (typeof messages)[number]): void => {
2014
+ if (legacyContentEnabled && isLegacyConvertible(message)) {
2015
+ const flattened = flattenLegacyContent(
2016
+ message.content as MessageContentComplex[]
2017
+ );
2018
+ message.content = flattened;
2019
+ message.lc_kwargs.content = flattened;
2020
+ }
2021
+ messages.push(message);
2022
+ };
1999
2023
  let pendingSteerAnchor = false;
2000
2024
  /**
2001
2025
  * Emits the deferred anchor ahead of `next` — the message about to be
@@ -2018,7 +2042,7 @@ export const formatAgentMessages = (
2018
2042
  'assistant'
2019
2043
  );
2020
2044
  stampSourceMessageIdentity(anchor, undefined, 0, 'synthetic');
2021
- messages.push(anchor);
2045
+ emitFormattedMessage(anchor);
2022
2046
  };
2023
2047
  // If indexTokenCountMap is provided, create a new map to track the updated indices
2024
2048
  const updatedIndexTokenCountMap: Record<number, number> = {};
@@ -2098,7 +2122,7 @@ export const formatAgentMessages = (
2098
2122
  provenanceParts
2099
2123
  );
2100
2124
  flushSteerAnchor(formattedMessage);
2101
- messages.push(formattedMessage);
2125
+ emitFormattedMessage(formattedMessage);
2102
2126
 
2103
2127
  // Update the index mapping for this message
2104
2128
  indexMapping[i] = [messages.length - 1];
@@ -2359,7 +2383,7 @@ export const formatAgentMessages = (
2359
2383
  flushSteerAnchor(formattedMessages[0]);
2360
2384
  }
2361
2385
  for (const formattedMessage of formattedMessages) {
2362
- messages.push(formattedMessage);
2386
+ emitFormattedMessage(formattedMessage);
2363
2387
  }
2364
2388
  if (endsWithSteerMessage(formattedMessages)) {
2365
2389
  pendingSteerAnchor = true;
@@ -2390,7 +2414,7 @@ export const formatAgentMessages = (
2390
2414
  'user'
2391
2415
  );
2392
2416
  stampSourceMessageIdentity(skillMessage, undefined, 0, 'synthetic');
2393
- messages.push(skillMessage);
2417
+ emitFormattedMessage(skillMessage);
2394
2418
  }
2395
2419
  }
2396
2420
  }
@@ -16,3 +16,4 @@ export * from './reducer';
16
16
  export * from './recency';
17
17
  export * from './assistantPhase';
18
18
  export * from './provenance';
19
+ export * from './projectionInvariant';
@@ -0,0 +1,134 @@
1
+ import type { BaseMessage } from '@langchain/core/messages';
2
+ import { inspectProviderMessageProvenance } from './provenance';
3
+
4
+ export type ProviderMessageProjectionInvariantMode =
5
+ | 'off'
6
+ | 'observe'
7
+ | 'assert';
8
+
9
+ export type ProviderMessageProjectionIssueCode =
10
+ | 'absent_provenance'
11
+ | 'invalid_provenance'
12
+ | 'unsourced_non_synthetic_part';
13
+
14
+ export interface ProviderMessageProjectionInvariantIssue {
15
+ readonly code: ProviderMessageProjectionIssueCode;
16
+ readonly messageIndex: number;
17
+ readonly messageType: string;
18
+ }
19
+
20
+ export interface ProviderMessageProjectionInvariantReport {
21
+ readonly valid: boolean;
22
+ readonly messageCount: number;
23
+ readonly sourceBackedMessageCount: number;
24
+ readonly syntheticMessageCount: number;
25
+ readonly gapMessageCount: number;
26
+ readonly issues: readonly ProviderMessageProjectionInvariantIssue[];
27
+ }
28
+
29
+ const MAX_REPORTED_PROJECTION_ISSUES = 64;
30
+
31
+ function getMessageType(message: BaseMessage): string {
32
+ try {
33
+ return typeof message.type === 'string' ? message.type : 'unknown';
34
+ } catch {
35
+ return 'unknown';
36
+ }
37
+ }
38
+
39
+ function appendIssue(
40
+ issues: ProviderMessageProjectionInvariantIssue[],
41
+ code: ProviderMessageProjectionIssueCode,
42
+ messageIndex: number,
43
+ messageType: string
44
+ ): void {
45
+ if (issues.length >= MAX_REPORTED_PROJECTION_ISSUES) {
46
+ return;
47
+ }
48
+ issues.push(Object.freeze({ code, messageIndex, messageType }));
49
+ }
50
+
51
+ /** Resolves the opt-in provider projection invariant without enabling it for
52
+ * unrecognized values. */
53
+ export function resolveProviderMessageProjectionInvariantMode(
54
+ value = process.env.AGENT_MESSAGE_PROJECTION_INVARIANT
55
+ ): ProviderMessageProjectionInvariantMode {
56
+ if (value === 'observe' || value === 'assert') {
57
+ return value;
58
+ }
59
+ return 'off';
60
+ }
61
+
62
+ /** Inspects provider-bound lineage without reading message content or source ids. */
63
+ export function inspectProviderMessageProjection(
64
+ messages: readonly BaseMessage[]
65
+ ): ProviderMessageProjectionInvariantReport {
66
+ const issues: ProviderMessageProjectionInvariantIssue[] = [];
67
+ let sourceBackedMessageCount = 0;
68
+ let syntheticMessageCount = 0;
69
+ let gapMessageCount = 0;
70
+
71
+ for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
72
+ const message = messages[messageIndex];
73
+ const messageType = getMessageType(message);
74
+ const state = inspectProviderMessageProvenance(message);
75
+ if (state.status !== 'valid') {
76
+ gapMessageCount++;
77
+ appendIssue(
78
+ issues,
79
+ state.status === 'absent'
80
+ ? 'absent_provenance'
81
+ : 'invalid_provenance',
82
+ messageIndex,
83
+ messageType
84
+ );
85
+ continue;
86
+ }
87
+
88
+ let hasSourceBackedPart = false;
89
+ let hasGap = false;
90
+ for (const part of state.provenance.parts) {
91
+ if (part.sourceMessageId != null) {
92
+ hasSourceBackedPart = true;
93
+ continue;
94
+ }
95
+ if (part.attribution === 'synthetic') {
96
+ continue;
97
+ }
98
+ hasGap = true;
99
+ appendIssue(
100
+ issues,
101
+ 'unsourced_non_synthetic_part',
102
+ messageIndex,
103
+ messageType
104
+ );
105
+ }
106
+
107
+ if (hasGap) {
108
+ gapMessageCount++;
109
+ } else if (hasSourceBackedPart) {
110
+ sourceBackedMessageCount++;
111
+ } else {
112
+ syntheticMessageCount++;
113
+ }
114
+ }
115
+
116
+ return Object.freeze({
117
+ valid: gapMessageCount === 0,
118
+ messageCount: messages.length,
119
+ sourceBackedMessageCount,
120
+ syntheticMessageCount,
121
+ gapMessageCount,
122
+ issues: Object.freeze(issues),
123
+ });
124
+ }
125
+
126
+ export class ProviderMessageProjectionInvariantError extends Error {
127
+ readonly report: ProviderMessageProjectionInvariantReport;
128
+
129
+ constructor(report: ProviderMessageProjectionInvariantReport) {
130
+ super('Provider message projection invariant failed');
131
+ this.name = 'ProviderMessageProjectionInvariantError';
132
+ this.report = report;
133
+ }
134
+ }
@@ -766,11 +766,14 @@ function publishProviderMessageProvenance(
766
766
  }
767
767
  }
768
768
 
769
- /** Replaces typed provenance and synchronizes its stable plural source ids. */
770
- export function setProviderMessageProvenance(
771
- message: BaseMessage,
769
+ interface ResolvedProvenancePublication {
770
+ provenance: ProviderMessageProvenance;
771
+ sourceMessageIds?: readonly string[];
772
+ }
773
+
774
+ function resolveProvenancePublication(
772
775
  parts: readonly ProviderMessageProvenancePart[]
773
- ): void {
776
+ ): ResolvedProvenancePublication {
774
777
  const normalizedParts = normalizeProvenanceParts(parts);
775
778
  const provenance: ProviderMessageProvenance = Object.freeze({
776
779
  version: PROVIDER_MESSAGE_PROVENANCE_VERSION,
@@ -782,21 +785,60 @@ export function setProviderMessageProvenance(
782
785
  for (const part of normalizedParts) {
783
786
  appendSourceMessageId(sourceMessageIds, seen, part.sourceMessageId);
784
787
  }
785
- if (sourceMessageIds.length > 0) {
786
- const immutableSourceMessageIds = Object.freeze(sourceMessageIds);
787
- immutableProviderSourceMessageIds.add(immutableSourceMessageIds);
788
- immutableProviderMessageSourceIds.set(
789
- provenance,
790
- immutableSourceMessageIds
791
- );
792
- publishProviderMessageProvenance(
793
- message,
794
- provenance,
795
- immutableSourceMessageIds
796
- );
797
- return;
788
+ if (sourceMessageIds.length === 0) {
789
+ return { provenance };
798
790
  }
799
- publishProviderMessageProvenance(message, provenance);
791
+ const immutableSourceMessageIds = Object.freeze(sourceMessageIds);
792
+ immutableProviderSourceMessageIds.add(immutableSourceMessageIds);
793
+ immutableProviderMessageSourceIds.set(provenance, immutableSourceMessageIds);
794
+ return { provenance, sourceMessageIds: immutableSourceMessageIds };
795
+ }
796
+
797
+ /** Replaces typed provenance and synchronizes its stable plural source ids. */
798
+ export function setProviderMessageProvenance(
799
+ message: BaseMessage,
800
+ parts: readonly ProviderMessageProvenancePart[]
801
+ ): void {
802
+ const resolved = resolveProvenancePublication(parts);
803
+ publishProviderMessageProvenance(
804
+ message,
805
+ resolved.provenance,
806
+ resolved.sourceMessageIds
807
+ );
808
+ }
809
+
810
+ /**
811
+ * {@link setProviderMessageProvenance} for a message the caller itself just
812
+ * constructed from locally built plain objects. Such a message cannot carry
813
+ * proxies, accessors, or foreign aliases in `additional_kwargs`/`lc_kwargs`,
814
+ * so the publication skips the hardened descriptor walks while producing the
815
+ * same end state: fresh replacement objects on both slots, with the
816
+ * serialization mirror aliasing the live kwargs. Never call this with a
817
+ * message received across a seam — that is what the hardened variant is for.
818
+ */
819
+ export function setFreshProviderMessageProvenance(
820
+ message: BaseMessage,
821
+ parts: readonly ProviderMessageProvenancePart[]
822
+ ): void {
823
+ const resolved = resolveProvenancePublication(parts);
824
+ const replacement: UntrustedProviderMessageAdditionalKwargs = {
825
+ ...message.additional_kwargs,
826
+ };
827
+ replacement.provenance = resolved.provenance;
828
+ const sourceMessageIds = resolved.sourceMessageIds;
829
+ if (sourceMessageIds != null && sourceMessageIds.length > 0) {
830
+ replacement.sourceMessageIds = sourceMessageIds;
831
+ replacement.sourceMessageId = sourceMessageIds[sourceMessageIds.length - 1];
832
+ } else {
833
+ delete replacement.sourceMessageIds;
834
+ delete replacement.sourceMessageId;
835
+ }
836
+ const serializedReplacement: BaseMessage['lc_kwargs'] = {
837
+ ...message.lc_kwargs,
838
+ };
839
+ serializedReplacement.additional_kwargs = replacement;
840
+ message.additional_kwargs = replacement;
841
+ message.lc_kwargs = serializedReplacement;
800
842
  }
801
843
 
802
844
  /** Publishes the canonical fail-closed marker for malformed provenance. */