billion-context-pi 0.1.38 → 0.1.39
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/config.d.ts +33 -4
- package/dist/density.d.ts +19 -0
- package/dist/index.js +250 -466
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +12 -0
- package/dist/tokens.d.ts +6 -0
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ function createInitialState() {
|
|
|
76
76
|
return {
|
|
77
77
|
blocks: [],
|
|
78
78
|
messageRefs: { byRaw: {}, byRef: {} },
|
|
79
|
+
tokenSnapshot: {},
|
|
79
80
|
nudge: {
|
|
80
81
|
lastPerMessageNudgeTokens: 0,
|
|
81
82
|
lastNudgeShownTokens: 0,
|
|
@@ -245,11 +246,23 @@ function syncBlocks(messages, state) {
|
|
|
245
246
|
byRaw: { ...state.messageRefs.byRaw },
|
|
246
247
|
byRef: { ...state.messageRefs.byRef }
|
|
247
248
|
},
|
|
249
|
+
// Snapshot is keyed by ref with primitive values — shallow copy suffices.
|
|
250
|
+
tokenSnapshot: { ...state.tokenSnapshot ?? {} },
|
|
248
251
|
nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
|
|
249
252
|
stats: { ...state.stats },
|
|
250
253
|
nextBlockId: state.nextBlockId,
|
|
251
254
|
nextRunId: state.nextRunId
|
|
252
255
|
};
|
|
256
|
+
const liveRefs = new Set(
|
|
257
|
+
messages.map((m) => result.messageRefs.byRaw[m.id]).filter((r) => typeof r === "string")
|
|
258
|
+
);
|
|
259
|
+
if (Object.keys(result.tokenSnapshot).length !== liveRefs.size) {
|
|
260
|
+
const pruned = {};
|
|
261
|
+
for (const [ref, n] of Object.entries(result.tokenSnapshot)) {
|
|
262
|
+
if (liveRefs.has(ref)) pruned[ref] = n;
|
|
263
|
+
}
|
|
264
|
+
result.tokenSnapshot = pruned;
|
|
265
|
+
}
|
|
253
266
|
const consumedBlockIds = /* @__PURE__ */ new Set();
|
|
254
267
|
for (const block of result.blocks) {
|
|
255
268
|
for (const consumedId of block.directBlockIds) {
|
|
@@ -724,7 +737,7 @@ var TAG_CLOSE = LT + "/acp" + GT;
|
|
|
724
737
|
function acpTag(ref, tokens, type) {
|
|
725
738
|
return TAG_OPEN + 'tokens="' + formatTokens(tokens) + '" type="' + type + '"' + GT + ref + TAG_CLOSE;
|
|
726
739
|
}
|
|
727
|
-
function renderMessage(message, map, countTokens, strategy) {
|
|
740
|
+
function renderMessage(message, map, countTokens, strategy, snapshot = null) {
|
|
728
741
|
const ref = refForRaw(map, message.id);
|
|
729
742
|
if (!ref || ref === BLOCKED_REF) return message;
|
|
730
743
|
if (strategy === "none") return message;
|
|
@@ -735,26 +748,33 @@ function renderMessage(message, map, countTokens, strategy) {
|
|
|
735
748
|
"^" + escapeRegex(TAG_OPEN) + "[^>]*" + GT + escapeRegex(ref) + escapeRegex(TAG_CLOSE) + "\\n?"
|
|
736
749
|
);
|
|
737
750
|
const cleanText = (message.text || "").replace(ownTagRe, "");
|
|
738
|
-
const tokens = countTokens(cleanText);
|
|
751
|
+
const tokens = snapshot ? snapshot[ref] ?? (snapshot[ref] = countTokens(cleanText)) : countTokens(cleanText);
|
|
739
752
|
const type = classifyType(message);
|
|
740
753
|
const prefix = acpTag(ref, tokens, type) + "\n";
|
|
741
754
|
if (!cleanText) return { ...message, text: prefix };
|
|
742
755
|
return { ...message, text: prefix + cleanText };
|
|
743
756
|
}
|
|
744
|
-
function
|
|
757
|
+
function renderWithSnapshot(messages, state, countTokens = (text) => Math.ceil(text.length / 4), strategy = "all") {
|
|
745
758
|
const map = state.messageRefs;
|
|
746
|
-
|
|
747
|
-
|
|
759
|
+
const snapshot = { ...state.tokenSnapshot ?? {} };
|
|
760
|
+
const rendered = messages.map(
|
|
761
|
+
(message) => renderMessage(message, map, countTokens, strategy, snapshot)
|
|
748
762
|
);
|
|
763
|
+
return { messages: rendered, tokenSnapshot: snapshot };
|
|
749
764
|
}
|
|
750
765
|
function createRenderRefsNode(strategy) {
|
|
751
766
|
return {
|
|
752
767
|
name: "render-refs",
|
|
753
768
|
run(io, ctx) {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
769
|
+
const { messages, tokenSnapshot } = renderWithSnapshot(
|
|
770
|
+
io.messages,
|
|
771
|
+
io.state,
|
|
772
|
+
ctx.countTokens,
|
|
773
|
+
strategy
|
|
774
|
+
);
|
|
775
|
+
const prev = io.state.tokenSnapshot;
|
|
776
|
+
const changed = !prev || Object.keys(tokenSnapshot).length !== Object.keys(prev).length;
|
|
777
|
+
return changed ? { ...io, messages, state: { ...io.state, tokenSnapshot } } : { ...io, messages };
|
|
758
778
|
}
|
|
759
779
|
};
|
|
760
780
|
}
|
|
@@ -950,6 +970,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
|
|
|
950
970
|
ref,
|
|
951
971
|
refNum: rn,
|
|
952
972
|
tokens: countTokens(msg.text ?? ""),
|
|
973
|
+
chars: (msg.text ?? "").length,
|
|
953
974
|
isTool: isToolMessage(msg),
|
|
954
975
|
isUser: msg.role === "user"
|
|
955
976
|
});
|
|
@@ -970,6 +991,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
|
|
|
970
991
|
endRef: info.ref,
|
|
971
992
|
count: 1,
|
|
972
993
|
tokens: info.tokens,
|
|
994
|
+
chars: info.chars,
|
|
973
995
|
toolPct: info.isTool ? 100 : 0,
|
|
974
996
|
textPct: info.isTool ? 0 : 100
|
|
975
997
|
};
|
|
@@ -977,6 +999,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
|
|
|
977
999
|
cur.endRef = info.ref;
|
|
978
1000
|
cur.count++;
|
|
979
1001
|
cur.tokens += info.tokens;
|
|
1002
|
+
cur.chars = (cur.chars ?? 0) + info.chars;
|
|
980
1003
|
if (info.isTool) {
|
|
981
1004
|
cur.toolPct = Math.round((cur.toolPct * (cur.count - 1) + 100) / cur.count);
|
|
982
1005
|
} else {
|
|
@@ -1024,6 +1047,7 @@ function mergeBatch(batch) {
|
|
|
1024
1047
|
const last = batch[batch.length - 1];
|
|
1025
1048
|
const count = batch.reduce((s, r) => s + r.count, 0);
|
|
1026
1049
|
const tokens = batch.reduce((s, r) => s + r.tokens, 0);
|
|
1050
|
+
const chars = batch.reduce((s, r) => s + rangeChars(r), 0);
|
|
1027
1051
|
const toolPct = Math.round(
|
|
1028
1052
|
batch.reduce((s, r) => s + r.toolPct * r.count, 0) / count
|
|
1029
1053
|
);
|
|
@@ -1032,6 +1056,7 @@ function mergeBatch(batch) {
|
|
|
1032
1056
|
endRef: last.endRef,
|
|
1033
1057
|
count,
|
|
1034
1058
|
tokens,
|
|
1059
|
+
chars,
|
|
1035
1060
|
toolPct,
|
|
1036
1061
|
textPct: 100 - toolPct
|
|
1037
1062
|
};
|
|
@@ -1040,16 +1065,21 @@ function mergeBatch(batch) {
|
|
|
1040
1065
|
}
|
|
1041
1066
|
return merged;
|
|
1042
1067
|
}
|
|
1068
|
+
function rangeChars(r) {
|
|
1069
|
+
return r.chars ?? r.tokens * 4;
|
|
1070
|
+
}
|
|
1043
1071
|
function mergeRangesToThreshold(ranges, minChars) {
|
|
1044
1072
|
if (minChars <= 0 || ranges.length === 0) return ranges;
|
|
1045
1073
|
const result = [];
|
|
1046
1074
|
let batch = [];
|
|
1075
|
+
let batchChars = 0;
|
|
1047
1076
|
for (const r of ranges) {
|
|
1048
1077
|
batch.push(r);
|
|
1049
|
-
|
|
1050
|
-
if (
|
|
1078
|
+
batchChars += rangeChars(r);
|
|
1079
|
+
if (batchChars >= minChars) {
|
|
1051
1080
|
result.push(mergeBatch(batch));
|
|
1052
1081
|
batch = [];
|
|
1082
|
+
batchChars = 0;
|
|
1053
1083
|
}
|
|
1054
1084
|
}
|
|
1055
1085
|
if (batch.length > 0) {
|
|
@@ -1637,7 +1667,7 @@ function resolveAdaptiveGrowth(modelContextLimit, nudge) {
|
|
|
1637
1667
|
function pendingByTier(state, recommendation, countTokens, minCompressRange) {
|
|
1638
1668
|
const out = {};
|
|
1639
1669
|
const merged = recommendation?.recommendedRanges ?? [];
|
|
1640
|
-
const effective = minCompressRange > 0 ? merged.filter((r) => r.tokens * 4 >= minCompressRange) : merged;
|
|
1670
|
+
const effective = minCompressRange > 0 ? merged.filter((r) => (r.chars ?? r.tokens * 4) >= minCompressRange) : merged;
|
|
1641
1671
|
out[1] = { pending: effective.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };
|
|
1642
1672
|
const active = activeBlocks(state);
|
|
1643
1673
|
const t1 = active.filter((b) => b.tier === 1);
|
|
@@ -1799,6 +1829,7 @@ function cloneState(state) {
|
|
|
1799
1829
|
byRaw: { ...state.messageRefs.byRaw },
|
|
1800
1830
|
byRef: { ...state.messageRefs.byRef }
|
|
1801
1831
|
},
|
|
1832
|
+
tokenSnapshot: { ...state.tokenSnapshot ?? {} },
|
|
1802
1833
|
nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
|
|
1803
1834
|
stats: { ...state.stats },
|
|
1804
1835
|
nextBlockId: state.nextBlockId,
|
|
@@ -2464,9 +2495,9 @@ function charBigrams(text) {
|
|
|
2464
2495
|
}
|
|
2465
2496
|
return grams;
|
|
2466
2497
|
}
|
|
2467
|
-
function tfMap(text,
|
|
2498
|
+
function tfMap(text, stem2) {
|
|
2468
2499
|
const m = /* @__PURE__ */ new Map();
|
|
2469
|
-
for (const t of tokenize(text, { stem:
|
|
2500
|
+
for (const t of tokenize(text, { stem: stem2 })) m.set(t, (m.get(t) ?? 0) + 1);
|
|
2470
2501
|
return m;
|
|
2471
2502
|
}
|
|
2472
2503
|
var bm25Algorithm = {
|
|
@@ -2671,7 +2702,20 @@ function resolveDelegate(adapter) {
|
|
|
2671
2702
|
displayUsage: adapter.displayUsage ?? "separate"
|
|
2672
2703
|
};
|
|
2673
2704
|
}
|
|
2674
|
-
function
|
|
2705
|
+
function mergeCompress(global, provider, model) {
|
|
2706
|
+
return {
|
|
2707
|
+
maxContextLimit: model?.maxContextLimit ?? provider?.maxContextLimit ?? global?.maxContextLimit,
|
|
2708
|
+
emergencyThresholdPercent: model?.emergencyThresholdPercent ?? provider?.emergencyThresholdPercent ?? global?.emergencyThresholdPercent,
|
|
2709
|
+
nudgeGrowthTokens: model?.nudgeGrowthTokens ?? provider?.nudgeGrowthTokens ?? global?.nudgeGrowthTokens
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
function resolveCompress(compress, provider, modelId) {
|
|
2713
|
+
if (!compress) return {};
|
|
2714
|
+
const prov = provider ? compress.providers?.[provider] : void 0;
|
|
2715
|
+
const model = prov && modelId ? prov.models?.[modelId] : void 0;
|
|
2716
|
+
return mergeCompress(compress, prov, model);
|
|
2717
|
+
}
|
|
2718
|
+
function resolveConfig(adapter, liveContextLimit, provider, modelId) {
|
|
2675
2719
|
const envLimit = process.env.ACP_MODEL_CONTEXT_LIMIT;
|
|
2676
2720
|
const envLimitNum = envLimit ? Number(envLimit) : NaN;
|
|
2677
2721
|
const FALLBACK_LIMIT = 15e4;
|
|
@@ -2681,14 +2725,14 @@ function resolveConfig(adapter, liveContextLimit) {
|
|
|
2681
2725
|
preserveRecentMessages: adapter.preserveRecentMessages ?? 5,
|
|
2682
2726
|
...adapter.coreOverrides
|
|
2683
2727
|
});
|
|
2684
|
-
const c = adapter.compress;
|
|
2685
|
-
if (c
|
|
2686
|
-
if (c
|
|
2728
|
+
const c = resolveCompress(adapter.compress, provider, modelId);
|
|
2729
|
+
if (c.maxContextLimit !== void 0) config.nudge.maxContextLimitPct = parsePercent(c.maxContextLimit);
|
|
2730
|
+
if (c.emergencyThresholdPercent !== void 0) {
|
|
2687
2731
|
const pct2 = parsePercent(c.emergencyThresholdPercent);
|
|
2688
2732
|
config.nudge.emergencyThresholdPct = pct2;
|
|
2689
2733
|
config.truncate.threshold = pct2;
|
|
2690
2734
|
}
|
|
2691
|
-
if (c
|
|
2735
|
+
if (c.nudgeGrowthTokens !== void 0) {
|
|
2692
2736
|
config.nudge.growthFloor = c.nudgeGrowthTokens;
|
|
2693
2737
|
config.nudge.growthCap = c.nudgeGrowthTokens;
|
|
2694
2738
|
}
|
|
@@ -2701,6 +2745,89 @@ function parsePercent(v) {
|
|
|
2701
2745
|
return Number(s);
|
|
2702
2746
|
}
|
|
2703
2747
|
|
|
2748
|
+
// src/density.ts
|
|
2749
|
+
var DENSITY_MIN = 0.5;
|
|
2750
|
+
var DENSITY_MAX = 2.5;
|
|
2751
|
+
var MIN_DELTA_EST = 50;
|
|
2752
|
+
var CONFIRM_RATIO = 0.2;
|
|
2753
|
+
var INITIAL_DENSITY = 1;
|
|
2754
|
+
var DensityEstimator = class {
|
|
2755
|
+
models = /* @__PURE__ */ new Map();
|
|
2756
|
+
/** 重置指定模型(模型切换/会话开始时调用)。 */
|
|
2757
|
+
resetModel(modelId) {
|
|
2758
|
+
this.models.delete(modelId);
|
|
2759
|
+
}
|
|
2760
|
+
/** 返回当前密度系数(未知模型返回初始 1)。 */
|
|
2761
|
+
densityFor(modelId) {
|
|
2762
|
+
return this.models.get(modelId)?.density ?? INITIAL_DENSITY;
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* 每轮 context 事件调用。realTotal 为 provider 真实 usage(可空),
|
|
2766
|
+
* estTotal 为本地估算总 token。postCompression 为压缩刚发生标志。
|
|
2767
|
+
*/
|
|
2768
|
+
update(modelId, realTotal, estTotal, postCompression = false) {
|
|
2769
|
+
if (realTotal === null) return;
|
|
2770
|
+
let est = this.models.get(modelId);
|
|
2771
|
+
if (!est) {
|
|
2772
|
+
est = {
|
|
2773
|
+
density: INITIAL_DENSITY,
|
|
2774
|
+
anchorReal: null,
|
|
2775
|
+
anchorEst: null,
|
|
2776
|
+
pendingDensity: null,
|
|
2777
|
+
confirmCount: 0,
|
|
2778
|
+
postCompressionSkip: false
|
|
2779
|
+
};
|
|
2780
|
+
this.models.set(modelId, est);
|
|
2781
|
+
}
|
|
2782
|
+
if (postCompression) {
|
|
2783
|
+
est.postCompressionSkip = true;
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
if (est.postCompressionSkip) {
|
|
2787
|
+
est.postCompressionSkip = false;
|
|
2788
|
+
est.anchorReal = realTotal;
|
|
2789
|
+
est.anchorEst = estTotal;
|
|
2790
|
+
est.pendingDensity = null;
|
|
2791
|
+
est.confirmCount = 0;
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
if (est.anchorReal === null || est.anchorEst === null) {
|
|
2795
|
+
est.anchorReal = realTotal;
|
|
2796
|
+
est.anchorEst = estTotal;
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
const dReal = realTotal - est.anchorReal;
|
|
2800
|
+
const dEst = estTotal - est.anchorEst;
|
|
2801
|
+
if (dEst < MIN_DELTA_EST) return;
|
|
2802
|
+
est.anchorReal = realTotal;
|
|
2803
|
+
est.anchorEst = estTotal;
|
|
2804
|
+
const instant = clamp(dReal / dEst, DENSITY_MIN, DENSITY_MAX);
|
|
2805
|
+
if (est.pendingDensity === null) {
|
|
2806
|
+
est.pendingDensity = instant;
|
|
2807
|
+
est.confirmCount = 1;
|
|
2808
|
+
} else if (Math.abs(instant - est.pendingDensity) / est.pendingDensity <= CONFIRM_RATIO) {
|
|
2809
|
+
est.confirmCount += 1;
|
|
2810
|
+
} else {
|
|
2811
|
+
est.pendingDensity = instant;
|
|
2812
|
+
est.confirmCount = 1;
|
|
2813
|
+
}
|
|
2814
|
+
if (est.confirmCount >= 2) {
|
|
2815
|
+
est.density = est.pendingDensity;
|
|
2816
|
+
est.confirmCount = 0;
|
|
2817
|
+
est.pendingDensity = null;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
/** 注入器:估算文本 token = defaultCountTokens × density。 */
|
|
2821
|
+
estimateWithDensity(modelId, text) {
|
|
2822
|
+
const d = this.densityFor(modelId);
|
|
2823
|
+
if (d === 1) return defaultCountTokens(text);
|
|
2824
|
+
return Math.round(defaultCountTokens(text) * d);
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
function clamp(v, lo, hi) {
|
|
2828
|
+
return v < lo ? lo : v > hi ? hi : v;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2704
2831
|
// src/messages.ts
|
|
2705
2832
|
var REF_TAG_SOURCE = "(?:<acp\\s[^>]*>m\\d{5}</acp>|\\[m\\d{1,5}\\])";
|
|
2706
2833
|
var REF_TAG = new RegExp(`^${REF_TAG_SOURCE}\\s?\\n?`);
|
|
@@ -3206,6 +3333,7 @@ function mergeInitialState(parsed) {
|
|
|
3206
3333
|
return {
|
|
3207
3334
|
blocks: parsed.blocks ?? fresh.blocks,
|
|
3208
3335
|
messageRefs: parsed.messageRefs ?? fresh.messageRefs,
|
|
3336
|
+
tokenSnapshot: parsed.tokenSnapshot ?? fresh.tokenSnapshot,
|
|
3209
3337
|
nudge: { ...fresh.nudge, ...parsed.nudge ?? {} },
|
|
3210
3338
|
stats: { ...fresh.stats, ...parsed.stats ?? {} },
|
|
3211
3339
|
nextBlockId: parsed.nextBlockId ?? fresh.nextBlockId,
|
|
@@ -3428,8 +3556,14 @@ function pruneOrphanRefs(state, messages) {
|
|
|
3428
3556
|
}
|
|
3429
3557
|
}
|
|
3430
3558
|
function createRuntime(adapter) {
|
|
3431
|
-
const
|
|
3559
|
+
const density = new DensityEstimator();
|
|
3560
|
+
let countModelId = "default";
|
|
3561
|
+
const core = createCore({
|
|
3562
|
+
// 密度校准版 countTokens(Phase 2):默认回落 defaultCountTokens(density=1)
|
|
3563
|
+
countTokens: (text) => density.estimateWithDensity(countModelId, text)
|
|
3564
|
+
});
|
|
3432
3565
|
const store = new SessionStateStore();
|
|
3566
|
+
const lastActiveBlockIds = /* @__PURE__ */ new Map();
|
|
3433
3567
|
const locks = /* @__PURE__ */ new Map();
|
|
3434
3568
|
let adapterRef = adapter;
|
|
3435
3569
|
let promptsRef = defaultPrompts;
|
|
@@ -3454,7 +3588,8 @@ function createRuntime(adapter) {
|
|
|
3454
3588
|
return m?.contextWindow ?? 0;
|
|
3455
3589
|
}
|
|
3456
3590
|
function configFor(ctx) {
|
|
3457
|
-
|
|
3591
|
+
const m = ctx.model;
|
|
3592
|
+
return resolveConfig(adapterRef, liveContextLimit(ctx), m?.provider, m?.id);
|
|
3458
3593
|
}
|
|
3459
3594
|
async function stateFor(ctx, liveMessages) {
|
|
3460
3595
|
const sm = ctx.sessionManager;
|
|
@@ -3477,7 +3612,19 @@ function createRuntime(adapter) {
|
|
|
3477
3612
|
const sm = ctx.sessionManager;
|
|
3478
3613
|
await store.save(state, sm.getSessionFile() ?? void 0, sm.getSessionId());
|
|
3479
3614
|
}
|
|
3480
|
-
|
|
3615
|
+
function noteActiveBlocks(sid, activeBlockIds) {
|
|
3616
|
+
const current = new Set(activeBlockIds);
|
|
3617
|
+
const prev = lastActiveBlockIds.get(sid);
|
|
3618
|
+
const isNew = prev !== void 0 && activeBlockIds.some((id) => !prev.has(id));
|
|
3619
|
+
lastActiveBlockIds.set(sid, current);
|
|
3620
|
+
return isNew;
|
|
3621
|
+
}
|
|
3622
|
+
function clearSessionTracking(sid) {
|
|
3623
|
+
lastActiveBlockIds.delete(sid);
|
|
3624
|
+
}
|
|
3625
|
+
return { core, store, density, setCountModel: (m) => {
|
|
3626
|
+
countModelId = m;
|
|
3627
|
+
}, noteActiveBlocks, clearSessionTracking, get adapter() {
|
|
3481
3628
|
return adapterRef;
|
|
3482
3629
|
}, setAdapter: (a) => {
|
|
3483
3630
|
adapterRef = a;
|
|
@@ -7904,6 +8051,9 @@ function estimateTokens(messages, coveredIds) {
|
|
|
7904
8051
|
}
|
|
7905
8052
|
return tokens;
|
|
7906
8053
|
}
|
|
8054
|
+
function calibrateTokens(estimate, density) {
|
|
8055
|
+
return density === 1 ? estimate : Math.round(estimate * density);
|
|
8056
|
+
}
|
|
7907
8057
|
function lastUserMessageId(entries) {
|
|
7908
8058
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
7909
8059
|
const e = entries[i];
|
|
@@ -7974,6 +8124,7 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
|
7974
8124
|
if (ranges.length === 0) return "No ranges provided.";
|
|
7975
8125
|
const { state: initialState, coreMessages } = await runtime.stateFor(ctx);
|
|
7976
8126
|
const config = runtime.configFor(ctx);
|
|
8127
|
+
const modelId = ctx.model?.id ?? "default";
|
|
7977
8128
|
const systemPromptText = getSystemPromptText(ctx);
|
|
7978
8129
|
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
7979
8130
|
const sentTokens = estimateTokens(coreMessages, collectCoveredMessageIds(initialState)) + systemPromptTokens;
|
|
@@ -7981,15 +8132,18 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
|
7981
8132
|
messages: coreMessages,
|
|
7982
8133
|
state: initialState,
|
|
7983
8134
|
config,
|
|
7984
|
-
tokenCount: sentTokens
|
|
8135
|
+
tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId))
|
|
7985
8136
|
});
|
|
7986
8137
|
const state = turn.state;
|
|
7987
8138
|
const messages = turn.messages;
|
|
7988
|
-
const
|
|
8139
|
+
const density = runtime.density.densityFor(modelId);
|
|
8140
|
+
const beforeTokens = calibrateTokens(estimateTokens(messages, collectCoveredMessageIds(state)), density);
|
|
7989
8141
|
const summaryMaxChars = args.summaryMaxChars;
|
|
7990
8142
|
const topLevelTopic = args.topic;
|
|
7991
8143
|
debug.event("compress-in", {
|
|
7992
8144
|
sid: ctx.sessionManager.getSessionId(),
|
|
8145
|
+
modelId,
|
|
8146
|
+
density,
|
|
7993
8147
|
ranges: ranges.length,
|
|
7994
8148
|
spans: ranges.map((r) => ({ span: `${r.startId}..${r.endId}`, summaryLen: r.summary.length, summary: r.summary, topic: r.topic ?? topLevelTopic ?? null })),
|
|
7995
8149
|
blocksBefore: state.blocks.length,
|
|
@@ -8046,7 +8200,8 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
|
8046
8200
|
|
|
8047
8201
|
// src/decompress-tool.ts
|
|
8048
8202
|
import { writeFile, mkdir } from "fs/promises";
|
|
8049
|
-
import {
|
|
8203
|
+
import { existsSync as existsSync2, lstatSync, readlinkSync, realpathSync } from "fs";
|
|
8204
|
+
import { resolve, relative, isAbsolute, join as join3, basename as basename2, dirname as dirname3 } from "path";
|
|
8050
8205
|
import { tmpdir, homedir as homedir2 } from "os";
|
|
8051
8206
|
var AUTO_DIR = join3(homedir2() || tmpdir(), ".cache", "pi", "acp-decompress");
|
|
8052
8207
|
var PREVIEW_CHARS = 600;
|
|
@@ -8090,14 +8245,39 @@ var ALLOWED_DIRS = [
|
|
|
8090
8245
|
function resolveToFilePath(targetPath) {
|
|
8091
8246
|
const expanded = targetPath.startsWith("~/") ? join3(homedir2(), targetPath.slice(2)) : targetPath;
|
|
8092
8247
|
const resolved = resolve(expanded);
|
|
8093
|
-
|
|
8094
|
-
|
|
8248
|
+
let probe = resolved;
|
|
8249
|
+
const suffix = [];
|
|
8250
|
+
while (!existsSync2(probe) && probe !== dirname3(probe)) {
|
|
8251
|
+
suffix.unshift(basename2(probe));
|
|
8252
|
+
probe = dirname3(probe);
|
|
8253
|
+
}
|
|
8254
|
+
const real = existsSync2(probe) ? realpathSync(probe) : probe;
|
|
8255
|
+
let checked = real;
|
|
8256
|
+
for (const part of suffix) {
|
|
8257
|
+
checked = join3(checked, part);
|
|
8258
|
+
try {
|
|
8259
|
+
if (lstatSync(checked).isSymbolicLink()) {
|
|
8260
|
+
const target = readlinkSync(checked);
|
|
8261
|
+
checked = isAbsolute(target) ? resolve(target) : resolve(dirname3(checked), target);
|
|
8262
|
+
}
|
|
8263
|
+
} catch {
|
|
8264
|
+
}
|
|
8265
|
+
}
|
|
8266
|
+
const allowed = ALLOWED_DIRS.map((d) => {
|
|
8267
|
+
try {
|
|
8268
|
+
return realpathSync(d);
|
|
8269
|
+
} catch {
|
|
8270
|
+
return d;
|
|
8271
|
+
}
|
|
8272
|
+
});
|
|
8273
|
+
const isAllowed = allowed.some((dir) => {
|
|
8274
|
+
const rel = relative(dir, checked);
|
|
8095
8275
|
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
8096
8276
|
});
|
|
8097
8277
|
if (!isAllowed) {
|
|
8098
8278
|
return { error: `Error: toFile path must be under ${tmpdir()}, ~/.cache/opencode, or ~/.cache/pi. Got: ${targetPath}` };
|
|
8099
8279
|
}
|
|
8100
|
-
return
|
|
8280
|
+
return checked;
|
|
8101
8281
|
}
|
|
8102
8282
|
function autoFilePath(blockId) {
|
|
8103
8283
|
return join3(AUTO_DIR, `${blockId}-${Date.now()}.txt`);
|
|
@@ -8163,7 +8343,7 @@ ${text}`;
|
|
|
8163
8343
|
}
|
|
8164
8344
|
async function handleDecompress(args, runtime, ctx) {
|
|
8165
8345
|
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
8166
|
-
const arg = args.blockId.trim();
|
|
8346
|
+
const arg = (args.blockId ?? "").trim();
|
|
8167
8347
|
const owner = state.blocks.find((b) => b.effectiveMessageIds.includes(arg));
|
|
8168
8348
|
if (owner) {
|
|
8169
8349
|
return handleMessageRef(arg, owner.blockId, args, ctx);
|
|
@@ -8335,420 +8515,6 @@ function formatSize(tokens) {
|
|
|
8335
8515
|
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
8336
8516
|
}
|
|
8337
8517
|
|
|
8338
|
-
// node_modules/billion-context-kit/node_modules/acp-kernel/dist/index.js
|
|
8339
|
-
import { createRequire as createRequire2 } from "module";
|
|
8340
|
-
var BLOCKED_REF2 = "BLOCKED";
|
|
8341
|
-
function refForRaw2(map, rawId) {
|
|
8342
|
-
return map.byRaw[rawId] ?? null;
|
|
8343
|
-
}
|
|
8344
|
-
var require22 = createRequire2(import.meta.url);
|
|
8345
|
-
function defaultCountTokens2(text) {
|
|
8346
|
-
if (!text) return 0;
|
|
8347
|
-
const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
|
|
8348
|
-
const cjkCount = cjk?.length ?? 0;
|
|
8349
|
-
return cjkCount + Math.ceil((text.length - cjkCount) / 4);
|
|
8350
|
-
}
|
|
8351
|
-
function formatTokens3(tokens) {
|
|
8352
|
-
if (tokens < 1e3) return String(tokens);
|
|
8353
|
-
if (tokens < 1e4) return (tokens / 1e3).toFixed(1) + "K";
|
|
8354
|
-
return Math.round(tokens / 1e3) + "K";
|
|
8355
|
-
}
|
|
8356
|
-
function classifyType2(message) {
|
|
8357
|
-
if (message.contentType === "tool-call" || message.contentType === "tool-result") {
|
|
8358
|
-
return message.toolName || "tool";
|
|
8359
|
-
}
|
|
8360
|
-
return message.contentType;
|
|
8361
|
-
}
|
|
8362
|
-
function escapeRegex2(s) {
|
|
8363
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8364
|
-
}
|
|
8365
|
-
var LT2 = "<";
|
|
8366
|
-
var GT2 = ">";
|
|
8367
|
-
var TAG_OPEN2 = LT2 + "acp ";
|
|
8368
|
-
var TAG_CLOSE2 = LT2 + "/acp" + GT2;
|
|
8369
|
-
function acpTag2(ref, tokens, type) {
|
|
8370
|
-
return TAG_OPEN2 + 'tokens="' + formatTokens3(tokens) + '" type="' + type + '"' + GT2 + ref + TAG_CLOSE2;
|
|
8371
|
-
}
|
|
8372
|
-
function renderMessage2(message, map, countTokens, strategy) {
|
|
8373
|
-
const ref = refForRaw2(map, message.id);
|
|
8374
|
-
if (!ref || ref === BLOCKED_REF2) return message;
|
|
8375
|
-
if (strategy === "none") return message;
|
|
8376
|
-
if (strategy === "text-only" && message.contentType !== "text") {
|
|
8377
|
-
return message;
|
|
8378
|
-
}
|
|
8379
|
-
const ownTagRe = new RegExp(
|
|
8380
|
-
"^" + escapeRegex2(TAG_OPEN2) + "[^>]*" + GT2 + escapeRegex2(ref) + escapeRegex2(TAG_CLOSE2) + "\\n?"
|
|
8381
|
-
);
|
|
8382
|
-
const cleanText = (message.text || "").replace(ownTagRe, "");
|
|
8383
|
-
const tokens = countTokens(cleanText);
|
|
8384
|
-
const type = classifyType2(message);
|
|
8385
|
-
const prefix = acpTag2(ref, tokens, type) + "\n";
|
|
8386
|
-
if (!cleanText) return { ...message, text: prefix };
|
|
8387
|
-
return { ...message, text: prefix + cleanText };
|
|
8388
|
-
}
|
|
8389
|
-
function renderVisibleRefs2(messages, state, countTokens = (text) => Math.ceil(text.length / 4), strategy = "all") {
|
|
8390
|
-
const map = state.messageRefs;
|
|
8391
|
-
return messages.map(
|
|
8392
|
-
(message) => renderMessage2(message, map, countTokens, strategy)
|
|
8393
|
-
);
|
|
8394
|
-
}
|
|
8395
|
-
function createRenderRefsNode2(strategy) {
|
|
8396
|
-
return {
|
|
8397
|
-
name: "render-refs",
|
|
8398
|
-
run(io, ctx) {
|
|
8399
|
-
return {
|
|
8400
|
-
...io,
|
|
8401
|
-
messages: renderVisibleRefs2(io.messages, io.state, ctx.countTokens, strategy)
|
|
8402
|
-
};
|
|
8403
|
-
}
|
|
8404
|
-
};
|
|
8405
|
-
}
|
|
8406
|
-
var renderRefsNode2 = createRenderRefsNode2("all");
|
|
8407
|
-
var COMPRESS_PHILOSOPHY2 = `Compression Philosophy:
|
|
8408
|
-
- All compression serves the primary task, but be frugal.
|
|
8409
|
-
- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
|
|
8410
|
-
- Compress by need, not by percentage.
|
|
8411
|
-
- Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.`;
|
|
8412
|
-
var HOW_TO_COMPRESS_RULES2 = `HOW TO COMPRESS
|
|
8413
|
-
|
|
8414
|
-
When you call \`compress\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original.
|
|
8415
|
-
|
|
8416
|
-
KEEP VERBATIM \u2014 never paraphrase or abbreviate these:
|
|
8417
|
-
- Full file paths with line numbers, directory prefix on every mention (\`lib/hooks.ts:347\`, \`src/index.ts:12-18\`, \`gatenet_v3/model.py:45\`). Never abbreviate to a bare filename (\`hooks.ts\`, \`model.py\`) \u2014 they are ambiguous and cannot be grepped or decompressed-to later.
|
|
8418
|
-
- Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic \u2014 the line that IS the finding, not just the function name (e.g. \`kv_keys += define_gate * a_key[i](emb)\` is more useful than "see model_kvnet.py").
|
|
8419
|
-
- Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).
|
|
8420
|
-
- Key details from reports and analyses \u2014 not just the conclusion. Keep the comparison numbers and the mechanism, not "X is worse" alone (write "1.76\xD7 PPL gap because KV store is static", not "KVNet underperforms").
|
|
8421
|
-
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing; without it the decision looks arbitrary).
|
|
8422
|
-
- Constraints discovered ("must support Node 22", "no new dependencies", "AGENTS.md forbids \`as any\`").
|
|
8423
|
-
- Exact values: versions, config keys, thresholds, magic numbers.
|
|
8424
|
-
- User intent \u2014 quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., "User said: ..."), not as current directives. Losing these changes the task itself.
|
|
8425
|
-
- The user's overall goal and any changes to it \u2014 the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., "initially: fix bug X \u2192 pivoted to: refactor module Y after discovering root cause"). Losing the goal or its evolution makes all subsequent work appear unmotivated.
|
|
8426
|
-
- Purpose behind each significant action \u2014 preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.
|
|
8427
|
-
- Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.
|
|
8428
|
-
- Message refs of key anchors (\`m00420\`, \`m00510\u2013m00520\`) \u2014 they let you or a later reader jump back via decompress to the exact original.
|
|
8429
|
-
|
|
8430
|
-
DROP \u2014 extract the signal, discard the vessel:
|
|
8431
|
-
- Verbose logs (build/test/\`npm\` output) once you have captured the error line or the result.
|
|
8432
|
-
- Duplicate file reads once the needed content is recorded.
|
|
8433
|
-
- Consumed exploration \u2014 search hits, agent return values, successful tool outputs \u2014 once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).
|
|
8434
|
-
- Dead-end exploration \u2014 but PRESERVE the lesson in one line: "tried X, failed because Y".
|
|
8435
|
-
- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).
|
|
8436
|
-
- Repeated status checks (\`git status\`, \`ls\`) once state is known.
|
|
8437
|
-
|
|
8438
|
-
For each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers \u2014 not where it lives. Bad: "probe script at /path/probe_kvnet.py". Good: "probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention." This lets a later decompress target the right block by relevance, not by guessing locations.
|
|
8439
|
-
|
|
8440
|
-
PRIORITY \u2014 when the summary must be compact, preserve in this order:
|
|
8441
|
-
1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).
|
|
8442
|
-
2. Decisions and rationale.
|
|
8443
|
-
3. Exact technical artifacts: paths, signatures, errors, values.
|
|
8444
|
-
4. Conclusions and key findings.
|
|
8445
|
-
5. Lessons learned: what failed and why.
|
|
8446
|
-
|
|
8447
|
-
Write dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;
|
|
8448
|
-
var TIER2_DISTILL_RULES2 = `TIER 2 COMPRESSION \u2014 DISTILLATION
|
|
8449
|
-
|
|
8450
|
-
You are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.
|
|
8451
|
-
|
|
8452
|
-
KEEP \u2014 these are the only things that survive distillation:
|
|
8453
|
-
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing).
|
|
8454
|
-
- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.
|
|
8455
|
-
- Key lessons: what failed and why ("tried X, failed because Y"). These prevent repeating mistakes.
|
|
8456
|
-
- Critical constraints discovered ("must support Node 22", "AGENTS.md forbids as any").
|
|
8457
|
-
- Design decisions with architectural impact ("chose compress-as-anchor over synthetic messages because prefix cache").
|
|
8458
|
-
- Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: "[SUPERSEDED by PR #NNN]" or "[OBSOLETE: deleted in vX.Y.Z]". Do NOT keep the obsolete content's details \u2014 just the marker and reason.
|
|
8459
|
-
- Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., "fixed filterCompressedRanges in prune.ts", "added SessionStateRegistry in state.ts". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.
|
|
8460
|
-
- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line ("explored X, not viable because Y"). Do not keep the exploration process.
|
|
8461
|
-
|
|
8462
|
-
DROP \u2014 these were useful during the work but are no longer needed:
|
|
8463
|
-
- Exact line numbers, diffs, verbose function signatures, full code listings.
|
|
8464
|
-
- Build/deploy process details, test execution steps.
|
|
8465
|
-
- Review process details (who reviewed, what rounds, test counts).
|
|
8466
|
-
- Verbose logs, command output, intermediate debugging steps.
|
|
8467
|
-
|
|
8468
|
-
FORMAT:
|
|
8469
|
-
- Start each distilled block with a source header line:
|
|
8470
|
-
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
8471
|
-
Example: \`Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]\`
|
|
8472
|
-
- 3-5 bullet points per source block, each a self-contained fact.
|
|
8473
|
-
- Dense, scannable \u2014 no narrative prose.
|
|
8474
|
-
- Start with the outcome, not the process: "v1.13.0 shipped (7 PRs bundled)" not "implemented 7 PRs then reviewed then merged".
|
|
8475
|
-
- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.
|
|
8476
|
-
|
|
8477
|
-
SIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by "[no actionable content]."`;
|
|
8478
|
-
var TIER3_CONDENSE_RULES2 = `TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION
|
|
8479
|
-
|
|
8480
|
-
You are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.
|
|
8481
|
-
|
|
8482
|
-
PRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:
|
|
8483
|
-
1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.
|
|
8484
|
-
2. Open work (PRs/issues still pending) \u2014 these may need follow-up.
|
|
8485
|
-
3. Key decisions with architectural impact ("chose X over Y because Z").
|
|
8486
|
-
4. Critical constraints ("must support Node 22").
|
|
8487
|
-
Drop everything else. Tier 3 is a lookup index, not a knowledge base.
|
|
8488
|
-
|
|
8489
|
-
FORMAT:
|
|
8490
|
-
- Start with a source header line:
|
|
8491
|
-
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
8492
|
-
- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.
|
|
8493
|
-
- No explanations, no rationale, no process \u2014 just the fact.
|
|
8494
|
-
- Format: "[PR/Issue/Version] \u2014 [outcome in \u22648 words]"
|
|
8495
|
-
- Merge related facts from different source blocks if they concern the same topic.
|
|
8496
|
-
|
|
8497
|
-
EXAMPLES:
|
|
8498
|
-
- "v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)"
|
|
8499
|
-
- "PR #196 merged \u2014 preserve-first-user (supersedes #169)"
|
|
8500
|
-
- "Bug 1214 fixed \u2014 compress consumed all user messages"
|
|
8501
|
-
- "Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection"
|
|
8502
|
-
- "Constraint: AGENTS.md forbids as any \u2014 never suppress types"
|
|
8503
|
-
|
|
8504
|
-
DROP:
|
|
8505
|
-
- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.
|
|
8506
|
-
- Lessons learned ("tried X, failed because Y") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.
|
|
8507
|
-
- Design rationale details \u2014 keep the decision, drop the "because" unless it's a critical constraint.
|
|
8508
|
-
- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
|
|
8509
|
-
|
|
8510
|
-
SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
|
|
8511
|
-
var defaultPrompts2 = Object.freeze({
|
|
8512
|
-
compressPhilosophy: COMPRESS_PHILOSOPHY2,
|
|
8513
|
-
howToCompressRules: HOW_TO_COMPRESS_RULES2,
|
|
8514
|
-
tier2DistillRules: TIER2_DISTILL_RULES2,
|
|
8515
|
-
tier3CondenseRules: TIER3_CONDENSE_RULES2
|
|
8516
|
-
});
|
|
8517
|
-
function formatK3(n) {
|
|
8518
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
8519
|
-
return `${n}`;
|
|
8520
|
-
}
|
|
8521
|
-
function formatRanges2(compressible, protectedRanges) {
|
|
8522
|
-
if (compressible.length === 0 && protectedRanges.length === 0) {
|
|
8523
|
-
return "[No specific ranges detected \u2014 compress any consumed content.]";
|
|
8524
|
-
}
|
|
8525
|
-
const refNum2 = (ref) => {
|
|
8526
|
-
const m = ref.match(/\d+/);
|
|
8527
|
-
return m ? parseInt(m[0], 10) : 0;
|
|
8528
|
-
};
|
|
8529
|
-
const entries = [];
|
|
8530
|
-
for (const r of compressible) {
|
|
8531
|
-
entries.push({
|
|
8532
|
-
startRef: r.startRef,
|
|
8533
|
-
endRef: r.endRef,
|
|
8534
|
-
startNum: refNum2(r.startRef),
|
|
8535
|
-
endNum: refNum2(r.endRef),
|
|
8536
|
-
count: r.count,
|
|
8537
|
-
tokens: r.tokens,
|
|
8538
|
-
toolPct: r.toolPct,
|
|
8539
|
-
textPct: r.textPct,
|
|
8540
|
-
compressibleTokens: r.tokens,
|
|
8541
|
-
compressibleCount: r.count,
|
|
8542
|
-
protectedTokens: 0,
|
|
8543
|
-
protectedCount: 0,
|
|
8544
|
-
protectedTools: [],
|
|
8545
|
-
dangerous: r.dangerous ?? false
|
|
8546
|
-
});
|
|
8547
|
-
}
|
|
8548
|
-
for (const r of protectedRanges) {
|
|
8549
|
-
entries.push({
|
|
8550
|
-
startRef: r.startRef,
|
|
8551
|
-
endRef: r.endRef,
|
|
8552
|
-
startNum: refNum2(r.startRef),
|
|
8553
|
-
endNum: refNum2(r.endRef),
|
|
8554
|
-
count: r.count,
|
|
8555
|
-
tokens: r.tokens,
|
|
8556
|
-
toolPct: 0,
|
|
8557
|
-
textPct: 0,
|
|
8558
|
-
compressibleTokens: 0,
|
|
8559
|
-
compressibleCount: 0,
|
|
8560
|
-
protectedTokens: r.tokens,
|
|
8561
|
-
protectedCount: r.count,
|
|
8562
|
-
protectedTools: [...r.tools],
|
|
8563
|
-
dangerous: false
|
|
8564
|
-
});
|
|
8565
|
-
}
|
|
8566
|
-
entries.sort((a, b) => a.startNum - b.startNum);
|
|
8567
|
-
const merged = [];
|
|
8568
|
-
for (const e of entries) {
|
|
8569
|
-
const last = merged[merged.length - 1];
|
|
8570
|
-
if (last && e.startNum <= last.endNum + 1) {
|
|
8571
|
-
last.endRef = e.endRef;
|
|
8572
|
-
last.endNum = Math.max(last.endNum, e.endNum);
|
|
8573
|
-
last.count += e.count;
|
|
8574
|
-
last.tokens += e.tokens;
|
|
8575
|
-
last.compressibleTokens += e.compressibleTokens;
|
|
8576
|
-
last.compressibleCount += e.compressibleCount;
|
|
8577
|
-
last.protectedTokens += e.protectedTokens;
|
|
8578
|
-
last.protectedCount += e.protectedCount;
|
|
8579
|
-
if (e.dangerous) last.dangerous = true;
|
|
8580
|
-
for (const t of e.protectedTools) {
|
|
8581
|
-
if (!last.protectedTools.includes(t)) last.protectedTools.push(t);
|
|
8582
|
-
}
|
|
8583
|
-
} else {
|
|
8584
|
-
merged.push({ ...e });
|
|
8585
|
-
}
|
|
8586
|
-
}
|
|
8587
|
-
const lines = merged.map((e) => {
|
|
8588
|
-
const suffix = e.dangerous && e.compressibleTokens > 0 ? " \u26A0\uFE0F NOT recommended unless you are certain." : "";
|
|
8589
|
-
if (e.protectedTokens > 0 && e.compressibleTokens === 0) {
|
|
8590
|
-
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK3(e.tokens)} [PROTECTED: ${e.protectedTools.join(", ")} \u2014 not compressible]${suffix}`;
|
|
8591
|
-
}
|
|
8592
|
-
if (e.protectedTokens > 0 && e.compressibleTokens > 0) {
|
|
8593
|
-
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK3(e.tokens)} [${formatK3(e.compressibleTokens)} compressible | ${formatK3(e.protectedTokens)} protected: ${e.protectedTools.join(", ")}]${suffix}`;
|
|
8594
|
-
}
|
|
8595
|
-
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK3(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;
|
|
8596
|
-
});
|
|
8597
|
-
return `Compressible ranges (${merged.length}, oldest first):
|
|
8598
|
-
${lines.join("\n")}`;
|
|
8599
|
-
}
|
|
8600
|
-
var substringAlgorithm2 = {
|
|
8601
|
-
name: "substring",
|
|
8602
|
-
description: "Exact substring counting (original baseline). Predictable, no normalization.",
|
|
8603
|
-
score(docs, query) {
|
|
8604
|
-
const terms = query.toLowerCase().trim().split(/\s+/).filter((t) => t.length > 0);
|
|
8605
|
-
if (terms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
8606
|
-
return docs.map((d) => {
|
|
8607
|
-
const haystack = d.text.toLowerCase();
|
|
8608
|
-
let score = 0;
|
|
8609
|
-
for (const term of terms) score += countOccurrences22(haystack, term);
|
|
8610
|
-
return { ref: d.ref, score };
|
|
8611
|
-
});
|
|
8612
|
-
}
|
|
8613
|
-
};
|
|
8614
|
-
function countOccurrences22(haystack, needle) {
|
|
8615
|
-
if (!needle) return 0;
|
|
8616
|
-
return haystack.split(needle).length - 1;
|
|
8617
|
-
}
|
|
8618
|
-
function stem2(word) {
|
|
8619
|
-
let w = word;
|
|
8620
|
-
if (w.length <= 3) return w;
|
|
8621
|
-
if (w.endsWith("ies")) w = w.slice(0, -3) + "y";
|
|
8622
|
-
else if (w.endsWith("ses") || w.endsWith("xes") || w.endsWith("zes")) w = w.slice(0, -2);
|
|
8623
|
-
else if (w.endsWith("ches") || w.endsWith("shes")) w = w.slice(0, -2);
|
|
8624
|
-
else if (w.endsWith("s") && !w.endsWith("ss")) w = w.slice(0, -1);
|
|
8625
|
-
if (w.endsWith("ing") && w.length > 5) w = w.slice(0, -3);
|
|
8626
|
-
if (w.endsWith("ed") && w.length > 4) w = w.slice(0, -2);
|
|
8627
|
-
if (w.endsWith("ation") && w.length > 6) w = w.slice(0, -3);
|
|
8628
|
-
else if (w.endsWith("tion") && w.length > 5) w = w.slice(0, -4) + "t";
|
|
8629
|
-
else if (w.endsWith("ion") && w.length > 4) w = w.slice(0, -3);
|
|
8630
|
-
if (w.endsWith("ment") && w.length > 6) w = w.slice(0, -4);
|
|
8631
|
-
if (w.endsWith("ness") && w.length > 6) w = w.slice(0, -4);
|
|
8632
|
-
if (w.endsWith("ly") && w.length > 4) w = w.slice(0, -2);
|
|
8633
|
-
return w;
|
|
8634
|
-
}
|
|
8635
|
-
var CJK2 = /[\u3400-\u9fff\uf900-\ufaff\u3040-\u30ff\uac00-\ud7af]/;
|
|
8636
|
-
var CJK_RUN2 = new RegExp(`${CJK2.source}+`, "g");
|
|
8637
|
-
var LATIN_WORD2 = /[a-z][a-z0-9_]*[a-z0-9]|[a-z0-9]/g;
|
|
8638
|
-
function tokenize2(text, opts = {}) {
|
|
8639
|
-
const lower = text.toLowerCase();
|
|
8640
|
-
const tokens = [];
|
|
8641
|
-
const latin = lower.match(LATIN_WORD2) ?? [];
|
|
8642
|
-
for (let w of latin) {
|
|
8643
|
-
if (w.length >= 2) {
|
|
8644
|
-
if (opts.stem) w = stem2(w);
|
|
8645
|
-
tokens.push(w);
|
|
8646
|
-
}
|
|
8647
|
-
}
|
|
8648
|
-
const cjkRuns = lower.match(CJK_RUN2) ?? [];
|
|
8649
|
-
for (const run of cjkRuns) {
|
|
8650
|
-
if (run.length === 1) {
|
|
8651
|
-
tokens.push(run);
|
|
8652
|
-
} else {
|
|
8653
|
-
for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2));
|
|
8654
|
-
for (const ch of run) tokens.push(ch);
|
|
8655
|
-
}
|
|
8656
|
-
}
|
|
8657
|
-
return tokens;
|
|
8658
|
-
}
|
|
8659
|
-
function charBigrams2(text) {
|
|
8660
|
-
const grams = [];
|
|
8661
|
-
for (let i = 0; i < text.length - 1; i++) {
|
|
8662
|
-
const pair = text.slice(i, i + 2);
|
|
8663
|
-
if (pair.trim().length === pair.length) grams.push(pair);
|
|
8664
|
-
}
|
|
8665
|
-
return grams;
|
|
8666
|
-
}
|
|
8667
|
-
function tfMap2(text, stem22) {
|
|
8668
|
-
const m = /* @__PURE__ */ new Map();
|
|
8669
|
-
for (const t of tokenize2(text, { stem: stem22 })) m.set(t, (m.get(t) ?? 0) + 1);
|
|
8670
|
-
return m;
|
|
8671
|
-
}
|
|
8672
|
-
var bm25Algorithm2 = {
|
|
8673
|
-
name: "bm25",
|
|
8674
|
-
description: "BM25 with stemming + CJK bigram tokenization. IR-standard relevance ranking.",
|
|
8675
|
-
score(docs, query) {
|
|
8676
|
-
const N = docs.length;
|
|
8677
|
-
const k1 = 1.2;
|
|
8678
|
-
const b = 0.75;
|
|
8679
|
-
const parsed = docs.map((d) => {
|
|
8680
|
-
const text = d.text;
|
|
8681
|
-
const tf = tfMap2(text, true);
|
|
8682
|
-
let len = 0;
|
|
8683
|
-
for (const v of tf.values()) len += v;
|
|
8684
|
-
return { id: d.ref, tf, len };
|
|
8685
|
-
});
|
|
8686
|
-
const avgdl = parsed.reduce((s, d) => s + d.len, 0) / (N || 1);
|
|
8687
|
-
const qTerms = tokenize2(query, { stem: true });
|
|
8688
|
-
if (qTerms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
8689
|
-
const idf = /* @__PURE__ */ new Map();
|
|
8690
|
-
for (const t of new Set(qTerms)) {
|
|
8691
|
-
let df = 0;
|
|
8692
|
-
for (const d of parsed) if (d.tf.has(t)) df++;
|
|
8693
|
-
idf.set(t, Math.log(1 + (N - df + 0.5) / (df + 0.5)));
|
|
8694
|
-
}
|
|
8695
|
-
return parsed.map((d) => {
|
|
8696
|
-
let score = 0;
|
|
8697
|
-
for (const t of qTerms) {
|
|
8698
|
-
const f = d.tf.get(t) ?? 0;
|
|
8699
|
-
if (f === 0) continue;
|
|
8700
|
-
const idfT = idf.get(t) ?? 0;
|
|
8701
|
-
score += idfT * (f * (k1 + 1)) / (f + k1 * (1 - b + b * d.len / (avgdl || 1)));
|
|
8702
|
-
}
|
|
8703
|
-
return { ref: d.id, score };
|
|
8704
|
-
});
|
|
8705
|
-
}
|
|
8706
|
-
};
|
|
8707
|
-
var fuzzyAlgorithm2 = {
|
|
8708
|
-
name: "fuzzy",
|
|
8709
|
-
description: "Character bigram overlap. Typo-tolerant, script-agnostic, high recall.",
|
|
8710
|
-
score(docs, query) {
|
|
8711
|
-
const qTokens = query.toLowerCase().split(/[\s,]+/).filter((t) => t.length >= 4);
|
|
8712
|
-
if (qTokens.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
8713
|
-
const qGrams = /* @__PURE__ */ new Set();
|
|
8714
|
-
for (const t of qTokens) for (const g of charBigrams2(t)) qGrams.add(g);
|
|
8715
|
-
if (qGrams.size === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
8716
|
-
return docs.map((d) => {
|
|
8717
|
-
const haystack = d.text.toLowerCase();
|
|
8718
|
-
const docGrams = new Set(charBigrams2(haystack));
|
|
8719
|
-
let hits = 0;
|
|
8720
|
-
for (const g of qGrams) if (docGrams.has(g)) hits++;
|
|
8721
|
-
return { ref: d.ref, score: hits / qGrams.size };
|
|
8722
|
-
});
|
|
8723
|
-
}
|
|
8724
|
-
};
|
|
8725
|
-
var W_BM252 = 0.7;
|
|
8726
|
-
var W_FUZZY2 = 0.3;
|
|
8727
|
-
var hybridAlgorithm2 = {
|
|
8728
|
-
name: "hybrid",
|
|
8729
|
-
description: "Weighted BM25(stem) + fuzzy n-gram. Default \u2014 best precision + recall.",
|
|
8730
|
-
score(docs, query) {
|
|
8731
|
-
const bm = bm25Algorithm2.score(docs, query);
|
|
8732
|
-
const fz = fuzzyAlgorithm2.score(docs, query);
|
|
8733
|
-
const maxBm = Math.max(...bm.map((r) => r.score), 1e-9);
|
|
8734
|
-
const maxFz = Math.max(...fz.map((r) => r.score), 1e-9);
|
|
8735
|
-
const bmMap = new Map(bm.map((r) => [r.ref, r.score / maxBm]));
|
|
8736
|
-
const fzMap = new Map(fz.map((r) => [r.ref, r.score / maxFz]));
|
|
8737
|
-
return docs.map((d) => ({
|
|
8738
|
-
ref: d.ref,
|
|
8739
|
-
score: W_BM252 * (bmMap.get(d.ref) ?? 0) + W_FUZZY2 * (fzMap.get(d.ref) ?? 0)
|
|
8740
|
-
}));
|
|
8741
|
-
}
|
|
8742
|
-
};
|
|
8743
|
-
var registry22 = /* @__PURE__ */ new Map();
|
|
8744
|
-
function registerSearchAlgorithm2(algo) {
|
|
8745
|
-
registry22.set(algo.name, algo);
|
|
8746
|
-
}
|
|
8747
|
-
registerSearchAlgorithm2(substringAlgorithm2);
|
|
8748
|
-
registerSearchAlgorithm2(bm25Algorithm2);
|
|
8749
|
-
registerSearchAlgorithm2(fuzzyAlgorithm2);
|
|
8750
|
-
registerSearchAlgorithm2(hybridAlgorithm2);
|
|
8751
|
-
|
|
8752
8518
|
// node_modules/billion-context-kit/dist/index.js
|
|
8753
8519
|
var VIABLE_RANGE_MIN_TOKENS = 200;
|
|
8754
8520
|
function viableRanges(ranges) {
|
|
@@ -8831,14 +8597,14 @@ function buildStatusPanel(input) {
|
|
|
8831
8597
|
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
8832
8598
|
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
8833
8599
|
lines.push("");
|
|
8834
|
-
lines.push(
|
|
8600
|
+
lines.push(formatRanges(ranges, protectedRanges));
|
|
8835
8601
|
}
|
|
8836
8602
|
if (activeBlocksList.length > 0) {
|
|
8837
8603
|
lines.push("");
|
|
8838
8604
|
lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
|
|
8839
8605
|
for (const b of activeBlocksList) {
|
|
8840
8606
|
const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
|
|
8841
|
-
const summaryTok =
|
|
8607
|
+
const summaryTok = defaultCountTokens(b.summary || "");
|
|
8842
8608
|
const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;
|
|
8843
8609
|
lines.push(` [${b.blockId}] T${b.tier} ${fmt2(origTok)}\u2192${fmt2(summaryTok)}${topic}`);
|
|
8844
8610
|
}
|
|
@@ -8858,10 +8624,10 @@ function buildStatusPanel(input) {
|
|
|
8858
8624
|
import {
|
|
8859
8625
|
spawn
|
|
8860
8626
|
} from "child_process";
|
|
8861
|
-
import { createWriteStream, existsSync as
|
|
8627
|
+
import { createWriteStream, existsSync as existsSync3 } from "fs";
|
|
8862
8628
|
import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
|
|
8863
8629
|
import { tmpdir as tmpdir2 } from "os";
|
|
8864
|
-
import { dirname as
|
|
8630
|
+
import { dirname as dirname4, join as join4, resolve as resolvePath } from "path";
|
|
8865
8631
|
|
|
8866
8632
|
// src/footer-status.ts
|
|
8867
8633
|
var FOOTER_STATUS_KEY = "billion-context-pi";
|
|
@@ -9277,11 +9043,11 @@ function delegateSpawnOptions(cwd, env) {
|
|
|
9277
9043
|
var PI_CLI_ENTRY_RE = /[\\/]pi-coding-agent[\\/]dist[\\/]cli\.js$/;
|
|
9278
9044
|
var PI_PACKAGE_REL = join4("@earendil-works", "pi-coding-agent", "dist", "cli.js");
|
|
9279
9045
|
function probeUpFromArgv(argv1) {
|
|
9280
|
-
let dir = resolvePath(
|
|
9046
|
+
let dir = resolvePath(dirname4(argv1) || process.cwd());
|
|
9281
9047
|
for (; ; ) {
|
|
9282
9048
|
const candidate = join4(dir, "node_modules", PI_PACKAGE_REL);
|
|
9283
|
-
if (
|
|
9284
|
-
const parent =
|
|
9049
|
+
if (existsSync3(candidate)) return candidate;
|
|
9050
|
+
const parent = dirname4(dir);
|
|
9285
9051
|
if (parent === dir) return null;
|
|
9286
9052
|
dir = parent;
|
|
9287
9053
|
}
|
|
@@ -9306,7 +9072,7 @@ function resolvePiCliEntry(argv1, env = process.env, piHost = true) {
|
|
|
9306
9072
|
const probed = probeUpFromArgv(argv1);
|
|
9307
9073
|
if (probed) return probed;
|
|
9308
9074
|
for (const candidate of piCliGlobalCandidates(env)) {
|
|
9309
|
-
if (
|
|
9075
|
+
if (existsSync3(candidate)) return candidate;
|
|
9310
9076
|
}
|
|
9311
9077
|
logWarn("delegate", { event: "cli-entry-unresolved", argv1, fallback: "argv[1]" });
|
|
9312
9078
|
}
|
|
@@ -10107,6 +9873,7 @@ async function handleStatus(args, runtime, ctx) {
|
|
|
10107
9873
|
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
10108
9874
|
const config = runtime.configFor(ctx);
|
|
10109
9875
|
const coveredIds = collectCoveredMessageIds(state);
|
|
9876
|
+
const modelId = ctx.model?.id ?? "default";
|
|
10110
9877
|
const systemPromptText = getSystemPromptText(ctx);
|
|
10111
9878
|
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
10112
9879
|
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
@@ -10114,7 +9881,7 @@ async function handleStatus(args, runtime, ctx) {
|
|
|
10114
9881
|
messages: coreMessages,
|
|
10115
9882
|
state,
|
|
10116
9883
|
config,
|
|
10117
|
-
tokenCount: sentTokens
|
|
9884
|
+
tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId))
|
|
10118
9885
|
});
|
|
10119
9886
|
const processed = turn.messages;
|
|
10120
9887
|
const base = buildStatusReport(turn.state, processed, defaultCountTokens, {
|
|
@@ -10232,9 +9999,10 @@ async function statusReport(runtime, ctx) {
|
|
|
10232
9999
|
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
10233
10000
|
const sessionTokens = realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : defaultCountTokens(coreMessages.map((m) => m.text ?? "").join("\n"));
|
|
10234
10001
|
const coveredIds = collectCoveredMessageIds(state);
|
|
10002
|
+
const modelId = ctx.model?.id ?? "default";
|
|
10235
10003
|
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
10236
|
-
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: sentTokens });
|
|
10237
|
-
const versionStr = "0.1.
|
|
10004
|
+
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId)) });
|
|
10005
|
+
const versionStr = "0.1.39" ? `billion-context-pi@${"0.1.39"}` : void 0;
|
|
10238
10006
|
let text = buildStatusPanel({
|
|
10239
10007
|
version: versionStr,
|
|
10240
10008
|
tokenCount: sessionTokens,
|
|
@@ -10447,7 +10215,7 @@ function wireToolGuardrails(pi, runtime) {
|
|
|
10447
10215
|
|
|
10448
10216
|
// src/update.ts
|
|
10449
10217
|
import { readFile, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
10450
|
-
import { join as join5, dirname as
|
|
10218
|
+
import { join as join5, dirname as dirname5 } from "path";
|
|
10451
10219
|
import { fileURLToPath } from "url";
|
|
10452
10220
|
import { execFile } from "child_process";
|
|
10453
10221
|
import { homedir as homedir3 } from "os";
|
|
@@ -10480,7 +10248,7 @@ async function readLastCheck() {
|
|
|
10480
10248
|
}
|
|
10481
10249
|
async function writeLastCheck(timestamp) {
|
|
10482
10250
|
try {
|
|
10483
|
-
await mkdir3(
|
|
10251
|
+
await mkdir3(dirname5(THROTTLE_FILE), { recursive: true });
|
|
10484
10252
|
await writeFile3(THROTTLE_FILE, String(timestamp), "utf-8");
|
|
10485
10253
|
} catch {
|
|
10486
10254
|
}
|
|
@@ -10494,20 +10262,20 @@ async function readPackageJson(path4) {
|
|
|
10494
10262
|
}
|
|
10495
10263
|
}
|
|
10496
10264
|
function findNpmRoot(extDir) {
|
|
10497
|
-
let dir =
|
|
10265
|
+
let dir = dirname5(extDir);
|
|
10498
10266
|
for (; ; ) {
|
|
10499
|
-
if (dir.endsWith("node_modules")) return
|
|
10500
|
-
const parent =
|
|
10267
|
+
if (dir.endsWith("node_modules")) return dirname5(dir);
|
|
10268
|
+
const parent = dirname5(dir);
|
|
10501
10269
|
if (parent === dir) return void 0;
|
|
10502
10270
|
dir = parent;
|
|
10503
10271
|
}
|
|
10504
10272
|
}
|
|
10505
10273
|
async function findExtensionDir() {
|
|
10506
|
-
let dir =
|
|
10274
|
+
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
10507
10275
|
for (; ; ) {
|
|
10508
10276
|
const pkg = await readPackageJson(join5(dir, "package.json"));
|
|
10509
10277
|
if (pkg?.name === PACKAGE_NAME) return dir;
|
|
10510
|
-
const parent =
|
|
10278
|
+
const parent = dirname5(dir);
|
|
10511
10279
|
if (parent === dir) return void 0;
|
|
10512
10280
|
dir = parent;
|
|
10513
10281
|
}
|
|
@@ -10556,7 +10324,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
10556
10324
|
const data = await res.json();
|
|
10557
10325
|
const latest = data.version;
|
|
10558
10326
|
if (!latest) return;
|
|
10559
|
-
const current = runtimeVersion ?? "0.1.
|
|
10327
|
+
const current = runtimeVersion ?? "0.1.39";
|
|
10560
10328
|
const hasUpdate = isNewer(latest, current);
|
|
10561
10329
|
debug.event("update-check", {
|
|
10562
10330
|
current,
|
|
@@ -10592,7 +10360,7 @@ async function getRuntimeVersion() {
|
|
|
10592
10360
|
|
|
10593
10361
|
// src/setup-subagent-tools.ts
|
|
10594
10362
|
import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename } from "fs/promises";
|
|
10595
|
-
import { existsSync as
|
|
10363
|
+
import { existsSync as existsSync4 } from "fs";
|
|
10596
10364
|
import { homedir as homedir4 } from "os";
|
|
10597
10365
|
import { join as join6 } from "path";
|
|
10598
10366
|
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME3 } from "@earendil-works/pi-coding-agent";
|
|
@@ -10657,7 +10425,7 @@ async function ensureSubagentAcpTools(settingsPath) {
|
|
|
10657
10425
|
return { path: path4, action: "skipped", reason: "all builtin agents already have ACP tools" };
|
|
10658
10426
|
}
|
|
10659
10427
|
const backupPath = `${path4}.acp-bak`;
|
|
10660
|
-
if (!
|
|
10428
|
+
if (!existsSync4(backupPath)) {
|
|
10661
10429
|
try {
|
|
10662
10430
|
await copyFile(path4, backupPath);
|
|
10663
10431
|
} catch {
|
|
@@ -10804,10 +10572,14 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
10804
10572
|
pi.on("session_start", async (_event, ctx) => {
|
|
10805
10573
|
runtime.store.invalidate();
|
|
10806
10574
|
runtime.clearNudgeTracking();
|
|
10575
|
+
const modelId = ctx.model?.id ?? "default";
|
|
10576
|
+
runtime.density.resetModel(modelId);
|
|
10807
10577
|
resetDelegateUsage();
|
|
10808
10578
|
setDelegateDisplayUsage("separate");
|
|
10809
10579
|
const sid = ctx.sessionManager.getSessionId();
|
|
10810
|
-
|
|
10580
|
+
runtime.clearSessionTracking(sid);
|
|
10581
|
+
const modelInfo = ctx.model;
|
|
10582
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.39" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
|
|
10811
10583
|
try {
|
|
10812
10584
|
const user = await loadUserConfig(ctx.cwd);
|
|
10813
10585
|
runtime.setAdapter(applyUserConfig(runtime.adapter, user));
|
|
@@ -10843,6 +10615,8 @@ function wireContextTransform(pi, runtime) {
|
|
|
10843
10615
|
const sid = ctx.sessionManager.getSessionId();
|
|
10844
10616
|
const release = await runtime.acquireLock(sid);
|
|
10845
10617
|
try {
|
|
10618
|
+
const modelId = ctx.model?.id ?? "default";
|
|
10619
|
+
runtime.setCountModel(modelId);
|
|
10846
10620
|
const { state, coreMessages, entries } = await runtime.stateFor(ctx, event.messages);
|
|
10847
10621
|
const config = runtime.configFor(ctx);
|
|
10848
10622
|
const coveredIds = collectCoveredMessageIds(state);
|
|
@@ -10850,9 +10624,15 @@ function wireContextTransform(pi, runtime) {
|
|
|
10850
10624
|
const systemPromptText = getSystemPromptText(ctx);
|
|
10851
10625
|
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
10852
10626
|
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
10853
|
-
const tokenCount = sentTokens;
|
|
10627
|
+
const tokenCount = calibrateTokens(sentTokens, runtime.density.densityFor(modelId));
|
|
10628
|
+
const postCompression = runtime.noteActiveBlocks(
|
|
10629
|
+
sid,
|
|
10630
|
+
state.blocks.filter((b) => b.active).map((b) => b.blockId)
|
|
10631
|
+
);
|
|
10854
10632
|
debug.event("context-in", {
|
|
10855
10633
|
sid,
|
|
10634
|
+
modelId,
|
|
10635
|
+
density: runtime.density.densityFor(modelId),
|
|
10856
10636
|
eventMsgs: event.messages?.length ?? 0,
|
|
10857
10637
|
entries: entries.length,
|
|
10858
10638
|
coreMsgs: coreMessages.length,
|
|
@@ -10864,8 +10644,10 @@ function wireContextTransform(pi, runtime) {
|
|
|
10864
10644
|
});
|
|
10865
10645
|
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
10866
10646
|
await runtime.save(turn.state, ctx);
|
|
10647
|
+
runtime.density.update(modelId, realUsage?.tokens ?? null, sentTokens, postCompression);
|
|
10867
10648
|
logInfo("turn", {
|
|
10868
10649
|
sid,
|
|
10650
|
+
model: ctx.model?.id ?? null,
|
|
10869
10651
|
inMsgs: coreMessages.length,
|
|
10870
10652
|
outMsgs: turn.messages.length,
|
|
10871
10653
|
tokens: tokenCount,
|
|
@@ -10877,6 +10659,8 @@ function wireContextTransform(pi, runtime) {
|
|
|
10877
10659
|
activeBlocks: turn.state.blocks.filter((b) => b.active).length
|
|
10878
10660
|
});
|
|
10879
10661
|
debug.event("processTurn", {
|
|
10662
|
+
modelId,
|
|
10663
|
+
density: runtime.density.densityFor(modelId),
|
|
10880
10664
|
outMsgs: turn.messages.length,
|
|
10881
10665
|
summaryMsgs: turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
|
|
10882
10666
|
prunedMsgs: coreMessages.length - turn.messages.length + turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
|