@librechat/agents 3.3.0 → 3.3.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 (129) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +116 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +2 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +229 -48
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +102 -27
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/bedrock/index.cjs +11 -2
  10. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  11. package/dist/cjs/llm/contextOverflowRecovery.cjs +132 -0
  12. package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -0
  13. package/dist/cjs/llm/google/index.cjs +1 -1
  14. package/dist/cjs/llm/invoke.cjs +60 -3
  15. package/dist/cjs/llm/invoke.cjs.map +1 -1
  16. package/dist/cjs/main.cjs +20 -7
  17. package/dist/cjs/messages/prune.cjs +16 -5
  18. package/dist/cjs/messages/prune.cjs.map +1 -1
  19. package/dist/cjs/messages/recency.cjs +2 -0
  20. package/dist/cjs/messages/recency.cjs.map +1 -1
  21. package/dist/cjs/run.cjs +15 -2
  22. package/dist/cjs/run.cjs.map +1 -1
  23. package/dist/cjs/stream.cjs +205 -49
  24. package/dist/cjs/stream.cjs.map +1 -1
  25. package/dist/cjs/summarization/node.cjs +55 -7
  26. package/dist/cjs/summarization/node.cjs.map +1 -1
  27. package/dist/cjs/tools/BashExecutor.cjs +2 -2
  28. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  29. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -2
  30. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  31. package/dist/cjs/tools/CodeExecutor.cjs +69 -8
  32. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  33. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +19 -15
  34. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  35. package/dist/cjs/tools/ToolNode.cjs +32 -3
  36. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  37. package/dist/cjs/utils/errors.cjs +317 -52
  38. package/dist/cjs/utils/errors.cjs.map +1 -1
  39. package/dist/esm/agents/AgentContext.mjs +117 -3
  40. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  41. package/dist/esm/common/enum.mjs +2 -0
  42. package/dist/esm/common/enum.mjs.map +1 -1
  43. package/dist/esm/graphs/Graph.mjs +227 -46
  44. package/dist/esm/graphs/Graph.mjs.map +1 -1
  45. package/dist/esm/graphs/MultiAgentGraph.mjs +102 -27
  46. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  47. package/dist/esm/llm/bedrock/index.mjs +11 -2
  48. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  49. package/dist/esm/llm/contextOverflowRecovery.mjs +130 -0
  50. package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -0
  51. package/dist/esm/llm/google/index.mjs +1 -1
  52. package/dist/esm/llm/invoke.mjs +59 -4
  53. package/dist/esm/llm/invoke.mjs.map +1 -1
  54. package/dist/esm/main.mjs +9 -9
  55. package/dist/esm/messages/prune.mjs +14 -6
  56. package/dist/esm/messages/prune.mjs.map +1 -1
  57. package/dist/esm/messages/recency.mjs +2 -1
  58. package/dist/esm/messages/recency.mjs.map +1 -1
  59. package/dist/esm/run.mjs +15 -2
  60. package/dist/esm/run.mjs.map +1 -1
  61. package/dist/esm/stream.mjs +205 -49
  62. package/dist/esm/stream.mjs.map +1 -1
  63. package/dist/esm/summarization/node.mjs +55 -7
  64. package/dist/esm/summarization/node.mjs.map +1 -1
  65. package/dist/esm/tools/BashExecutor.mjs +3 -3
  66. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  67. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +3 -3
  68. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  69. package/dist/esm/tools/CodeExecutor.mjs +62 -9
  70. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  71. package/dist/esm/tools/ProgrammaticToolCalling.mjs +20 -16
  72. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  73. package/dist/esm/tools/ToolNode.mjs +32 -3
  74. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  75. package/dist/esm/utils/errors.mjs +317 -53
  76. package/dist/esm/utils/errors.mjs.map +1 -1
  77. package/dist/types/agents/AgentContext.d.ts +62 -3
  78. package/dist/types/common/enum.d.ts +2 -0
  79. package/dist/types/graphs/Graph.d.ts +16 -2
  80. package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
  81. package/dist/types/llm/contextOverflowRecovery.d.ts +85 -0
  82. package/dist/types/llm/invoke.d.ts +35 -6
  83. package/dist/types/messages/prune.d.ts +10 -2
  84. package/dist/types/messages/recency.d.ts +1 -0
  85. package/dist/types/run.d.ts +2 -0
  86. package/dist/types/tools/CodeExecutor.d.ts +14 -1
  87. package/dist/types/types/llm.d.ts +7 -4
  88. package/dist/types/types/stream.d.ts +5 -4
  89. package/dist/types/types/summarize.d.ts +22 -0
  90. package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +40 -0
  91. package/dist/types/utils/errors.d.ts +65 -16
  92. package/dist/types/utils/redactSecrets.d.ts +3 -0
  93. package/package.json +7 -8
  94. package/src/agents/AgentContext.ts +188 -7
  95. package/src/agents/__tests__/AgentContext.overflow.test.ts +205 -0
  96. package/src/common/enum.ts +2 -0
  97. package/src/graphs/Graph.ts +389 -58
  98. package/src/graphs/MultiAgentGraph.ts +184 -46
  99. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +631 -0
  100. package/src/llm/__tests__/contextOverflowRecovery.test.ts +401 -0
  101. package/src/llm/__tests__/fallbackOverflow.test.ts +287 -0
  102. package/src/llm/bedrock/index.ts +25 -12
  103. package/src/llm/contextOverflowRecovery.ts +292 -0
  104. package/src/llm/invoke.ts +119 -4
  105. package/src/messages/prune.ts +24 -11
  106. package/src/messages/recency.ts +3 -1
  107. package/src/run.ts +24 -1
  108. package/src/scripts/context-overflow-probe.ts +997 -0
  109. package/src/specs/agent-handoffs.test.ts +903 -1
  110. package/src/specs/context-overflow-recovery.live.test.ts +213 -0
  111. package/src/splitStream.test.ts +882 -0
  112. package/src/stream.ts +315 -51
  113. package/src/summarization/__tests__/aggregator.test.ts +83 -0
  114. package/src/summarization/__tests__/node.test.ts +139 -0
  115. package/src/summarization/node.ts +99 -14
  116. package/src/tools/BashExecutor.ts +4 -2
  117. package/src/tools/BashProgrammaticToolCalling.ts +4 -7
  118. package/src/tools/CodeExecutor.ts +119 -8
  119. package/src/tools/ProgrammaticToolCalling.ts +29 -27
  120. package/src/tools/ToolNode.ts +50 -8
  121. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +297 -3
  122. package/src/types/llm.ts +8 -1
  123. package/src/types/stream.ts +5 -4
  124. package/src/types/summarize.ts +22 -0
  125. package/src/utils/__tests__/errors.test.ts +270 -0
  126. package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +336 -0
  127. package/src/utils/__tests__/redactSecrets.test.ts +56 -0
  128. package/src/utils/errors.ts +484 -66
  129. package/src/utils/redactSecrets.ts +61 -0
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Live end-to-end verification that a real provider rejection for an
3
+ * over-limit prompt is absorbed by forced compaction instead of surfacing.
4
+ *
5
+ * Each case deliberately configures `maxContextTokens` far above the model's
6
+ * real window, so the proactive pruner does not prevent the overflow and the
7
+ * reactive recovery path is what gets exercised.
8
+ *
9
+ * Run with:
10
+ * RUN_CONTEXT_OVERFLOW_LIVE_TESTS=1 OPENROUTER_API_KEY=... npm test -- context-overflow-recovery.live.test.ts --runInBand
11
+ *
12
+ * Cases self-skip when their provider credentials are absent. Models are the
13
+ * smallest-window, cheapest option per provider — the prompt has to exceed
14
+ * the window for the test to mean anything, so a large window is a large bill.
15
+ *
16
+ * The Bedrock case needs `NODE_OPTIONS='--experimental-vm-modules'`; the AWS
17
+ * SDK uses a dynamic import that jest otherwise refuses.
18
+ */
19
+ import { config as dotenvConfig } from 'dotenv';
20
+ dotenvConfig();
21
+
22
+ import { HumanMessage } from '@langchain/core/messages';
23
+ import { describe, expect, it, jest } from '@jest/globals';
24
+ import type { BaseMessage } from '@langchain/core/messages';
25
+ import type * as t from '@/types';
26
+ import { createTokenCounter } from '@/utils/tokens';
27
+ import { hasAnyEnv, hasEnv } from '@/specs/spec.utils';
28
+ import { GraphEvents, Providers } from '@/common';
29
+ import { Run } from '@/run';
30
+
31
+ jest.setTimeout(300_000);
32
+
33
+ const liveEnabled = process.env.RUN_CONTEXT_OVERFLOW_LIVE_TESTS === '1';
34
+
35
+ interface LiveCase {
36
+ label: string;
37
+ provider: Providers;
38
+ model: string;
39
+ /** The model's real input window. */
40
+ contextWindow: number;
41
+ envKeys: readonly string[];
42
+ clientOptions: Record<string, unknown>;
43
+ }
44
+
45
+ const LIVE_CASES: readonly LiveCase[] = [
46
+ {
47
+ label: 'openrouter / qwen-2.5-7b (32k window)',
48
+ provider: Providers.OPENROUTER,
49
+ model: 'qwen/qwen-2.5-7b-instruct',
50
+ contextWindow: 32_768,
51
+ envKeys: ['OPENROUTER_API_KEY'],
52
+ clientOptions: {
53
+ apiKey: process.env.OPENROUTER_API_KEY,
54
+ configuration: {
55
+ baseURL:
56
+ process.env.OPENROUTER_BASE_URL ?? 'https://openrouter.ai/api/v1',
57
+ },
58
+ },
59
+ },
60
+ {
61
+ /**
62
+ * Google's text models all carry a ~1M window, which would make this the
63
+ * most expensive case in the file by an order of magnitude. This model
64
+ * accepts ordinary text and caps its input at 64k, so it exercises the
65
+ * same Gemini API rejection for a fraction of the tokens.
66
+ */
67
+ label: 'google / gemini-3.1-flash-image (64k window)',
68
+ provider: Providers.GOOGLE,
69
+ model: 'gemini-3.1-flash-image',
70
+ contextWindow: 65_536,
71
+ envKeys: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
72
+ clientOptions: {
73
+ apiKey: process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY,
74
+ },
75
+ },
76
+ {
77
+ label: 'anthropic / haiku (200k window)',
78
+ provider: Providers.ANTHROPIC,
79
+ model: 'claude-haiku-4-5-20251001',
80
+ contextWindow: 200_000,
81
+ envKeys: ['ANTHROPIC_API_KEY'],
82
+ clientOptions: { apiKey: process.env.ANTHROPIC_API_KEY },
83
+ },
84
+ {
85
+ label: 'bedrock / haiku (200k window)',
86
+ provider: Providers.BEDROCK,
87
+ model: 'us.anthropic.claude-haiku-4-5-20251001-v1:0',
88
+ contextWindow: 200_000,
89
+ envKeys: ['BEDROCK_AWS_ACCESS_KEY_ID', 'AWS_ACCESS_KEY_ID'],
90
+ clientOptions: {
91
+ region:
92
+ process.env.BEDROCK_AWS_REGION ?? process.env.AWS_REGION ?? 'us-east-1',
93
+ },
94
+ },
95
+ ];
96
+
97
+ /**
98
+ * Single-token words, so an N-word message is at least N tokens under every
99
+ * provider's tokenizer — the prompt must clear the window with certainty,
100
+ * since a request that squeaks under it is billed in full and proves nothing.
101
+ */
102
+ const FILLER =
103
+ 'the quick brown fox jumps over lazy dog and then runs past'.split(' ');
104
+
105
+ function buildOversizedHistory(totalWords: number): BaseMessage[] {
106
+ const perMessage = Math.ceil(totalWords / 4);
107
+ const messages: BaseMessage[] = [];
108
+ for (let turn = 0; turn < 4; turn++) {
109
+ const words: string[] = new Array(perMessage);
110
+ for (let i = 0; i < perMessage; i++) {
111
+ words[i] = FILLER[(i + turn) % FILLER.length];
112
+ }
113
+ messages.push(new HumanMessage(`Notes part ${turn}: ${words.join(' ')}`));
114
+ }
115
+ messages.push(
116
+ new HumanMessage('In one short sentence, what were those notes about?')
117
+ );
118
+ return messages;
119
+ }
120
+
121
+ const describeIfLive = liveEnabled ? describe : describe.skip;
122
+
123
+ describeIfLive('context overflow recovery (live)', () => {
124
+ for (const testCase of LIVE_CASES) {
125
+ const runnable = hasAnyEnv(testCase.envKeys);
126
+ const maybeIt = runnable ? it : it.skip;
127
+
128
+ maybeIt(
129
+ `recovers without surfacing an error — ${testCase.label}`,
130
+ async () => {
131
+ const summarizeEvents: t.SummarizeCompleteEvent[] = [];
132
+ const tokenCounter = await createTokenCounter();
133
+
134
+ const run = await Run.create<t.IState>({
135
+ runId: `overflow-live-${testCase.provider}-${Date.now()}`,
136
+ graphConfig: {
137
+ type: 'standard',
138
+ llmConfig: {
139
+ provider: testCase.provider,
140
+ ...testCase.clientOptions,
141
+ model: testCase.model,
142
+ } as t.LLMConfig,
143
+ /**
144
+ * Deliberately wrong, by a wide margin: the pruner will happily
145
+ * build a prompt the provider cannot accept, which is exactly the
146
+ * situation the recovery path exists for.
147
+ */
148
+ maxContextTokens: testCase.contextWindow * 2,
149
+ summarizationEnabled: true,
150
+ summarizationConfig: {
151
+ provider: testCase.provider,
152
+ model: testCase.model,
153
+ },
154
+ },
155
+ returnContent: true,
156
+ /**
157
+ * The assertions below read post-run `AgentContext` state, and the
158
+ * default cleanup path calls `clearHeavyState()` → `reset()`, which
159
+ * deliberately undoes the overflow correction for the next turn.
160
+ */
161
+ skipCleanup: true,
162
+ tokenCounter,
163
+ customHandlers: {
164
+ [GraphEvents.ON_SUMMARIZE_COMPLETE]: {
165
+ handle: (_event: string, data: t.StreamEventData): void => {
166
+ summarizeEvents.push(
167
+ data as unknown as t.SummarizeCompleteEvent
168
+ );
169
+ },
170
+ },
171
+ },
172
+ });
173
+
174
+ const messages = buildOversizedHistory(
175
+ Math.ceil(testCase.contextWindow * 1.3)
176
+ );
177
+
178
+ const content = await run.processStream({ messages }, {
179
+ configurable: { thread_id: `overflow-live-${testCase.provider}` },
180
+ streamMode: 'values',
181
+ version: 'v2',
182
+ } as never);
183
+
184
+ expect(content).toBeDefined();
185
+ expect(Array.isArray(content)).toBe(true);
186
+ expect((content ?? []).length).toBeGreaterThan(0);
187
+
188
+ const agentContext = run.Graph?.agentContexts.get('default');
189
+ expect(agentContext?.overflowRecoveryAttempts).toBeGreaterThan(0);
190
+ /** The corrected budget must be below the fiction we started with. */
191
+ expect(agentContext?.maxContextTokens).toBeLessThan(
192
+ testCase.contextWindow * 2
193
+ );
194
+ /**
195
+ * No summarization assertion: the first recovery compacts by
196
+ * compressing tool output, so a run that fits after compression alone
197
+ * never spends a summarization call. Any summary event that does
198
+ * arrive must at least carry no error.
199
+ */
200
+ for (const event of summarizeEvents) {
201
+ expect(event.error).toBeUndefined();
202
+ }
203
+ }
204
+ );
205
+ }
206
+
207
+ it('has at least one runnable provider configured', () => {
208
+ const anyRunnable = LIVE_CASES.some((testCase) =>
209
+ testCase.envKeys.some(hasEnv)
210
+ );
211
+ expect(anyRunnable).toBe(true);
212
+ });
213
+ });