opencode-acp 1.12.2 → 1.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/README.zh-CN.md +16 -0
- package/dist/index.js +348 -75
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/status.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +3 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +15 -3
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -904,6 +904,9 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
904
904
|
"compress.protectUserMessages",
|
|
905
905
|
"compress.maxSummaryLengthHard",
|
|
906
906
|
"compress.minCompressRange",
|
|
907
|
+
"compress.minNudgeGrowthRatio",
|
|
908
|
+
"compress.minNudgeGrowthFloor",
|
|
909
|
+
"compress.emergencyThresholdPercent",
|
|
907
910
|
"compress.maxVisibleSegments",
|
|
908
911
|
"compress.keepEmbedMaxChars",
|
|
909
912
|
"gc",
|
|
@@ -1187,6 +1190,61 @@ function validateConfigTypes(config) {
|
|
|
1187
1190
|
actual: `${compress.minCompressRange}`
|
|
1188
1191
|
});
|
|
1189
1192
|
}
|
|
1193
|
+
if (compress.minNudgeGrowthRatio !== void 0 && typeof compress.minNudgeGrowthRatio !== "number") {
|
|
1194
|
+
errors.push({
|
|
1195
|
+
key: "compress.minNudgeGrowthRatio",
|
|
1196
|
+
expected: "number",
|
|
1197
|
+
actual: typeof compress.minNudgeGrowthRatio
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
if (typeof compress.minNudgeGrowthRatio === "number" && (compress.minNudgeGrowthRatio < 0 || compress.minNudgeGrowthRatio > 1)) {
|
|
1201
|
+
errors.push({
|
|
1202
|
+
key: "compress.minNudgeGrowthRatio",
|
|
1203
|
+
expected: "number in range [0, 1]",
|
|
1204
|
+
actual: `${compress.minNudgeGrowthRatio}`
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
if (compress.minNudgeGrowthFloor !== void 0 && typeof compress.minNudgeGrowthFloor !== "number") {
|
|
1208
|
+
errors.push({
|
|
1209
|
+
key: "compress.minNudgeGrowthFloor",
|
|
1210
|
+
expected: "number",
|
|
1211
|
+
actual: typeof compress.minNudgeGrowthFloor
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
if (typeof compress.minNudgeGrowthFloor === "number" && compress.minNudgeGrowthFloor < 0) {
|
|
1215
|
+
errors.push({
|
|
1216
|
+
key: "compress.minNudgeGrowthFloor",
|
|
1217
|
+
expected: "non-negative number (>= 0)",
|
|
1218
|
+
actual: `${compress.minNudgeGrowthFloor}`
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
const emergencyThreshold = compress.emergencyThresholdPercent;
|
|
1222
|
+
if (emergencyThreshold !== void 0) {
|
|
1223
|
+
if (typeof emergencyThreshold === "number") {
|
|
1224
|
+
if (emergencyThreshold < 0) {
|
|
1225
|
+
errors.push({
|
|
1226
|
+
key: "compress.emergencyThresholdPercent",
|
|
1227
|
+
expected: 'non-negative number or "${number}%" (0\u2013100)',
|
|
1228
|
+
actual: `${emergencyThreshold}`
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
} else if (typeof emergencyThreshold === "string" && emergencyThreshold.endsWith("%")) {
|
|
1232
|
+
const parsed = parseFloat(emergencyThreshold.slice(0, -1));
|
|
1233
|
+
if (isNaN(parsed) || parsed < 0 || parsed > 100) {
|
|
1234
|
+
errors.push({
|
|
1235
|
+
key: "compress.emergencyThresholdPercent",
|
|
1236
|
+
expected: '"${number}%" with percentage in [0, 100]',
|
|
1237
|
+
actual: JSON.stringify(emergencyThreshold)
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
} else {
|
|
1241
|
+
errors.push({
|
|
1242
|
+
key: "compress.emergencyThresholdPercent",
|
|
1243
|
+
expected: 'number | "${number}%"',
|
|
1244
|
+
actual: JSON.stringify(emergencyThreshold)
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1190
1248
|
if (compress.maxVisibleSegments !== void 0 && typeof compress.maxVisibleSegments !== "number") {
|
|
1191
1249
|
errors.push({
|
|
1192
1250
|
key: "compress.maxVisibleSegments",
|
|
@@ -1529,7 +1587,10 @@ var defaultConfig = {
|
|
|
1529
1587
|
protectTags: false,
|
|
1530
1588
|
protectUserMessages: false,
|
|
1531
1589
|
maxSummaryLengthHard: 1e4,
|
|
1532
|
-
minCompressRange:
|
|
1590
|
+
minCompressRange: 5e3,
|
|
1591
|
+
minNudgeGrowthRatio: 0.45,
|
|
1592
|
+
minNudgeGrowthFloor: 5e3,
|
|
1593
|
+
emergencyThresholdPercent: "98%",
|
|
1533
1594
|
maxVisibleSegments: 50,
|
|
1534
1595
|
keepEmbedMaxChars: 2e3
|
|
1535
1596
|
},
|
|
@@ -1688,6 +1749,9 @@ function mergeCompress(base, override) {
|
|
|
1688
1749
|
protectUserMessages: override.protectUserMessages ?? base.protectUserMessages,
|
|
1689
1750
|
maxSummaryLengthHard: override.maxSummaryLengthHard ?? base.maxSummaryLengthHard,
|
|
1690
1751
|
minCompressRange: override.minCompressRange ?? base.minCompressRange,
|
|
1752
|
+
minNudgeGrowthRatio: override.minNudgeGrowthRatio ?? base.minNudgeGrowthRatio,
|
|
1753
|
+
minNudgeGrowthFloor: override.minNudgeGrowthFloor ?? base.minNudgeGrowthFloor,
|
|
1754
|
+
emergencyThresholdPercent: override.emergencyThresholdPercent ?? base.emergencyThresholdPercent,
|
|
1691
1755
|
maxVisibleSegments: override.maxVisibleSegments ?? base.maxVisibleSegments,
|
|
1692
1756
|
keepEmbedMaxChars: override.keepEmbedMaxChars ?? base.keepEmbedMaxChars
|
|
1693
1757
|
};
|
|
@@ -1949,27 +2013,22 @@ function getCurrentTokenUsage(state, messages) {
|
|
|
1949
2013
|
continue;
|
|
1950
2014
|
}
|
|
1951
2015
|
const assistantInfo = msg.info;
|
|
1952
|
-
if ((assistantInfo.tokens?.output || 0) <= 0) {
|
|
1953
|
-
continue;
|
|
1954
|
-
}
|
|
1955
|
-
if (state.lastCompaction > 0 && (msg.info.time.created < state.lastCompaction || msg.info.summary === true && msg.info.time.created === state.lastCompaction)) {
|
|
1956
|
-
return 0;
|
|
1957
|
-
}
|
|
1958
2016
|
const input = assistantInfo.tokens?.input || 0;
|
|
1959
2017
|
const output = assistantInfo.tokens?.output || 0;
|
|
1960
2018
|
const reasoning = assistantInfo.tokens?.reasoning || 0;
|
|
1961
2019
|
const cacheRead = assistantInfo.tokens?.cache?.read || 0;
|
|
1962
2020
|
const cacheWrite = assistantInfo.tokens?.cache?.write || 0;
|
|
2021
|
+
if (input <= 0 && output <= 0) {
|
|
2022
|
+
continue;
|
|
2023
|
+
}
|
|
2024
|
+
if (state.lastCompaction > 0 && (msg.info.time.created < state.lastCompaction || msg.info.summary === true && msg.info.time.created === state.lastCompaction)) {
|
|
2025
|
+
return 0;
|
|
2026
|
+
}
|
|
1963
2027
|
return input + cacheRead + cacheWrite + output + reasoning;
|
|
1964
2028
|
}
|
|
1965
2029
|
let estimated = 0;
|
|
1966
2030
|
for (const m of messages) {
|
|
1967
|
-
|
|
1968
|
-
for (const part of parts) {
|
|
1969
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
1970
|
-
estimated += countTokens2(part.text);
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
2031
|
+
estimated += countAllMessageTokens(m);
|
|
1973
2032
|
}
|
|
1974
2033
|
return estimated;
|
|
1975
2034
|
}
|
|
@@ -5678,9 +5737,9 @@ import { tool as tool4 } from "@opencode-ai/plugin";
|
|
|
5678
5737
|
import { createHash } from "crypto";
|
|
5679
5738
|
var SUMMARY_ID_HASH_LENGTH = 16;
|
|
5680
5739
|
var ACP_RECAP_TOOL_NAME = "acp_context_recap";
|
|
5681
|
-
var DCP_BLOCK_ID_TAG_REGEX = /(<dcp-message-id
|
|
5682
|
-
var DCP_MESSAGE_REF_TAG_REGEX = /<dcp-message-id
|
|
5683
|
-
var DCP_PAIRED_TAG_REGEX = /<dcp[^>]*>[\s\S]*?<\/(?:dcp|acp)[^>]*>/gi;
|
|
5740
|
+
var DCP_BLOCK_ID_TAG_REGEX = /(<(?:dcp|acp)-message-id[^>]*>)b\d+(<\/(?:dcp|acp)-message-id>)/g;
|
|
5741
|
+
var DCP_MESSAGE_REF_TAG_REGEX = /<(?:dcp|acp)-message-id[^>]*>m\d+<\/(?:dcp|acp)-message-id>/g;
|
|
5742
|
+
var DCP_PAIRED_TAG_REGEX = /<(?:dcp|acp)[^>]*>[\s\S]*?<\/(?:dcp|acp)[^>]*>/gi;
|
|
5684
5743
|
var DCP_UNPAIRED_TAG_REGEX = /<\/?(?:dcp|acp)[^>]*>/gi;
|
|
5685
5744
|
var generateStableId = (prefix, seed) => {
|
|
5686
5745
|
const hash = createHash("sha256").update(seed).digest("hex").slice(0, SUMMARY_ID_HASH_LENGTH);
|
|
@@ -6600,20 +6659,41 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
6600
6659
|
const nudgeParts = [];
|
|
6601
6660
|
if (config.compress.mode === "message") {
|
|
6602
6661
|
if (state.nudges.contextLimitAnchors.size > 0) {
|
|
6603
|
-
for (const { index } of collectAnchoredMessages(
|
|
6604
|
-
|
|
6662
|
+
for (const { index } of collectAnchoredMessages(
|
|
6663
|
+
state.nudges.contextLimitAnchors,
|
|
6664
|
+
messages
|
|
6665
|
+
)) {
|
|
6666
|
+
const guidance = buildMessagePriorityGuidance(
|
|
6667
|
+
messages,
|
|
6668
|
+
compressionPriorities,
|
|
6669
|
+
index,
|
|
6670
|
+
MESSAGE_MODE_NUDGE_PRIORITY
|
|
6671
|
+
);
|
|
6605
6672
|
nudgeParts.push(appendGuidanceToDcpTag(prompts.contextLimitNudge, guidance));
|
|
6606
6673
|
}
|
|
6607
6674
|
}
|
|
6608
6675
|
if (turnNudgeAnchors.size > 0) {
|
|
6609
6676
|
for (const { index } of collectAnchoredMessages(turnNudgeAnchors, messages)) {
|
|
6610
|
-
const guidance = buildMessagePriorityGuidance(
|
|
6677
|
+
const guidance = buildMessagePriorityGuidance(
|
|
6678
|
+
messages,
|
|
6679
|
+
compressionPriorities,
|
|
6680
|
+
index,
|
|
6681
|
+
MESSAGE_MODE_NUDGE_PRIORITY
|
|
6682
|
+
);
|
|
6611
6683
|
nudgeParts.push(appendGuidanceToDcpTag(prompts.turnNudge, guidance));
|
|
6612
6684
|
}
|
|
6613
6685
|
}
|
|
6614
6686
|
if (state.nudges.iterationNudgeAnchors.size > 0) {
|
|
6615
|
-
for (const { index } of collectAnchoredMessages(
|
|
6616
|
-
|
|
6687
|
+
for (const { index } of collectAnchoredMessages(
|
|
6688
|
+
state.nudges.iterationNudgeAnchors,
|
|
6689
|
+
messages
|
|
6690
|
+
)) {
|
|
6691
|
+
const guidance = buildMessagePriorityGuidance(
|
|
6692
|
+
messages,
|
|
6693
|
+
compressionPriorities,
|
|
6694
|
+
index,
|
|
6695
|
+
MESSAGE_MODE_NUDGE_PRIORITY
|
|
6696
|
+
);
|
|
6617
6697
|
nudgeParts.push(appendGuidanceToDcpTag(prompts.iterationNudge, guidance));
|
|
6618
6698
|
}
|
|
6619
6699
|
}
|
|
@@ -6661,12 +6741,7 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
6661
6741
|
prompts.contextLimitNudge,
|
|
6662
6742
|
""
|
|
6663
6743
|
);
|
|
6664
|
-
applyRangeModeAnchoredNudge(
|
|
6665
|
-
turnNudgeAnchors,
|
|
6666
|
-
messages,
|
|
6667
|
-
prompts.turnNudge,
|
|
6668
|
-
""
|
|
6669
|
-
);
|
|
6744
|
+
applyRangeModeAnchoredNudge(turnNudgeAnchors, messages, prompts.turnNudge, "");
|
|
6670
6745
|
applyRangeModeAnchoredNudge(
|
|
6671
6746
|
state.nudges.iterationNudgeAnchors,
|
|
6672
6747
|
messages,
|
|
@@ -6687,11 +6762,12 @@ function estimateCodeTokens(text) {
|
|
|
6687
6762
|
}
|
|
6688
6763
|
return Math.round(codeChars / 4);
|
|
6689
6764
|
}
|
|
6690
|
-
function estimateContextComposition(messages, state) {
|
|
6765
|
+
function estimateContextComposition(messages, state, protectedTools = [], protectedFilePatterns = []) {
|
|
6691
6766
|
let toolTokens = 0;
|
|
6692
6767
|
let codeTokens = 0;
|
|
6693
6768
|
let summaryTokens = 0;
|
|
6694
6769
|
let messageTokens = 0;
|
|
6770
|
+
let protectedTokens = 0;
|
|
6695
6771
|
const perMessage = [];
|
|
6696
6772
|
const perTool = [];
|
|
6697
6773
|
const perCode = [];
|
|
@@ -6701,6 +6777,7 @@ function estimateContextComposition(messages, state) {
|
|
|
6701
6777
|
const text = (msg.parts || []).filter((p) => p.type === "text").map((p) => p.text || "").join("");
|
|
6702
6778
|
const msgId = msg.info?.id || "";
|
|
6703
6779
|
const isSummary = msgId.startsWith("msg_dcp_summary") || text.includes("[Compressed conversation section]");
|
|
6780
|
+
const isProtected = (protectedTools.length > 0 || protectedFilePatterns.length > 0) && messageContainsProtectedTool(msg, protectedTools, protectedFilePatterns);
|
|
6704
6781
|
let msgTotal = 0;
|
|
6705
6782
|
let msgTool = 0;
|
|
6706
6783
|
let msgCode = 0;
|
|
@@ -6733,6 +6810,9 @@ function estimateContextComposition(messages, state) {
|
|
|
6733
6810
|
if (!msgToolName) msgToolName = toolName;
|
|
6734
6811
|
}
|
|
6735
6812
|
}
|
|
6813
|
+
if (isProtected && !isSummary) {
|
|
6814
|
+
protectedTokens += msgTotal;
|
|
6815
|
+
}
|
|
6736
6816
|
if (!isSummary) {
|
|
6737
6817
|
const ref = state?.messageIds?.byRawId?.get(msgId) || "?";
|
|
6738
6818
|
if (msgTotal > 500) perMessage.push({ ref, tokens: msgTotal });
|
|
@@ -6752,6 +6832,7 @@ function estimateContextComposition(messages, state) {
|
|
|
6752
6832
|
summaryTokens,
|
|
6753
6833
|
messageTokens,
|
|
6754
6834
|
textTokens: Math.max(0, messageTokens - codeTokens),
|
|
6835
|
+
protectedTokens,
|
|
6755
6836
|
total: toolTokens + summaryTokens + messageTokens,
|
|
6756
6837
|
largestRanges: perMessage.slice(0, 15),
|
|
6757
6838
|
largestToolRanges: perTool.slice(0, 15),
|
|
@@ -6760,12 +6841,33 @@ function estimateContextComposition(messages, state) {
|
|
|
6760
6841
|
toolTypeBreakdown
|
|
6761
6842
|
};
|
|
6762
6843
|
}
|
|
6763
|
-
function
|
|
6844
|
+
function refNum(ref) {
|
|
6845
|
+
const n = parseInt(ref.slice(1), 10);
|
|
6846
|
+
return Number.isNaN(n) ? -1 : n;
|
|
6847
|
+
}
|
|
6848
|
+
function buildCompressibleRanges(messages, state, protectedTools = [], protectedFilePatterns = []) {
|
|
6764
6849
|
const msgInfo = [];
|
|
6850
|
+
const protectedMsgInfo = [];
|
|
6765
6851
|
for (const msg of messages) {
|
|
6766
6852
|
if (isSyntheticMessage(msg)) continue;
|
|
6767
6853
|
const ref = state.messageIds.byRawId.get(msg.info.id);
|
|
6768
6854
|
if (!ref) continue;
|
|
6855
|
+
const rn = parseInt(ref.slice(1), 10);
|
|
6856
|
+
if ((protectedTools.length > 0 || protectedFilePatterns.length > 0) && messageContainsProtectedTool(msg, protectedTools, protectedFilePatterns)) {
|
|
6857
|
+
let tokens2 = 0;
|
|
6858
|
+
const tools = /* @__PURE__ */ new Set();
|
|
6859
|
+
for (const part of msg.parts || []) {
|
|
6860
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
6861
|
+
tokens2 += Math.round(part.text.length / 4);
|
|
6862
|
+
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
6863
|
+
tokens2 += Math.round(JSON.stringify(part).length / 4);
|
|
6864
|
+
const toolName = part?.tool;
|
|
6865
|
+
if (toolName) tools.add(toolName);
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6868
|
+
protectedMsgInfo.push({ ref, refNum: rn, tokens: tokens2, tools: [...tools] });
|
|
6869
|
+
continue;
|
|
6870
|
+
}
|
|
6769
6871
|
let tokens = 0;
|
|
6770
6872
|
let isTool = false;
|
|
6771
6873
|
for (const part of msg.parts || []) {
|
|
@@ -6776,10 +6878,8 @@ function buildCompressibleRanges(messages, state) {
|
|
|
6776
6878
|
isTool = true;
|
|
6777
6879
|
}
|
|
6778
6880
|
}
|
|
6779
|
-
|
|
6780
|
-
msgInfo.push({ ref, refNum, tokens, isTool, isUser: msg.info.role === "user" });
|
|
6881
|
+
msgInfo.push({ ref, refNum: rn, tokens, isTool, isUser: msg.info.role === "user" });
|
|
6781
6882
|
}
|
|
6782
|
-
if (msgInfo.length === 0) return [];
|
|
6783
6883
|
const groups = [];
|
|
6784
6884
|
let cur = null;
|
|
6785
6885
|
let prevRefNum = -2;
|
|
@@ -6812,14 +6912,114 @@ function buildCompressibleRanges(messages, state) {
|
|
|
6812
6912
|
}
|
|
6813
6913
|
}
|
|
6814
6914
|
if (cur) groups.push(cur);
|
|
6815
|
-
|
|
6915
|
+
const protectedGroups = [];
|
|
6916
|
+
let pcur = null;
|
|
6917
|
+
let pPrevRefNum = -2;
|
|
6918
|
+
for (const info of protectedMsgInfo) {
|
|
6919
|
+
const hasGap = info.refNum > pPrevRefNum + 1;
|
|
6920
|
+
if (pcur && hasGap) {
|
|
6921
|
+
protectedGroups.push(pcur);
|
|
6922
|
+
pcur = null;
|
|
6923
|
+
}
|
|
6924
|
+
pPrevRefNum = info.refNum;
|
|
6925
|
+
if (!pcur) {
|
|
6926
|
+
pcur = {
|
|
6927
|
+
startRef: info.ref,
|
|
6928
|
+
endRef: info.ref,
|
|
6929
|
+
count: 1,
|
|
6930
|
+
tokens: info.tokens,
|
|
6931
|
+
tools: [...info.tools]
|
|
6932
|
+
};
|
|
6933
|
+
} else {
|
|
6934
|
+
pcur.endRef = info.ref;
|
|
6935
|
+
pcur.count++;
|
|
6936
|
+
pcur.tokens += info.tokens;
|
|
6937
|
+
for (const t of info.tools) {
|
|
6938
|
+
if (!pcur.tools.includes(t)) pcur.tools.push(t);
|
|
6939
|
+
}
|
|
6940
|
+
}
|
|
6941
|
+
}
|
|
6942
|
+
if (pcur) protectedGroups.push(pcur);
|
|
6943
|
+
return {
|
|
6944
|
+
compressible: groups.filter((g) => g.tokens > 0),
|
|
6945
|
+
protected: protectedGroups
|
|
6946
|
+
};
|
|
6816
6947
|
}
|
|
6817
|
-
function formatCompressibleRanges(ranges) {
|
|
6818
|
-
if (ranges.length === 0) return "";
|
|
6948
|
+
function formatCompressibleRanges(ranges, protectedRanges) {
|
|
6819
6949
|
const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6950
|
+
if (!protectedRanges || protectedRanges.length === 0) {
|
|
6951
|
+
if (ranges.length === 0) return "";
|
|
6952
|
+
const lines2 = ranges.map((r, i) => {
|
|
6953
|
+
const suffix = i === ranges.length - 1 ? " (recent \u2014 may still be in active use)" : "";
|
|
6954
|
+
return ` ${r.startRef}\u2013${r.endRef} ${r.count} msgs ${fmt(r.tokens)} [tool ${r.toolPct}% | text ${r.textPct}%]${suffix}`;
|
|
6955
|
+
});
|
|
6956
|
+
return `Compressible ranges (oldest first):
|
|
6957
|
+
${lines2.join("\n")}`;
|
|
6958
|
+
}
|
|
6959
|
+
const entries = [];
|
|
6960
|
+
for (const r of ranges) {
|
|
6961
|
+
entries.push({
|
|
6962
|
+
startRef: r.startRef,
|
|
6963
|
+
endRef: r.endRef,
|
|
6964
|
+
startNum: refNum(r.startRef),
|
|
6965
|
+
endNum: refNum(r.endRef),
|
|
6966
|
+
count: r.count,
|
|
6967
|
+
tokens: r.tokens,
|
|
6968
|
+
toolPct: r.toolPct,
|
|
6969
|
+
textPct: r.textPct,
|
|
6970
|
+
compressibleTokens: r.tokens,
|
|
6971
|
+
compressibleCount: r.count,
|
|
6972
|
+
protectedTokens: 0,
|
|
6973
|
+
protectedCount: 0,
|
|
6974
|
+
protectedTools: []
|
|
6975
|
+
});
|
|
6976
|
+
}
|
|
6977
|
+
for (const r of protectedRanges) {
|
|
6978
|
+
entries.push({
|
|
6979
|
+
startRef: r.startRef,
|
|
6980
|
+
endRef: r.endRef,
|
|
6981
|
+
startNum: refNum(r.startRef),
|
|
6982
|
+
endNum: refNum(r.endRef),
|
|
6983
|
+
count: r.count,
|
|
6984
|
+
tokens: r.tokens,
|
|
6985
|
+
toolPct: 0,
|
|
6986
|
+
textPct: 0,
|
|
6987
|
+
compressibleTokens: 0,
|
|
6988
|
+
compressibleCount: 0,
|
|
6989
|
+
protectedTokens: r.tokens,
|
|
6990
|
+
protectedCount: r.count,
|
|
6991
|
+
protectedTools: [...r.tools]
|
|
6992
|
+
});
|
|
6993
|
+
}
|
|
6994
|
+
entries.sort((a, b) => a.startNum - b.startNum);
|
|
6995
|
+
const merged = [];
|
|
6996
|
+
for (const entry of entries) {
|
|
6997
|
+
const last = merged[merged.length - 1];
|
|
6998
|
+
if (last && entry.startNum <= last.endNum + 1) {
|
|
6999
|
+
last.endRef = entry.endRef;
|
|
7000
|
+
last.endNum = Math.max(last.endNum, entry.endNum);
|
|
7001
|
+
last.count += entry.count;
|
|
7002
|
+
last.tokens += entry.tokens;
|
|
7003
|
+
last.compressibleTokens += entry.compressibleTokens;
|
|
7004
|
+
last.compressibleCount += entry.compressibleCount;
|
|
7005
|
+
last.protectedTokens += entry.protectedTokens;
|
|
7006
|
+
last.protectedCount += entry.protectedCount;
|
|
7007
|
+
for (const t of entry.protectedTools) {
|
|
7008
|
+
if (!last.protectedTools.includes(t)) last.protectedTools.push(t);
|
|
7009
|
+
}
|
|
7010
|
+
} else {
|
|
7011
|
+
merged.push({ ...entry });
|
|
7012
|
+
}
|
|
7013
|
+
}
|
|
7014
|
+
const lines = merged.map((e, i) => {
|
|
7015
|
+
const suffix = i === merged.length - 1 && e.compressibleTokens > 0 ? " (recent \u2014 may still be in active use)" : "";
|
|
7016
|
+
if (e.protectedTokens > 0 && e.compressibleTokens === 0) {
|
|
7017
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${fmt(e.tokens)} [PROTECTED: ${e.protectedTools.join(", ")} \u2014 not compressible]${suffix}`;
|
|
7018
|
+
}
|
|
7019
|
+
if (e.protectedTokens > 0 && e.compressibleTokens > 0) {
|
|
7020
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${fmt(e.tokens)} [${fmt(e.compressibleTokens)} compressible | ${fmt(e.protectedTokens)} protected: ${e.protectedTools.join(", ")}]${suffix}`;
|
|
7021
|
+
}
|
|
7022
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${fmt(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;
|
|
6823
7023
|
});
|
|
6824
7024
|
return `Compressible ranges (oldest first):
|
|
6825
7025
|
${lines.join("\n")}`;
|
|
@@ -6995,8 +7195,13 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6995
7195
|
}
|
|
6996
7196
|
}
|
|
6997
7197
|
const suffixMessage = createSuffixMessage(messages);
|
|
6998
|
-
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
6999
7198
|
const nudgeGrowthTokens = config.compress?.nudgeGrowthTokens ?? resolveAdaptiveNudgeGrowth(modelContextLimit);
|
|
7199
|
+
const growthFloor = Math.max(
|
|
7200
|
+
config.compress?.minNudgeGrowthFloor ?? 5e3,
|
|
7201
|
+
(config.compress?.minNudgeGrowthRatio ?? 0.45) * nudgeGrowthTokens
|
|
7202
|
+
);
|
|
7203
|
+
const emergencyThreshold = resolveEmergencyThreshold(config, modelContextLimit);
|
|
7204
|
+
const emergencyOverride = emergencyThreshold !== void 0 && currentTokens !== void 0 && currentTokens >= emergencyThreshold;
|
|
7000
7205
|
if (currentTokens !== void 0 && state.nudges.lastPerMessageNudgeTokens !== void 0 && currentTokens < state.nudges.lastPerMessageNudgeTokens - nudgeGrowthTokens) {
|
|
7001
7206
|
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
7002
7207
|
state.nudges.lastNudgeShownTokens = void 0;
|
|
@@ -7014,14 +7219,25 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
7014
7219
|
minNudgeContextPercent: config.compress?.minNudgeContextPercent ?? 15,
|
|
7015
7220
|
nudgeGrowthTokens: effectiveThreshold
|
|
7016
7221
|
});
|
|
7017
|
-
|
|
7222
|
+
const growthSinceBaseline = currentTokens !== void 0 && growthReference !== void 0 ? currentTokens - growthReference : void 0;
|
|
7223
|
+
const nudgeAllowed = emergencyOverride || growthSinceBaseline !== void 0 && growthSinceBaseline >= growthFloor;
|
|
7224
|
+
state.nudges.shouldInjectThisTurn = nudgeAllowed;
|
|
7225
|
+
const effectiveTipsVariant = emergencyOverride ? "maxLimit" : decision.tipsVariant;
|
|
7226
|
+
if (nudgeAllowed) {
|
|
7227
|
+
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
7228
|
+
}
|
|
7018
7229
|
if (state.nudges.lastPerMessageNudgeTokens === void 0 && currentTokens !== void 0) {
|
|
7019
7230
|
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
7020
7231
|
baselineReEstablished = true;
|
|
7021
7232
|
}
|
|
7022
|
-
const composition = estimateContextComposition(
|
|
7233
|
+
const composition = estimateContextComposition(
|
|
7234
|
+
messages,
|
|
7235
|
+
state,
|
|
7236
|
+
config.compress.protectedTools,
|
|
7237
|
+
config.protectedFilePatterns
|
|
7238
|
+
);
|
|
7023
7239
|
let tipsText = null;
|
|
7024
|
-
if (
|
|
7240
|
+
if (nudgeAllowed) {
|
|
7025
7241
|
injectContextUsage(suffixMessage, config, currentTokens, modelContextLimit);
|
|
7026
7242
|
if (suffixMessage && composition.total > 0) {
|
|
7027
7243
|
const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
@@ -7029,30 +7245,40 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
7029
7245
|
const growth = currentTokens !== void 0 && state.nudges.lastPerMessageNudgeTokens !== void 0 ? currentTokens - state.nudges.lastPerMessageNudgeTokens : 0;
|
|
7030
7246
|
const growthStr = growth > 0 ? ` (+${fmt(growth)} since last nudge)` : "";
|
|
7031
7247
|
const plainTextTokens = composition.textTokens;
|
|
7032
|
-
const efficiencyNote =
|
|
7248
|
+
const efficiencyNote = effectiveTipsVariant !== "maxLimit" ? `
|
|
7033
7249
|
This is an efficiency nudge to compress early and keep context lean \u2014 not an overflow warning. A separate, stronger alert will appear if the context is actually full.
|
|
7034
7250
|
|
|
7035
7251
|
${COMPRESS_PHILOSOPHY}` : "";
|
|
7036
7252
|
let breakdown = `${efficiencyNote}
|
|
7037
7253
|
Breakdown: ${fmt(composition.toolTokens)} tool (${pct2(composition.toolTokens)}%) | ${fmt(composition.summaryTokens)} summaries (${pct2(composition.summaryTokens)}%) | ${fmt(composition.codeTokens)} code (${pct2(composition.codeTokens)}%) | ${fmt(plainTextTokens)} text (${pct2(plainTextTokens)}%)${growthStr}`;
|
|
7038
|
-
const
|
|
7039
|
-
if (
|
|
7254
|
+
const compressibleTokens = composition.total - composition.protectedTokens - composition.summaryTokens;
|
|
7255
|
+
if (composition.protectedTokens > 0) {
|
|
7256
|
+
breakdown += `
|
|
7257
|
+
\u26A0\uFE0F ${fmt(composition.protectedTokens)} tokens are protected (environment-managed tools) \u2014 not compressible. Effective compressible: ~${fmt(compressibleTokens)}.`;
|
|
7258
|
+
}
|
|
7259
|
+
const contextRanges = buildCompressibleRanges(
|
|
7260
|
+
messages,
|
|
7261
|
+
state,
|
|
7262
|
+
config.compress.protectedTools,
|
|
7263
|
+
config.protectedFilePatterns
|
|
7264
|
+
);
|
|
7265
|
+
if (contextRanges.compressible.length > 0) {
|
|
7040
7266
|
breakdown += `
|
|
7041
7267
|
|
|
7042
|
-
${formatCompressibleRanges(
|
|
7268
|
+
${formatCompressibleRanges(contextRanges.compressible, contextRanges.protected)}`;
|
|
7043
7269
|
breakdown += `
|
|
7044
7270
|
\u{1F4A1} Compress all ranges in one call (pass multiple content entries: \`content: [{...}, {...}]\`).`;
|
|
7045
7271
|
}
|
|
7046
7272
|
breakdown += `
|
|
7047
7273
|
Use \`acp_status({scope:"uncompressed"})\` to re-fetch compressible ranges after compressing, or \`acp_status\` for compressed block details.`;
|
|
7048
|
-
if (
|
|
7274
|
+
if (effectiveTipsVariant !== "maxLimit") {
|
|
7049
7275
|
breakdown += `
|
|
7050
7276
|
|
|
7051
7277
|
${HOW_TO_COMPRESS_RULES}`;
|
|
7052
7278
|
}
|
|
7053
7279
|
appendToLastTextPart(suffixMessage, breakdown);
|
|
7054
7280
|
}
|
|
7055
|
-
if (
|
|
7281
|
+
if (effectiveTipsVariant === "maxLimit") {
|
|
7056
7282
|
tipsText = '\n\n\u26A0\uFE0F Context limit reached \u2014 compress now. Prioritize consumed tool outputs.\n\n{ "topic": "...", "content": [{ "startId": "<ID>", "endId": "<ID>", "summary": "..." }] }\n\nOnly use IDs from visible messages above. Compress older work first.';
|
|
7057
7283
|
}
|
|
7058
7284
|
state.nudges.lastNudgeShownTokens = currentTokens;
|
|
@@ -7090,11 +7316,21 @@ ${HOW_TO_COMPRESS_RULES}`;
|
|
|
7090
7316
|
}
|
|
7091
7317
|
}
|
|
7092
7318
|
}
|
|
7093
|
-
if (anchorsChanged ||
|
|
7319
|
+
if (anchorsChanged || nudgeAllowed || baselineReEstablished || baselineCorrected) {
|
|
7094
7320
|
saveSessionState(state, logger).catch(() => {
|
|
7095
7321
|
});
|
|
7096
7322
|
}
|
|
7097
7323
|
};
|
|
7324
|
+
function resolveEmergencyThreshold(config, modelContextLimit) {
|
|
7325
|
+
const threshold = config.compress?.emergencyThresholdPercent;
|
|
7326
|
+
if (threshold === void 0 || modelContextLimit === void 0) return void 0;
|
|
7327
|
+
if (typeof threshold === "number") return threshold;
|
|
7328
|
+
if (!threshold.endsWith("%")) return void 0;
|
|
7329
|
+
const parsedPercent = parseFloat(threshold.slice(0, -1));
|
|
7330
|
+
if (isNaN(parsedPercent)) return void 0;
|
|
7331
|
+
const clampedPercent = Math.max(0, Math.min(100, Math.round(parsedPercent)));
|
|
7332
|
+
return Math.round(clampedPercent / 100 * modelContextLimit);
|
|
7333
|
+
}
|
|
7098
7334
|
function injectContextUsage(target, config, currentTokens, modelContextLimit) {
|
|
7099
7335
|
if (!target) return;
|
|
7100
7336
|
const rawUsage = buildContextUsageGuidance(config, currentTokens, modelContextLimit);
|
|
@@ -7124,14 +7360,11 @@ var injectMessageIds = (state, config, messages, compressionPriorities) => {
|
|
|
7124
7360
|
const priority = config.compress.mode === "message" && !isBlockedMessage ? compressionPriorities?.get(message.info.id)?.priority : void 0;
|
|
7125
7361
|
const msgType = classifyMessageType(message.parts);
|
|
7126
7362
|
const msgTokens = Math.round(countMessageCharacters(message) / 4);
|
|
7127
|
-
const tag = formatMessageIdTag(
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
tokens: formatTokenSize(msgTokens)
|
|
7133
|
-
}
|
|
7134
|
-
);
|
|
7363
|
+
const tag = formatMessageIdTag(isBlockedMessage ? "BLOCKED" : messageRef, {
|
|
7364
|
+
priority: priority ?? void 0,
|
|
7365
|
+
type: msgType,
|
|
7366
|
+
tokens: formatTokenSize(msgTokens)
|
|
7367
|
+
});
|
|
7135
7368
|
if (message.info.role === "user") {
|
|
7136
7369
|
let injected = false;
|
|
7137
7370
|
for (const part of message.parts) {
|
|
@@ -7732,7 +7965,9 @@ function renderOverview(visibleMessages, summaryTokens, blocks, fetchFailed, raw
|
|
|
7732
7965
|
);
|
|
7733
7966
|
const topTypes = Array.from(toolTypeMap.entries()).map(([tool8, tokens]) => ({ tool: tool8, tokens })).sort((a, b) => b.tokens - a.tokens).slice(0, 3);
|
|
7734
7967
|
if (topTypes.length > 0) {
|
|
7735
|
-
lines.push(
|
|
7968
|
+
lines.push(
|
|
7969
|
+
` Top tools: ${topTypes.map((t) => `${t.tool} (${pct(t.tokens, total)}%)`).join(", ")}`
|
|
7970
|
+
);
|
|
7736
7971
|
}
|
|
7737
7972
|
}
|
|
7738
7973
|
lines.push("");
|
|
@@ -7751,7 +7986,9 @@ function renderOverview(visibleMessages, summaryTokens, blocks, fetchFailed, raw
|
|
|
7751
7986
|
const ageStr = formatAge(b.createdAt);
|
|
7752
7987
|
const range = formatIdRange(b);
|
|
7753
7988
|
const topic = b.topic || "(no topic)";
|
|
7754
|
-
lines.push(
|
|
7989
|
+
lines.push(
|
|
7990
|
+
` b${b.blockId} ${formatTokens(b.compressedTokens)}\u2192${formatTokens(b.summaryTokens)} ${ageStr} ${range} "${topic}"`
|
|
7991
|
+
);
|
|
7755
7992
|
}
|
|
7756
7993
|
}
|
|
7757
7994
|
if (!fetchFailed) {
|
|
@@ -7761,15 +7998,24 @@ function renderOverview(visibleMessages, summaryTokens, blocks, fetchFailed, raw
|
|
|
7761
7998
|
const entry = pruneMap.get(msgId);
|
|
7762
7999
|
return !entry || entry.activeBlockIds.length === 0;
|
|
7763
8000
|
});
|
|
7764
|
-
const
|
|
7765
|
-
|
|
8001
|
+
const contextRanges = buildCompressibleRanges(
|
|
8002
|
+
visibleRaw,
|
|
8003
|
+
ctx.state,
|
|
8004
|
+
ctx.config?.compress?.protectedTools ?? [],
|
|
8005
|
+
ctx.config?.protectedFilePatterns ?? []
|
|
8006
|
+
);
|
|
8007
|
+
if (contextRanges.compressible.length > 0 || contextRanges.protected.length > 0) {
|
|
7766
8008
|
lines.push("");
|
|
7767
|
-
lines.push(
|
|
8009
|
+
lines.push(
|
|
8010
|
+
formatCompressibleRanges(contextRanges.compressible, contextRanges.protected)
|
|
8011
|
+
);
|
|
7768
8012
|
}
|
|
7769
8013
|
}
|
|
7770
8014
|
lines.push("");
|
|
7771
8015
|
const hintTool = topToolName || "bash";
|
|
7772
|
-
lines.push(
|
|
8016
|
+
lines.push(
|
|
8017
|
+
`Tip: acp_status({scope:"uncompressed", view:"messages", tool:"${hintTool}"}) for per-message listing`
|
|
8018
|
+
);
|
|
7773
8019
|
return lines;
|
|
7774
8020
|
}
|
|
7775
8021
|
function renderUncompressedRanges(rawMessages, ctx) {
|
|
@@ -7779,16 +8025,24 @@ function renderUncompressedRanges(rawMessages, ctx) {
|
|
|
7779
8025
|
const entry = pruneMap.get(msgId);
|
|
7780
8026
|
return !entry || entry.activeBlockIds.length === 0;
|
|
7781
8027
|
});
|
|
7782
|
-
const
|
|
7783
|
-
|
|
7784
|
-
|
|
8028
|
+
const contextRanges = buildCompressibleRanges(
|
|
8029
|
+
visibleMessages,
|
|
8030
|
+
ctx.state,
|
|
8031
|
+
ctx.config?.compress?.protectedTools ?? [],
|
|
8032
|
+
ctx.config?.protectedFilePatterns ?? []
|
|
8033
|
+
);
|
|
8034
|
+
const compressible = contextRanges.compressible;
|
|
8035
|
+
const totalTokens = compressible.reduce((s, r) => s + r.tokens, 0);
|
|
8036
|
+
const totalMsgs = compressible.reduce((s, r) => s + r.count, 0);
|
|
7785
8037
|
const lines = [];
|
|
7786
|
-
lines.push(
|
|
8038
|
+
lines.push(
|
|
8039
|
+
`UNCOMPRESSED \u2014 ${formatTokens(totalTokens)} | ${totalMsgs} msgs in ${compressible.length} ranges`
|
|
8040
|
+
);
|
|
7787
8041
|
lines.push("");
|
|
7788
|
-
if (
|
|
8042
|
+
if (compressible.length === 0 && contextRanges.protected.length === 0) {
|
|
7789
8043
|
lines.push(" (no compressible ranges)");
|
|
7790
8044
|
} else {
|
|
7791
|
-
lines.push(formatCompressibleRanges(
|
|
8045
|
+
lines.push(formatCompressibleRanges(compressible, contextRanges.protected));
|
|
7792
8046
|
}
|
|
7793
8047
|
lines.push("");
|
|
7794
8048
|
lines.push(`Per-message listing: acp_status({scope:"uncompressed", view:"messages"})`);
|
|
@@ -7820,7 +8074,9 @@ function renderUncompressedDrilldown(visibleMessages, toolFilter, sort, limit) {
|
|
|
7820
8074
|
}
|
|
7821
8075
|
if (filtered.length > shown.length) {
|
|
7822
8076
|
lines.push("");
|
|
7823
|
-
lines.push(
|
|
8077
|
+
lines.push(
|
|
8078
|
+
`${shown.length} of ${filtered.length} shown (${filtered.length - shown.length} hidden).`
|
|
8079
|
+
);
|
|
7824
8080
|
}
|
|
7825
8081
|
if (filtered.length > 1 && sort !== "time") {
|
|
7826
8082
|
const refs = filtered.map((m) => m.index);
|
|
@@ -7847,7 +8103,9 @@ function renderCompressedDrilldown(blocks, sort, limit) {
|
|
|
7847
8103
|
}
|
|
7848
8104
|
const totalSummary = sorted.reduce((s, b) => s + (b.summaryTokens || 0), 0);
|
|
7849
8105
|
const totalCompressed = sorted.reduce((s, b) => s + (b.compressedTokens || 0), 0);
|
|
7850
|
-
lines.push(
|
|
8106
|
+
lines.push(
|
|
8107
|
+
`COMPRESSED \u2014 ${sorted.length} blocks | ${formatTokens(totalCompressed)} original \u2192 ${formatTokens(totalSummary)} summary`
|
|
8108
|
+
);
|
|
7851
8109
|
lines.push(`Sorted by ${sort === "time" ? "time" : sort === "age" ? "age" : "size"}`);
|
|
7852
8110
|
lines.push("");
|
|
7853
8111
|
const shown = sorted.slice(0, limit);
|
|
@@ -7867,7 +8125,9 @@ function renderCompressedDrilldown(blocks, sort, limit) {
|
|
|
7867
8125
|
lines.push(`${shown.length} of ${sorted.length} shown.`);
|
|
7868
8126
|
}
|
|
7869
8127
|
lines.push("");
|
|
7870
|
-
lines.push(
|
|
8128
|
+
lines.push(
|
|
8129
|
+
"Use decompress to restore a block's content, or search_context to search within blocks."
|
|
8130
|
+
);
|
|
7871
8131
|
return lines;
|
|
7872
8132
|
}
|
|
7873
8133
|
function createAcpStatusTool(ctx) {
|
|
@@ -7876,8 +8136,12 @@ function createAcpStatusTool(ctx) {
|
|
|
7876
8136
|
description: ACP_STATUS_TOOL_DESCRIPTION,
|
|
7877
8137
|
args: {
|
|
7878
8138
|
scope: tool5.schema.string().optional().describe('Drill down: "compressed" or "uncompressed". No arg = overview of both.'),
|
|
7879
|
-
view: tool5.schema.string().optional().describe(
|
|
7880
|
-
|
|
8139
|
+
view: tool5.schema.string().optional().describe(
|
|
8140
|
+
'Display format for scope:"uncompressed": "ranges" (default, grouped by turn \u2014 matches nudge format) or "messages" (per-message listing with sort/filter)'
|
|
8141
|
+
),
|
|
8142
|
+
tool: tool5.schema.string().optional().describe(
|
|
8143
|
+
'Filter by tool type (only with scope:"uncompressed", view:"messages"). e.g., "bash", "todowrite", "write"'
|
|
8144
|
+
),
|
|
7881
8145
|
sort: tool5.schema.string().optional().describe('Sort order: "size" (default), "time", or "tool"'),
|
|
7882
8146
|
limit: tool5.schema.number().optional().describe("Max items to list (default 30)")
|
|
7883
8147
|
},
|
|
@@ -7915,7 +8179,16 @@ function createAcpStatusTool(ctx) {
|
|
|
7915
8179
|
lines.push(...renderUncompressedRanges(rawMessages, ctx));
|
|
7916
8180
|
}
|
|
7917
8181
|
} else {
|
|
7918
|
-
lines.push(
|
|
8182
|
+
lines.push(
|
|
8183
|
+
...renderOverview(
|
|
8184
|
+
visibleMsgs,
|
|
8185
|
+
summaryTokens,
|
|
8186
|
+
allBlocks,
|
|
8187
|
+
fetchFailed,
|
|
8188
|
+
rawMessages,
|
|
8189
|
+
ctx
|
|
8190
|
+
)
|
|
8191
|
+
);
|
|
7919
8192
|
}
|
|
7920
8193
|
return lines.join("\n");
|
|
7921
8194
|
}
|