@sayknow-cli/agent-core 0.5.6 → 0.5.8
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 +7 -1
- package/dist/types/compaction/adaptive.d.ts +50 -0
- package/dist/types/compaction/compaction.d.ts +14 -1
- package/dist/types/compaction/index.d.ts +1 -0
- package/package.json +4 -4
- package/src/compaction/adaptive.ts +116 -0
- package/src/compaction/compaction.ts +87 -3
- package/src/compaction/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [0.5.
|
|
5
|
+
## [0.5.8] - 2026-09-10
|
|
6
|
+
|
|
7
|
+
## [0.5.7] - 2026-09-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Opt-in adaptive compaction. A fixed threshold compacts at the same context percentage no matter how fast a session fills its window, so a tool-call burst can overshoot between two checks while a quiet session compacts more often than it needs to. `AdaptiveCompactionTracker` records calls per window and `computeAdaptiveThresholdPercent` lowers the threshold in proportion to that rate, bounded by a configurable floor. Disabled by default and inert when disabled: a fixed `thresholdTokens` still wins, `thresholdPercent` is returned unchanged, and the reserve-based default path is untouched. When enabled it still returns the base while the context sits below 70% of it, and during a short post-compaction grace, so a burst cannot chain compactions.
|
|
6
12
|
|
|
7
13
|
## [0.5.3] - 2026-08-28
|
|
8
14
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive compaction state.
|
|
3
|
+
*
|
|
4
|
+
* A fixed threshold compacts at the same context percentage regardless of how
|
|
5
|
+
* fast the session is filling the window. During a tool-call burst the context
|
|
6
|
+
* can jump well past the threshold between two checks, while a slow session
|
|
7
|
+
* compacts more often than it needs to. The tracker records call rate per
|
|
8
|
+
* window so {@link ../compaction!computeAdaptiveThresholdPercent} can lower the
|
|
9
|
+
* threshold while a session is busy and leave it alone otherwise.
|
|
10
|
+
*
|
|
11
|
+
* Opt-in: with `compaction.adaptive.enabled` false the tracker is still cheap to
|
|
12
|
+
* run but its state never reaches a threshold decision.
|
|
13
|
+
*/
|
|
14
|
+
export interface AdaptiveCompactionState {
|
|
15
|
+
turnsSinceCompact: number;
|
|
16
|
+
callsInWindow: number;
|
|
17
|
+
windowStart: number;
|
|
18
|
+
lastContextTokens: number;
|
|
19
|
+
lastCompactContextTokens: number | null;
|
|
20
|
+
lastCompactTs: number | null;
|
|
21
|
+
}
|
|
22
|
+
/** The subset a threshold decision reads; keeps the decision path pure. */
|
|
23
|
+
export interface AdaptiveCompactionDecisionState {
|
|
24
|
+
turnsSinceCompact: number;
|
|
25
|
+
callsInWindow: number;
|
|
26
|
+
lastContextTokens?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface AdaptiveCompactionOptions {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
/** Minutes of recent calls considered when measuring call rate. */
|
|
31
|
+
turnWindow: number;
|
|
32
|
+
/** Context percentage used when the session is not busy. */
|
|
33
|
+
baseThresholdPercent: number;
|
|
34
|
+
/** How strongly call rate lowers the threshold, 0 to 1. */
|
|
35
|
+
aggression: number;
|
|
36
|
+
/** Lowest percentage the threshold may be lowered to. */
|
|
37
|
+
minThresholdPercent?: number;
|
|
38
|
+
}
|
|
39
|
+
export declare class AdaptiveCompactionTracker {
|
|
40
|
+
#private;
|
|
41
|
+
constructor(windowMs?: number, now?: number);
|
|
42
|
+
get windowMs(): number;
|
|
43
|
+
/** Changing the window restarts the current count so a resize cannot inherit a rate measured over a different span. */
|
|
44
|
+
setWindowMs(windowMs: number, now?: number): void;
|
|
45
|
+
reset(now?: number): void;
|
|
46
|
+
recordCall(contextTokens: number, now?: number): void;
|
|
47
|
+
recordCompact(contextTokens: number, now?: number): void;
|
|
48
|
+
snapshot(): AdaptiveCompactionState;
|
|
49
|
+
decisionState(): AdaptiveCompactionDecisionState;
|
|
50
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { type MessageAttribution, type Model, type ProviderSessionState, type Usage } from "@sayknow-cli/ai";
|
|
8
8
|
import { type AgentTelemetry } from "../telemetry";
|
|
9
9
|
import type { AgentMessage, AgentTool } from "../types";
|
|
10
|
+
import type { AdaptiveCompactionDecisionState, AdaptiveCompactionOptions } from "./adaptive";
|
|
10
11
|
import type { SessionEntry } from "./entries";
|
|
11
12
|
import { type ConvertToLlm } from "./messages";
|
|
12
13
|
import { type FileOperations } from "./utils";
|
|
@@ -32,6 +33,10 @@ export interface CompactionSettings {
|
|
|
32
33
|
strategy?: "context-full" | "handoff" | "off";
|
|
33
34
|
thresholdPercent?: number;
|
|
34
35
|
thresholdTokens?: number;
|
|
36
|
+
/** Opt-in adaptive threshold controls. Absent or disabled keeps the fixed threshold. */
|
|
37
|
+
adaptive?: AdaptiveCompactionOptions;
|
|
38
|
+
/** Call-rate state a threshold decision reads; supplied by the session's tracker. */
|
|
39
|
+
adaptiveState?: AdaptiveCompactionDecisionState;
|
|
35
40
|
reserveTokens: number;
|
|
36
41
|
keepRecentTokens: number;
|
|
37
42
|
autoContinue?: boolean;
|
|
@@ -127,7 +132,15 @@ export declare const DEFAULT_EMERGENCY_COMPACTION_LIMITS: EmergencyCompactionLim
|
|
|
127
132
|
* normal pair-safe `compact()` cut logic so a tool_use/tool_result pair is never split.
|
|
128
133
|
*/
|
|
129
134
|
export declare function emergencyCompactionReason(sample: EmergencyCompactionSample, limits?: EmergencyCompactionLimits): CompactionTriggerReason | null;
|
|
130
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Lower the compaction threshold while a session is filling its window quickly.
|
|
137
|
+
*
|
|
138
|
+
* Returns `basePercent` untouched unless adaptive mode is enabled and the session
|
|
139
|
+
* is both near the base threshold and past a short post-compaction grace, so a
|
|
140
|
+
* quiet or just-compacted session keeps the fixed behavior exactly.
|
|
141
|
+
*/
|
|
142
|
+
export declare function computeAdaptiveThresholdPercent(basePercent: number, contextTokens: number, contextWindow: number, state: AdaptiveCompactionDecisionState | undefined, options: AdaptiveCompactionOptions | undefined): number;
|
|
143
|
+
export declare function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings, maxOutputTokens?: number, contextTokens?: number): number;
|
|
131
144
|
/**
|
|
132
145
|
* Image content has no tokenizer representation; charge a fixed estimate
|
|
133
146
|
* matching what providers typically bill for inline images.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/agent-core",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.8",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://sayknow-cli.com",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@sayknow-cli/ai": "0.5.
|
|
39
|
-
"@sayknow-cli/natives": "0.5.
|
|
40
|
-
"@sayknow-cli/utils": "0.5.
|
|
38
|
+
"@sayknow-cli/ai": "0.5.8",
|
|
39
|
+
"@sayknow-cli/natives": "0.5.8",
|
|
40
|
+
"@sayknow-cli/utils": "0.5.8",
|
|
41
41
|
"@opentelemetry/api": "^1.9.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive compaction state.
|
|
3
|
+
*
|
|
4
|
+
* A fixed threshold compacts at the same context percentage regardless of how
|
|
5
|
+
* fast the session is filling the window. During a tool-call burst the context
|
|
6
|
+
* can jump well past the threshold between two checks, while a slow session
|
|
7
|
+
* compacts more often than it needs to. The tracker records call rate per
|
|
8
|
+
* window so {@link ../compaction!computeAdaptiveThresholdPercent} can lower the
|
|
9
|
+
* threshold while a session is busy and leave it alone otherwise.
|
|
10
|
+
*
|
|
11
|
+
* Opt-in: with `compaction.adaptive.enabled` false the tracker is still cheap to
|
|
12
|
+
* run but its state never reaches a threshold decision.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface AdaptiveCompactionState {
|
|
16
|
+
turnsSinceCompact: number;
|
|
17
|
+
callsInWindow: number;
|
|
18
|
+
windowStart: number;
|
|
19
|
+
lastContextTokens: number;
|
|
20
|
+
lastCompactContextTokens: number | null;
|
|
21
|
+
lastCompactTs: number | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The subset a threshold decision reads; keeps the decision path pure. */
|
|
25
|
+
export interface AdaptiveCompactionDecisionState {
|
|
26
|
+
turnsSinceCompact: number;
|
|
27
|
+
callsInWindow: number;
|
|
28
|
+
lastContextTokens?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AdaptiveCompactionOptions {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
/** Minutes of recent calls considered when measuring call rate. */
|
|
34
|
+
turnWindow: number;
|
|
35
|
+
/** Context percentage used when the session is not busy. */
|
|
36
|
+
baseThresholdPercent: number;
|
|
37
|
+
/** How strongly call rate lowers the threshold, 0 to 1. */
|
|
38
|
+
aggression: number;
|
|
39
|
+
/** Lowest percentage the threshold may be lowered to. */
|
|
40
|
+
minThresholdPercent?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const DEFAULT_WINDOW_MS = 60_000;
|
|
44
|
+
|
|
45
|
+
function initialState(now: number): AdaptiveCompactionState {
|
|
46
|
+
return {
|
|
47
|
+
turnsSinceCompact: 0,
|
|
48
|
+
callsInWindow: 0,
|
|
49
|
+
windowStart: now,
|
|
50
|
+
lastContextTokens: 0,
|
|
51
|
+
lastCompactContextTokens: null,
|
|
52
|
+
lastCompactTs: null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class AdaptiveCompactionTracker {
|
|
57
|
+
#state: AdaptiveCompactionState;
|
|
58
|
+
#windowMs: number;
|
|
59
|
+
|
|
60
|
+
constructor(windowMs: number = DEFAULT_WINDOW_MS, now: number = Date.now()) {
|
|
61
|
+
this.#windowMs = Number.isFinite(windowMs) && windowMs > 0 ? windowMs : DEFAULT_WINDOW_MS;
|
|
62
|
+
this.#state = initialState(now);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get windowMs(): number {
|
|
66
|
+
return this.#windowMs;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Changing the window restarts the current count so a resize cannot inherit a rate measured over a different span. */
|
|
70
|
+
setWindowMs(windowMs: number, now: number = Date.now()): void {
|
|
71
|
+
if (!Number.isFinite(windowMs)) return;
|
|
72
|
+
const nextWindowMs = Math.max(1, windowMs);
|
|
73
|
+
if (nextWindowMs === this.#windowMs) return;
|
|
74
|
+
this.#windowMs = nextWindowMs;
|
|
75
|
+
this.#state.windowStart = now;
|
|
76
|
+
this.#state.callsInWindow = 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
reset(now: number = Date.now()): void {
|
|
80
|
+
this.#state = initialState(now);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
recordCall(contextTokens: number, now: number = Date.now()): void {
|
|
84
|
+
const timestamp = Number.isFinite(now) ? now : Date.now();
|
|
85
|
+
this.#state.turnsSinceCompact += 1;
|
|
86
|
+
if (timestamp - this.#state.windowStart >= this.#windowMs) {
|
|
87
|
+
this.#state.windowStart = timestamp;
|
|
88
|
+
this.#state.callsInWindow = 0;
|
|
89
|
+
}
|
|
90
|
+
this.#state.callsInWindow += 1;
|
|
91
|
+
this.#state.lastContextTokens = Number.isFinite(contextTokens) ? Math.max(0, contextTokens) : 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
recordCompact(contextTokens: number, now: number = Date.now()): void {
|
|
95
|
+
const timestamp = Number.isFinite(now) ? now : Date.now();
|
|
96
|
+
const safeContextTokens = Number.isFinite(contextTokens) ? Math.max(0, contextTokens) : 0;
|
|
97
|
+
this.#state.turnsSinceCompact = 0;
|
|
98
|
+
this.#state.callsInWindow = 0;
|
|
99
|
+
this.#state.windowStart = timestamp;
|
|
100
|
+
this.#state.lastContextTokens = safeContextTokens;
|
|
101
|
+
this.#state.lastCompactContextTokens = safeContextTokens;
|
|
102
|
+
this.#state.lastCompactTs = timestamp;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
snapshot(): AdaptiveCompactionState {
|
|
106
|
+
return { ...this.#state };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
decisionState(): AdaptiveCompactionDecisionState {
|
|
110
|
+
return {
|
|
111
|
+
turnsSinceCompact: this.#state.turnsSinceCompact,
|
|
112
|
+
callsInWindow: this.#state.callsInWindow,
|
|
113
|
+
lastContextTokens: this.#state.lastContextTokens,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { logger, prompt } from "@sayknow-cli/utils";
|
|
19
19
|
import { type AgentTelemetry, instrumentedCompleteSimple } from "../telemetry";
|
|
20
20
|
import type { AgentMessage, AgentTool } from "../types";
|
|
21
|
+
import type { AdaptiveCompactionDecisionState, AdaptiveCompactionOptions } from "./adaptive";
|
|
21
22
|
import type { CompactionEntry, SessionEntry } from "./entries";
|
|
22
23
|
import { type ConvertToLlm, convertToLlm, createBranchSummaryMessage, createCustomMessage } from "./messages";
|
|
23
24
|
import {
|
|
@@ -136,6 +137,10 @@ export interface CompactionSettings {
|
|
|
136
137
|
strategy?: "context-full" | "handoff" | "off";
|
|
137
138
|
thresholdPercent?: number;
|
|
138
139
|
thresholdTokens?: number;
|
|
140
|
+
/** Opt-in adaptive threshold controls. Absent or disabled keeps the fixed threshold. */
|
|
141
|
+
adaptive?: AdaptiveCompactionOptions;
|
|
142
|
+
/** Call-rate state a threshold decision reads; supplied by the session's tracker. */
|
|
143
|
+
adaptiveState?: AdaptiveCompactionDecisionState;
|
|
139
144
|
reserveTokens: number;
|
|
140
145
|
keepRecentTokens: number;
|
|
141
146
|
autoContinue?: boolean;
|
|
@@ -244,7 +249,7 @@ export function shouldCompact(
|
|
|
244
249
|
maxOutputTokens = 0,
|
|
245
250
|
): boolean {
|
|
246
251
|
if (!settings.enabled || settings.strategy === "off" || contextWindow <= 0) return false;
|
|
247
|
-
const thresholdTokens = resolveThresholdTokens(contextWindow, settings, maxOutputTokens);
|
|
252
|
+
const thresholdTokens = resolveThresholdTokens(contextWindow, settings, maxOutputTokens, contextTokens);
|
|
248
253
|
return contextTokens > thresholdTokens;
|
|
249
254
|
}
|
|
250
255
|
|
|
@@ -377,10 +382,63 @@ export function emergencyCompactionReason(
|
|
|
377
382
|
return null;
|
|
378
383
|
}
|
|
379
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Lower the compaction threshold while a session is filling its window quickly.
|
|
387
|
+
*
|
|
388
|
+
* Returns `basePercent` untouched unless adaptive mode is enabled and the session
|
|
389
|
+
* is both near the base threshold and past a short post-compaction grace, so a
|
|
390
|
+
* quiet or just-compacted session keeps the fixed behavior exactly.
|
|
391
|
+
*/
|
|
392
|
+
export function computeAdaptiveThresholdPercent(
|
|
393
|
+
basePercent: number,
|
|
394
|
+
contextTokens: number,
|
|
395
|
+
contextWindow: number,
|
|
396
|
+
state: AdaptiveCompactionDecisionState | undefined,
|
|
397
|
+
options: AdaptiveCompactionOptions | undefined,
|
|
398
|
+
): number {
|
|
399
|
+
if (!options?.enabled) return basePercent;
|
|
400
|
+
const clampedBasePercent = Number.isFinite(basePercent) ? Math.min(99, Math.max(1, basePercent)) : 85;
|
|
401
|
+
if (!state || !Number.isFinite(contextWindow) || contextWindow <= 0) return clampedBasePercent;
|
|
402
|
+
if (!Number.isFinite(options.turnWindow) || options.turnWindow <= 0) return clampedBasePercent;
|
|
403
|
+
|
|
404
|
+
// Far from the base threshold there is nothing to bring forward.
|
|
405
|
+
const safeContextTokens = Number.isFinite(contextTokens) ? Math.max(0, contextTokens) : 0;
|
|
406
|
+
const fillRatio = safeContextTokens / contextWindow;
|
|
407
|
+
const baseRatio = clampedBasePercent / 100;
|
|
408
|
+
if (fillRatio < baseRatio * 0.7) return clampedBasePercent;
|
|
409
|
+
|
|
410
|
+
// Grace after a compaction so a burst cannot chain compactions back to back.
|
|
411
|
+
const turnsSinceCompact = Number.isFinite(state.turnsSinceCompact) ? Math.max(0, state.turnsSinceCompact) : 0;
|
|
412
|
+
if (turnsSinceCompact <= 3) return clampedBasePercent;
|
|
413
|
+
|
|
414
|
+
const callsInWindow = Number.isFinite(state.callsInWindow) ? Math.max(0, state.callsInWindow) : 0;
|
|
415
|
+
const windowTurns = Math.max(1, options.turnWindow * 4);
|
|
416
|
+
const intensity = Math.min(1, callsInWindow / windowTurns);
|
|
417
|
+
const aggression = Number.isFinite(options.aggression) ? Math.min(1, Math.max(0, options.aggression)) : 0;
|
|
418
|
+
const configuredMinThresholdPercent = options.minThresholdPercent;
|
|
419
|
+
const minThresholdPercent = Math.min(
|
|
420
|
+
clampedBasePercent,
|
|
421
|
+
typeof configuredMinThresholdPercent === "number" && Number.isFinite(configuredMinThresholdPercent)
|
|
422
|
+
? Math.max(1, configuredMinThresholdPercent)
|
|
423
|
+
: clampedBasePercent * 0.5,
|
|
424
|
+
);
|
|
425
|
+
const loweredPercent = clampedBasePercent - (clampedBasePercent - minThresholdPercent) * aggression * intensity;
|
|
426
|
+
return Math.max(1, Math.min(99, Math.round(loweredPercent)));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function adaptiveContextTokens(contextTokens: number | undefined, lastContextTokens: number | undefined): number {
|
|
430
|
+
if (contextTokens !== undefined && Number.isFinite(contextTokens)) return Math.max(0, contextTokens);
|
|
431
|
+
if (typeof lastContextTokens === "number" && Number.isFinite(lastContextTokens)) {
|
|
432
|
+
return Math.max(0, lastContextTokens);
|
|
433
|
+
}
|
|
434
|
+
return 0;
|
|
435
|
+
}
|
|
436
|
+
|
|
380
437
|
export function resolveThresholdTokens(
|
|
381
438
|
contextWindow: number,
|
|
382
439
|
settings: CompactionSettings,
|
|
383
440
|
maxOutputTokens = 0,
|
|
441
|
+
contextTokens?: number,
|
|
384
442
|
): number {
|
|
385
443
|
// Fixed token limit takes priority over percentage
|
|
386
444
|
const thresholdTokens = settings.thresholdTokens;
|
|
@@ -389,13 +447,39 @@ export function resolveThresholdTokens(
|
|
|
389
447
|
return Math.min(contextWindow - 1, Math.max(1, thresholdTokens));
|
|
390
448
|
}
|
|
391
449
|
|
|
450
|
+
const effectiveContextTokens = adaptiveContextTokens(contextTokens, settings.adaptiveState?.lastContextTokens);
|
|
451
|
+
|
|
392
452
|
// Percentage-based threshold
|
|
393
453
|
const thresholdPercent = settings.thresholdPercent;
|
|
394
454
|
if (typeof thresholdPercent !== "number" || !Number.isFinite(thresholdPercent) || thresholdPercent <= 0) {
|
|
395
|
-
|
|
455
|
+
if (!settings.adaptive?.enabled) {
|
|
456
|
+
return contextWindow - effectiveReserveTokens(contextWindow, settings, maxOutputTokens);
|
|
457
|
+
}
|
|
458
|
+
// No configured percentage: adaptive supplies its own base.
|
|
459
|
+
const adaptiveBasePercent = Number.isFinite(settings.adaptive.baseThresholdPercent)
|
|
460
|
+
? Math.min(99, Math.max(1, settings.adaptive.baseThresholdPercent))
|
|
461
|
+
: 85;
|
|
462
|
+
const adaptiveThresholdPercent = computeAdaptiveThresholdPercent(
|
|
463
|
+
adaptiveBasePercent,
|
|
464
|
+
effectiveContextTokens,
|
|
465
|
+
contextWindow,
|
|
466
|
+
settings.adaptiveState,
|
|
467
|
+
settings.adaptive,
|
|
468
|
+
);
|
|
469
|
+
return Math.floor(contextWindow * (adaptiveThresholdPercent / 100));
|
|
396
470
|
}
|
|
397
471
|
const clampedThresholdPercent = Math.min(99, Math.max(1, thresholdPercent));
|
|
398
|
-
|
|
472
|
+
if (!settings.adaptive?.enabled) {
|
|
473
|
+
return Math.floor(contextWindow * (clampedThresholdPercent / 100));
|
|
474
|
+
}
|
|
475
|
+
const adaptiveThresholdPercent = computeAdaptiveThresholdPercent(
|
|
476
|
+
settings.adaptive.baseThresholdPercent ?? clampedThresholdPercent,
|
|
477
|
+
effectiveContextTokens,
|
|
478
|
+
contextWindow,
|
|
479
|
+
settings.adaptiveState,
|
|
480
|
+
settings.adaptive,
|
|
481
|
+
);
|
|
482
|
+
return Math.floor(contextWindow * (adaptiveThresholdPercent / 100));
|
|
399
483
|
}
|
|
400
484
|
|
|
401
485
|
// ============================================================================
|