pi-pier 0.2.6 → 0.3.0
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 +20 -7
- package/package.json +7 -2
- package/schemas/efficiency-config.schema.json +77 -0
- package/skills/pi-herdr-workflow/SKILL.md +13 -1
- package/src/ansi-text.ts +83 -0
- package/src/ask-multi.ts +260 -0
- package/src/ask-user.ts +458 -0
- package/src/bootstrap.ts +5 -2
- package/src/compact-coordinator.ts +454 -0
- package/src/compact-economics-core.ts +396 -0
- package/src/config-catalog-core.ts +452 -0
- package/src/config-command.ts +181 -0
- package/src/config-guide.ts +361 -0
- package/src/core/grid-shape.ts +4 -2
- package/src/core/heat-plan.ts +160 -0
- package/src/core/observation.ts +555 -0
- package/src/core/subagent.ts +187 -393
- package/src/core/terminal.ts +270 -47
- package/src/core/todo.ts +49 -14
- package/src/dashboard-command.ts +89 -0
- package/src/efficiency-config-core.ts +661 -0
- package/src/efficiency-config-io.ts +53 -0
- package/src/efficiency-store.ts +312 -0
- package/src/focus-poller.ts +244 -0
- package/src/gc-core.ts +85 -0
- package/src/git-adapter.ts +12 -3
- package/src/herdr-client.ts +292 -36
- package/src/index-locks.ts +27 -9
- package/src/index-master.ts +5 -0
- package/src/index-notices.ts +13 -2
- package/src/index-pipe.ts +5 -0
- package/src/index-worker.ts +5 -0
- package/src/index.ts +360 -60
- package/src/jev-client.ts +146 -0
- package/src/jev-core.ts +381 -0
- package/src/lock-core.ts +105 -16
- package/src/observation-core.ts +248 -0
- package/src/pier-options.ts +96 -0
- package/src/pipe-channel.ts +52 -7
- package/src/reducer-core.ts +358 -0
- package/src/reducer-invoker.ts +392 -0
- package/src/renderers.ts +198 -0
- package/src/role-loader.ts +2 -1
- package/src/role-manifest.ts +0 -4
- package/src/runtime-policy.ts +5 -8
- package/src/session-tail.ts +60 -5
- package/src/settle-wake-core.ts +3 -1
- package/src/slim-frame.ts +60 -3
- package/src/storage-layout.ts +23 -0
- package/src/subagent-core.ts +3 -1
- package/src/subagent-execute.ts +328 -0
- package/src/subagent-gc.ts +33 -13
- package/src/subagent-launch.ts +4 -0
- package/src/subagent-list-action.ts +69 -0
- package/src/subagent-output-action.ts +104 -0
- package/src/subagent-output-adapter.ts +27 -0
- package/src/subagent-output-core.ts +263 -0
- package/src/subagent-poll-loop.ts +59 -39
- package/src/subagent-poller.ts +46 -0
- package/src/subagent-ready.ts +89 -0
- package/src/subagent-registry.ts +70 -0
- package/src/subagent-resolution.ts +41 -0
- package/src/subagent-scope.ts +3 -1
- package/src/subagent-session-io.ts +115 -32
- package/src/subagent-spawn.ts +153 -8
- package/src/swallow.ts +75 -0
- package/src/terminal-core.ts +161 -10
- package/src/todo-core.ts +5 -4
- package/src/todo-reminder-core.ts +8 -1
- package/src/todos-service.ts +18 -6
- package/src/tool-error.ts +26 -0
- package/src/vocab.ts +7 -4
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D100 Online Context Compact Economics & Feasibility Core.
|
|
3
|
+
*
|
|
4
|
+
* Implements pure economics decision-making, request horizon estimation,
|
|
5
|
+
* cache debt tracking, and native compaction feasibility preflights.
|
|
6
|
+
*
|
|
7
|
+
* Zero-dependency / pure algorithm core (except Pi's public cut point helper).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
findCutPoint,
|
|
12
|
+
sessionEntryToContextMessages,
|
|
13
|
+
type SessionEntry,
|
|
14
|
+
} from '@earendil-works/pi-coding-agent';
|
|
15
|
+
|
|
16
|
+
export interface CompactionEconomics {
|
|
17
|
+
readonly remainingRequestScale: number;
|
|
18
|
+
readonly remainingRequestStddevK: number;
|
|
19
|
+
readonly windowReserveTokens: number;
|
|
20
|
+
readonly firstCompactionRequestScale: number;
|
|
21
|
+
readonly subsequentCompactionMargin: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_COMPACTION_ECONOMICS: CompactionEconomics = Object.freeze({
|
|
25
|
+
remainingRequestScale: 1.0,
|
|
26
|
+
remainingRequestStddevK: 0.0,
|
|
27
|
+
windowReserveTokens: 16_384,
|
|
28
|
+
firstCompactionRequestScale: 2.0,
|
|
29
|
+
subsequentCompactionMargin: 1.5,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Token-account cache ratio: a compaction request re-reads the whole current
|
|
34
|
+
* context once (writeTokens), so breakeven = writeTokens / savingTokens ⇔ ratio 2.
|
|
35
|
+
* `auto` fallback when no usable cache pricing exists (2026-09-17 review: every
|
|
36
|
+
* model actually in use reports zero cacheWrite; the old `null` disabled OCC
|
|
37
|
+
* for all of them — 61/61 decisions `cache_ratio_unavailable`, zero compactions).
|
|
38
|
+
*/
|
|
39
|
+
export const TOKEN_ACCOUNT_CACHE_RATIO = 2.0;
|
|
40
|
+
|
|
41
|
+
/** Provider-family `auto` fallbacks (implicit-cache families without a write SKU). */
|
|
42
|
+
const PROVIDER_FAMILY_CACHE_RATIOS: Readonly<Record<string, number>> = Object.freeze({
|
|
43
|
+
gemini: 4,
|
|
44
|
+
grok: 10,
|
|
45
|
+
deepseek: 10,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export type CompactionReason =
|
|
49
|
+
| 'economic'
|
|
50
|
+
| 'window_protection'
|
|
51
|
+
| 'deferred_economic'
|
|
52
|
+
| 'deferred_subsequent_margin'
|
|
53
|
+
| 'deferred_carried_debt'
|
|
54
|
+
| 'horizon_unavailable'
|
|
55
|
+
| 'cache_ratio_unavailable'
|
|
56
|
+
| 'native_not_compactable'
|
|
57
|
+
| 'non_positive_saving';
|
|
58
|
+
|
|
59
|
+
export interface RequestHorizonEstimate {
|
|
60
|
+
readonly completedBoundaryRequestCounts: readonly number[];
|
|
61
|
+
readonly requestsPerBoundaryMean: number;
|
|
62
|
+
readonly requestsPerBoundaryLowerBound: number;
|
|
63
|
+
readonly unboundedExpectedRemainingRequests: number;
|
|
64
|
+
readonly averageContextTokenIncrement: number | null;
|
|
65
|
+
readonly windowRequestUpperBound: number | null;
|
|
66
|
+
readonly expectedRemainingRequests: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CompactionDecision {
|
|
70
|
+
readonly writeTokens: number;
|
|
71
|
+
readonly archiveTokens: number;
|
|
72
|
+
readonly memoTokens: number;
|
|
73
|
+
readonly contextTokens: number;
|
|
74
|
+
readonly completedBoundaryRequestCounts: readonly number[] | null;
|
|
75
|
+
readonly requestsPerBoundaryMean: number | null;
|
|
76
|
+
readonly requestsPerBoundaryLowerBound: number | null;
|
|
77
|
+
readonly unboundedExpectedRemainingRequests: number | null;
|
|
78
|
+
readonly averageContextTokenIncrement: number | null;
|
|
79
|
+
readonly windowRequestUpperBound: number | null;
|
|
80
|
+
readonly expectedRemainingRequests: number | null;
|
|
81
|
+
readonly breakevenRequests: number | null;
|
|
82
|
+
readonly combinedBreakevenRequests: number | null;
|
|
83
|
+
readonly effectiveHorizonRequests: number | null;
|
|
84
|
+
readonly remainingBoundaries: number;
|
|
85
|
+
readonly cacheWriteReadRatio: number | null;
|
|
86
|
+
readonly incrementalCacheCostRatio: number | null;
|
|
87
|
+
readonly priorCompactionCount: number;
|
|
88
|
+
readonly carriedDebtTokens: number;
|
|
89
|
+
readonly cacheDebtRepaymentTokens: number;
|
|
90
|
+
readonly compact: boolean;
|
|
91
|
+
readonly reason: CompactionReason;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const MINIMUM_VARIANCE_SAMPLES = 3;
|
|
95
|
+
const SMALL_SAMPLE_SCALE = 0.5;
|
|
96
|
+
/** Retained boundary-request sample cap so the mean tracks recent pacing. */
|
|
97
|
+
export const MAX_BOUNDARY_SAMPLES = 16;
|
|
98
|
+
|
|
99
|
+
export function estimateRemainingRequests(input: {
|
|
100
|
+
readonly completedBoundaryRequestCounts: readonly number[];
|
|
101
|
+
readonly remainingBoundaries: number;
|
|
102
|
+
readonly scale: number;
|
|
103
|
+
readonly standardDeviationK: number;
|
|
104
|
+
readonly contextTokens: number;
|
|
105
|
+
readonly contextWindowTokens: number | null;
|
|
106
|
+
readonly averageContextTokenIncrement: number | null;
|
|
107
|
+
}): RequestHorizonEstimate {
|
|
108
|
+
const counts = input.completedBoundaryRequestCounts;
|
|
109
|
+
const mean =
|
|
110
|
+
counts.reduce((total, count) => total + count, 0) /
|
|
111
|
+
Math.max(1, counts.length);
|
|
112
|
+
let lowerBound = mean;
|
|
113
|
+
if (input.standardDeviationK !== 0) {
|
|
114
|
+
if (counts.length < MINIMUM_VARIANCE_SAMPLES) {
|
|
115
|
+
lowerBound *= SMALL_SAMPLE_SCALE;
|
|
116
|
+
} else {
|
|
117
|
+
const variance = counts.reduce((total, count) => total + (count - mean) ** 2, 0);
|
|
118
|
+
const deviation = Math.sqrt(variance / (counts.length - 1));
|
|
119
|
+
lowerBound = Math.max(0, mean - input.standardDeviationK * deviation);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const unboundedExpectedRemainingRequests =
|
|
124
|
+
1 + Math.floor(lowerBound * Math.max(0, input.remainingBoundaries) * input.scale);
|
|
125
|
+
const windowRequestUpperBound =
|
|
126
|
+
input.contextWindowTokens === null ||
|
|
127
|
+
input.averageContextTokenIncrement === null ||
|
|
128
|
+
input.averageContextTokenIncrement <= 0
|
|
129
|
+
? null
|
|
130
|
+
: Math.max(
|
|
131
|
+
0,
|
|
132
|
+
Math.floor((input.contextWindowTokens - input.contextTokens) / input.averageContextTokenIncrement),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
completedBoundaryRequestCounts: [...counts],
|
|
137
|
+
requestsPerBoundaryMean: mean,
|
|
138
|
+
requestsPerBoundaryLowerBound: lowerBound,
|
|
139
|
+
unboundedExpectedRemainingRequests,
|
|
140
|
+
averageContextTokenIncrement: input.averageContextTokenIncrement,
|
|
141
|
+
windowRequestUpperBound,
|
|
142
|
+
expectedRemainingRequests:
|
|
143
|
+
windowRequestUpperBound === null
|
|
144
|
+
? unboundedExpectedRemainingRequests
|
|
145
|
+
: Math.min(unboundedExpectedRemainingRequests, windowRequestUpperBound),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function decideCompaction(input: {
|
|
150
|
+
readonly writeTokens: number;
|
|
151
|
+
readonly archiveTokens: number;
|
|
152
|
+
readonly memoTokens: number;
|
|
153
|
+
readonly contextTokens: number;
|
|
154
|
+
readonly completedBoundaryRequestCounts: readonly number[] | null;
|
|
155
|
+
readonly remainingBoundaries: number;
|
|
156
|
+
readonly averageContextTokenIncrement: number | null;
|
|
157
|
+
readonly contextWindowTokens: number | null;
|
|
158
|
+
readonly priorCompactionCount: number;
|
|
159
|
+
readonly carriedDebtTokens: number;
|
|
160
|
+
readonly cacheDebtRepaymentTokens: number;
|
|
161
|
+
readonly cacheWriteReadRatio: number | null;
|
|
162
|
+
readonly economics?: CompactionEconomics;
|
|
163
|
+
}): CompactionDecision {
|
|
164
|
+
const economics = input.economics ?? DEFAULT_COMPACTION_ECONOMICS;
|
|
165
|
+
const horizon =
|
|
166
|
+
input.completedBoundaryRequestCounts === null
|
|
167
|
+
? null
|
|
168
|
+
: estimateRemainingRequests({
|
|
169
|
+
completedBoundaryRequestCounts: input.completedBoundaryRequestCounts,
|
|
170
|
+
remainingBoundaries: input.remainingBoundaries,
|
|
171
|
+
scale: economics.remainingRequestScale,
|
|
172
|
+
standardDeviationK: economics.remainingRequestStddevK,
|
|
173
|
+
contextTokens: input.contextTokens,
|
|
174
|
+
contextWindowTokens: input.contextWindowTokens,
|
|
175
|
+
averageContextTokenIncrement: input.averageContextTokenIncrement,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const savingTokens = input.archiveTokens - input.memoTokens;
|
|
179
|
+
const incrementalCacheCostRatio =
|
|
180
|
+
input.cacheWriteReadRatio === null ? null : Math.max(0, input.cacheWriteReadRatio - 1);
|
|
181
|
+
|
|
182
|
+
const breakevenRequests =
|
|
183
|
+
savingTokens > 0 && incrementalCacheCostRatio !== null
|
|
184
|
+
? (input.writeTokens * incrementalCacheCostRatio) / savingTokens
|
|
185
|
+
: null;
|
|
186
|
+
|
|
187
|
+
const combinedBreakevenRequests =
|
|
188
|
+
savingTokens > 0 && incrementalCacheCostRatio !== null
|
|
189
|
+
? (input.carriedDebtTokens + input.writeTokens * incrementalCacheCostRatio) / savingTokens
|
|
190
|
+
: null;
|
|
191
|
+
const firstCompaction = input.priorCompactionCount === 0;
|
|
192
|
+
// The first-compaction relaxation survives cold-start sample noise when real work
|
|
193
|
+
// remains. With zero remaining boundaries the horizon is 1 (nothing left to amortize
|
|
194
|
+
// over); scaling it by firstCompactionRequestScale would "discover" a horizon of 2
|
|
195
|
+
// and burn a full-context summarization for zero future benefit (2026-09-17 review).
|
|
196
|
+
const relaxFirstCompaction = firstCompaction && input.remainingBoundaries > 0;
|
|
197
|
+
const effectiveHorizonRequests =
|
|
198
|
+
horizon === null
|
|
199
|
+
? null
|
|
200
|
+
: relaxFirstCompaction
|
|
201
|
+
? Math.min(
|
|
202
|
+
horizon.expectedRemainingRequests * economics.firstCompactionRequestScale,
|
|
203
|
+
horizon.windowRequestUpperBound ?? Number.POSITIVE_INFINITY,
|
|
204
|
+
)
|
|
205
|
+
: horizon.expectedRemainingRequests;
|
|
206
|
+
|
|
207
|
+
const windowProtection =
|
|
208
|
+
input.contextWindowTokens !== null &&
|
|
209
|
+
input.contextTokens >= input.contextWindowTokens - economics.windowReserveTokens;
|
|
210
|
+
|
|
211
|
+
const baseEconomic =
|
|
212
|
+
horizon !== null &&
|
|
213
|
+
horizon.expectedRemainingRequests > 0 &&
|
|
214
|
+
breakevenRequests !== null &&
|
|
215
|
+
breakevenRequests <= horizon.expectedRemainingRequests;
|
|
216
|
+
|
|
217
|
+
const firstEconomic =
|
|
218
|
+
firstCompaction &&
|
|
219
|
+
effectiveHorizonRequests !== null &&
|
|
220
|
+
effectiveHorizonRequests > 0 &&
|
|
221
|
+
breakevenRequests !== null &&
|
|
222
|
+
breakevenRequests <= effectiveHorizonRequests;
|
|
223
|
+
|
|
224
|
+
const subsequentMarginOpen =
|
|
225
|
+
!firstCompaction &&
|
|
226
|
+
horizon !== null &&
|
|
227
|
+
breakevenRequests !== null &&
|
|
228
|
+
breakevenRequests * economics.subsequentCompactionMargin <= horizon.expectedRemainingRequests;
|
|
229
|
+
|
|
230
|
+
const carriedDebtGateOpen =
|
|
231
|
+
!firstCompaction &&
|
|
232
|
+
horizon !== null &&
|
|
233
|
+
combinedBreakevenRequests !== null &&
|
|
234
|
+
combinedBreakevenRequests <= horizon.expectedRemainingRequests;
|
|
235
|
+
|
|
236
|
+
const economic = firstCompaction ? firstEconomic : baseEconomic && subsequentMarginOpen && carriedDebtGateOpen;
|
|
237
|
+
const compressible = savingTokens > 0;
|
|
238
|
+
const compact = compressible && (windowProtection || economic);
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
writeTokens: input.writeTokens,
|
|
242
|
+
archiveTokens: input.archiveTokens,
|
|
243
|
+
memoTokens: input.memoTokens,
|
|
244
|
+
contextTokens: input.contextTokens,
|
|
245
|
+
...(horizon ?? {
|
|
246
|
+
completedBoundaryRequestCounts: null,
|
|
247
|
+
requestsPerBoundaryMean: null,
|
|
248
|
+
requestsPerBoundaryLowerBound: null,
|
|
249
|
+
unboundedExpectedRemainingRequests: null,
|
|
250
|
+
averageContextTokenIncrement: input.averageContextTokenIncrement,
|
|
251
|
+
windowRequestUpperBound: null,
|
|
252
|
+
expectedRemainingRequests: null,
|
|
253
|
+
}),
|
|
254
|
+
breakevenRequests,
|
|
255
|
+
combinedBreakevenRequests,
|
|
256
|
+
effectiveHorizonRequests,
|
|
257
|
+
remainingBoundaries: input.remainingBoundaries,
|
|
258
|
+
cacheWriteReadRatio: input.cacheWriteReadRatio,
|
|
259
|
+
incrementalCacheCostRatio,
|
|
260
|
+
priorCompactionCount: input.priorCompactionCount,
|
|
261
|
+
carriedDebtTokens: input.carriedDebtTokens,
|
|
262
|
+
cacheDebtRepaymentTokens: input.cacheDebtRepaymentTokens,
|
|
263
|
+
compact,
|
|
264
|
+
reason: !compressible
|
|
265
|
+
? 'non_positive_saving'
|
|
266
|
+
: windowProtection
|
|
267
|
+
? 'window_protection'
|
|
268
|
+
: economic
|
|
269
|
+
? 'economic'
|
|
270
|
+
: horizon === null
|
|
271
|
+
? 'horizon_unavailable'
|
|
272
|
+
: breakevenRequests === null
|
|
273
|
+
? 'cache_ratio_unavailable'
|
|
274
|
+
: !firstCompaction && baseEconomic && !subsequentMarginOpen
|
|
275
|
+
? 'deferred_subsequent_margin'
|
|
276
|
+
: !firstCompaction && baseEconomic && !carriedDebtGateOpen
|
|
277
|
+
? 'deferred_carried_debt'
|
|
278
|
+
: 'deferred_economic',
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Resolve the cache write/read cost ratio for economic compaction decisions.
|
|
284
|
+
*
|
|
285
|
+
* Priority:
|
|
286
|
+
* 1. Explicit numeric config → use as-is (e.g. 12.5 for Anthropic-style pricing)
|
|
287
|
+
* 2. Model cost metadata:
|
|
288
|
+
* a. cacheWrite > 0 && cacheRead > 0 → write/read (explicit cache SKU)
|
|
289
|
+
* b. cacheWrite == 0 && cacheRead > 0 && input > 0 → input/cacheRead
|
|
290
|
+
* (implicit cache: rewriting the prefix is billed at the input price)
|
|
291
|
+
* 3. Provider-family fallback (gemini≈4, grok/deepseek≈10 — implicit-cache
|
|
292
|
+
* families without a local cacheWrite SKU)
|
|
293
|
+
* 4. Token-account fallback 2.0: a compaction re-reads writeTokens once,
|
|
294
|
+
* so breakeven = writeTokens/savingTokens ⇔ ratio 2
|
|
295
|
+
*
|
|
296
|
+
* Returns null only for an explicitly invalid numeric config.
|
|
297
|
+
*
|
|
298
|
+
* History (P0-1): `auto` used to return null when cacheWrite==0, reading that
|
|
299
|
+
* as "no KV cache at all". The 2026-09-17 trial disproved it on this machine:
|
|
300
|
+
* grok/deepseek/gemini sessions all show non-zero cacheRead usage while
|
|
301
|
+
* cacheWrite stays 0 — there is no separate write SKU, not "no cache". null
|
|
302
|
+
* disabled OCC for every model actually used (61/61 decisions
|
|
303
|
+
* `cache_ratio_unavailable`, zero compactions). The floor matters as much as
|
|
304
|
+
* the fix: never implicitly fall back below 2.0, because ratio <= 1 zeroes the
|
|
305
|
+
* incremental cost and would compact even on the last boundary with nothing
|
|
306
|
+
* left to amortize (the original P0-1 failure mode).
|
|
307
|
+
*/
|
|
308
|
+
export function resolveCacheRatioFromCost(
|
|
309
|
+
ratioConfig: number | 'auto',
|
|
310
|
+
cost?: { input?: number; cacheRead?: number; cacheWrite?: number } | null,
|
|
311
|
+
identity?: { provider?: string; modelId?: string } | null,
|
|
312
|
+
): number | null {
|
|
313
|
+
// Explicit config takes absolute precedence
|
|
314
|
+
if (typeof ratioConfig === 'number') {
|
|
315
|
+
return Number.isFinite(ratioConfig) && ratioConfig >= 0 ? ratioConfig : null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (cost) {
|
|
319
|
+
const read = cost.cacheRead ?? 0;
|
|
320
|
+
const write = cost.cacheWrite ?? 0;
|
|
321
|
+
const input = cost.input ?? 0;
|
|
322
|
+
|
|
323
|
+
if (read > 0 && write > 0) return write / read;
|
|
324
|
+
if (read > 0 && write === 0 && input > 0) return input / read;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const familyKey = `${identity?.provider ?? ''}/${identity?.modelId ?? ''}`.toLowerCase();
|
|
328
|
+
for (const [family, ratio] of Object.entries(PROVIDER_FAMILY_CACHE_RATIOS)) {
|
|
329
|
+
if (familyKey.includes(family)) return ratio;
|
|
330
|
+
}
|
|
331
|
+
return TOKEN_ACCOUNT_CACHE_RATIO;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function compactionMessageCount(entries: readonly SessionEntry[], startIndex: number, endIndex: number): number {
|
|
335
|
+
let count = 0;
|
|
336
|
+
for (let index = startIndex; index < endIndex; index++) {
|
|
337
|
+
const entry = entries[index];
|
|
338
|
+
if (entry && entry.type !== 'compaction' && sessionEntryToContextMessages(entry).length > 0) {
|
|
339
|
+
count++;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return count;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function branchAfterAbort(entries: readonly SessionEntry[]): SessionEntry[] {
|
|
346
|
+
const last = entries.at(-1);
|
|
347
|
+
const markerProvider = 'pi-herdr';
|
|
348
|
+
return [
|
|
349
|
+
...entries,
|
|
350
|
+
{
|
|
351
|
+
type: 'message',
|
|
352
|
+
id: 'pi-herdr-online-context-compact-abort-marker',
|
|
353
|
+
parentId: last?.id ?? null,
|
|
354
|
+
timestamp: new Date(0).toISOString(),
|
|
355
|
+
message: {
|
|
356
|
+
role: 'assistant',
|
|
357
|
+
content: [],
|
|
358
|
+
api: markerProvider,
|
|
359
|
+
provider: markerProvider,
|
|
360
|
+
model: 'aborted',
|
|
361
|
+
usage: {
|
|
362
|
+
input: 0,
|
|
363
|
+
output: 0,
|
|
364
|
+
cacheRead: 0,
|
|
365
|
+
cacheWrite: 0,
|
|
366
|
+
totalTokens: 0,
|
|
367
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
368
|
+
},
|
|
369
|
+
stopReason: 'aborted',
|
|
370
|
+
timestamp: 0,
|
|
371
|
+
},
|
|
372
|
+
} as SessionEntry,
|
|
373
|
+
];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function nativeCompactionFeasible(entries: readonly SessionEntry[], keepRecentTokens: number): boolean {
|
|
377
|
+
if (!Array.isArray(entries) || entries.length === 0) return false;
|
|
378
|
+
const path = branchAfterAbort(entries);
|
|
379
|
+
let startIndex = 0;
|
|
380
|
+
for (let index = path.length - 1; index >= 0; index--) {
|
|
381
|
+
const entry = path[index];
|
|
382
|
+
if (entry?.type !== 'compaction') continue;
|
|
383
|
+
const keptIndex = path.findIndex((item) => item.id === (entry as { firstKeptEntryId?: string }).firstKeptEntryId);
|
|
384
|
+
startIndex = keptIndex >= 0 ? keptIndex : index + 1;
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const cut = findCutPoint(path, startIndex, path.length, keepRecentTokens);
|
|
389
|
+
const historyEnd = cut.isSplitTurn ? cut.turnStartIndex : cut.firstKeptEntryIndex;
|
|
390
|
+
const historyMessages = historyEnd > startIndex ? compactionMessageCount(path, startIndex, historyEnd) : 0;
|
|
391
|
+
const prefixMessages =
|
|
392
|
+
cut.isSplitTurn && cut.turnStartIndex >= 0
|
|
393
|
+
? compactionMessageCount(path, cut.turnStartIndex, cut.firstKeptEntryIndex)
|
|
394
|
+
: 0;
|
|
395
|
+
return historyMessages > 0 || prefixMessages > 0;
|
|
396
|
+
}
|