@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.
- package/dist/cjs/agents/AgentContext.cjs +116 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +229 -48
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +102 -27
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +11 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/contextOverflowRecovery.cjs +132 -0
- package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -0
- package/dist/cjs/llm/google/index.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +60 -3
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/main.cjs +20 -7
- package/dist/cjs/messages/prune.cjs +16 -5
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +2 -0
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/run.cjs +15 -2
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +205 -49
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +55 -7
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +2 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -2
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +69 -8
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +19 -15
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +32 -3
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/utils/errors.cjs +317 -52
- package/dist/cjs/utils/errors.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +117 -3
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +227 -46
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +102 -27
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +11 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/contextOverflowRecovery.mjs +130 -0
- package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -0
- package/dist/esm/llm/google/index.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +59 -4
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/main.mjs +9 -9
- package/dist/esm/messages/prune.mjs +14 -6
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +2 -1
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/run.mjs +15 -2
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +205 -49
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +55 -7
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -3
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +3 -3
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +62 -9
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +20 -16
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +32 -3
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/utils/errors.mjs +317 -53
- package/dist/esm/utils/errors.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +62 -3
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +16 -2
- package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
- package/dist/types/llm/contextOverflowRecovery.d.ts +85 -0
- package/dist/types/llm/invoke.d.ts +35 -6
- package/dist/types/messages/prune.d.ts +10 -2
- package/dist/types/messages/recency.d.ts +1 -0
- package/dist/types/run.d.ts +2 -0
- package/dist/types/tools/CodeExecutor.d.ts +14 -1
- package/dist/types/types/llm.d.ts +7 -4
- package/dist/types/types/stream.d.ts +5 -4
- package/dist/types/types/summarize.d.ts +22 -0
- package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +40 -0
- package/dist/types/utils/errors.d.ts +65 -16
- package/dist/types/utils/redactSecrets.d.ts +3 -0
- package/package.json +7 -8
- package/src/agents/AgentContext.ts +188 -7
- package/src/agents/__tests__/AgentContext.overflow.test.ts +205 -0
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +389 -58
- package/src/graphs/MultiAgentGraph.ts +184 -46
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +631 -0
- package/src/llm/__tests__/contextOverflowRecovery.test.ts +401 -0
- package/src/llm/__tests__/fallbackOverflow.test.ts +287 -0
- package/src/llm/bedrock/index.ts +25 -12
- package/src/llm/contextOverflowRecovery.ts +292 -0
- package/src/llm/invoke.ts +119 -4
- package/src/messages/prune.ts +24 -11
- package/src/messages/recency.ts +3 -1
- package/src/run.ts +24 -1
- package/src/scripts/context-overflow-probe.ts +997 -0
- package/src/specs/agent-handoffs.test.ts +903 -1
- package/src/specs/context-overflow-recovery.live.test.ts +213 -0
- package/src/splitStream.test.ts +882 -0
- package/src/stream.ts +315 -51
- package/src/summarization/__tests__/aggregator.test.ts +83 -0
- package/src/summarization/__tests__/node.test.ts +139 -0
- package/src/summarization/node.ts +99 -14
- package/src/tools/BashExecutor.ts +4 -2
- package/src/tools/BashProgrammaticToolCalling.ts +4 -7
- package/src/tools/CodeExecutor.ts +119 -8
- package/src/tools/ProgrammaticToolCalling.ts +29 -27
- package/src/tools/ToolNode.ts +50 -8
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +297 -3
- package/src/types/llm.ts +8 -1
- package/src/types/stream.ts +5 -4
- package/src/types/summarize.ts +22 -0
- package/src/utils/__tests__/errors.test.ts +270 -0
- package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +336 -0
- package/src/utils/__tests__/redactSecrets.test.ts +56 -0
- package/src/utils/errors.ts +484 -66
- package/src/utils/redactSecrets.ts +61 -0
|
@@ -1,41 +1,256 @@
|
|
|
1
|
+
let _langchain_core_errors = require("@langchain/core/errors");
|
|
1
2
|
//#region src/utils/errors.ts
|
|
2
3
|
/**
|
|
3
|
-
* Context overflow error detection
|
|
4
|
+
* Context overflow error detection.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Providers disagree on how they report "your input is bigger than I can
|
|
7
|
+
* take" — the class thrown, the HTTP status, whether numbers are reported,
|
|
8
|
+
* and even whether it arrives as an HTTP error at all. Every pattern below
|
|
9
|
+
* was captured from a live over-limit request (see
|
|
10
|
+
* `src/scripts/context-overflow-probe.ts` and
|
|
11
|
+
* `docs/context-overflow-signatures.md`); nothing here is guessed.
|
|
12
|
+
*
|
|
13
|
+
* Consumed by the graph's overflow recovery loop, which converts a detection
|
|
14
|
+
* into a forced summarization pass instead of surfacing the error.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Fraction of the believed budget the prompt must reach before an ambiguous
|
|
18
|
+
* provider error is read as overflow. Well above normal traffic, low enough to
|
|
19
|
+
* catch the case the budget itself was miscalibrated.
|
|
9
20
|
*/
|
|
21
|
+
const CONTEXT_PRESSURE_RATIO = .8;
|
|
10
22
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
23
|
+
* Ordered most-specific first; the first match wins. Patterns that capture
|
|
24
|
+
* both numbers come before the bare-phrase fallbacks for the same provider so
|
|
25
|
+
* a recoverable error still yields the numbers when the provider reported
|
|
26
|
+
* them.
|
|
13
27
|
*/
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
const OVERFLOW_PATTERNS = [
|
|
29
|
+
(
|
|
30
|
+
/** Anthropic, and Bedrock's passthrough of the same upstream. */
|
|
31
|
+
{
|
|
32
|
+
kind: "context_window",
|
|
33
|
+
re: /prompt is too long:\s*(\d+)\s*tokens\s*>\s*(\d+)\s*maximum/i,
|
|
34
|
+
requestedGroup: 1,
|
|
35
|
+
limitGroup: 2,
|
|
36
|
+
requestedIsPromptOnly: true
|
|
37
|
+
}),
|
|
38
|
+
(
|
|
39
|
+
/**
|
|
40
|
+
* OpenAI's own wording, which measures the messages and nothing else.
|
|
41
|
+
* Ordered ahead of the shared sentence below so the prompt-only reading is
|
|
42
|
+
* preferred when OpenAI is the one answering.
|
|
43
|
+
*/
|
|
44
|
+
{
|
|
45
|
+
kind: "context_window",
|
|
46
|
+
re: /maximum context length is\s*(\d+)\s*tokens\.\s*however,\s*your messages resulted in\s*(\d+)/i,
|
|
47
|
+
limitGroup: 1,
|
|
48
|
+
requestedGroup: 2,
|
|
49
|
+
requestedIsPromptOnly: true
|
|
50
|
+
}),
|
|
51
|
+
(
|
|
52
|
+
/**
|
|
53
|
+
* OpenRouter (`you requested about`) and DeepSeek (`you requested`). Their
|
|
54
|
+
* total folds in the completion allowance — both then break it down in
|
|
55
|
+
* parentheses, which `PROMPT_ONLY_BREAKDOWN_RE` recovers.
|
|
56
|
+
*/
|
|
57
|
+
{
|
|
58
|
+
kind: "context_window",
|
|
59
|
+
re: /maximum context length is\s*(\d+)\s*tokens\.\s*however,\s*you requested(?:\s*about)?\s*(\d+)/i,
|
|
60
|
+
limitGroup: 1,
|
|
61
|
+
requestedGroup: 2
|
|
62
|
+
}),
|
|
63
|
+
(
|
|
64
|
+
/**
|
|
65
|
+
* xAI. Says "prompt length" rather than "context length", and does not say
|
|
66
|
+
* whether the count it quotes includes the completion allowance — so it is
|
|
67
|
+
* not trusted as a prompt measurement.
|
|
68
|
+
*/
|
|
69
|
+
{
|
|
70
|
+
kind: "context_window",
|
|
71
|
+
re: /maximum prompt length is\s*(\d+)\s*(?:tokens\s*)?but the request contains\s*(\d+)\s*tokens/i,
|
|
72
|
+
limitGroup: 1,
|
|
73
|
+
requestedGroup: 2
|
|
74
|
+
}),
|
|
75
|
+
(
|
|
76
|
+
/** Mistral. */
|
|
77
|
+
{
|
|
78
|
+
kind: "context_window",
|
|
79
|
+
re: /prompt contains\s*(\d+)\s*tokens[^.]*?too large for model with\s*(\d+)\s*maximum context length/i,
|
|
80
|
+
requestedGroup: 1,
|
|
81
|
+
limitGroup: 2,
|
|
82
|
+
requestedIsPromptOnly: true
|
|
83
|
+
}),
|
|
84
|
+
(
|
|
85
|
+
/** Google Gemini / Vertex. Reports the ceiling only. */
|
|
86
|
+
{
|
|
87
|
+
kind: "context_window",
|
|
88
|
+
re: /input token count exceeds the maximum number of tokens allowed\s*\((\d+)\)/i,
|
|
89
|
+
limitGroup: 1
|
|
90
|
+
}),
|
|
91
|
+
(
|
|
92
|
+
/**
|
|
93
|
+
* OpenAI's token-bucket rejection. Numbers are validated by the caller:
|
|
94
|
+
* `Requested >= Limit` means no retry can ever succeed.
|
|
95
|
+
*/
|
|
96
|
+
{
|
|
97
|
+
kind: "request_too_large",
|
|
98
|
+
re: /request too large[\s\S]*?limit\s*(\d+),\s*requested\s*(\d+)/i,
|
|
99
|
+
limitGroup: 1,
|
|
100
|
+
requestedGroup: 2
|
|
101
|
+
}),
|
|
102
|
+
(
|
|
103
|
+
/** Bedrock (Llama upstream) and any provider naming the ceiling alone. */
|
|
104
|
+
{
|
|
105
|
+
kind: "context_window",
|
|
106
|
+
re: /(?:maximum context length|maximum prompt length) is\s*(\d+)\s*tokens/i,
|
|
107
|
+
limitGroup: 1
|
|
108
|
+
}),
|
|
109
|
+
(
|
|
110
|
+
/** Bedrock (Nova upstream). */
|
|
111
|
+
{
|
|
112
|
+
kind: "context_window",
|
|
113
|
+
re: /number of input tokens exceeds maximum length|input tokens exceeded/i
|
|
114
|
+
}),
|
|
115
|
+
(
|
|
116
|
+
/** Bedrock (Claude Sonnet upstream). */
|
|
117
|
+
{
|
|
118
|
+
kind: "context_window",
|
|
119
|
+
re: /\binput (?:is )?too long(?: for requested model)?\b/i
|
|
120
|
+
}),
|
|
121
|
+
(
|
|
122
|
+
/** OpenAI-compatible error code, and the phrases LangChain itself keys on. */
|
|
123
|
+
{
|
|
124
|
+
kind: "context_window",
|
|
125
|
+
re: /context[_ ]length[_ ]exceeded|input tokens exceed the configured limit|exceeds the context window|exceeds model context window/i
|
|
126
|
+
}),
|
|
127
|
+
(
|
|
128
|
+
/** Generic long-tail phrasings observed across OpenAI-compatible gateways. */
|
|
129
|
+
{
|
|
130
|
+
kind: "context_window",
|
|
131
|
+
re: /prompt is too long|too large for model|reduce the length of (?:the |your )?(?:messages|prompt)/i
|
|
132
|
+
}),
|
|
133
|
+
(
|
|
134
|
+
/**
|
|
135
|
+
* Vertex AI. Its gaxios path discards the API error document, so an
|
|
136
|
+
* over-limit prompt arrives as a bare status line with no reason —
|
|
137
|
+
* identical to every other 400 from the same endpoint. Only a corroborated
|
|
138
|
+
* near-budget prompt makes this readable as overflow.
|
|
139
|
+
*/
|
|
140
|
+
{
|
|
141
|
+
kind: "context_window",
|
|
142
|
+
re: /google request failed with status code 400(?!\s*:)/i,
|
|
143
|
+
requiresContextPressure: true
|
|
144
|
+
})
|
|
28
145
|
];
|
|
29
146
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
147
|
+
* Errors that mention size or limits but are NOT recoverable by compaction.
|
|
148
|
+
* Checked before the positive patterns.
|
|
149
|
+
*
|
|
150
|
+
* Note the deliberate precision: OpenAI's *recoverable* "Request too large"
|
|
151
|
+
* body links to `platform.openai.com/account/rate-limits`, so a loose
|
|
152
|
+
* /rate.?limit/ test would discard the very case this module exists to catch.
|
|
153
|
+
* URLs are stripped from the haystack before matching, and genuine throttling
|
|
154
|
+
* is matched on its own distinct phrasing.
|
|
155
|
+
*/
|
|
156
|
+
const NON_RECOVERABLE_RE = /rate limit reached|requests per (?:min|day)|\brpm\b|too many requests|insufficient[_ ]quota|quota exceeded|billing|payment required|invalid[_ ]api[_ ]key|authentication|unauthorized|permission denied|forbidden/i;
|
|
157
|
+
/**
|
|
158
|
+
* Output-cap complaints. `max_tokens` appears in both families, so these are
|
|
159
|
+
* matched on the surrounding grammar rather than the bare parameter name.
|
|
160
|
+
*/
|
|
161
|
+
const OUTPUT_LIMIT_RE = /max_?(?:completion_?)?tokens\s*(?:must be|is too|cannot|exceeds|too large|greater than)|maximum number of output tokens|max_tokens.*less than or equal/i;
|
|
162
|
+
/**
|
|
163
|
+
* Recovers the input-only figure from providers that quote a combined total
|
|
164
|
+
* and then break it down — OpenRouter's "(56811 of text input, 16 in the
|
|
165
|
+
* output)" and DeepSeek's "(1179652 in the messages, 16 in the completion)".
|
|
166
|
+
*/
|
|
167
|
+
const PROMPT_ONLY_BREAKDOWN_RE = /\(\s*(\d+)\s*(?:of\s+text\s+input|in\s+the\s+messages|of\s+input|input\s+tokens)\b/i;
|
|
168
|
+
/** Broader hints for the deliberately fuzzy `isLikelyContextOverflowError`. */
|
|
169
|
+
const CONTEXT_OVERFLOW_HINT_RE = /413|payload too large|content_too_large|request entity too large|too many tokens|token count.*exceed|exceed.*token count/i;
|
|
170
|
+
const MAX_CAUSE_DEPTH = 4;
|
|
171
|
+
function asRecord(value) {
|
|
172
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Flattens an error into a single searchable string.
|
|
176
|
+
*
|
|
177
|
+
* Necessary because providers bury the useful sentence at different depths:
|
|
178
|
+
* Anthropic puts a JSON document in `message`, Mistral puts one in `body`,
|
|
179
|
+
* LangChain's `ContextOverflowError` keeps the original API error under
|
|
180
|
+
* `cause`, and the OpenAI SDK nests the body under `error`.
|
|
181
|
+
*/
|
|
182
|
+
function collectErrorText(error, depth = 0) {
|
|
183
|
+
if (error == null || depth > MAX_CAUSE_DEPTH) return "";
|
|
184
|
+
if (typeof error === "string") return error;
|
|
185
|
+
const record = asRecord(error);
|
|
186
|
+
if (record == null) return String(error);
|
|
187
|
+
const parts = [];
|
|
188
|
+
if (typeof record.message === "string") parts.push(record.message);
|
|
189
|
+
for (const reason of [
|
|
190
|
+
record.code,
|
|
191
|
+
record.type,
|
|
192
|
+
record.status,
|
|
193
|
+
record.reason
|
|
194
|
+
]) if (typeof reason === "string") parts.push(reason);
|
|
195
|
+
for (const nested of [
|
|
196
|
+
record.error,
|
|
197
|
+
record.cause,
|
|
198
|
+
record.body,
|
|
199
|
+
record.data,
|
|
200
|
+
record.response
|
|
201
|
+
]) {
|
|
202
|
+
if (nested == null) continue;
|
|
203
|
+
parts.push(typeof nested === "string" ? nested : collectErrorText(nested, depth + 1));
|
|
204
|
+
}
|
|
205
|
+
if (parts.length === 0) try {
|
|
206
|
+
/** Non-objects returned above, so this always yields a string. */
|
|
207
|
+
return JSON.stringify(error);
|
|
208
|
+
} catch {
|
|
209
|
+
return String(error);
|
|
210
|
+
}
|
|
211
|
+
return parts.join(" ");
|
|
212
|
+
}
|
|
213
|
+
/** Strips URLs so their path segments cannot trip the negative matchers. */
|
|
214
|
+
function stripUrls(text) {
|
|
215
|
+
return text.replace(/https?:\/\/\S+/gi, " ");
|
|
216
|
+
}
|
|
217
|
+
function readNumber(match, group) {
|
|
218
|
+
if (group == null) return;
|
|
219
|
+
const parsed = Number(match[group]);
|
|
220
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The provider's count of the prompt alone: its own breakdown when it gave
|
|
224
|
+
* one, otherwise the quoted total but only for providers that quote the
|
|
225
|
+
* prompt rather than the whole request. Returns undefined when the number on
|
|
226
|
+
* offer includes the completion allowance, since treating that as a prompt
|
|
227
|
+
* measurement would overstate how much the prompt has to shrink.
|
|
32
228
|
*/
|
|
33
|
-
|
|
229
|
+
function resolvePromptTokens(haystack, pattern, requestedTokens) {
|
|
230
|
+
const breakdown = haystack.match(PROMPT_ONLY_BREAKDOWN_RE);
|
|
231
|
+
if (breakdown != null) {
|
|
232
|
+
const parsed = Number(breakdown[1]);
|
|
233
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
234
|
+
}
|
|
235
|
+
return pattern.requestedIsPromptOnly === true ? requestedTokens : void 0;
|
|
236
|
+
}
|
|
34
237
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
238
|
+
* True when the caller's own accounting says the failed prompt was close
|
|
239
|
+
* enough to the budget that an otherwise ambiguous provider error is best
|
|
240
|
+
* explained by overflow.
|
|
37
241
|
*/
|
|
38
|
-
|
|
242
|
+
function hasContextPressure(context) {
|
|
243
|
+
const estimated = context?.estimatedPromptTokens;
|
|
244
|
+
const budget = context?.maxContextTokens;
|
|
245
|
+
if (estimated == null || budget == null || !Number.isFinite(estimated) || !Number.isFinite(budget) || budget <= 0) return false;
|
|
246
|
+
return estimated / budget >= CONTEXT_PRESSURE_RATIO;
|
|
247
|
+
}
|
|
248
|
+
function isLangChainOverflowError(error) {
|
|
249
|
+
if (_langchain_core_errors.ContextOverflowError.isInstance(error)) return true;
|
|
250
|
+
/** Duplicate `@langchain/core` copies break branding; the name survives. */
|
|
251
|
+
const record = asRecord(error);
|
|
252
|
+
return record?.name === "ContextOverflowError" || record?.lc_error_code === "CONTEXT_OVERFLOW";
|
|
253
|
+
}
|
|
39
254
|
/**
|
|
40
255
|
* Extracts a human-readable error message from an unknown error value.
|
|
41
256
|
*/
|
|
@@ -43,12 +258,14 @@ function extractErrorMessage(error) {
|
|
|
43
258
|
if (error == null) return "";
|
|
44
259
|
if (typeof error === "string") return error;
|
|
45
260
|
if (error instanceof Error) return error.message;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
261
|
+
const record = asRecord(error);
|
|
262
|
+
if (record == null)
|
|
263
|
+
/** Functions and symbols serialize to nothing; describe them instead. */
|
|
264
|
+
return String(error);
|
|
265
|
+
if (typeof record.message === "string") return record.message;
|
|
266
|
+
if (typeof record.error === "string") return record.error;
|
|
267
|
+
const nested = asRecord(record.error);
|
|
268
|
+
if (typeof nested?.message === "string") return nested.message;
|
|
52
269
|
try {
|
|
53
270
|
return JSON.stringify(error);
|
|
54
271
|
} catch {
|
|
@@ -56,34 +273,82 @@ function extractErrorMessage(error) {
|
|
|
56
273
|
}
|
|
57
274
|
}
|
|
58
275
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
276
|
+
* Classifies a provider error as a recoverable context overflow, returning
|
|
277
|
+
* whatever the provider disclosed about the limit and the request size.
|
|
278
|
+
*
|
|
279
|
+
* Returns `null` for anything that compaction cannot fix — genuine
|
|
280
|
+
* throttling, auth, quota, and output-token-cap errors all mention limits
|
|
281
|
+
* and must not be mistaken for overflow.
|
|
282
|
+
*
|
|
283
|
+
* The reported numbers are the point of the return value: they let the
|
|
284
|
+
* caller retarget the token budget to the provider's real ceiling instead of
|
|
285
|
+
* retrying blindly against a configured value that was evidently wrong.
|
|
286
|
+
*/
|
|
287
|
+
function getContextOverflowInfo(error, context) {
|
|
288
|
+
const provider = context?.provider;
|
|
289
|
+
const haystack = stripUrls(collectErrorText(error));
|
|
290
|
+
if (haystack === "") return null;
|
|
291
|
+
if (OUTPUT_LIMIT_RE.test(haystack)) return null;
|
|
292
|
+
const langChainFlagged = isLangChainOverflowError(error);
|
|
293
|
+
if (!langChainFlagged && NON_RECOVERABLE_RE.test(haystack)) return null;
|
|
294
|
+
const underContextPressure = hasContextPressure(context);
|
|
295
|
+
for (const pattern of OVERFLOW_PATTERNS) {
|
|
296
|
+
const match = haystack.match(pattern.re);
|
|
297
|
+
if (match == null) continue;
|
|
298
|
+
if (pattern.requiresContextPressure === true && !underContextPressure) continue;
|
|
299
|
+
const limitTokens = readNumber(match, pattern.limitGroup);
|
|
300
|
+
const requestedTokens = readNumber(match, pattern.requestedGroup);
|
|
301
|
+
/**
|
|
302
|
+
* A token-bucket rejection is only unrecoverable-by-waiting when the
|
|
303
|
+
* request alone overruns the bucket. When it merely fills it, the account
|
|
304
|
+
* was busy and the request will fit once the window drains — so equality
|
|
305
|
+
* belongs on the retry side, not the compaction side. Losing conversation
|
|
306
|
+
* history to a temporarily busy account is the worse error.
|
|
307
|
+
*/
|
|
308
|
+
if (pattern.kind === "request_too_large" && limitTokens != null && requestedTokens != null && requestedTokens <= limitTokens) return null;
|
|
309
|
+
return {
|
|
310
|
+
kind: pattern.kind,
|
|
311
|
+
limitTokens,
|
|
312
|
+
requestedTokens,
|
|
313
|
+
promptTokens: resolvePromptTokens(haystack, pattern, requestedTokens),
|
|
314
|
+
source: "pattern",
|
|
315
|
+
provider
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (langChainFlagged) return {
|
|
319
|
+
kind: "context_window",
|
|
320
|
+
source: "langchain",
|
|
321
|
+
provider
|
|
322
|
+
};
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Returns true if the error definitively indicates a context overflow.
|
|
61
327
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
328
|
+
* Accepts either a raw error or a pre-extracted message; passing the error
|
|
329
|
+
* itself is preferred, since several providers report the decisive detail in
|
|
330
|
+
* a nested body rather than in `message`.
|
|
64
331
|
*/
|
|
65
|
-
function isContextOverflowError(
|
|
66
|
-
|
|
67
|
-
const lower = errorMessage.toLowerCase();
|
|
68
|
-
if (FALSE_POSITIVE_RE.test(lower)) return false;
|
|
69
|
-
return CONTEXT_OVERFLOW_PHRASES.some((phrase) => lower.includes(phrase));
|
|
332
|
+
function isContextOverflowError(error, context) {
|
|
333
|
+
return getContextOverflowInfo(error, context) != null;
|
|
70
334
|
}
|
|
71
335
|
/**
|
|
72
|
-
* Returns true if the error
|
|
73
|
-
*
|
|
336
|
+
* Returns true if the error likely indicates a context overflow, adding
|
|
337
|
+
* body-size and token-count heuristics on top of the definitive patterns.
|
|
74
338
|
*
|
|
75
|
-
* May produce false positives
|
|
76
|
-
*
|
|
339
|
+
* May produce false positives on unusual messages. Use when the cost of
|
|
340
|
+
* being wrong is one extra compaction pass.
|
|
77
341
|
*/
|
|
78
|
-
function isLikelyContextOverflowError(
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
return CONTEXT_OVERFLOW_HINT_RE.test(
|
|
342
|
+
function isLikelyContextOverflowError(error, context) {
|
|
343
|
+
if (isContextOverflowError(error, context)) return true;
|
|
344
|
+
const haystack = stripUrls(collectErrorText(error));
|
|
345
|
+
if (haystack === "" || OUTPUT_LIMIT_RE.test(haystack)) return false;
|
|
346
|
+
if (NON_RECOVERABLE_RE.test(haystack)) return false;
|
|
347
|
+
return CONTEXT_OVERFLOW_HINT_RE.test(haystack);
|
|
84
348
|
}
|
|
85
349
|
//#endregion
|
|
86
350
|
exports.extractErrorMessage = extractErrorMessage;
|
|
351
|
+
exports.getContextOverflowInfo = getContextOverflowInfo;
|
|
87
352
|
exports.isContextOverflowError = isContextOverflowError;
|
|
88
353
|
exports.isLikelyContextOverflowError = isLikelyContextOverflowError;
|
|
89
354
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.cjs","names":[],"sources":["../../../src/utils/errors.ts"],"sourcesContent":["/**\n * Context overflow error detection utilities.\n *\n * Identifies provider-specific error messages that indicate the request\n * exceeded the model's context window. Used by the overflow recovery loop\n * to decide whether to retry with truncation/compaction vs. propagating\n * the error.\n */\n\n/**\n * Exact phrases that definitively indicate a context overflow error.\n * These are returned by various LLM providers when the prompt is too large.\n */\nconst CONTEXT_OVERFLOW_PHRASES = [\n 'request_too_large',\n 'context length exceeded',\n 'maximum context length',\n 'prompt is too long',\n 'exceeds model context window',\n 'exceeds the model',\n 'too large for model',\n 'context_length_exceeded',\n 'max_tokens',\n 'token limit',\n 'input too long',\n 'payload too large',\n 'content_too_large',\n] as const;\n\n/**\n * HTTP status codes and broader hints that suggest context overflow.\n * Used by the less-strict `isLikelyContextOverflowError`.\n */\nconst CONTEXT_OVERFLOW_HINT_RE =\n /413|too large|too long|context.*exceed|exceed.*context|token.*limit|limit.*token|prompt.*size|size.*limit|maximum.*length|length.*maximum/i;\n\n/**\n * Patterns that should NOT be treated as context overflow even if they\n * contain words like \"limit\" or \"too large\".\n */\nconst FALSE_POSITIVE_RE =\n /rate.?limit|too many requests|quota|billing|auth|permission|forbidden/i;\n\n/**\n * Extracts a human-readable error message from an unknown error value.\n */\nexport function extractErrorMessage(error: unknown): string {\n if (error == null) {\n return '';\n }\n if (typeof error === 'string') {\n return error;\n }\n if (error instanceof Error) {\n return error.message;\n }\n if (typeof error === 'object') {\n const record = error as Record<string, unknown>;\n if (typeof record.message === 'string') {\n return record.message;\n }\n if (typeof record.error === 'string') {\n return record.error;\n }\n if (\n typeof record.error === 'object' &&\n record.error != null &&\n typeof (record.error as Record<string, unknown>).message === 'string'\n ) {\n return (record.error as Record<string, unknown>).message as string;\n }\n }\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n}\n\n/**\n * Returns true if the error message definitively indicates a context\n * overflow / prompt-too-large error from the provider.\n *\n * This is the strict check: only matches known, unambiguous phrases.\n * Use this when you want high confidence before taking recovery action.\n */\nexport function isContextOverflowError(errorMessage?: string): boolean {\n if (!errorMessage) {\n return false;\n }\n const lower = errorMessage.toLowerCase();\n if (FALSE_POSITIVE_RE.test(lower)) {\n return false;\n }\n return CONTEXT_OVERFLOW_PHRASES.some((phrase) => lower.includes(phrase));\n}\n\n/**\n * Returns true if the error message likely indicates a context overflow.\n * Uses broader heuristic matching (regex) in addition to exact phrases.\n *\n * May produce false positives for unusual error messages. Use this when\n * the cost of a false positive (one extra retry) is acceptable.\n */\nexport function isLikelyContextOverflowError(errorMessage?: string): boolean {\n if (!errorMessage) {\n return false;\n }\n if (isContextOverflowError(errorMessage)) {\n return true;\n }\n const lower = errorMessage.toLowerCase();\n if (FALSE_POSITIVE_RE.test(lower)) {\n return false;\n }\n return CONTEXT_OVERFLOW_HINT_RE.test(lower);\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,2BAA2B;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAMA,MAAM,2BACJ;;;;;AAMF,MAAM,oBACJ;;;;AAKF,SAAgB,oBAAoB,OAAwB;CAC1D,IAAI,SAAS,MACX,OAAO;CAET,IAAI,OAAO,UAAU,UACnB,OAAO;CAET,IAAI,iBAAiB,OACnB,OAAO,MAAM;CAEf,IAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,SAAS;EACf,IAAI,OAAO,OAAO,YAAY,UAC5B,OAAO,OAAO;EAEhB,IAAI,OAAO,OAAO,UAAU,UAC1B,OAAO,OAAO;EAEhB,IACE,OAAO,OAAO,UAAU,YACxB,OAAO,SAAS,QAChB,OAAQ,OAAO,MAAkC,YAAY,UAE7D,OAAQ,OAAO,MAAkC;CAErD;CACA,IAAI;EACF,OAAO,KAAK,UAAU,KAAK;CAC7B,QAAQ;EACN,OAAO,OAAO,KAAK;CACrB;AACF;;;;;;;;AASA,SAAgB,uBAAuB,cAAgC;CACrE,IAAI,CAAC,cACH,OAAO;CAET,MAAM,QAAQ,aAAa,YAAY;CACvC,IAAI,kBAAkB,KAAK,KAAK,GAC9B,OAAO;CAET,OAAO,yBAAyB,MAAM,WAAW,MAAM,SAAS,MAAM,CAAC;AACzE;;;;;;;;AASA,SAAgB,6BAA6B,cAAgC;CAC3E,IAAI,CAAC,cACH,OAAO;CAET,IAAI,uBAAuB,YAAY,GACrC,OAAO;CAET,MAAM,QAAQ,aAAa,YAAY;CACvC,IAAI,kBAAkB,KAAK,KAAK,GAC9B,OAAO;CAET,OAAO,yBAAyB,KAAK,KAAK;AAC5C"}
|
|
1
|
+
{"version":3,"file":"errors.cjs","names":["ContextOverflowError"],"sources":["../../../src/utils/errors.ts"],"sourcesContent":["/**\n * Context overflow error detection.\n *\n * Providers disagree on how they report \"your input is bigger than I can\n * take\" — the class thrown, the HTTP status, whether numbers are reported,\n * and even whether it arrives as an HTTP error at all. Every pattern below\n * was captured from a live over-limit request (see\n * `src/scripts/context-overflow-probe.ts` and\n * `docs/context-overflow-signatures.md`); nothing here is guessed.\n *\n * Consumed by the graph's overflow recovery loop, which converts a detection\n * into a forced summarization pass instead of surfacing the error.\n */\nimport { ContextOverflowError } from '@langchain/core/errors';\nimport type { Providers } from '@/common';\n\n/**\n * Why the request was rejected. Both kinds are fixed by shrinking the\n * prompt, which is what makes them recoverable; they are distinguished\n * because only `context_window` tells us the model's true window.\n */\nexport type ContextOverflowKind =\n /** Input exceeded the model's context window. */\n | 'context_window'\n /**\n * A single request exceeded a per-minute token allowance. Waiting cannot\n * help — the request can never fit the bucket — so this is a payload\n * problem wearing a 429, not throttling.\n */\n | 'request_too_large';\n\nexport interface ContextOverflowInfo {\n kind: ContextOverflowKind;\n /** Ceiling the provider reported, when it named one. */\n limitTokens?: number;\n /**\n * Token count the provider attributed to the whole request. Several\n * providers fold the requested completion allowance into this number, so it\n * is not interchangeable with the prompt size.\n */\n requestedTokens?: number;\n /**\n * The prompt alone, counted by the provider — set only when the provider\n * distinguished input from output, either by reporting an input-only figure\n * or by breaking the total down. Callers comparing provider counts against\n * their own prompt estimate must use this and not `requestedTokens`, whose\n * completion component would inflate the comparison.\n */\n promptTokens?: number;\n /** Which layer produced the verdict. Surfaced in logs and asserted in tests. */\n source: 'langchain' | 'pattern';\n provider?: Providers;\n}\n\ninterface OverflowPattern {\n readonly kind: ContextOverflowKind;\n readonly re: RegExp;\n readonly limitGroup?: number;\n readonly requestedGroup?: number;\n /**\n * Whether `requestedGroup` counts the prompt alone. Providers that report a\n * combined input+completion total leave this false, and their number is\n * never used as a prompt measurement.\n */\n readonly requestedIsPromptOnly?: boolean;\n /**\n * Marks a signature that is consistent with overflow but not exclusive to\n * it, so it only counts when the caller can corroborate that the prompt was\n * actually near the budget. Without corroboration the error propagates\n * untouched rather than triggering a needless compaction.\n */\n readonly requiresContextPressure?: boolean;\n}\n\n/**\n * Fraction of the believed budget the prompt must reach before an ambiguous\n * provider error is read as overflow. Well above normal traffic, low enough to\n * catch the case the budget itself was miscalibrated.\n */\nconst CONTEXT_PRESSURE_RATIO = 0.8;\n\nexport interface ContextOverflowContext {\n provider?: Providers;\n /** Our own estimate of the prompt size for the call that failed. */\n estimatedPromptTokens?: number;\n /** The budget we believed applied when we built that prompt. */\n maxContextTokens?: number;\n}\n\n/**\n * Ordered most-specific first; the first match wins. Patterns that capture\n * both numbers come before the bare-phrase fallbacks for the same provider so\n * a recoverable error still yields the numbers when the provider reported\n * them.\n */\nconst OVERFLOW_PATTERNS: readonly OverflowPattern[] = [\n /** Anthropic, and Bedrock's passthrough of the same upstream. */\n {\n kind: 'context_window',\n re: /prompt is too long:\\s*(\\d+)\\s*tokens\\s*>\\s*(\\d+)\\s*maximum/i,\n requestedGroup: 1,\n limitGroup: 2,\n requestedIsPromptOnly: true,\n },\n /**\n * OpenAI's own wording, which measures the messages and nothing else.\n * Ordered ahead of the shared sentence below so the prompt-only reading is\n * preferred when OpenAI is the one answering.\n */\n {\n kind: 'context_window',\n re: /maximum context length is\\s*(\\d+)\\s*tokens\\.\\s*however,\\s*your messages resulted in\\s*(\\d+)/i,\n limitGroup: 1,\n requestedGroup: 2,\n requestedIsPromptOnly: true,\n },\n /**\n * OpenRouter (`you requested about`) and DeepSeek (`you requested`). Their\n * total folds in the completion allowance — both then break it down in\n * parentheses, which `PROMPT_ONLY_BREAKDOWN_RE` recovers.\n */\n {\n kind: 'context_window',\n re: /maximum context length is\\s*(\\d+)\\s*tokens\\.\\s*however,\\s*you requested(?:\\s*about)?\\s*(\\d+)/i,\n limitGroup: 1,\n requestedGroup: 2,\n },\n /**\n * xAI. Says \"prompt length\" rather than \"context length\", and does not say\n * whether the count it quotes includes the completion allowance — so it is\n * not trusted as a prompt measurement.\n */\n {\n kind: 'context_window',\n re: /maximum prompt length is\\s*(\\d+)\\s*(?:tokens\\s*)?but the request contains\\s*(\\d+)\\s*tokens/i,\n limitGroup: 1,\n requestedGroup: 2,\n },\n /** Mistral. */\n {\n kind: 'context_window',\n re: /prompt contains\\s*(\\d+)\\s*tokens[^.]*?too large for model with\\s*(\\d+)\\s*maximum context length/i,\n requestedGroup: 1,\n limitGroup: 2,\n requestedIsPromptOnly: true,\n },\n /** Google Gemini / Vertex. Reports the ceiling only. */\n {\n kind: 'context_window',\n re: /input token count exceeds the maximum number of tokens allowed\\s*\\((\\d+)\\)/i,\n limitGroup: 1,\n },\n /**\n * OpenAI's token-bucket rejection. Numbers are validated by the caller:\n * `Requested >= Limit` means no retry can ever succeed.\n */\n {\n kind: 'request_too_large',\n re: /request too large[\\s\\S]*?limit\\s*(\\d+),\\s*requested\\s*(\\d+)/i,\n limitGroup: 1,\n requestedGroup: 2,\n },\n /** Bedrock (Llama upstream) and any provider naming the ceiling alone. */\n {\n kind: 'context_window',\n re: /(?:maximum context length|maximum prompt length) is\\s*(\\d+)\\s*tokens/i,\n limitGroup: 1,\n },\n /** Bedrock (Nova upstream). */\n {\n kind: 'context_window',\n re: /number of input tokens exceeds maximum length|input tokens exceeded/i,\n },\n /** Bedrock (Claude Sonnet upstream). */\n {\n kind: 'context_window',\n re: /\\binput (?:is )?too long(?: for requested model)?\\b/i,\n },\n /** OpenAI-compatible error code, and the phrases LangChain itself keys on. */\n {\n kind: 'context_window',\n re: /context[_ ]length[_ ]exceeded|input tokens exceed the configured limit|exceeds the context window|exceeds model context window/i,\n },\n /** Generic long-tail phrasings observed across OpenAI-compatible gateways. */\n {\n kind: 'context_window',\n re: /prompt is too long|too large for model|reduce the length of (?:the |your )?(?:messages|prompt)/i,\n },\n /**\n * Vertex AI. Its gaxios path discards the API error document, so an\n * over-limit prompt arrives as a bare status line with no reason —\n * identical to every other 400 from the same endpoint. Only a corroborated\n * near-budget prompt makes this readable as overflow.\n */\n {\n kind: 'context_window',\n re: /google request failed with status code 400(?!\\s*:)/i,\n requiresContextPressure: true,\n },\n] as const;\n\n/**\n * Errors that mention size or limits but are NOT recoverable by compaction.\n * Checked before the positive patterns.\n *\n * Note the deliberate precision: OpenAI's *recoverable* \"Request too large\"\n * body links to `platform.openai.com/account/rate-limits`, so a loose\n * /rate.?limit/ test would discard the very case this module exists to catch.\n * URLs are stripped from the haystack before matching, and genuine throttling\n * is matched on its own distinct phrasing.\n */\nconst NON_RECOVERABLE_RE =\n /rate limit reached|requests per (?:min|day)|\\brpm\\b|too many requests|insufficient[_ ]quota|quota exceeded|billing|payment required|invalid[_ ]api[_ ]key|authentication|unauthorized|permission denied|forbidden/i;\n\n/**\n * Output-cap complaints. `max_tokens` appears in both families, so these are\n * matched on the surrounding grammar rather than the bare parameter name.\n */\nconst OUTPUT_LIMIT_RE =\n /max_?(?:completion_?)?tokens\\s*(?:must be|is too|cannot|exceeds|too large|greater than)|maximum number of output tokens|max_tokens.*less than or equal/i;\n\n/**\n * Recovers the input-only figure from providers that quote a combined total\n * and then break it down — OpenRouter's \"(56811 of text input, 16 in the\n * output)\" and DeepSeek's \"(1179652 in the messages, 16 in the completion)\".\n */\nconst PROMPT_ONLY_BREAKDOWN_RE =\n /\\(\\s*(\\d+)\\s*(?:of\\s+text\\s+input|in\\s+the\\s+messages|of\\s+input|input\\s+tokens)\\b/i;\n\n/** Broader hints for the deliberately fuzzy `isLikelyContextOverflowError`. */\nconst CONTEXT_OVERFLOW_HINT_RE =\n /413|payload too large|content_too_large|request entity too large|too many tokens|token count.*exceed|exceed.*token count/i;\n\nconst MAX_CAUSE_DEPTH = 4;\n\ninterface NestedErrorShape {\n message?: unknown;\n code?: unknown;\n type?: unknown;\n status?: unknown;\n reason?: unknown;\n error?: unknown;\n cause?: unknown;\n body?: unknown;\n response?: unknown;\n /** gaxios-style clients (Vertex AI) put the API error document here. */\n data?: unknown;\n}\n\nfunction asRecord(value: unknown): NestedErrorShape | undefined {\n return typeof value === 'object' && value !== null\n ? (value as NestedErrorShape)\n : undefined;\n}\n\n/**\n * Flattens an error into a single searchable string.\n *\n * Necessary because providers bury the useful sentence at different depths:\n * Anthropic puts a JSON document in `message`, Mistral puts one in `body`,\n * LangChain's `ContextOverflowError` keeps the original API error under\n * `cause`, and the OpenAI SDK nests the body under `error`.\n */\nfunction collectErrorText(error: unknown, depth = 0): string {\n if (error == null || depth > MAX_CAUSE_DEPTH) {\n return '';\n }\n if (typeof error === 'string') {\n return error;\n }\n const record = asRecord(error);\n if (record == null) {\n return String(error);\n }\n\n const parts: string[] = [];\n if (typeof record.message === 'string') {\n parts.push(record.message);\n }\n for (const reason of [\n record.code,\n record.type,\n record.status,\n record.reason,\n ]) {\n if (typeof reason === 'string') {\n parts.push(reason);\n }\n }\n for (const nested of [\n record.error,\n record.cause,\n record.body,\n record.data,\n record.response,\n ]) {\n if (nested == null) {\n continue;\n }\n parts.push(\n typeof nested === 'string' ? nested : collectErrorText(nested, depth + 1)\n );\n }\n if (parts.length === 0) {\n try {\n /** Non-objects returned above, so this always yields a string. */\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n }\n return parts.join(' ');\n}\n\n/** Strips URLs so their path segments cannot trip the negative matchers. */\nfunction stripUrls(text: string): string {\n return text.replace(/https?:\\/\\/\\S+/gi, ' ');\n}\n\nfunction readNumber(\n match: RegExpMatchArray,\n group?: number\n): number | undefined {\n if (group == null) {\n return undefined;\n }\n const parsed = Number(match[group]);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\n/**\n * The provider's count of the prompt alone: its own breakdown when it gave\n * one, otherwise the quoted total but only for providers that quote the\n * prompt rather than the whole request. Returns undefined when the number on\n * offer includes the completion allowance, since treating that as a prompt\n * measurement would overstate how much the prompt has to shrink.\n */\nfunction resolvePromptTokens(\n haystack: string,\n pattern: OverflowPattern,\n requestedTokens: number | undefined\n): number | undefined {\n const breakdown = haystack.match(PROMPT_ONLY_BREAKDOWN_RE);\n if (breakdown != null) {\n const parsed = Number(breakdown[1]);\n if (Number.isFinite(parsed) && parsed > 0) {\n return parsed;\n }\n }\n return pattern.requestedIsPromptOnly === true ? requestedTokens : undefined;\n}\n\n/**\n * True when the caller's own accounting says the failed prompt was close\n * enough to the budget that an otherwise ambiguous provider error is best\n * explained by overflow.\n */\nfunction hasContextPressure(context?: ContextOverflowContext): boolean {\n const estimated = context?.estimatedPromptTokens;\n const budget = context?.maxContextTokens;\n if (\n estimated == null ||\n budget == null ||\n !Number.isFinite(estimated) ||\n !Number.isFinite(budget) ||\n budget <= 0\n ) {\n return false;\n }\n return estimated / budget >= CONTEXT_PRESSURE_RATIO;\n}\n\nfunction isLangChainOverflowError(error: unknown): boolean {\n if (ContextOverflowError.isInstance(error)) {\n return true;\n }\n /** Duplicate `@langchain/core` copies break branding; the name survives. */\n const record = asRecord(error) as\n | { name?: unknown; lc_error_code?: unknown }\n | undefined;\n return (\n record?.name === 'ContextOverflowError' ||\n record?.lc_error_code === 'CONTEXT_OVERFLOW'\n );\n}\n\n/**\n * Extracts a human-readable error message from an unknown error value.\n */\nexport function extractErrorMessage(error: unknown): string {\n if (error == null) {\n return '';\n }\n if (typeof error === 'string') {\n return error;\n }\n if (error instanceof Error) {\n return error.message;\n }\n const record = asRecord(error);\n if (record == null) {\n /** Functions and symbols serialize to nothing; describe them instead. */\n return String(error);\n }\n if (typeof record.message === 'string') {\n return record.message;\n }\n if (typeof record.error === 'string') {\n return record.error;\n }\n const nested = asRecord(record.error);\n if (typeof nested?.message === 'string') {\n return nested.message;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n}\n\n/**\n * Classifies a provider error as a recoverable context overflow, returning\n * whatever the provider disclosed about the limit and the request size.\n *\n * Returns `null` for anything that compaction cannot fix — genuine\n * throttling, auth, quota, and output-token-cap errors all mention limits\n * and must not be mistaken for overflow.\n *\n * The reported numbers are the point of the return value: they let the\n * caller retarget the token budget to the provider's real ceiling instead of\n * retrying blindly against a configured value that was evidently wrong.\n */\nexport function getContextOverflowInfo(\n error: unknown,\n context?: ContextOverflowContext\n): ContextOverflowInfo | null {\n const provider = context?.provider;\n const haystack = stripUrls(collectErrorText(error));\n if (haystack === '') {\n return null;\n }\n\n if (OUTPUT_LIMIT_RE.test(haystack)) {\n return null;\n }\n\n const langChainFlagged = isLangChainOverflowError(error);\n if (!langChainFlagged && NON_RECOVERABLE_RE.test(haystack)) {\n return null;\n }\n\n const underContextPressure = hasContextPressure(context);\n\n for (const pattern of OVERFLOW_PATTERNS) {\n const match = haystack.match(pattern.re);\n if (match == null) {\n continue;\n }\n if (pattern.requiresContextPressure === true && !underContextPressure) {\n continue;\n }\n const limitTokens = readNumber(match, pattern.limitGroup);\n const requestedTokens = readNumber(match, pattern.requestedGroup);\n\n /**\n * A token-bucket rejection is only unrecoverable-by-waiting when the\n * request alone overruns the bucket. When it merely fills it, the account\n * was busy and the request will fit once the window drains — so equality\n * belongs on the retry side, not the compaction side. Losing conversation\n * history to a temporarily busy account is the worse error.\n */\n if (\n pattern.kind === 'request_too_large' &&\n limitTokens != null &&\n requestedTokens != null &&\n requestedTokens <= limitTokens\n ) {\n return null;\n }\n\n return {\n kind: pattern.kind,\n limitTokens,\n requestedTokens,\n promptTokens: resolvePromptTokens(haystack, pattern, requestedTokens),\n source: 'pattern',\n provider,\n };\n }\n\n if (langChainFlagged) {\n return { kind: 'context_window', source: 'langchain', provider };\n }\n\n return null;\n}\n\n/**\n * Returns true if the error definitively indicates a context overflow.\n *\n * Accepts either a raw error or a pre-extracted message; passing the error\n * itself is preferred, since several providers report the decisive detail in\n * a nested body rather than in `message`.\n */\nexport function isContextOverflowError(\n error?: unknown,\n context?: ContextOverflowContext\n): boolean {\n return getContextOverflowInfo(error, context) != null;\n}\n\n/**\n * Returns true if the error likely indicates a context overflow, adding\n * body-size and token-count heuristics on top of the definitive patterns.\n *\n * May produce false positives on unusual messages. Use when the cost of\n * being wrong is one extra compaction pass.\n */\nexport function isLikelyContextOverflowError(\n error?: unknown,\n context?: ContextOverflowContext\n): boolean {\n if (isContextOverflowError(error, context)) {\n return true;\n }\n const haystack = stripUrls(collectErrorText(error));\n if (haystack === '' || OUTPUT_LIMIT_RE.test(haystack)) {\n return false;\n }\n if (NON_RECOVERABLE_RE.test(haystack)) {\n return false;\n }\n return CONTEXT_OVERFLOW_HINT_RE.test(haystack);\n}\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { ANTHROPIC_TOOL_TOKEN_MULTIPLIER, DEFAULT_TOOL_TOKEN_MULTIPLIER } from "
|
|
|
2
2
|
import "../common/enum.mjs";
|
|
3
3
|
import "../common/index.mjs";
|
|
4
4
|
import { apportionTokenCounts } from "../utils/tokens.mjs";
|
|
5
|
-
import { createPruneMessages } from "../messages/prune.mjs";
|
|
5
|
+
import { clampCalibrationRatio, createPruneMessages } from "../messages/prune.mjs";
|
|
6
6
|
import { syncBudgetDerivedFields } from "../messages/budget.mjs";
|
|
7
7
|
import { addCacheControlToStablePrefixMessages, addTailCacheControl, buildAnthropicCacheControl, buildBedrockCachePoint, cloneMessage, resolveBedrockPromptCacheTtl, resolvePromptCacheTtl } from "../messages/cache.mjs";
|
|
8
8
|
import "../messages/index.mjs";
|
|
@@ -128,8 +128,20 @@ var AgentContext = class AgentContext {
|
|
|
128
128
|
calibrationRatio = 1;
|
|
129
129
|
/** Provider-observed instruction overhead from the pruner's best-variance turn. */
|
|
130
130
|
resolvedInstructionOverhead;
|
|
131
|
+
_pendingOriginalToolContent;
|
|
132
|
+
pendingOriginalToolContentChars = 0;
|
|
131
133
|
/** Pre-masking tool content keyed by message index, consumed by the summarize node. */
|
|
132
|
-
pendingOriginalToolContent
|
|
134
|
+
get pendingOriginalToolContent() {
|
|
135
|
+
return this._pendingOriginalToolContent;
|
|
136
|
+
}
|
|
137
|
+
set pendingOriginalToolContent(value) {
|
|
138
|
+
this._pendingOriginalToolContent = value;
|
|
139
|
+
this.pendingOriginalToolContentChars = 0;
|
|
140
|
+
if (value != null) {
|
|
141
|
+
for (const content of value.values()) this.pendingOriginalToolContentChars += content.length;
|
|
142
|
+
this.enforcePendingOriginalContentCap();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
133
145
|
/** Total instruction overhead: system message + tool schemas + pending summary. */
|
|
134
146
|
get instructionTokens() {
|
|
135
147
|
const summaryOverhead = this._summaryLocation === "user_message" ? this.summaryTokenCount : 0;
|
|
@@ -219,6 +231,25 @@ var AgentContext = class AgentContext {
|
|
|
219
231
|
*/
|
|
220
232
|
_lastSummarizationMsgCount = 0;
|
|
221
233
|
/**
|
|
234
|
+
* Forced compactions performed after a provider rejected a prompt as too
|
|
235
|
+
* large. Bounds the recovery loop so a model that keeps refusing cannot
|
|
236
|
+
* make the run compact indefinitely.
|
|
237
|
+
*/
|
|
238
|
+
_overflowRecoveryAttempts = 0;
|
|
239
|
+
/**
|
|
240
|
+
* Budget in force before the first overflow correction of the current run.
|
|
241
|
+
* Recorded so `reset()` can undo the correction for the next run without
|
|
242
|
+
* disturbing a `maxContextTokens` that no correction ever touched.
|
|
243
|
+
*/
|
|
244
|
+
_preOverflowMaxContextTokens;
|
|
245
|
+
/**
|
|
246
|
+
* Prompt size, normalized into the local counter's uncalibrated units, at
|
|
247
|
+
* the last overflow correction. Keeping both measurements in the same units
|
|
248
|
+
* lets a later overflow prove whether compaction changed anything even when
|
|
249
|
+
* the provider observation updated calibration between attempts.
|
|
250
|
+
*/
|
|
251
|
+
_lastOverflowPromptTokens;
|
|
252
|
+
/**
|
|
222
253
|
* Handoff context when this agent receives control via handoff.
|
|
223
254
|
* Contains source and parallel execution info for system message context.
|
|
224
255
|
*/
|
|
@@ -510,7 +541,7 @@ The following tools are available exclusively through the \`${programmaticTool.n
|
|
|
510
541
|
/**
|
|
511
542
|
* Reset context for a new run
|
|
512
543
|
*/
|
|
513
|
-
reset() {
|
|
544
|
+
reset(options) {
|
|
514
545
|
this.systemMessageTokens = 0;
|
|
515
546
|
this.dynamicInstructionTokens = 0;
|
|
516
547
|
this.toolSchemaTokens = 0;
|
|
@@ -528,11 +559,13 @@ The following tools are available exclusively through the \`${programmaticTool.n
|
|
|
528
559
|
this.currentTokenType = "text";
|
|
529
560
|
this.discoveredToolNames.clear();
|
|
530
561
|
this.handoffContext = void 0;
|
|
562
|
+
if (options?.preserveOriginalToolContent !== true) this.pendingOriginalToolContent = void 0;
|
|
531
563
|
this.summaryText = this._durableSummaryText;
|
|
532
564
|
this.summaryTokenCount = this._durableSummaryTokenCount;
|
|
533
565
|
this._lastSummarizationMsgCount = 0;
|
|
534
566
|
this.lastCallUsage = void 0;
|
|
535
567
|
this.totalTokensFresh = false;
|
|
568
|
+
this.restoreContextBudgetAfterOverflow();
|
|
536
569
|
if (this.tokenCounter) {
|
|
537
570
|
this.initializeSystemRunnable();
|
|
538
571
|
const baseTokenMap = { ...this.baseIndexTokenCountMap };
|
|
@@ -757,6 +790,87 @@ The following tools are available exclusively through the \`${programmaticTool.n
|
|
|
757
790
|
markSummarizationTriggered(msgCount) {
|
|
758
791
|
this._lastSummarizationMsgCount = msgCount;
|
|
759
792
|
}
|
|
793
|
+
get overflowRecoveryAttempts() {
|
|
794
|
+
return this._overflowRecoveryAttempts;
|
|
795
|
+
}
|
|
796
|
+
shouldSummarizeOverflow() {
|
|
797
|
+
return this.summarizationEnabled === true && (this.tokenCounter == null || this.maxContextTokens == null || this._overflowRecoveryAttempts > 0);
|
|
798
|
+
}
|
|
799
|
+
/** Preserves the earliest full tool output recorded for each message index. */
|
|
800
|
+
preserveOriginalToolContent(originalToolContent) {
|
|
801
|
+
if (originalToolContent == null || originalToolContent.size === 0) return;
|
|
802
|
+
if (this.pendingOriginalToolContent == null) this.pendingOriginalToolContent = /* @__PURE__ */ new Map();
|
|
803
|
+
for (const [index, content] of originalToolContent) if (!this.pendingOriginalToolContent.has(index)) {
|
|
804
|
+
this.pendingOriginalToolContent.set(index, content);
|
|
805
|
+
this.pendingOriginalToolContentChars += content.length;
|
|
806
|
+
}
|
|
807
|
+
this.enforcePendingOriginalContentCap();
|
|
808
|
+
}
|
|
809
|
+
enforcePendingOriginalContentCap() {
|
|
810
|
+
const pending = this._pendingOriginalToolContent;
|
|
811
|
+
if (pending == null) return;
|
|
812
|
+
while (this.pendingOriginalToolContentChars > 2e6 && pending.size > 0) {
|
|
813
|
+
const oldest = pending.keys().next();
|
|
814
|
+
if (oldest.done === true) break;
|
|
815
|
+
const removed = pending.get(oldest.value);
|
|
816
|
+
if (removed != null) this.pendingOriginalToolContentChars -= removed.length;
|
|
817
|
+
pending.delete(oldest.value);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Retargets the context budget after a provider rejected the prompt as too
|
|
822
|
+
* large, and clears the memoized pruner so the next call is planned against
|
|
823
|
+
* the corrected budget rather than the one that was evidently wrong.
|
|
824
|
+
*
|
|
825
|
+
* Also clears the "already summarized at this message count" guard: that
|
|
826
|
+
* guard exists to stop redundant summarization of an unchanged history, but
|
|
827
|
+
* here the history has not changed and compaction is exactly what is
|
|
828
|
+
* needed.
|
|
829
|
+
*/
|
|
830
|
+
applyContextBudgetCorrection(budgetTokens, promptTokens) {
|
|
831
|
+
if (this._overflowRecoveryAttempts === 0) this._preOverflowMaxContextTokens = this.maxContextTokens;
|
|
832
|
+
if (budgetTokens != null) this.maxContextTokens = budgetTokens;
|
|
833
|
+
this.pruneMessages = void 0;
|
|
834
|
+
this._lastSummarizationMsgCount = 0;
|
|
835
|
+
this._lastOverflowPromptTokens = promptTokens != null ? this.normalizePromptTokens(promptTokens) : promptTokens;
|
|
836
|
+
this._overflowRecoveryAttempts += 1;
|
|
837
|
+
}
|
|
838
|
+
/** Applies token calibration only when the observation came from this provider. */
|
|
839
|
+
applyObservedOverflowCalibration(provider, observedCalibrationRatio) {
|
|
840
|
+
if (provider !== this.provider || observedCalibrationRatio == null || observedCalibrationRatio <= 0) return;
|
|
841
|
+
this.calibrationRatio = clampCalibrationRatio(observedCalibrationRatio);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* True when a previous correction failed to make the prompt any smaller —
|
|
845
|
+
* the signature of a state nothing can compact further (an emptied message
|
|
846
|
+
* list carrying its content in an injected summary, for example). Retrying
|
|
847
|
+
* from there resends a byte-identical prompt, so the caller should stop.
|
|
848
|
+
*/
|
|
849
|
+
overflowRecoveryStalled(currentPromptTokens) {
|
|
850
|
+
const previous = this._lastOverflowPromptTokens;
|
|
851
|
+
if (previous == null || currentPromptTokens == null || !Number.isFinite(currentPromptTokens)) return false;
|
|
852
|
+
return this.normalizePromptTokens(currentPromptTokens) >= previous;
|
|
853
|
+
}
|
|
854
|
+
normalizePromptTokens(promptTokens) {
|
|
855
|
+
if (this.calibrationRatio <= 0) return promptTokens;
|
|
856
|
+
const messageTokens = Math.max(0, promptTokens - this.instructionTokens);
|
|
857
|
+
return this.instructionTokens + messageTokens / this.calibrationRatio;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Undoes overflow corrections so a reused context starts the next run with
|
|
861
|
+
* the budget it was configured with and a fresh recovery allowance.
|
|
862
|
+
*
|
|
863
|
+
* Without this, a single overflow would permanently shrink the budget for
|
|
864
|
+
* every later turn, and two would exhaust the per-run allowance for the
|
|
865
|
+
* lifetime of the context.
|
|
866
|
+
*/
|
|
867
|
+
restoreContextBudgetAfterOverflow() {
|
|
868
|
+
if (this._overflowRecoveryAttempts === 0) return;
|
|
869
|
+
this.maxContextTokens = this._preOverflowMaxContextTokens;
|
|
870
|
+
this._preOverflowMaxContextTokens = void 0;
|
|
871
|
+
this._lastOverflowPromptTokens = void 0;
|
|
872
|
+
this._overflowRecoveryAttempts = 0;
|
|
873
|
+
}
|
|
760
874
|
clearSummary() {
|
|
761
875
|
if (this.summaryText != null) {
|
|
762
876
|
this.summaryText = void 0;
|