@librechat/agents 3.7.15 → 3.7.16

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 (120) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +91 -0
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/agents/projection.cjs +2 -1
  4. package/dist/cjs/agents/projection.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +47 -6
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +20 -7
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +1 -1
  10. package/dist/cjs/llm/openai/utils/index.cjs +1 -1
  11. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  12. package/dist/cjs/llm/preempt.cjs +1 -10
  13. package/dist/cjs/llm/preempt.cjs.map +1 -1
  14. package/dist/cjs/main.cjs +23 -3
  15. package/dist/cjs/messages/contextPruning.cjs +1 -1
  16. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  17. package/dist/cjs/messages/core.cjs +29 -1
  18. package/dist/cjs/messages/core.cjs.map +1 -1
  19. package/dist/cjs/messages/fading.cjs +122 -0
  20. package/dist/cjs/messages/fading.cjs.map +1 -0
  21. package/dist/cjs/messages/format.cjs +1 -1
  22. package/dist/cjs/messages/index.cjs +1 -0
  23. package/dist/cjs/messages/prune.cjs +364 -236
  24. package/dist/cjs/messages/prune.cjs.map +1 -1
  25. package/dist/cjs/run.cjs +27 -0
  26. package/dist/cjs/run.cjs.map +1 -1
  27. package/dist/cjs/session/AgentSession.cjs +213 -8
  28. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  29. package/dist/cjs/session/JsonlSessionStore.cjs +41 -0
  30. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
  31. package/dist/cjs/session/index.cjs +1 -1
  32. package/dist/cjs/stream.cjs +1 -1
  33. package/dist/cjs/summarization/node.cjs +1 -1
  34. package/dist/cjs/tools/ToolNode.cjs +1 -1
  35. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +2 -0
  36. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  37. package/dist/cjs/tools/subagent/SubagentReplay.cjs +2 -1
  38. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  39. package/dist/cjs/utils/index.cjs +1 -1
  40. package/dist/cjs/utils/truncation.cjs +9 -0
  41. package/dist/cjs/utils/truncation.cjs.map +1 -1
  42. package/dist/esm/agents/AgentContext.mjs +92 -1
  43. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  44. package/dist/esm/agents/projection.mjs +2 -1
  45. package/dist/esm/agents/projection.mjs.map +1 -1
  46. package/dist/esm/graphs/Graph.mjs +47 -6
  47. package/dist/esm/graphs/Graph.mjs.map +1 -1
  48. package/dist/esm/graphs/MultiAgentGraph.mjs +20 -7
  49. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  50. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +1 -1
  51. package/dist/esm/llm/openai/utils/index.mjs +2 -2
  52. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  53. package/dist/esm/llm/preempt.mjs +1 -10
  54. package/dist/esm/llm/preempt.mjs.map +1 -1
  55. package/dist/esm/main.mjs +6 -5
  56. package/dist/esm/messages/contextPruning.mjs +1 -1
  57. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  58. package/dist/esm/messages/core.mjs +29 -2
  59. package/dist/esm/messages/core.mjs.map +1 -1
  60. package/dist/esm/messages/fading.mjs +108 -0
  61. package/dist/esm/messages/fading.mjs.map +1 -0
  62. package/dist/esm/messages/format.mjs +1 -1
  63. package/dist/esm/messages/index.mjs +1 -0
  64. package/dist/esm/messages/prune.mjs +363 -235
  65. package/dist/esm/messages/prune.mjs.map +1 -1
  66. package/dist/esm/run.mjs +27 -0
  67. package/dist/esm/run.mjs.map +1 -1
  68. package/dist/esm/session/AgentSession.mjs +213 -8
  69. package/dist/esm/session/AgentSession.mjs.map +1 -1
  70. package/dist/esm/session/JsonlSessionStore.mjs +41 -0
  71. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
  72. package/dist/esm/session/index.mjs +1 -1
  73. package/dist/esm/stream.mjs +1 -1
  74. package/dist/esm/summarization/node.mjs +1 -1
  75. package/dist/esm/tools/ToolNode.mjs +1 -1
  76. package/dist/esm/tools/subagent/SubagentExecutor.mjs +2 -0
  77. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  78. package/dist/esm/tools/subagent/SubagentReplay.mjs +2 -1
  79. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  80. package/dist/esm/utils/index.mjs +1 -1
  81. package/dist/esm/utils/truncation.mjs +7 -1
  82. package/dist/esm/utils/truncation.mjs.map +1 -1
  83. package/dist/types/agents/AgentContext.d.ts +32 -1
  84. package/dist/types/agents/projection.d.ts +3 -1
  85. package/dist/types/graphs/Graph.d.ts +14 -1
  86. package/dist/types/graphs/MultiAgentGraph.d.ts +7 -0
  87. package/dist/types/messages/contextPruning.d.ts +2 -0
  88. package/dist/types/messages/core.d.ts +2 -0
  89. package/dist/types/messages/fading.d.ts +81 -0
  90. package/dist/types/messages/index.d.ts +1 -0
  91. package/dist/types/messages/prune.d.ts +71 -24
  92. package/dist/types/run.d.ts +16 -0
  93. package/dist/types/session/AgentSession.d.ts +15 -0
  94. package/dist/types/session/JsonlSessionStore.d.ts +4 -1
  95. package/dist/types/session/types.d.ts +9 -0
  96. package/dist/types/tools/subagent/SubagentReplay.d.ts +3 -1
  97. package/dist/types/types/graph.d.ts +23 -0
  98. package/dist/types/types/run.d.ts +10 -0
  99. package/dist/types/utils/truncation.d.ts +9 -0
  100. package/package.json +1 -1
  101. package/src/agents/AgentContext.ts +175 -1
  102. package/src/agents/projection.ts +4 -0
  103. package/src/graphs/Graph.ts +97 -11
  104. package/src/graphs/MultiAgentGraph.ts +54 -6
  105. package/src/llm/openai/utils/index.ts +3 -1
  106. package/src/llm/preempt.ts +4 -17
  107. package/src/messages/contextPruning.ts +7 -1
  108. package/src/messages/core.ts +52 -0
  109. package/src/messages/fading.ts +301 -0
  110. package/src/messages/index.ts +1 -0
  111. package/src/messages/prune.ts +719 -507
  112. package/src/run.ts +52 -0
  113. package/src/session/AgentSession.ts +397 -8
  114. package/src/session/JsonlSessionStore.ts +71 -0
  115. package/src/session/types.ts +10 -0
  116. package/src/tools/subagent/SubagentExecutor.ts +6 -0
  117. package/src/tools/subagent/SubagentReplay.ts +15 -2
  118. package/src/types/graph.ts +25 -0
  119. package/src/types/run.ts +10 -0
  120. package/src/utils/truncation.ts +25 -0
@@ -0,0 +1,301 @@
1
+ import type { FadingTier } from '@/types/graph';
2
+ import {
3
+ calculateMaxToolCallInputChars,
4
+ calculateMaxToolResultChars,
5
+ } from '@/utils/truncation';
6
+
7
+ export const FADING_TIER_VERSION = 1;
8
+
9
+ /** Context pressure at which observation masking activates. */
10
+ export const PRESSURE_THRESHOLD_MASKING = 0.8;
11
+
12
+ /** Smallest token budget the ladder can shrink to; keeps the emergency floor near 200 chars. */
13
+ export const FADING_MIN_BUDGET_TOKENS = 170;
14
+
15
+ /** Floor for masked (consumed) tool results. */
16
+ export const MASKED_RESULT_MIN_CHARS = 300;
17
+
18
+ /** Fraction of a fresh result's cap that a masked (consumed) result keeps. */
19
+ const MASKED_RESULT_CAP_RATIO = 0.1;
20
+
21
+ /**
22
+ * Pressure bands expressed as extra rungs on top of the fit rung, so the
23
+ * budget factors land at ×0.5 (85 %), ×0.25 (90 %) and ×0.0625 (99 %).
24
+ */
25
+ const PRESSURE_BAND_RUNGS: readonly (readonly [number, number])[] = [
26
+ [0.99, 4],
27
+ [0.9, 2],
28
+ [0.85, 1],
29
+ ];
30
+
31
+ export type FadingSignals = {
32
+ /** calibratedTotal / pruningBudget, measured before any truncation. */
33
+ contextPressure: number;
34
+ /** (pruningBudget − instruction tokens) ÷ calibrationRatio, in raw token space. */
35
+ effectiveRawTokens: number;
36
+ summarizationEnabled: boolean;
37
+ /** Largest number of parallel calls observed in one assistant exchange. */
38
+ toolExchangeWidth?: number;
39
+ /** Recovery paths force at least this rung on the current window's ladder. */
40
+ minRung?: number;
41
+ };
42
+
43
+ export type FadingCaps = {
44
+ budgetTokens: number;
45
+ /** Cap for tool results the model has not answered yet. */
46
+ resultChars: number;
47
+ /** Cap for consumed tool results; equals `resultChars` until masking activates. */
48
+ consumedChars: number;
49
+ /** Cap for historical tool-call inputs. */
50
+ inputChars: number;
51
+ };
52
+
53
+ function floorBudgetTokens(window: number): number {
54
+ return Math.min(FADING_MIN_BUDGET_TOKENS, window);
55
+ }
56
+
57
+ /** Tier for a conversation that has never faded: the whole window, nothing masked. */
58
+ export function createFadingTier(window: number): FadingTier {
59
+ return { v: FADING_TIER_VERSION, budgetTokens: window, masked: false };
60
+ }
61
+
62
+ export function isFadingTier(value: unknown): value is FadingTier {
63
+ if (typeof value !== 'object' || value === null) {
64
+ return false;
65
+ }
66
+ const { v, budgetTokens, masked, latched } = value as Partial<
67
+ Record<keyof FadingTier, unknown>
68
+ >;
69
+ return (
70
+ v === FADING_TIER_VERSION &&
71
+ typeof budgetTokens === 'number' &&
72
+ Number.isFinite(budgetTokens) &&
73
+ budgetTokens > 0 &&
74
+ typeof masked === 'boolean' &&
75
+ (latched === undefined || latched === true)
76
+ );
77
+ }
78
+
79
+ /** Deepest rung for a window: the point where the budget reaches its floor. */
80
+ export function maxFadingRung(window: number): number {
81
+ const floor = floorBudgetTokens(window);
82
+ if (!(floor > 0)) {
83
+ return 0;
84
+ }
85
+ return Math.max(0, Math.ceil(Math.log2(window / floor)));
86
+ }
87
+
88
+ /** Token budget at a rung: the window halved per rung, never below the floor. */
89
+ export function fadingBudgetTokens(window: number, rung: number): number {
90
+ return Math.max(floorBudgetTokens(window), Math.floor(window / 2 ** rung));
91
+ }
92
+
93
+ /**
94
+ * Restores a tier persisted by the host. The budget is absolute, so a tier
95
+ * survives a mid-run budget correction and the return to the normal window
96
+ * on the next run; it is only clamped to the current window. Anything
97
+ * invalid starts fresh.
98
+ */
99
+ export function seedFadingTier(window: number, seed?: unknown): FadingTier {
100
+ if (!isFadingTier(seed)) {
101
+ return createFadingTier(window);
102
+ }
103
+ /** The ladder never produces a budget below its floor, so a persisted one
104
+ * is corrupt; clamping it up keeps every cap positive. */
105
+ const budgetTokens = Math.max(
106
+ floorBudgetTokens(window),
107
+ Math.min(seed.budgetTokens, window)
108
+ );
109
+ /** Only a tier that was tightened, masked, or clamped carries information.
110
+ * A fresh tier re-seeded through a pruner rebuild must stay fresh, or it
111
+ * would be reported and pin later runs to this window. */
112
+ const informative =
113
+ seed.latched === true ||
114
+ seed.masked ||
115
+ budgetTokens !== seed.budgetTokens ||
116
+ budgetTokens < window;
117
+ if (!informative) {
118
+ return createFadingTier(window);
119
+ }
120
+ return {
121
+ v: FADING_TIER_VERSION,
122
+ budgetTokens,
123
+ masked: seed.masked,
124
+ latched: true,
125
+ };
126
+ }
127
+
128
+ /** Shallowest rung whose fresh-result cap is at most `targetChars`. */
129
+ export function fadingRungForResultChars(
130
+ window: number,
131
+ targetChars: number,
132
+ maxToolResultChars?: number
133
+ ): number {
134
+ const deepest = maxFadingRung(window);
135
+ for (let rung = 0; rung < deepest; rung++) {
136
+ const windowResultChars = calculateMaxToolResultChars(
137
+ fadingBudgetTokens(window, rung)
138
+ );
139
+ const effectiveResultChars =
140
+ maxToolResultChars == null
141
+ ? windowResultChars
142
+ : Math.min(windowResultChars, maxToolResultChars);
143
+ if (effectiveResultChars <= targetChars) {
144
+ return rung;
145
+ }
146
+ }
147
+ return deepest;
148
+ }
149
+
150
+ /**
151
+ * Shallowest rung at which the configured number of parallel tool-call inputs
152
+ * and fresh results fit together within `rawTokens`, the effective budget in
153
+ * raw token space. This prevents orphan repair from dropping results merely
154
+ * because their shared assistant message used the room left by a configured
155
+ * result cap.
156
+ */
157
+ export function fadingRungForBudget(
158
+ window: number,
159
+ rawTokens: number,
160
+ maxToolResultChars?: number,
161
+ toolExchangeWidth = 1
162
+ ): number {
163
+ if (!(rawTokens > 0)) {
164
+ return 0;
165
+ }
166
+ const targetChars = Math.floor(rawTokens) * 4;
167
+ const width = Math.max(1, Math.floor(toolExchangeWidth));
168
+ const deepest = maxFadingRung(window);
169
+ for (let rung = 0; rung < deepest; rung++) {
170
+ const budgetTokens = fadingBudgetTokens(window, rung);
171
+ const windowResultChars = calculateMaxToolResultChars(budgetTokens);
172
+ const resultChars =
173
+ maxToolResultChars == null
174
+ ? windowResultChars
175
+ : Math.min(windowResultChars, maxToolResultChars);
176
+ const inputChars = calculateMaxToolCallInputChars(budgetTokens);
177
+ if (width * (resultChars + inputChars) <= targetChars) {
178
+ return rung;
179
+ }
180
+ }
181
+ return deepest;
182
+ }
183
+
184
+ /**
185
+ * Shallowest rung at which one complete tool exchange (a fresh result plus its
186
+ * call input) fits within `targetChars`. The emergency pass uses it so a small
187
+ * configured result cap cannot satisfy the target while inputs stay uncapped.
188
+ */
189
+ export function fadingRungForExchangeChars(
190
+ window: number,
191
+ targetChars: number,
192
+ maxToolResultChars?: number
193
+ ): number {
194
+ const deepest = maxFadingRung(window);
195
+ for (let rung = 0; rung < deepest; rung++) {
196
+ const budgetTokens = fadingBudgetTokens(window, rung);
197
+ const windowResultChars = calculateMaxToolResultChars(budgetTokens);
198
+ const resultChars =
199
+ maxToolResultChars == null
200
+ ? windowResultChars
201
+ : Math.min(windowResultChars, maxToolResultChars);
202
+ if (resultChars + calculateMaxToolCallInputChars(budgetTokens) <= targetChars) {
203
+ return rung;
204
+ }
205
+ }
206
+ return deepest;
207
+ }
208
+
209
+ /** Character caps for a tier; a pure function of `(budgetTokens, masked)`. */
210
+ export function resolveFadingCaps(
211
+ tier: FadingTier,
212
+ maxToolResultChars?: number
213
+ ): FadingCaps {
214
+ const { budgetTokens } = tier;
215
+ const windowResultChars = calculateMaxToolResultChars(budgetTokens);
216
+ const resultChars =
217
+ maxToolResultChars != null
218
+ ? Math.min(windowResultChars, maxToolResultChars)
219
+ : windowResultChars;
220
+ const consumedChars = tier.masked
221
+ ? Math.min(
222
+ resultChars,
223
+ Math.max(
224
+ MASKED_RESULT_MIN_CHARS,
225
+ Math.floor(resultChars * MASKED_RESULT_CAP_RATIO)
226
+ )
227
+ )
228
+ : resultChars;
229
+ return {
230
+ budgetTokens,
231
+ resultChars,
232
+ consumedChars,
233
+ inputChars: calculateMaxToolCallInputChars(budgetTokens),
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Advances a tier from this call's signals on the current window's ladder.
239
+ * The budget only ever shrinks and masking only ever activates, which is the
240
+ * hysteresis: drift in calibration, instruction overhead or message count
241
+ * can escalate once at a rung boundary but never oscillate. Returns the same
242
+ * object when nothing changes.
243
+ *
244
+ * - The fit rung guarantees the widest observed parallel tool exchange fits
245
+ * the effective budget as a complete input/result batch.
246
+ * - Pressure bands add rungs on top of the fit rung when summarization is
247
+ * off, mirroring the staged budget factors of progressive context fading.
248
+ * - A window larger than the latched budget (model switch) does not loosen
249
+ * the tier; only compaction, which rewrites the prefix anyway, resets it.
250
+ */
251
+ export function resolveFadingTier(
252
+ tier: FadingTier,
253
+ window: number,
254
+ signals: FadingSignals,
255
+ maxToolResultChars?: number
256
+ ): FadingTier {
257
+ const fitRung = fadingRungForBudget(
258
+ window,
259
+ signals.effectiveRawTokens,
260
+ maxToolResultChars,
261
+ signals.toolExchangeWidth
262
+ );
263
+ const bandRung = signals.summarizationEnabled
264
+ ? 0
265
+ : (PRESSURE_BAND_RUNGS.find(
266
+ ([threshold]) => signals.contextPressure >= threshold
267
+ )?.[1] ?? 0);
268
+ const rung = Math.min(
269
+ maxFadingRung(window),
270
+ Math.max(fitRung + bandRung, signals.minRung ?? 0)
271
+ );
272
+ const budgetTokens = Math.min(
273
+ tier.budgetTokens,
274
+ fadingBudgetTokens(window, rung)
275
+ );
276
+ const masked =
277
+ tier.masked || signals.contextPressure >= PRESSURE_THRESHOLD_MASKING;
278
+ if (budgetTokens === tier.budgetTokens && masked === tier.masked) {
279
+ return tier;
280
+ }
281
+ return { v: FADING_TIER_VERSION, budgetTokens, masked, latched: true };
282
+ }
283
+
284
+ /**
285
+ * Whether a tier carries information a host should persist: masking has
286
+ * activated or the budget sits below the pruner's window. A fresh tier only
287
+ * seeds what the next run derives on its own.
288
+ */
289
+ export function isInformativeFadingTier(
290
+ tier: FadingTier | undefined,
291
+ window: number | undefined
292
+ ): tier is FadingTier {
293
+ if (tier == null) {
294
+ return false;
295
+ }
296
+ return (
297
+ tier.latched === true ||
298
+ tier.masked ||
299
+ (window != null && window > 0 && tier.budgetTokens < window)
300
+ );
301
+ }
@@ -17,3 +17,4 @@ export * from './recency';
17
17
  export * from './assistantPhase';
18
18
  export * from './provenance';
19
19
  export * from './projectionInvariant';
20
+ export * from './fading';