@juspay/neurolink 10.10.3 → 10.10.4
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/CHANGELOG.md +6 -0
- package/dist/browser/neurolink.min.js +198 -198
- package/dist/context/stepBudgetGuard.js +32 -3
- package/dist/lib/context/stepBudgetGuard.js +32 -3
- package/dist/lib/utils/tokenEstimation.d.ts +1 -1
- package/dist/lib/utils/tokenEstimation.js +1 -1
- package/dist/utils/tokenEstimation.d.ts +1 -1
- package/dist/utils/tokenEstimation.js +1 -1
- package/package.json +2 -1
|
@@ -46,6 +46,22 @@ const TOKENS_PER_TOOL_DEFINITION = 200;
|
|
|
46
46
|
const MAX_CALIBRATION_RATIO = 3;
|
|
47
47
|
/** Messages at the end of the conversation the guard never modifies. */
|
|
48
48
|
const PROTECTED_TAIL_MESSAGES = 4;
|
|
49
|
+
/**
|
|
50
|
+
* Fraction of the context window the guard reclaims DOWN TO once it fires.
|
|
51
|
+
*
|
|
52
|
+
* The high-water mark (`thresholdRatio`) decides *when* to act; this low-water
|
|
53
|
+
* mark decides *how far*. Reclaiming only back to the threshold meant the very
|
|
54
|
+
* next step — which appends an assistant turn plus its tool results — crossed
|
|
55
|
+
* it again, so the guard mutated the message prefix on every single step of a
|
|
56
|
+
* long agentic run. Each of those mutations invalidates the Anthropic
|
|
57
|
+
* `cache_control` prefix from the edit point onward (see
|
|
58
|
+
* anthropicCacheBreakpoints), turning a ~0.1x cached read into full-price
|
|
59
|
+
* input every step.
|
|
60
|
+
*
|
|
61
|
+
* One deeper reclaim every N steps saves the same tokens and leaves the prefix
|
|
62
|
+
* stable in between, which is what makes the cache worth having.
|
|
63
|
+
*/
|
|
64
|
+
const CONTEXT_GUARD_LOW_WATER_RATIO = 0.6;
|
|
49
65
|
/** Stage-1 preview budget for an old tool output (bytes). */
|
|
50
66
|
const OLD_TOOL_OUTPUT_PREVIEW_BYTES = 2_048;
|
|
51
67
|
/** Stage-1 preview budget for an old tool output (lines). */
|
|
@@ -295,14 +311,22 @@ export function createStepBudgetGuard(config) {
|
|
|
295
311
|
lastRawEstimate = rawEstimate;
|
|
296
312
|
return undefined;
|
|
297
313
|
}
|
|
314
|
+
// Reclaim down to the LOW-WATER mark, not merely back under the threshold.
|
|
315
|
+
// See CONTEXT_GUARD_LOW_WATER_RATIO: stopping at the threshold guaranteed
|
|
316
|
+
// the next step crossed it again, mutating the cached prefix every step.
|
|
317
|
+
const lowWaterTokens = Math.floor((availableInput * CONTEXT_GUARD_LOW_WATER_RATIO) / calibration);
|
|
298
318
|
// Stage 1: shrink old tool outputs to previews.
|
|
299
319
|
const stage1 = truncateOldToolOutputs([...messages]);
|
|
300
320
|
let compacted = stage1.messages;
|
|
301
321
|
let newEstimate = overheadTokens + estimateStepMessagesTokens(compacted, provider);
|
|
302
|
-
// Stage 2: drop oldest complete tool exchanges
|
|
322
|
+
// Stage 2: drop oldest complete tool exchanges until under the low-water
|
|
323
|
+
// mark. Stage order is deliberately unchanged — truncating first preserves
|
|
324
|
+
// a preview of each output, and since BOTH stages edit the oldest messages
|
|
325
|
+
// the cache prefix is invalidated at roughly the same point either way.
|
|
326
|
+
// Frequency, not stage order, is what governs cache retention here.
|
|
303
327
|
let droppedExchanges = 0;
|
|
304
|
-
if (newEstimate >
|
|
305
|
-
const stage2 = dropOldestToolExchanges(compacted,
|
|
328
|
+
if (newEstimate > lowWaterTokens) {
|
|
329
|
+
const stage2 = dropOldestToolExchanges(compacted, lowWaterTokens, overheadTokens, provider);
|
|
306
330
|
compacted = stage2.messages;
|
|
307
331
|
droppedExchanges = stage2.droppedExchanges;
|
|
308
332
|
newEstimate =
|
|
@@ -317,8 +341,13 @@ export function createStepBudgetGuard(config) {
|
|
|
317
341
|
model,
|
|
318
342
|
estimatedTokens: rawEstimate,
|
|
319
343
|
thresholdTokens: effectiveThreshold,
|
|
344
|
+
lowWaterTokens,
|
|
320
345
|
calibration,
|
|
321
346
|
afterTokens: newEstimate,
|
|
347
|
+
// Headroom reclaimed below the firing threshold. Roughly how many further
|
|
348
|
+
// steps can run before the guard mutates the prefix again — a value near
|
|
349
|
+
// zero means the cache is being invalidated every step.
|
|
350
|
+
headroomTokens: effectiveThreshold - newEstimate,
|
|
322
351
|
toolOutputsTruncated: stage1.truncated,
|
|
323
352
|
exchangesDropped: droppedExchanges,
|
|
324
353
|
});
|
|
@@ -46,6 +46,22 @@ const TOKENS_PER_TOOL_DEFINITION = 200;
|
|
|
46
46
|
const MAX_CALIBRATION_RATIO = 3;
|
|
47
47
|
/** Messages at the end of the conversation the guard never modifies. */
|
|
48
48
|
const PROTECTED_TAIL_MESSAGES = 4;
|
|
49
|
+
/**
|
|
50
|
+
* Fraction of the context window the guard reclaims DOWN TO once it fires.
|
|
51
|
+
*
|
|
52
|
+
* The high-water mark (`thresholdRatio`) decides *when* to act; this low-water
|
|
53
|
+
* mark decides *how far*. Reclaiming only back to the threshold meant the very
|
|
54
|
+
* next step — which appends an assistant turn plus its tool results — crossed
|
|
55
|
+
* it again, so the guard mutated the message prefix on every single step of a
|
|
56
|
+
* long agentic run. Each of those mutations invalidates the Anthropic
|
|
57
|
+
* `cache_control` prefix from the edit point onward (see
|
|
58
|
+
* anthropicCacheBreakpoints), turning a ~0.1x cached read into full-price
|
|
59
|
+
* input every step.
|
|
60
|
+
*
|
|
61
|
+
* One deeper reclaim every N steps saves the same tokens and leaves the prefix
|
|
62
|
+
* stable in between, which is what makes the cache worth having.
|
|
63
|
+
*/
|
|
64
|
+
const CONTEXT_GUARD_LOW_WATER_RATIO = 0.6;
|
|
49
65
|
/** Stage-1 preview budget for an old tool output (bytes). */
|
|
50
66
|
const OLD_TOOL_OUTPUT_PREVIEW_BYTES = 2_048;
|
|
51
67
|
/** Stage-1 preview budget for an old tool output (lines). */
|
|
@@ -295,14 +311,22 @@ export function createStepBudgetGuard(config) {
|
|
|
295
311
|
lastRawEstimate = rawEstimate;
|
|
296
312
|
return undefined;
|
|
297
313
|
}
|
|
314
|
+
// Reclaim down to the LOW-WATER mark, not merely back under the threshold.
|
|
315
|
+
// See CONTEXT_GUARD_LOW_WATER_RATIO: stopping at the threshold guaranteed
|
|
316
|
+
// the next step crossed it again, mutating the cached prefix every step.
|
|
317
|
+
const lowWaterTokens = Math.floor((availableInput * CONTEXT_GUARD_LOW_WATER_RATIO) / calibration);
|
|
298
318
|
// Stage 1: shrink old tool outputs to previews.
|
|
299
319
|
const stage1 = truncateOldToolOutputs([...messages]);
|
|
300
320
|
let compacted = stage1.messages;
|
|
301
321
|
let newEstimate = overheadTokens + estimateStepMessagesTokens(compacted, provider);
|
|
302
|
-
// Stage 2: drop oldest complete tool exchanges
|
|
322
|
+
// Stage 2: drop oldest complete tool exchanges until under the low-water
|
|
323
|
+
// mark. Stage order is deliberately unchanged — truncating first preserves
|
|
324
|
+
// a preview of each output, and since BOTH stages edit the oldest messages
|
|
325
|
+
// the cache prefix is invalidated at roughly the same point either way.
|
|
326
|
+
// Frequency, not stage order, is what governs cache retention here.
|
|
303
327
|
let droppedExchanges = 0;
|
|
304
|
-
if (newEstimate >
|
|
305
|
-
const stage2 = dropOldestToolExchanges(compacted,
|
|
328
|
+
if (newEstimate > lowWaterTokens) {
|
|
329
|
+
const stage2 = dropOldestToolExchanges(compacted, lowWaterTokens, overheadTokens, provider);
|
|
306
330
|
compacted = stage2.messages;
|
|
307
331
|
droppedExchanges = stage2.droppedExchanges;
|
|
308
332
|
newEstimate =
|
|
@@ -317,8 +341,13 @@ export function createStepBudgetGuard(config) {
|
|
|
317
341
|
model,
|
|
318
342
|
estimatedTokens: rawEstimate,
|
|
319
343
|
thresholdTokens: effectiveThreshold,
|
|
344
|
+
lowWaterTokens,
|
|
320
345
|
calibration,
|
|
321
346
|
afterTokens: newEstimate,
|
|
347
|
+
// Headroom reclaimed below the firing threshold. Roughly how many further
|
|
348
|
+
// steps can run before the guard mutates the prefix again — a value near
|
|
349
|
+
// zero means the cache is being invalidated every step.
|
|
350
|
+
headroomTokens: effectiveThreshold - newEstimate,
|
|
322
351
|
toolOutputsTruncated: stage1.truncated,
|
|
323
352
|
exchangesDropped: droppedExchanges,
|
|
324
353
|
});
|
|
@@ -52,7 +52,7 @@ export declare function estimateTokens(text: string, provider?: string, isCode?:
|
|
|
52
52
|
* Includes message framing overhead.
|
|
53
53
|
*
|
|
54
54
|
* Counts `content` AND `args`. A `tool_call` is persisted with an EMPTY
|
|
55
|
-
* `content` and its entire payload in `args` (see
|
|
55
|
+
* `content` and its entire payload in `args` (see flushPendingToolData),
|
|
56
56
|
* so a content-only estimate scored a 39 KB Write call at ~28 tokens against a
|
|
57
57
|
* real cost near 9,750 — the budget checker, the compaction trigger and the
|
|
58
58
|
* summarization threshold were all blind to the single largest source of
|
|
@@ -109,7 +109,7 @@ function serializeForEstimate(value) {
|
|
|
109
109
|
* Includes message framing overhead.
|
|
110
110
|
*
|
|
111
111
|
* Counts `content` AND `args`. A `tool_call` is persisted with an EMPTY
|
|
112
|
-
* `content` and its entire payload in `args` (see
|
|
112
|
+
* `content` and its entire payload in `args` (see flushPendingToolData),
|
|
113
113
|
* so a content-only estimate scored a 39 KB Write call at ~28 tokens against a
|
|
114
114
|
* real cost near 9,750 — the budget checker, the compaction trigger and the
|
|
115
115
|
* summarization threshold were all blind to the single largest source of
|
|
@@ -52,7 +52,7 @@ export declare function estimateTokens(text: string, provider?: string, isCode?:
|
|
|
52
52
|
* Includes message framing overhead.
|
|
53
53
|
*
|
|
54
54
|
* Counts `content` AND `args`. A `tool_call` is persisted with an EMPTY
|
|
55
|
-
* `content` and its entire payload in `args` (see
|
|
55
|
+
* `content` and its entire payload in `args` (see flushPendingToolData),
|
|
56
56
|
* so a content-only estimate scored a 39 KB Write call at ~28 tokens against a
|
|
57
57
|
* real cost near 9,750 — the budget checker, the compaction trigger and the
|
|
58
58
|
* summarization threshold were all blind to the single largest source of
|
|
@@ -109,7 +109,7 @@ function serializeForEstimate(value) {
|
|
|
109
109
|
* Includes message framing overhead.
|
|
110
110
|
*
|
|
111
111
|
* Counts `content` AND `args`. A `tool_call` is persisted with an EMPTY
|
|
112
|
-
* `content` and its entire payload in `args` (see
|
|
112
|
+
* `content` and its entire payload in `args` (see flushPendingToolData),
|
|
113
113
|
* so a content-only estimate scored a 39 KB Write call at ~28 tokens against a
|
|
114
114
|
* real cost near 9,750 — the budget checker, the compaction trigger and the
|
|
115
115
|
* summarization threshold were all blind to the single largest source of
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "10.10.
|
|
3
|
+
"version": "10.10.4",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"test:memory": "npx tsx test/continuous-test-suite-memory.ts",
|
|
85
85
|
"test:tool-pairing": "npx tsx test/continuous-test-suite-tool-pairing.ts",
|
|
86
86
|
"test:token-accounting": "npx tsx test/continuous-test-suite-token-accounting.ts",
|
|
87
|
+
"test:step-guard": "npx tsx test/continuous-test-suite-step-guard.ts",
|
|
87
88
|
"test:middleware": "npx tsx test/continuous-test-suite-middleware.ts",
|
|
88
89
|
"test:observability": "npx tsx test/continuous-test-suite-observability.ts",
|
|
89
90
|
"test:ppt": "npx tsx test/continuous-test-suite-ppt.ts",
|