@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
@@ -50,8 +50,8 @@ import {
50
50
  addBedrockTailCacheControl,
51
51
  projectArtifactPayload,
52
52
  formatContentStrings,
53
+ cloneMessage,
53
54
  CALIBRATION_RATIO_MAX,
54
- REPLY_PRIMER_TOKENS,
55
55
  createPruneMessages,
56
56
  projectToolCallInputs,
57
57
  calculateMaxToolCallInputChars,
@@ -74,7 +74,6 @@ import {
74
74
  coalesceAdjacentUserTurns,
75
75
  strictAlternationProviders,
76
76
  appendPredecessorHandoffCue,
77
- removePredecessorHandoffCue,
78
77
  stampSyntheticProviderMessage,
79
78
  } from '@/messages';
80
79
  import {
@@ -104,9 +103,9 @@ import {
104
103
  tryFallbackProviders,
105
104
  getFallbackErrorContext,
106
105
  getFallbackOverflowCandidates,
107
- projectMessagesForProvider,
108
- resolveServingModelId,
109
106
  } from '@/llm/invoke';
107
+ import { prepareProviderRequest } from '@/llm/prepareProviderRequest';
108
+ import { createContextPressureMeter } from '@/llm/contextPressureMeter';
110
109
  import {
111
110
  resolveStreamLimits,
112
111
  StreamLimitExceededError,
@@ -3154,105 +3153,19 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3154
3153
  * baseline, then attribute it across retained messages. Provider
3155
3154
  * transforms can shrink one message while expanding or adding another;
3156
3155
  * per-origin accounting prevents that unrelated shrink from canceling
3157
- * the expansion. Raw counts are frozen before in-place formatters run.
3156
+ * the expansion. Exact counts are memoized across repeated projections.
3158
3157
  */
3159
- let providerMessageBaseline:
3160
- | Array<{ rawTokens: number; accountingWeight: number }>
3161
- | undefined;
3162
- const providerMessageOrigins = new WeakMap<BaseMessage, number>();
3163
- if (contextUsage != null && agentContext.tokenCounter != null) {
3164
- const sourceIndices = new WeakMap<BaseMessage, number>();
3165
- for (let i = 0; i < messages.length; i++) {
3166
- sourceIndices.set(messages[i], i);
3167
- }
3168
- providerMessageBaseline = messagesToUse.map((message, index) => {
3169
- const rawTokens = agentContext.tokenCounter!(message);
3170
- const sourceIndex = sourceIndices.get(message);
3171
- const indexedTokens =
3172
- sourceIndex != null
3173
- ? agentContext.indexTokenCountMap[sourceIndex]
3174
- : undefined;
3175
- const accountingWeight =
3176
- indexedTokens != null &&
3177
- Number.isFinite(indexedTokens) &&
3178
- indexedTokens >= 0
3179
- ? indexedTokens
3180
- : rawTokens;
3181
- if (!providerMessageOrigins.has(message)) {
3182
- providerMessageOrigins.set(message, index);
3183
- }
3184
- return { rawTokens, accountingWeight };
3185
- });
3186
- }
3187
-
3188
- const getProviderMessageOriginKey = (
3189
- message: BaseMessage
3190
- ): string | undefined => {
3191
- const type = message.getType();
3192
- if (
3193
- message instanceof ToolMessage &&
3194
- typeof message.tool_call_id === 'string' &&
3195
- message.tool_call_id.length > 0
3196
- ) {
3197
- return `tool:call:${message.tool_call_id}`;
3198
- }
3199
- if (typeof message.id === 'string' && message.id.length > 0) {
3200
- return `${type}:id:${message.id}`;
3201
- }
3202
- return undefined;
3203
- };
3204
-
3205
- /**
3206
- * Provider projections clone messages. Preserve their baseline origin
3207
- * without writing tracking metadata onto the wire. Synthetic fold
3208
- * messages intentionally remain unattributed and are charged in full.
3209
- */
3210
- const trackProviderMessageOrigins = (
3211
- before: BaseMessage[],
3212
- after: BaseMessage[]
3213
- ): BaseMessage[] => {
3214
- if (providerMessageBaseline == null || before === after) {
3215
- return after;
3216
- }
3217
- if (before.length === after.length) {
3218
- for (let i = 0; i < after.length; i++) {
3219
- const origin = providerMessageOrigins.get(before[i]);
3220
- if (
3221
- origin != null &&
3222
- !providerMessageOrigins.has(after[i]) &&
3223
- before[i].getType() === after[i].getType() &&
3224
- !isSyntheticProviderContextMessage(after[i])
3225
- ) {
3226
- providerMessageOrigins.set(after[i], origin);
3227
- }
3228
- }
3229
- return after;
3230
- }
3231
-
3232
- const keyedOrigins = new Map<string, number | null>();
3233
- for (const message of before) {
3234
- const origin = providerMessageOrigins.get(message);
3235
- const key = getProviderMessageOriginKey(message);
3236
- if (origin == null || key == null) {
3237
- continue;
3238
- }
3239
- keyedOrigins.set(key, keyedOrigins.has(key) ? null : origin);
3240
- }
3241
- for (const message of after) {
3242
- if (
3243
- providerMessageOrigins.has(message) ||
3244
- isSyntheticProviderContextMessage(message)
3245
- ) {
3246
- continue;
3247
- }
3248
- const key = getProviderMessageOriginKey(message);
3249
- const origin = key != null ? keyedOrigins.get(key) : undefined;
3250
- if (origin != null) {
3251
- providerMessageOrigins.set(message, origin);
3252
- }
3253
- }
3254
- return after;
3255
- };
3158
+ const contextPressure = createContextPressureMeter({
3159
+ tokenCounter: agentContext.tokenCounter,
3160
+ tokenCountCache: agentContext.contextPressureTokenCounts,
3161
+ sourceMessages: messages,
3162
+ retainedMessages: messagesToUse,
3163
+ indexTokenCountMap: agentContext.indexTokenCountMap,
3164
+ contextUsage,
3165
+ instructionTokens: agentContext.instructionTokens,
3166
+ calibrationRatio: agentContext.calibrationRatio,
3167
+ });
3168
+ const trackProviderMessageOrigins = contextPressure.trackProjection;
3256
3169
 
3257
3170
  if (agentContext.useLegacyContent) {
3258
3171
  const before = finalMessages;
@@ -3300,8 +3213,13 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3300
3213
  typeof lastMessageX.content === 'string'
3301
3214
  ) {
3302
3215
  const trimmed = lastMessageX.content.trim();
3303
- finalMessages[finalMessages.length - 2].content =
3304
- trimmed.length > 0 ? [{ type: 'text' as const, text: trimmed }] : '';
3216
+ const before = finalMessages;
3217
+ finalMessages = [...before];
3218
+ finalMessages[finalMessages.length - 2] = cloneMessage(
3219
+ lastMessageX,
3220
+ trimmed.length > 0 ? [{ type: 'text' as const, text: trimmed }] : ''
3221
+ );
3222
+ finalMessages = trackProviderMessageOrigins(before, finalMessages);
3305
3223
  }
3306
3224
 
3307
3225
  const localProviderOverflowMeasurements = new WeakMap<
@@ -3311,123 +3229,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3311
3229
  estimatedPromptTokens: number;
3312
3230
  }
3313
3231
  >();
3314
- const measureProviderPayload = (
3315
- candidate: BaseMessage[],
3316
- contextBudgetOverride?: number,
3317
- forceRawRecount = false
3318
- ): {
3319
- fits: boolean;
3320
- projectedMessageTokens?: number;
3321
- availableMessageTokens?: number;
3322
- contextBudget?: number;
3323
- effectiveInstructionTokens?: number;
3324
- } => {
3325
- const contextBudget =
3326
- contextBudgetOverride ?? contextUsage?.contextBudget;
3327
- const effectiveInstructionTokens =
3328
- contextUsage?.effectiveInstructionTokens ??
3329
- (forceRawRecount ? agentContext.instructionTokens : undefined);
3330
- if (
3331
- agentContext.tokenCounter == null ||
3332
- contextBudget == null ||
3333
- effectiveInstructionTokens == null
3334
- ) {
3335
- return { fits: true };
3336
- }
3337
- const availableMessageTokens = Math.max(
3338
- 0,
3339
- contextBudget - effectiveInstructionTokens
3340
- );
3341
- let usageRatio =
3342
- agentContext.calibrationRatio > 0 ? agentContext.calibrationRatio : 1;
3343
- if (
3344
- contextUsage?.calibrationRatio != null &&
3345
- contextUsage.calibrationRatio > 0
3346
- ) {
3347
- usageRatio = contextUsage.calibrationRatio;
3348
- }
3349
- if (forceRawRecount) {
3350
- usageRatio = Math.max(1, usageRatio);
3351
- }
3352
- const baselineRemaining = contextUsage?.remainingContextTokens;
3353
- const accountedMessageTokens =
3354
- !forceRawRecount &&
3355
- providerMessageBaseline != null &&
3356
- baselineRemaining != null &&
3357
- Number.isFinite(baselineRemaining)
3358
- ? availableMessageTokens -
3359
- Math.min(availableMessageTokens, Math.max(0, baselineRemaining))
3360
- : undefined;
3361
-
3362
- let projectedMessageTokens: number;
3363
- if (accountedMessageTokens != null && providerMessageBaseline != null) {
3364
- const replyPrimerTokens = Math.round(
3365
- REPLY_PRIMER_TOKENS * usageRatio
3366
- );
3367
- const rawWeights: Record<string, number> = {};
3368
- let totalWeight = 0;
3369
- for (let i = 0; i < providerMessageBaseline.length; i++) {
3370
- const weight = providerMessageBaseline[i].accountingWeight;
3371
- rawWeights[i] = weight;
3372
- totalWeight += weight;
3373
- }
3374
- const attributableTokens =
3375
- totalWeight > 0
3376
- ? Math.min(
3377
- Math.max(0, accountedMessageTokens - replyPrimerTokens),
3378
- Math.round(totalWeight * usageRatio)
3379
- )
3380
- : 0;
3381
- const apportionedTokens =
3382
- totalWeight > 0
3383
- ? apportionTokenCounts(
3384
- rawWeights,
3385
- attributableTokens / totalWeight,
3386
- attributableTokens
3387
- )
3388
- : {};
3389
- const attributedByOrigin = providerMessageBaseline.map(
3390
- (_, origin) => apportionedTokens[origin] || 0
3391
- );
3392
- projectedMessageTokens = Math.max(
3393
- replyPrimerTokens,
3394
- accountedMessageTokens - attributableTokens
3395
- );
3396
- let newRawTokens = 0;
3397
- const usedOrigins = new Set<number>();
3398
- for (const message of candidate) {
3399
- const rawTokens = agentContext.tokenCounter(message);
3400
- const origin = providerMessageOrigins.get(message);
3401
- if (origin == null || usedOrigins.has(origin)) {
3402
- newRawTokens += rawTokens;
3403
- continue;
3404
- }
3405
- usedOrigins.add(origin);
3406
- projectedMessageTokens += Math.max(
3407
- 0,
3408
- attributedByOrigin[origin] +
3409
- Math.round(
3410
- (rawTokens - providerMessageBaseline[origin].rawTokens) *
3411
- usageRatio
3412
- )
3413
- );
3414
- }
3415
- projectedMessageTokens += Math.round(newRawTokens * usageRatio);
3416
- } else {
3417
- let rawTokens = REPLY_PRIMER_TOKENS;
3418
- for (const message of candidate) {
3419
- rawTokens += agentContext.tokenCounter(message);
3420
- }
3421
- projectedMessageTokens = Math.round(rawTokens * usageRatio);
3422
- }
3423
- return {
3424
- fits: projectedMessageTokens <= availableMessageTokens,
3425
- projectedMessageTokens,
3426
- availableMessageTokens,
3427
- contextBudget,
3428
- effectiveInstructionTokens,
3429
- };
3430
- };
3232
+ const measureProviderPayload = contextPressure.measure;
3431
3233
 
3432
3234
  const createProviderPayloadOverflowError = ({
3433
3235
  projection,
@@ -3782,10 +3584,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3782
3584
  finalMessages = trackProviderMessageOrigins(
3783
3585
  beforeSanitizeMessages,
3784
3586
  sanitizeOrphanToolBlocks(beforeSanitizeMessages, (source, clone) => {
3785
- const origin = providerMessageOrigins.get(source);
3786
- if (origin != null) {
3787
- providerMessageOrigins.set(clone, origin);
3788
- }
3587
+ contextPressure.trackClone(source, clone);
3789
3588
  })
3790
3589
  );
3791
3590
  if (finalMessages.length !== beforeSanitize) {
@@ -3858,23 +3657,30 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3858
3657
 
3859
3658
  const fallbackBaseMessages = finalMessages;
3860
3659
  const beforeFinalProviderProjection = fallbackBaseMessages;
3861
- finalMessages = trackProviderMessageOrigins(
3862
- beforeFinalProviderProjection,
3863
- projectMessagesForProvider({
3864
- model: (this.overrideModel ?? model) as t.ChatModel,
3865
- messages: beforeFinalProviderProjection,
3866
- provider: agentContext.provider,
3867
- maxToolResultChars: maxProviderToolResultChars,
3868
- callOptions: config,
3869
- })
3870
- );
3660
+ const preparedRequest = prepareProviderRequest({
3661
+ model: (this.overrideModel ?? model) as t.ChatModel,
3662
+ messages: beforeFinalProviderProjection,
3663
+ provider: agentContext.provider,
3664
+ context: this,
3665
+ config,
3666
+ maxToolResultChars: maxProviderToolResultChars,
3667
+ measure: (preparedMessages) =>
3668
+ measureProviderPayload(
3669
+ trackProviderMessageOrigins(
3670
+ beforeFinalProviderProjection,
3671
+ preparedMessages
3672
+ )
3673
+ ),
3674
+ });
3675
+ finalMessages = preparedRequest.messages;
3871
3676
 
3872
3677
  /**
3873
3678
  * Prompt-cache placement and orphan sanitization are provider-wire
3874
3679
  * transforms too. Re-measure after both so no content added after the
3875
3680
  * earlier artifact/synthetic compaction decision can bypass the guard.
3876
3681
  */
3877
- finalProjection = measureProviderPayload(finalMessages);
3682
+ finalProjection =
3683
+ preparedRequest.measurement ?? measureProviderPayload(finalMessages);
3878
3684
  const preInvokeContextOverflowError = !finalProjection.fits
3879
3685
  ? createProviderPayloadOverflowError({
3880
3686
  projection: finalProjection,
@@ -4083,9 +3889,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4083
3889
  () =>
4084
3890
  attemptInvoke(
4085
3891
  {
4086
- model: (this.overrideModel ?? model) as t.ChatModel,
4087
- messages: finalMessages,
4088
- provider: agentContext.provider,
3892
+ request: preparedRequest,
4089
3893
  context: this,
4090
3894
  },
4091
3895
  invokeConfig
@@ -4288,7 +4092,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4288
4092
  estimatedPromptTokens: getEstimatedPromptTokens(contextUsage),
4289
4093
  maxContextTokens: agentContext.maxContextTokens,
4290
4094
  },
4291
- prepareProviderMessages: ({
4095
+ prepareProviderRequest: ({
4292
4096
  model: fallbackModel,
4293
4097
  messages: fallbackMessages,
4294
4098
  provider: fallbackProvider,
@@ -4300,36 +4104,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4300
4104
  calculateMaxToolResultChars(
4301
4105
  fallbackMaxContextTokens ?? agentContext.maxContextTokens
4302
4106
  );
4303
- /**
4304
- * Serving-provider cue shaping BEFORE the fallback payload
4305
- * is measured: a Claude fallback behind a tolerant primary
4306
- * gains the cue inside the guarded projection (a prompt
4307
- * within the cue's cost of the fallback budget must take
4308
- * the recovery path, not ship oversized), and a tolerant
4309
- * fallback behind an Anthropic primary sheds the baked cue
4310
- * before it is measured against the tighter budget. The
4311
- * attemptInvoke funnel pass then finds nothing to change.
4312
- */
4313
- const cueShapedFallbackMessages = trackProviderMessageOrigins(
4314
- fallbackMessages,
4315
- isAnthropicLike(fallbackProvider, {
4316
- model: resolveServingModelId(fallbackModel),
4317
- })
4318
- ? appendPredecessorHandoffCue(fallbackMessages, (m) =>
4319
- this.isRunProducedMessage(m)
4320
- )
4321
- : removePredecessorHandoffCue(fallbackMessages)
4322
- );
4323
- const projectedFallbackMessages = trackProviderMessageOrigins(
4324
- cueShapedFallbackMessages,
4325
- projectMessagesForProvider({
4326
- model: fallbackModel,
4327
- messages: cueShapedFallbackMessages,
4328
- provider: fallbackProvider,
4329
- maxToolResultChars: fallbackToolResultChars,
4330
- callOptions: fallbackConfig,
4331
- })
4332
- );
4333
4107
  const primaryContextBudget = contextUsage?.contextBudget;
4334
4108
  const fallbackContextBudget =
4335
4109
  fallbackMaxContextTokens == null
@@ -4338,11 +4112,31 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4338
4112
  primaryContextBudget ?? fallbackMaxContextTokens,
4339
4113
  fallbackMaxContextTokens
4340
4114
  );
4341
- const projection = measureProviderPayload(
4342
- projectedFallbackMessages,
4343
- fallbackContextBudget,
4344
- true
4345
- );
4115
+ const preparedFallbackRequest = prepareProviderRequest({
4116
+ model: fallbackModel,
4117
+ messages: fallbackMessages,
4118
+ provider: fallbackProvider,
4119
+ context: this,
4120
+ config: fallbackConfig,
4121
+ maxToolResultChars: fallbackToolResultChars,
4122
+ measure: (preparedMessages) =>
4123
+ measureProviderPayload(
4124
+ trackProviderMessageOrigins(
4125
+ fallbackMessages,
4126
+ preparedMessages
4127
+ ),
4128
+ {
4129
+ contextBudget: fallbackContextBudget,
4130
+ forceRawRecount: true,
4131
+ }
4132
+ ),
4133
+ });
4134
+ const projection =
4135
+ preparedFallbackRequest.measurement ??
4136
+ measureProviderPayload(preparedFallbackRequest.messages, {
4137
+ contextBudget: fallbackContextBudget,
4138
+ forceRawRecount: true,
4139
+ });
4346
4140
  if (!projection.fits) {
4347
4141
  throw createProviderPayloadOverflowError({
4348
4142
  projection,
@@ -4350,7 +4144,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4350
4144
  info: 'Fallback provider message formatting exceeded the context budget before invocation.',
4351
4145
  });
4352
4146
  }
4353
- return projectedFallbackMessages;
4147
+ return preparedFallbackRequest;
4354
4148
  },
4355
4149
  })
4356
4150
  );
package/src/index.ts CHANGED
@@ -69,6 +69,7 @@ export {
69
69
  export type { Interrupt } from '@langchain/langgraph';
70
70
 
71
71
  /* LLM */
72
+ export { markTokenCounterCacheCompatible } from './llm/tokenCounterCacheCompatibility';
72
73
  export { CustomOpenAIClient } from './llm/openai';
73
74
  export { ChatOpenRouter } from './llm/openrouter';
74
75
  export type {
@@ -88,6 +89,14 @@ export type { SmoothItem, SmoothPiece } from './llm/stream/smoother';
88
89
  export { FakeChatModel, createFakeStreamingLLM } from './llm/fake';
89
90
  export { initializeModel } from './llm/init';
90
91
  export { attemptInvoke, tryFallbackProviders } from './llm/invoke';
92
+ export { prepareProviderRequest } from './llm/prepareProviderRequest';
93
+ export type {
94
+ PreparedProviderRequest,
95
+ PrepareProviderRequestParams,
96
+ ProviderMessageProjectionMode,
97
+ ProviderPayloadMeasurement,
98
+ ProviderRequestContext,
99
+ } from './llm/prepareProviderRequest';
91
100
  export { canSealPreempt } from './llm/preempt';
92
101
  export { isThinkingEnabled, getMaxOutputTokensKey } from './llm/request';
93
102
  export {