@librechat/agents 3.6.11 → 3.6.13

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 (50) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +4 -0
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +45 -116
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/llm/contextPressureMeter.cjs +212 -0
  6. package/dist/cjs/llm/contextPressureMeter.cjs.map +1 -0
  7. package/dist/cjs/llm/invoke.cjs +46 -75
  8. package/dist/cjs/llm/invoke.cjs.map +1 -1
  9. package/dist/cjs/llm/prepareProviderRequest.cjs +108 -0
  10. package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -0
  11. package/dist/cjs/llm/tokenCounterCacheCompatibility.cjs +14 -0
  12. package/dist/cjs/llm/tokenCounterCacheCompatibility.cjs.map +1 -0
  13. package/dist/cjs/main.cjs +4 -0
  14. package/dist/cjs/messages/content.cjs +5 -4
  15. package/dist/cjs/messages/content.cjs.map +1 -1
  16. package/dist/cjs/utils/tokens.cjs +3 -2
  17. package/dist/cjs/utils/tokens.cjs.map +1 -1
  18. package/dist/esm/agents/AgentContext.mjs +4 -0
  19. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  20. package/dist/esm/graphs/Graph.mjs +48 -119
  21. package/dist/esm/graphs/Graph.mjs.map +1 -1
  22. package/dist/esm/llm/contextPressureMeter.mjs +211 -0
  23. package/dist/esm/llm/contextPressureMeter.mjs.map +1 -0
  24. package/dist/esm/llm/invoke.mjs +48 -75
  25. package/dist/esm/llm/invoke.mjs.map +1 -1
  26. package/dist/esm/llm/prepareProviderRequest.mjs +105 -0
  27. package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -0
  28. package/dist/esm/llm/tokenCounterCacheCompatibility.mjs +13 -0
  29. package/dist/esm/llm/tokenCounterCacheCompatibility.mjs.map +1 -0
  30. package/dist/esm/main.mjs +3 -1
  31. package/dist/esm/messages/content.mjs +5 -4
  32. package/dist/esm/messages/content.mjs.map +1 -1
  33. package/dist/esm/utils/tokens.mjs +3 -2
  34. package/dist/esm/utils/tokens.mjs.map +1 -1
  35. package/dist/types/agents/AgentContext.d.ts +3 -0
  36. package/dist/types/index.d.ts +3 -0
  37. package/dist/types/llm/contextPressureMeter.d.ts +36 -0
  38. package/dist/types/llm/invoke.d.ts +31 -33
  39. package/dist/types/llm/prepareProviderRequest.d.ts +55 -0
  40. package/dist/types/llm/tokenCounterCacheCompatibility.d.ts +7 -0
  41. package/package.json +2 -1
  42. package/src/agents/AgentContext.ts +9 -0
  43. package/src/graphs/Graph.ts +70 -276
  44. package/src/index.ts +9 -0
  45. package/src/llm/contextPressureMeter.ts +449 -0
  46. package/src/llm/invoke.ts +136 -296
  47. package/src/llm/prepareProviderRequest.ts +357 -0
  48. package/src/llm/tokenCounterCacheCompatibility.ts +22 -0
  49. package/src/messages/content.ts +3 -3
  50. package/src/utils/tokens.ts +3 -2
@@ -0,0 +1,449 @@
1
+ import { isProxy } from 'node:util/types';
2
+ import { ToolMessage } from '@langchain/core/messages';
3
+
4
+ import type { AIMessage, BaseMessage } from '@langchain/core/messages';
5
+ import type { ProviderPayloadMeasurement } from '@/llm/prepareProviderRequest';
6
+ import type * as t from '@/types';
7
+
8
+ import {
9
+ REPLY_PRIMER_TOKENS,
10
+ isSyntheticProviderContextMessage,
11
+ } from '@/messages';
12
+ import { apportionTokenCounts } from '@/utils';
13
+
14
+ interface ContextPressureUsage {
15
+ contextBudget?: number;
16
+ effectiveInstructionTokens?: number;
17
+ remainingContextTokens?: number;
18
+ calibrationRatio?: number;
19
+ }
20
+
21
+ interface ContextPressureMeterParams {
22
+ tokenCounter?: t.TokenCounter;
23
+ tokenCountCache?: ExactTokenCountCache;
24
+ sourceMessages: BaseMessage[];
25
+ retainedMessages: BaseMessage[];
26
+ indexTokenCountMap: Record<string, number | undefined>;
27
+ contextUsage?: ContextPressureUsage | null;
28
+ instructionTokens: number;
29
+ calibrationRatio: number;
30
+ }
31
+
32
+ interface ProviderPayloadMeasureOptions {
33
+ contextBudget?: number;
34
+ forceRawRecount?: boolean;
35
+ }
36
+
37
+ interface ProviderMessageBaseline {
38
+ rawTokens: number;
39
+ accountingWeight: number;
40
+ }
41
+
42
+ interface ProviderBaselineAttribution {
43
+ attributedByOrigin: number[];
44
+ projectedBaseTokens: number;
45
+ }
46
+
47
+ export interface ContextPressureMeter {
48
+ trackProjection(before: BaseMessage[], after: BaseMessage[]): BaseMessage[];
49
+ trackClone(source: BaseMessage, clone: BaseMessage): void;
50
+ measure(
51
+ messages: BaseMessage[],
52
+ options?: ProviderPayloadMeasureOptions
53
+ ): ProviderPayloadMeasurement;
54
+ }
55
+
56
+ export interface ExactTokenCountCache {
57
+ count(message: BaseMessage): number;
58
+ }
59
+
60
+ interface StableTokenSurface {
61
+ content: string;
62
+ messageType: string;
63
+ role?: string;
64
+ additionalType?: string;
65
+ }
66
+
67
+ interface ExactTokenCountCacheEntry {
68
+ surface: StableTokenSurface;
69
+ tokens: number;
70
+ }
71
+
72
+ function readDataProperty(
73
+ owner: object,
74
+ property: PropertyKey
75
+ ): { own: boolean; safe: boolean; value?: unknown } {
76
+ let descriptor: PropertyDescriptor | undefined;
77
+ try {
78
+ descriptor = Object.getOwnPropertyDescriptor(owner, property);
79
+ } catch {
80
+ return { own: false, safe: false };
81
+ }
82
+ if (descriptor == null) {
83
+ return { own: false, safe: true };
84
+ }
85
+ return 'value' in descriptor
86
+ ? { own: true, safe: true, value: descriptor.value }
87
+ : { own: true, safe: false };
88
+ }
89
+
90
+ function getStableTokenSurface(
91
+ message: BaseMessage
92
+ ): StableTokenSurface | undefined {
93
+ if (isProxy(message)) {
94
+ return undefined;
95
+ }
96
+ const contentProperty = readDataProperty(message, 'content');
97
+ if (
98
+ !contentProperty.safe ||
99
+ !contentProperty.own ||
100
+ typeof contentProperty.value !== 'string'
101
+ ) {
102
+ return undefined;
103
+ }
104
+ const messageType = message.getType();
105
+ const roleProperty = readDataProperty(message, 'role');
106
+ if (!roleProperty.safe || (!roleProperty.own && 'role' in message)) {
107
+ return undefined;
108
+ }
109
+ const role = roleProperty.value;
110
+ if (role != null && typeof role !== 'string') {
111
+ return undefined;
112
+ }
113
+
114
+ const additionalKwargsProperty = readDataProperty(
115
+ message,
116
+ 'additional_kwargs'
117
+ );
118
+ if (!additionalKwargsProperty.safe || !additionalKwargsProperty.own) {
119
+ return undefined;
120
+ }
121
+ const rawAdditionalKwargs = additionalKwargsProperty.value;
122
+ if (
123
+ rawAdditionalKwargs == null ||
124
+ typeof rawAdditionalKwargs !== 'object' ||
125
+ isProxy(rawAdditionalKwargs)
126
+ ) {
127
+ return undefined;
128
+ }
129
+ const additionalKwargs = rawAdditionalKwargs;
130
+ const typeProperty = readDataProperty(additionalKwargs, 'type');
131
+ if (!typeProperty.safe) {
132
+ return undefined;
133
+ }
134
+ if (messageType === 'ai' || role === 'assistant') {
135
+ const toolCallsProperty = readDataProperty(
136
+ message as AIMessage,
137
+ 'tool_calls'
138
+ );
139
+ if (
140
+ !toolCallsProperty.safe ||
141
+ (!toolCallsProperty.own && 'tool_calls' in message)
142
+ ) {
143
+ return undefined;
144
+ }
145
+ const toolCalls = toolCallsProperty.value;
146
+ if (
147
+ toolCalls != null &&
148
+ (!Array.isArray(toolCalls) || isProxy(toolCalls) || toolCalls.length > 0)
149
+ ) {
150
+ return undefined;
151
+ }
152
+ const functionCall = readDataProperty(additionalKwargs, 'function_call');
153
+ if (!functionCall.safe || functionCall.value != null) {
154
+ return undefined;
155
+ }
156
+ }
157
+
158
+ let additionalType: string | undefined;
159
+ if (messageType === 'tool') {
160
+ const typeValue = typeProperty.value;
161
+ if (typeValue != null && typeof typeValue !== 'string') {
162
+ return undefined;
163
+ }
164
+ additionalType = typeof typeValue === 'string' ? typeValue : undefined;
165
+ }
166
+
167
+ return {
168
+ content: contentProperty.value,
169
+ messageType,
170
+ ...(role != null && { role }),
171
+ ...(additionalType != null && { additionalType }),
172
+ };
173
+ }
174
+
175
+ function tokenSurfacesMatch(
176
+ left: StableTokenSurface,
177
+ right: StableTokenSurface
178
+ ): boolean {
179
+ return (
180
+ left.content === right.content &&
181
+ left.messageType === right.messageType &&
182
+ left.role === right.role &&
183
+ left.additionalType === right.additionalType
184
+ );
185
+ }
186
+
187
+ /** Reuses exact counts only while every token-relevant stable field matches. */
188
+ export function createExactTokenCountCache(
189
+ tokenCounter: t.TokenCounter
190
+ ): ExactTokenCountCache {
191
+ const entries = new WeakMap<BaseMessage, ExactTokenCountCacheEntry>();
192
+ return {
193
+ count(message): number {
194
+ const surface = getStableTokenSurface(message);
195
+ const cached = entries.get(message);
196
+ if (
197
+ surface != null &&
198
+ cached != null &&
199
+ tokenSurfacesMatch(cached.surface, surface)
200
+ ) {
201
+ return cached.tokens;
202
+ }
203
+ const tokens = tokenCounter(message);
204
+ if (surface != null) {
205
+ entries.set(message, { surface, tokens });
206
+ } else {
207
+ entries.delete(message);
208
+ }
209
+ return tokens;
210
+ },
211
+ };
212
+ }
213
+
214
+ function getProviderMessageOriginKey(message: BaseMessage): string | undefined {
215
+ const type = message.getType();
216
+ if (
217
+ message instanceof ToolMessage &&
218
+ typeof message.tool_call_id === 'string' &&
219
+ message.tool_call_id.length > 0
220
+ ) {
221
+ return `tool:call:${message.tool_call_id}`;
222
+ }
223
+ if (typeof message.id === 'string' && message.id.length > 0) {
224
+ return `${type}:id:${message.id}`;
225
+ }
226
+ return undefined;
227
+ }
228
+
229
+ /** Measures repeated provider projections while tokenizing each message object once. */
230
+ export function createContextPressureMeter({
231
+ tokenCounter,
232
+ tokenCountCache,
233
+ sourceMessages,
234
+ retainedMessages,
235
+ indexTokenCountMap,
236
+ contextUsage,
237
+ instructionTokens,
238
+ calibrationRatio,
239
+ }: ContextPressureMeterParams): ContextPressureMeter {
240
+ const tokenCounts = new WeakMap<BaseMessage, number>();
241
+ const origins = new WeakMap<BaseMessage, number>();
242
+ const baselineAttributions = new Map<number, ProviderBaselineAttribution>();
243
+ let baseline: ProviderMessageBaseline[] | undefined;
244
+ let baselineWeights: Record<string, number> | undefined;
245
+ let totalBaselineWeight = 0;
246
+
247
+ const count = (message: BaseMessage): number => {
248
+ const cached = tokenCounts.get(message);
249
+ if (cached != null) {
250
+ return cached;
251
+ }
252
+ const tokens =
253
+ tokenCountCache?.count(message) ?? tokenCounter?.(message) ?? 0;
254
+ tokenCounts.set(message, tokens);
255
+ return tokens;
256
+ };
257
+
258
+ if (
259
+ contextUsage != null &&
260
+ (tokenCounter != null || tokenCountCache != null)
261
+ ) {
262
+ const sourceIndices = new WeakMap<BaseMessage, number>();
263
+ for (let i = 0; i < sourceMessages.length; i++) {
264
+ sourceIndices.set(sourceMessages[i], i);
265
+ }
266
+ baseline = retainedMessages.map((message, index) => {
267
+ const rawTokens = count(message);
268
+ const sourceIndex = sourceIndices.get(message);
269
+ const indexedTokens =
270
+ sourceIndex != null ? indexTokenCountMap[sourceIndex] : undefined;
271
+ const accountingWeight =
272
+ indexedTokens != null &&
273
+ Number.isFinite(indexedTokens) &&
274
+ indexedTokens >= 0
275
+ ? indexedTokens
276
+ : rawTokens;
277
+ if (!origins.has(message)) {
278
+ origins.set(message, index);
279
+ }
280
+ return { rawTokens, accountingWeight };
281
+ });
282
+ baselineWeights = {};
283
+ for (let i = 0; i < baseline.length; i++) {
284
+ baselineWeights[i] = baseline[i].accountingWeight;
285
+ totalBaselineWeight += baseline[i].accountingWeight;
286
+ }
287
+ }
288
+
289
+ const trackClone = (source: BaseMessage, clone: BaseMessage): void => {
290
+ const origin = origins.get(source);
291
+ if (origin != null && !origins.has(clone)) {
292
+ origins.set(clone, origin);
293
+ }
294
+ };
295
+
296
+ const trackProjection = (
297
+ before: BaseMessage[],
298
+ after: BaseMessage[]
299
+ ): BaseMessage[] => {
300
+ if (baseline == null || before === after) {
301
+ return after;
302
+ }
303
+ if (before.length === after.length) {
304
+ for (let i = 0; i < after.length; i++) {
305
+ if (
306
+ before[i].getType() === after[i].getType() &&
307
+ !isSyntheticProviderContextMessage(after[i])
308
+ ) {
309
+ trackClone(before[i], after[i]);
310
+ }
311
+ }
312
+ return after;
313
+ }
314
+
315
+ const keyedOrigins = new Map<string, number | null>();
316
+ for (const message of before) {
317
+ const origin = origins.get(message);
318
+ const key = getProviderMessageOriginKey(message);
319
+ if (origin == null || key == null) {
320
+ continue;
321
+ }
322
+ keyedOrigins.set(key, keyedOrigins.has(key) ? null : origin);
323
+ }
324
+ for (const message of after) {
325
+ if (origins.has(message) || isSyntheticProviderContextMessage(message)) {
326
+ continue;
327
+ }
328
+ const key = getProviderMessageOriginKey(message);
329
+ const origin = key != null ? keyedOrigins.get(key) : undefined;
330
+ if (origin != null) {
331
+ origins.set(message, origin);
332
+ }
333
+ }
334
+ return after;
335
+ };
336
+
337
+ const measure = (
338
+ messages: BaseMessage[],
339
+ options?: ProviderPayloadMeasureOptions
340
+ ): ProviderPayloadMeasurement => {
341
+ const contextBudget = options?.contextBudget ?? contextUsage?.contextBudget;
342
+ const forceRawRecount = options?.forceRawRecount === true;
343
+ const effectiveInstructionTokens =
344
+ contextUsage?.effectiveInstructionTokens ??
345
+ (forceRawRecount ? instructionTokens : undefined);
346
+ if (
347
+ (tokenCounter == null && tokenCountCache == null) ||
348
+ contextBudget == null ||
349
+ effectiveInstructionTokens == null
350
+ ) {
351
+ return { fits: true };
352
+ }
353
+ const availableMessageTokens = Math.max(
354
+ 0,
355
+ contextBudget - effectiveInstructionTokens
356
+ );
357
+ let usageRatio = calibrationRatio > 0 ? calibrationRatio : 1;
358
+ if (
359
+ contextUsage?.calibrationRatio != null &&
360
+ contextUsage.calibrationRatio > 0
361
+ ) {
362
+ usageRatio = contextUsage.calibrationRatio;
363
+ }
364
+ if (forceRawRecount) {
365
+ usageRatio = Math.max(1, usageRatio);
366
+ }
367
+ const baselineRemaining = contextUsage?.remainingContextTokens;
368
+ const accountedMessageTokens =
369
+ !forceRawRecount &&
370
+ baseline != null &&
371
+ baselineRemaining != null &&
372
+ Number.isFinite(baselineRemaining)
373
+ ? availableMessageTokens -
374
+ Math.min(availableMessageTokens, Math.max(0, baselineRemaining))
375
+ : undefined;
376
+
377
+ let projectedMessageTokens: number;
378
+ if (
379
+ accountedMessageTokens != null &&
380
+ baseline != null &&
381
+ baselineWeights != null
382
+ ) {
383
+ let attribution = baselineAttributions.get(availableMessageTokens);
384
+ if (attribution == null) {
385
+ const replyPrimerTokens = Math.round(
386
+ REPLY_PRIMER_TOKENS * usageRatio
387
+ );
388
+ const attributableTokens =
389
+ totalBaselineWeight > 0
390
+ ? Math.min(
391
+ Math.max(0, accountedMessageTokens - replyPrimerTokens),
392
+ Math.round(totalBaselineWeight * usageRatio)
393
+ )
394
+ : 0;
395
+ const apportionedTokens =
396
+ totalBaselineWeight > 0
397
+ ? apportionTokenCounts(
398
+ baselineWeights,
399
+ attributableTokens / totalBaselineWeight,
400
+ attributableTokens
401
+ )
402
+ : {};
403
+ attribution = {
404
+ attributedByOrigin: baseline.map(
405
+ (_, origin) => apportionedTokens[origin] || 0
406
+ ),
407
+ projectedBaseTokens: Math.max(
408
+ replyPrimerTokens,
409
+ accountedMessageTokens - attributableTokens
410
+ ),
411
+ };
412
+ baselineAttributions.set(availableMessageTokens, attribution);
413
+ }
414
+ projectedMessageTokens = attribution.projectedBaseTokens;
415
+ let newRawTokens = 0;
416
+ const usedOrigins = new Set<number>();
417
+ for (const message of messages) {
418
+ const rawTokens = count(message);
419
+ const origin = origins.get(message);
420
+ if (origin == null || usedOrigins.has(origin)) {
421
+ newRawTokens += rawTokens;
422
+ continue;
423
+ }
424
+ usedOrigins.add(origin);
425
+ projectedMessageTokens += Math.max(
426
+ 0,
427
+ attribution.attributedByOrigin[origin] +
428
+ Math.round((rawTokens - baseline[origin].rawTokens) * usageRatio)
429
+ );
430
+ }
431
+ projectedMessageTokens += Math.round(newRawTokens * usageRatio);
432
+ } else {
433
+ let rawTokens = REPLY_PRIMER_TOKENS;
434
+ for (const message of messages) {
435
+ rawTokens += count(message);
436
+ }
437
+ projectedMessageTokens = Math.round(rawTokens * usageRatio);
438
+ }
439
+ return {
440
+ fits: projectedMessageTokens <= availableMessageTokens,
441
+ projectedMessageTokens,
442
+ availableMessageTokens,
443
+ contextBudget,
444
+ effectiveInstructionTokens,
445
+ };
446
+ };
447
+
448
+ return { trackProjection, trackClone, measure };
449
+ }