@monotykamary/pi-vcc 0.6.4 → 0.7.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/README.md +8 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/core/settings.ts +48 -11
- package/src/hooks/before-compact.ts +128 -2
- package/src/hooks/proactive-threshold.ts +12 -14
package/README.md
CHANGED
|
@@ -211,7 +211,7 @@ pi-vcc is one of four compaction approaches in the AI coding-agent ecosystem. He
|
|
|
211
211
|
- **Error resolution detection** — tsc errors in `[Outstanding Context]` are tagged `[RESOLVED]` when the file they reference was subsequently edited, letting the model skip stale errors.
|
|
212
212
|
- **Task-boundary-aware cut** — compaction splits at complete conversational turns, not mid-tool-call. If the assistant's response is in-flight (unmatched tool calls), the cut pushes back to keep the whole turn in the tail.
|
|
213
213
|
- **Structured anchors** — `[Anchors]` section lists commit hashes, error IDs, and key file paths for zero-tool-call recall. The model can find references at a glance instead of calling `vcc_recall`.
|
|
214
|
-
- **Per-model and global compaction thresholds** — configure different `reserveTokens` or `compactPercent` per model and globally, so models with different context windows compact at the right time. Proactive triggering on `agent_end` and `model_select` events compacts earlier for small-context models. Applies to both pi-vcc and pi-core compaction.
|
|
214
|
+
- **Per-model and global compaction thresholds** — configure different `reserveTokens`, `compactAtTokens`, or `compactPercent` per model and globally, so models with different context windows compact at the right time. Proactive triggering on `agent_end` and `model_select` events compacts earlier for small-context models. Applies to both pi-vcc and pi-core compaction.
|
|
215
215
|
|
|
216
216
|
## Install
|
|
217
217
|
|
|
@@ -567,24 +567,25 @@ Config lives at `~/.pi/agent/pi-vcc-config.json` (auto-scaffolded on first load
|
|
|
567
567
|
"modelThresholds": {
|
|
568
568
|
"neuralwatt/zai-org/GLM-5.1-FP8": { "reserveTokens": 32768 },
|
|
569
569
|
"neuralwatt/moonshotai/Kimi-K2.6": { "compactPercent": 65 },
|
|
570
|
-
"neuralwatt/
|
|
570
|
+
"neuralwatt/glm-5.1-long": { "compactAtTokens": 150000 }
|
|
571
571
|
},
|
|
572
|
-
"globalThreshold": { "
|
|
572
|
+
"globalThreshold": { "compactAtTokens": 150000 }
|
|
573
573
|
}
|
|
574
574
|
```
|
|
575
575
|
|
|
576
576
|
- **`overrideDefaultCompaction`** *(default `true`)*: when `true` (default), pi-vcc handles all compaction paths (`/compact`, auto-threshold, `/pi-vcc`). Set `false` to let pi core handle `/compact` and auto-threshold compactions via its default LLM-based compaction.
|
|
577
577
|
- **`debug`** *(default `false`)*: when `true`, each compaction writes detailed info to `/tmp/pi-vcc-debug.json` — message counts, cut boundary, summary preview, sections.
|
|
578
578
|
- **`modelThresholds`** *(default: none)*: per-model compaction thresholds. Keys match against `"provider/modelId"` (e.g., `"neuralwatt/zai-org/GLM-5.1-FP8"`) or just `"modelId"` (e.g., `"GLM-5.1"` — matched only when `provider/modelId` doesn't). Each value has:
|
|
579
|
-
- **`reserveTokens`**: tokens to reserve for the LLM response. Overrides pi-core's global `compaction.reserveTokens` for matching models. Controls *when* compaction triggers: `contextTokens > contextWindow − reserveTokens`. A higher value compacts earlier (more conservative); a lower value lets context grow larger. Takes precedence over `compactPercent` when
|
|
580
|
-
- **`
|
|
579
|
+
- **`reserveTokens`**: tokens to reserve for the LLM response. Overrides pi-core's global `compaction.reserveTokens` for matching models. Controls *when* compaction triggers: `contextTokens > contextWindow − reserveTokens`. A higher value compacts earlier (more conservative); a lower value lets context grow larger. Takes precedence over `compactAtTokens` and `compactPercent` when multiple are set.
|
|
580
|
+
- **`compactAtTokens`**: absolute context token count where compaction triggers: `contextTokens > compactAtTokens`. Useful when you want the same trigger point across models with different context windows, such as `{ "compactAtTokens": 150000 }`. Takes precedence over `compactPercent` when both are set.
|
|
581
|
+
- **`compactPercent`**: compaction trigger as a percentage of context window (1–99). Compaction fires when `contextTokens > contextWindow × compactPercent / 100`. E.g. `65` means "compact when context is 65% full". Ignored when `reserveTokens` or `compactAtTokens` is also set.
|
|
581
582
|
- **`keepRecentTokens`** *(optional)*: advisory token budget for pi-core's default compaction. Pi-vcc's own `buildOwnCut` uses task-boundary heuristics, so this only affects pi-core's cut when `overrideDefaultCompaction` is `false`.
|
|
582
|
-
- **`globalThreshold`** *(default: none)*: global threshold applied to all models not matched by `modelThresholds`. Uses `
|
|
583
|
+
- **`globalThreshold`** *(default: none)*: global threshold applied to all models not matched by `modelThresholds`. Uses `reserveTokens`, `compactAtTokens`, or `compactPercent`. If omitted, pi-core's global `compaction.reserveTokens` applies (no override).
|
|
583
584
|
- **`defaultThreshold`** *(default: none, deprecated)*: use `globalThreshold` instead. Backward compatible — still works.
|
|
584
585
|
|
|
585
586
|
### How compaction thresholds work
|
|
586
587
|
|
|
587
|
-
Pi-core's auto-compaction triggers when `contextTokens > contextWindow − reserveTokens`. The global `reserveTokens` (default 16384) is one-size-fits-all — but different models have very different context windows and cost profiles.
|
|
588
|
+
Pi-core's auto-compaction triggers when `contextTokens > contextWindow − reserveTokens`. The global `reserveTokens` (default 16384) is one-size-fits-all — but different models have very different context windows and cost profiles. Pi-vcc also supports `compactAtTokens` when you want an absolute trigger point independent of a model's context window.
|
|
588
589
|
|
|
589
590
|
Pi-vcc's thresholds provide proactive compaction at both the per-model and global level:
|
|
590
591
|
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monotykamary/pi-vcc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@monotykamary/pi-vcc",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.7.0",
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"knip": "6.14.1",
|
|
12
12
|
"typescript": "6.0.3"
|
package/package.json
CHANGED
package/src/core/settings.ts
CHANGED
|
@@ -19,9 +19,20 @@ export interface ModelThreshold {
|
|
|
19
19
|
* A higher value compacts earlier (more conservative); a lower value
|
|
20
20
|
* lets context grow larger before compacting.
|
|
21
21
|
*
|
|
22
|
-
* Takes precedence over compactPercent when
|
|
22
|
+
* Takes precedence over compactAtTokens and compactPercent when multiple are set.
|
|
23
23
|
*/
|
|
24
24
|
reserveTokens?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Absolute context token count where compaction triggers.
|
|
27
|
+
*
|
|
28
|
+
* This controls *when* compaction triggers:
|
|
29
|
+
* contextTokens > compactAtTokens
|
|
30
|
+
*
|
|
31
|
+
* Useful when you want the same trigger point across models with
|
|
32
|
+
* different context windows. Ignored when reserveTokens is also set;
|
|
33
|
+
* takes precedence over compactPercent.
|
|
34
|
+
*/
|
|
35
|
+
compactAtTokens?: number;
|
|
25
36
|
/**
|
|
26
37
|
* Compaction trigger as a percentage of context window (1–99).
|
|
27
38
|
* Compaction fires when: contextTokens > contextWindow × compactPercent / 100
|
|
@@ -29,7 +40,7 @@ export interface ModelThreshold {
|
|
|
29
40
|
* E.g. compactPercent: 65 means "compact when context is 65% full",
|
|
30
41
|
* equivalent to reserveTokens = 35% of contextWindow.
|
|
31
42
|
*
|
|
32
|
-
* Ignored when reserveTokens is also set.
|
|
43
|
+
* Ignored when reserveTokens or compactAtTokens is also set.
|
|
33
44
|
*/
|
|
34
45
|
compactPercent?: number;
|
|
35
46
|
/**
|
|
@@ -61,17 +72,16 @@ export interface PiVccSettings {
|
|
|
61
72
|
* "provider/modelId" (e.g., "neuralwatt/zai-org/GLM-5.1-FP8") or
|
|
62
73
|
* just "modelId" (e.g., "GLM-5.1-FP8").
|
|
63
74
|
*
|
|
64
|
-
* When a model matches, its reserveTokens/compactPercent
|
|
65
|
-
* pi-core's global compaction.reserveTokens for the *when to
|
|
66
|
-
* decision. This lets different models compact at different
|
|
67
|
-
* fill levels.
|
|
75
|
+
* When a model matches, its reserveTokens/compactAtTokens/compactPercent
|
|
76
|
+
* overrides pi-core's global compaction.reserveTokens for the *when to
|
|
77
|
+
* compact* decision. This lets different models compact at different
|
|
78
|
+
* context fill levels or absolute token counts.
|
|
68
79
|
*/
|
|
69
80
|
modelThresholds?: Record<string, ModelThreshold>;
|
|
70
81
|
/**
|
|
71
82
|
* Global threshold applied to all models not matched by modelThresholds.
|
|
72
|
-
* Uses
|
|
73
|
-
*
|
|
74
|
-
* compaction settings apply (no override).
|
|
83
|
+
* Uses reserveTokens, compactAtTokens, or compactPercent. If omitted,
|
|
84
|
+
* pi-core's global compaction settings apply (no override).
|
|
75
85
|
*/
|
|
76
86
|
globalThreshold?: ModelThreshold;
|
|
77
87
|
/**
|
|
@@ -91,8 +101,8 @@ const DEFAULT_SETTINGS: PiVccSettings = {
|
|
|
91
101
|
* Lookup order:
|
|
92
102
|
* 1. Exact match on "provider/modelId" key
|
|
93
103
|
* 2. Exact match on "modelId" key
|
|
94
|
-
*
|
|
95
|
-
*
|
|
104
|
+
* 3. globalThreshold from settings
|
|
105
|
+
* 4. undefined (no override — pi-core's global settings apply)
|
|
96
106
|
*/
|
|
97
107
|
export function getModelThreshold(
|
|
98
108
|
settings: PiVccSettings,
|
|
@@ -136,6 +146,33 @@ export function resolveReserveTokens(
|
|
|
136
146
|
return undefined;
|
|
137
147
|
}
|
|
138
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Resolve the context token count where compaction should trigger.
|
|
151
|
+
*
|
|
152
|
+
* Precedence: reserveTokens > compactAtTokens > compactPercent.
|
|
153
|
+
* Returns undefined when the threshold cannot produce a usable trigger.
|
|
154
|
+
*/
|
|
155
|
+
export function resolveTriggerTokens(
|
|
156
|
+
threshold: ModelThreshold,
|
|
157
|
+
contextWindow: number,
|
|
158
|
+
): number | undefined {
|
|
159
|
+
if (contextWindow <= 0) return undefined;
|
|
160
|
+
|
|
161
|
+
if (threshold.reserveTokens != null) {
|
|
162
|
+
return contextWindow - threshold.reserveTokens;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (threshold.compactAtTokens != null) {
|
|
166
|
+
const tokens = threshold.compactAtTokens;
|
|
167
|
+
if (!Number.isFinite(tokens) || tokens < 1) return undefined;
|
|
168
|
+
return Math.round(tokens);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const reserve = resolveReserveTokens(threshold, contextWindow);
|
|
172
|
+
if (reserve == null) return undefined;
|
|
173
|
+
return contextWindow - reserve;
|
|
174
|
+
}
|
|
175
|
+
|
|
139
176
|
const readJson = (path: string): Record<string, unknown> | null => {
|
|
140
177
|
try {
|
|
141
178
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -150,7 +150,88 @@ const findMidCycleBoundary = (liveMessages: EntryWithMessage[]): number => {
|
|
|
150
150
|
return best;
|
|
151
151
|
};
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
/** Rough token estimate (chars/4) for a live message, consistent with the
|
|
154
|
+
* kept-tokens estimate used elsewhere in this module. */
|
|
155
|
+
function estimateMessageTokens(message: { content: unknown }): number {
|
|
156
|
+
const c = message.content;
|
|
157
|
+
let chars = 0;
|
|
158
|
+
if (typeof c === "string") {
|
|
159
|
+
chars = c.length;
|
|
160
|
+
} else if (Array.isArray(c)) {
|
|
161
|
+
for (const part of c as any[]) {
|
|
162
|
+
if (part.text) chars += part.text.length;
|
|
163
|
+
else if (part.type === "toolCall") {
|
|
164
|
+
const args = part.arguments ?? part.input;
|
|
165
|
+
chars += (part.name?.length ?? 0) + (typeof args === "string" ? args.length : JSON.stringify(args ?? "").length);
|
|
166
|
+
} else if (part.type === "toolResult") {
|
|
167
|
+
chars += typeof part.content === "string" ? part.content.length : JSON.stringify(part.content ?? "").length;
|
|
168
|
+
} else if (part.type === "thinking") {
|
|
169
|
+
chars += (part.thinking?.length ?? 0);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return Math.ceil(chars / 4);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Find a completed tool-cycle boundary within `suffix` such that the kept
|
|
177
|
+
* tail (suffix[boundary+1 .. end]) fits within `budgetTokens`, keeping as
|
|
178
|
+
* much recent context as possible. Returns the index of the first message to
|
|
179
|
+
* KEEP, or -1 when the suffix can't be split to fit (single oversized cycle,
|
|
180
|
+
* or no completed cycles). */
|
|
181
|
+
const findSuffixSplitPoint = (
|
|
182
|
+
suffix: EntryWithMessage[],
|
|
183
|
+
budgetTokens: number,
|
|
184
|
+
): number => {
|
|
185
|
+
if (suffix.length <= 2) return -1;
|
|
186
|
+
|
|
187
|
+
// Completed-cycle end-indices (toolResult closing a cycle). Same detection
|
|
188
|
+
// as findMidCycleBoundary.
|
|
189
|
+
const cycleEnds: number[] = [];
|
|
190
|
+
let currentAssistantIdx = -1;
|
|
191
|
+
const pendingCalls = new Set<string>();
|
|
192
|
+
for (let i = 0; i < suffix.length; i++) {
|
|
193
|
+
const msg = suffix[i].message;
|
|
194
|
+
if (msg.role === "user") { currentAssistantIdx = -1; pendingCalls.clear(); continue; }
|
|
195
|
+
if (msg.role === "assistant") {
|
|
196
|
+
currentAssistantIdx = i;
|
|
197
|
+
pendingCalls.clear();
|
|
198
|
+
const content = msg.content;
|
|
199
|
+
if (Array.isArray(content)) {
|
|
200
|
+
for (const part of content) {
|
|
201
|
+
if (part.type === "toolCall" && part.id) pendingCalls.add(part.id);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (msg.role === "toolResult") {
|
|
207
|
+
const callId = (msg as any).toolCallId as string | undefined;
|
|
208
|
+
if (callId) pendingCalls.delete(callId);
|
|
209
|
+
if (pendingCalls.size === 0 && currentAssistantIdx >= 0) {
|
|
210
|
+
cycleEnds.push(i);
|
|
211
|
+
currentAssistantIdx = -1;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (cycleEnds.length === 0) return -1;
|
|
216
|
+
|
|
217
|
+
// tailTokens[i] = tokens of suffix[i .. end].
|
|
218
|
+
const tailTokens: number[] = new Array(suffix.length + 1).fill(0);
|
|
219
|
+
for (let i = suffix.length - 1; i >= 0; i--) {
|
|
220
|
+
tailTokens[i] = tailTokens[i + 1] + estimateMessageTokens(suffix[i].message);
|
|
221
|
+
}
|
|
222
|
+
// Earliest cycle boundary whose kept tail fits — keeps the most recent
|
|
223
|
+
// context while staying under budget. boundary+1 must be < length so the
|
|
224
|
+
// kept tail is non-empty.
|
|
225
|
+
for (const boundary of cycleEnds) {
|
|
226
|
+
if (boundary + 1 < suffix.length && tailTokens[boundary + 1] <= budgetTokens) {
|
|
227
|
+
return boundary + 1;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return -1;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export function buildOwnCut(branchEntries: any[], options?: { maxKeptTokens?: number }): OwnCutResult {
|
|
234
|
+
const maxKeptTokens = options?.maxKeptTokens ?? 0;
|
|
154
235
|
// Find the last compaction entry and its firstKeptEntryId
|
|
155
236
|
let lastCompactionIdx = -1;
|
|
156
237
|
let lastKeptId: string | undefined;
|
|
@@ -235,6 +316,38 @@ export function buildOwnCut(branchEntries: any[]): OwnCutResult {
|
|
|
235
316
|
}
|
|
236
317
|
}
|
|
237
318
|
|
|
319
|
+
// Oversized-turn guard (opt-in via options.maxKeptTokens > 0). When the
|
|
320
|
+
// kept suffix — the most recent turn from the last user message — exceeds
|
|
321
|
+
// the budget, keeping it whole would re-overflow on the compaction retry.
|
|
322
|
+
// Split the turn at a completed tool-cycle boundary so the oversized early
|
|
323
|
+
// part (typically a giant tool result) is summarized and only recent cycles
|
|
324
|
+
// that fit are kept. If a single cycle is itself oversized (or there are no
|
|
325
|
+
// completed cycles to split at), fall back to compact-all — safe because
|
|
326
|
+
// pi-vcc compiles summaries statically (no LLM call that could overflow).
|
|
327
|
+
if (cutIdx > 0 && maxKeptTokens > 0) {
|
|
328
|
+
const suffix = liveMessages.slice(cutIdx);
|
|
329
|
+
let suffixTokens = 0;
|
|
330
|
+
for (const e of suffix) suffixTokens += estimateMessageTokens(e.message);
|
|
331
|
+
if (suffixTokens > maxKeptTokens) {
|
|
332
|
+
const splitIdx = findSuffixSplitPoint(suffix, maxKeptTokens);
|
|
333
|
+
if (splitIdx >= 0) {
|
|
334
|
+
const globalIdx = cutIdx + splitIdx;
|
|
335
|
+
return {
|
|
336
|
+
ok: true,
|
|
337
|
+
messages: liveMessages.slice(0, globalIdx).map((e) => e.message),
|
|
338
|
+
firstKeptEntryId: liveMessages[globalIdx].entry.id,
|
|
339
|
+
compactAll: false,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
ok: true,
|
|
344
|
+
messages: liveMessages.map((e) => e.message),
|
|
345
|
+
firstKeptEntryId: "",
|
|
346
|
+
compactAll: true,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
238
351
|
if (cutIdx <= 0) {
|
|
239
352
|
// Single user prompt (or no user at all) with a long agentic chain.
|
|
240
353
|
// Instead of compact-all (which destroys the tail), find a completed
|
|
@@ -301,7 +414,20 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
301
414
|
// Otherwise, only handle when user opted in via settings.
|
|
302
415
|
if (!isPiVcc && !settings.overrideDefaultCompaction) return;
|
|
303
416
|
|
|
304
|
-
|
|
417
|
+
// Budget the kept tail so that summary + kept + system/tools + the model's
|
|
418
|
+
// OUTPUT budget (maxTokens) all fit in the window. Without reserving
|
|
419
|
+
// maxTokens, the compaction-retry request can be rejected upfront
|
|
420
|
+
// (input + maxTokens > contextWindow) and re-overflow. Falls back to
|
|
421
|
+
// pi-core's keepRecentTokens when the window is unknown.
|
|
422
|
+
const contextWindow = (ctx as any)?.model?.contextWindow ?? 0;
|
|
423
|
+
const maxTokens = (ctx as any)?.model?.maxTokens ?? 0;
|
|
424
|
+
const keepRecentTokens = (preparation as any)?.settings?.keepRecentTokens ?? 20000;
|
|
425
|
+
const overhead = contextWindow > 0 ? Math.min(32768, Math.floor(contextWindow * 0.2)) : 32768;
|
|
426
|
+
const outputReserve = maxTokens > 0 ? maxTokens : Math.floor(contextWindow * 0.5);
|
|
427
|
+
const maxKeptTokens = contextWindow > 0
|
|
428
|
+
? Math.max(2048, contextWindow - outputReserve - overhead)
|
|
429
|
+
: keepRecentTokens;
|
|
430
|
+
const ownCut = buildOwnCut(branchEntries as any[], { maxKeptTokens });
|
|
305
431
|
if (!ownCut.ok) {
|
|
306
432
|
const lastComp = [...branchEntries].reverse().find((e: any) => e.type === "compaction");
|
|
307
433
|
const lastCompIdx = lastComp ? (branchEntries as any[]).indexOf(lastComp) : -1;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { loadSettings, getModelThreshold,
|
|
2
|
+
import { loadSettings, getModelThreshold, resolveTriggerTokens } from "../core/settings";
|
|
3
3
|
|
|
4
4
|
const formatTokens = (n: number): string => {
|
|
5
5
|
if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
|
|
@@ -32,7 +32,7 @@ export const resetProactiveState = () => {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Check if
|
|
35
|
+
* Check if a configured threshold has been crossed and trigger compaction
|
|
36
36
|
* if so. Safe to call from multiple event handlers — cooldown prevents
|
|
37
37
|
* double-triggering.
|
|
38
38
|
*/
|
|
@@ -44,14 +44,13 @@ const checkAndTrigger = (ctx: { model?: any; getContextUsage?: () => any; compac
|
|
|
44
44
|
if (!threshold) return;
|
|
45
45
|
|
|
46
46
|
const contextWindow = ctx.model?.contextWindow ?? 0;
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
47
|
+
const effectiveThreshold = resolveTriggerTokens(threshold, contextWindow);
|
|
48
|
+
if (effectiveThreshold == null) return;
|
|
49
49
|
|
|
50
50
|
const usage = ctx.getContextUsage?.();
|
|
51
51
|
if (!usage || usage.tokens === null) return;
|
|
52
52
|
|
|
53
|
-
// This threshold's compaction trigger point
|
|
54
|
-
const effectiveThreshold = contextWindow - reserve;
|
|
53
|
+
// This threshold's compaction trigger point.
|
|
55
54
|
|
|
56
55
|
// Only trigger if context EXCEEDS the threshold.
|
|
57
56
|
if (usage.tokens <= effectiveThreshold) return;
|
|
@@ -80,19 +79,18 @@ const checkAndTrigger = (ctx: { model?: any; getContextUsage?: () => any; compac
|
|
|
80
79
|
};
|
|
81
80
|
|
|
82
81
|
/**
|
|
83
|
-
* Registers proactive
|
|
82
|
+
* Registers proactive configured compaction thresholds.
|
|
84
83
|
*
|
|
85
84
|
* Three triggers:
|
|
86
85
|
*
|
|
87
86
|
* 1. `agent_end` — after each agent run completes, check if context
|
|
88
|
-
* exceeds the
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* at this point. We step in and trigger it proactively.
|
|
87
|
+
* exceeds the active configured threshold. If that threshold is lower
|
|
88
|
+
* than pi-core's global threshold (meaning this config wants to compact
|
|
89
|
+
* earlier), pi-core won't trigger compaction — so we do.
|
|
92
90
|
*
|
|
93
|
-
* 2. `model_select` — when switching
|
|
94
|
-
* threshold
|
|
95
|
-
*
|
|
91
|
+
* 2. `model_select` — when switching models, the new model may have a
|
|
92
|
+
* different threshold. Check immediately in case current context exceeds
|
|
93
|
+
* the new threshold.
|
|
96
94
|
*
|
|
97
95
|
* 3. `session_compact` — cooldown tracking + clear proactiveTriggerActive.
|
|
98
96
|
* After any compaction completes, we set a cooldown to prevent
|