acp-kernel 0.0.7 → 0.0.9
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/compress.d.ts.map +1 -1
- package/dist/index.js +160 -76
- package/dist/index.js.map +1 -1
- package/dist/nudge-text.d.ts.map +1 -1
- package/dist/protected.d.ts +14 -0
- package/dist/protected.d.ts.map +1 -1
- package/dist/recommend.d.ts.map +1 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/tokenize.d.ts.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/compress.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAcpB,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,EAEN,WAAW,EAGX,iBAAiB,EAEjB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,KAAK;IACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACxD,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CAAC;IACvE,YAAY,IAAI,YAAY,EAAE,CAAC;IAC/B,UAAU,CACR,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,gBAAgB,GACtB,gBAAgB,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAAC;IACnE,MAAM,CACJ,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,YAAY,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnC;AAED,wBAAgB,UAAU,CAAC,KAAK,GAAE,KAAU,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAcpB,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,EAEN,WAAW,EAGX,iBAAiB,EAEjB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,KAAK;IACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACxD,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CAAC;IACvE,YAAY,IAAI,YAAY,EAAE,CAAC;IAC/B,UAAU,CACR,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,gBAAgB,GACtB,gBAAgB,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAAC;IACnE,MAAM,CACJ,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,YAAY,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnC;AAED,wBAAgB,UAAU,CAAC,KAAK,GAAE,KAAU,GAAG,eAAe,CAoN7D;AAsoBD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -81,7 +81,8 @@ function createInitialState() {
|
|
|
81
81
|
lastPerMessageNudgeTokens: 0,
|
|
82
82
|
lastNudgeShownTokens: 0,
|
|
83
83
|
baselineTokens: 0,
|
|
84
|
-
anchors: {}
|
|
84
|
+
anchors: {},
|
|
85
|
+
lastShownByTier: {}
|
|
85
86
|
},
|
|
86
87
|
stats: { tokensCompressed: 0, compressionCount: 0 },
|
|
87
88
|
nextBlockId: 1,
|
|
@@ -269,9 +270,9 @@ import { createRequire } from "module";
|
|
|
269
270
|
var require2 = createRequire(import.meta.url);
|
|
270
271
|
function defaultCountTokens(text) {
|
|
271
272
|
if (!text) return 0;
|
|
272
|
-
const ascii = text.match(/[a-zA-Z][a-zA-Z0-9_'-]*/g);
|
|
273
273
|
const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
|
|
274
|
-
|
|
274
|
+
const cjkCount = cjk?.length ?? 0;
|
|
275
|
+
return cjkCount + Math.ceil((text.length - cjkCount) / 4);
|
|
275
276
|
}
|
|
276
277
|
function estimateTokensFast(text) {
|
|
277
278
|
if (!text) return 0;
|
|
@@ -704,6 +705,7 @@ var renderRefsNode = {
|
|
|
704
705
|
};
|
|
705
706
|
|
|
706
707
|
// src/protected.ts
|
|
708
|
+
var ALWAYS_PROTECTED_TOOLS = ["compress"];
|
|
707
709
|
function matchToolPattern(toolName, pattern) {
|
|
708
710
|
if (pattern.endsWith("*")) {
|
|
709
711
|
return toolName.startsWith(pattern.slice(0, -1));
|
|
@@ -711,13 +713,34 @@ function matchToolPattern(toolName, pattern) {
|
|
|
711
713
|
return toolName === pattern;
|
|
712
714
|
}
|
|
713
715
|
function isMessageProtected(msg, config) {
|
|
714
|
-
if (msg.contentType !== "tool-call" || !msg.toolName)
|
|
716
|
+
if (msg.contentType !== "tool-call" && msg.contentType !== "tool-result" || !msg.toolName) {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
if (ALWAYS_PROTECTED_TOOLS.includes(msg.toolName)) {
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
715
722
|
for (const pattern of config.protectedTools) {
|
|
716
723
|
if (matchToolPattern(msg.toolName, pattern)) return true;
|
|
717
724
|
}
|
|
718
725
|
if (config.isToolProtected?.(msg.toolName, msg.text)) return true;
|
|
719
726
|
return false;
|
|
720
727
|
}
|
|
728
|
+
function collectProtectedToolCallIds(messages, config) {
|
|
729
|
+
const ids = /* @__PURE__ */ new Set();
|
|
730
|
+
for (const m of messages) {
|
|
731
|
+
if (m.contentType === "tool-call" && m.toolCallId && isMessageProtected(m, config)) {
|
|
732
|
+
ids.add(m.toolCallId);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return ids;
|
|
736
|
+
}
|
|
737
|
+
function isMessageProtectedWithPairing(msg, config, protectedCallIds) {
|
|
738
|
+
if (isMessageProtected(msg, config)) return true;
|
|
739
|
+
if (msg.contentType === "tool-result" && msg.toolCallId && protectedCallIds.has(msg.toolCallId)) {
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
721
744
|
|
|
722
745
|
// src/tool-pairs.ts
|
|
723
746
|
function adjustBoundariesForToolPairs(startIndex, endIndex, messages, maxScan = 20) {
|
|
@@ -795,24 +818,27 @@ function computeProtectedRefs(messages, state, config) {
|
|
|
795
818
|
tokenAccum += visible[i].tokens;
|
|
796
819
|
}
|
|
797
820
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
821
|
+
if (preserveN > 0) {
|
|
822
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
823
|
+
const msg = messages[i];
|
|
824
|
+
if (msg.role !== "user" || isSyntheticOrPruned(msg, state)) continue;
|
|
825
|
+
const ref = state.messageRefs.byRaw[msg.id];
|
|
826
|
+
if (ref && ref !== "BLOCKED") result.add(ref);
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
804
829
|
}
|
|
805
830
|
return result;
|
|
806
831
|
}
|
|
807
832
|
function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
|
|
808
833
|
const compressibleMsgs = [];
|
|
809
834
|
const protectedMsgs = [];
|
|
835
|
+
const protectedCallIds = collectProtectedToolCallIds(messages, config);
|
|
810
836
|
for (const msg of messages) {
|
|
811
837
|
if (isSyntheticOrPruned(msg, state)) continue;
|
|
812
838
|
const ref = state.messageRefs.byRaw[msg.id];
|
|
813
839
|
if (!ref || ref === "BLOCKED") continue;
|
|
814
840
|
const rn = refNum(ref);
|
|
815
|
-
if (
|
|
841
|
+
if (isMessageProtectedWithPairing(msg, config, protectedCallIds)) {
|
|
816
842
|
protectedMsgs.push({
|
|
817
843
|
ref,
|
|
818
844
|
refNum: rn,
|
|
@@ -828,7 +854,8 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
|
|
|
828
854
|
ref,
|
|
829
855
|
refNum: rn,
|
|
830
856
|
tokens: estimateMessageTokens(msg),
|
|
831
|
-
isTool: isToolMessage(msg)
|
|
857
|
+
isTool: isToolMessage(msg),
|
|
858
|
+
isUser: msg.role === "user"
|
|
832
859
|
});
|
|
833
860
|
}
|
|
834
861
|
const compressible = [];
|
|
@@ -836,7 +863,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
|
|
|
836
863
|
let prevRefNum = -2;
|
|
837
864
|
for (const info of compressibleMsgs) {
|
|
838
865
|
const hasGap = info.refNum > prevRefNum + 1;
|
|
839
|
-
if (cur && hasGap) {
|
|
866
|
+
if (cur && (info.isUser && cur.count >= 3 || hasGap)) {
|
|
840
867
|
compressible.push(cur);
|
|
841
868
|
cur = null;
|
|
842
869
|
}
|
|
@@ -919,6 +946,7 @@ function createCore(ports = {}) {
|
|
|
919
946
|
let blocksCreated = 0;
|
|
920
947
|
let tokensCompressed = 0;
|
|
921
948
|
const errors = [];
|
|
949
|
+
const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config);
|
|
922
950
|
const preExistingCoverage = collectCoverage(state);
|
|
923
951
|
const rangeIndexSets = [];
|
|
924
952
|
for (const spec of input.ranges) {
|
|
@@ -1006,7 +1034,7 @@ function createCore(ports = {}) {
|
|
|
1006
1034
|
state,
|
|
1007
1035
|
runId,
|
|
1008
1036
|
config: input.config,
|
|
1009
|
-
protectedMessageIds
|
|
1037
|
+
protectedMessageIds,
|
|
1010
1038
|
countTokens,
|
|
1011
1039
|
preExistingCoverage
|
|
1012
1040
|
});
|
|
@@ -1021,6 +1049,7 @@ function createCore(ports = {}) {
|
|
|
1021
1049
|
if (blocksCreated > 0) {
|
|
1022
1050
|
state.nudge.lastPerMessageNudgeTokens = 0;
|
|
1023
1051
|
state.nudge.lastNudgeShownTokens = 0;
|
|
1052
|
+
state.nudge.lastShownByTier = {};
|
|
1024
1053
|
}
|
|
1025
1054
|
return { state, result: { blocksCreated, tokensCompressed, errors } };
|
|
1026
1055
|
}
|
|
@@ -1156,7 +1185,8 @@ var nudgeNode = {
|
|
|
1156
1185
|
config: ctx.config,
|
|
1157
1186
|
state: io.state,
|
|
1158
1187
|
messages: io.messages,
|
|
1159
|
-
recommendation: io.effects.recommendation
|
|
1188
|
+
recommendation: io.effects.recommendation,
|
|
1189
|
+
countTokens: ctx.countTokens
|
|
1160
1190
|
});
|
|
1161
1191
|
const baseline = io.state.nudge.lastPerMessageNudgeTokens;
|
|
1162
1192
|
const nudgeGrowthTokens = resolveAdaptiveGrowth(
|
|
@@ -1173,6 +1203,9 @@ var nudgeNode = {
|
|
|
1173
1203
|
}
|
|
1174
1204
|
if (nudge.shouldInject) {
|
|
1175
1205
|
stamped.lastNudgeShownTokens = ctx.tokenCount;
|
|
1206
|
+
if (nudge.tier !== null) {
|
|
1207
|
+
stamped.lastShownByTier = { ...stamped.lastShownByTier, [nudge.tier]: ctx.tokenCount };
|
|
1208
|
+
}
|
|
1176
1209
|
}
|
|
1177
1210
|
return {
|
|
1178
1211
|
...io,
|
|
@@ -1248,11 +1281,25 @@ function applySingleRange(input) {
|
|
|
1248
1281
|
const directMessageIds = [...effectiveMessageIds].filter(
|
|
1249
1282
|
(id) => !input.preExistingCoverage.has(id)
|
|
1250
1283
|
);
|
|
1251
|
-
const
|
|
1284
|
+
const filteredIds = filterProtectedToolMessages(
|
|
1252
1285
|
directMessageIds,
|
|
1253
1286
|
input.messages,
|
|
1254
1287
|
input.config
|
|
1255
1288
|
);
|
|
1289
|
+
const protectedRefs = input.protectedMessageIds;
|
|
1290
|
+
const hitProtectedRaw = protectedRefs ? filteredIds.filter((id) => {
|
|
1291
|
+
const ref = input.state.messageRefs.byRaw[id];
|
|
1292
|
+
return ref !== void 0 && protectedRefs.has(ref);
|
|
1293
|
+
}) : [];
|
|
1294
|
+
if (hitProtectedRaw.length > 0) {
|
|
1295
|
+
const recentN = input.config.preserveRecentMessages;
|
|
1296
|
+
const hitRefs = hitProtectedRaw.map((id) => input.state.messageRefs.byRaw[id]).filter(Boolean);
|
|
1297
|
+
throw new Error(
|
|
1298
|
+
`Range includes protected messages (the last ${recentN} messages and/or the most recent user message) which must not be compressed: ${hitRefs.join(
|
|
1299
|
+
", "
|
|
1300
|
+
)}. Adjust startId/endId to exclude them.`
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1256
1303
|
validateCompressionRange(input, filteredIds, consumedBlockIds.length);
|
|
1257
1304
|
let compressedTokens = 0;
|
|
1258
1305
|
for (const id of filteredIds) {
|
|
@@ -1266,22 +1313,12 @@ function applySingleRange(input) {
|
|
|
1266
1313
|
}
|
|
1267
1314
|
}
|
|
1268
1315
|
const blockId = allocateBlockId(input.state);
|
|
1269
|
-
const finalSummary = appendedProtectedText.length > 0 ? `${input.spec.summary}
|
|
1270
|
-
|
|
1271
|
-
The following protected tool calls were used in this conversation as well:
|
|
1272
|
-
${appendedProtectedText.join("")}` : input.spec.summary;
|
|
1273
|
-
const maxLen = input.spec.summaryMaxChars ?? input.config.compress.maxSummaryLength;
|
|
1274
|
-
if (maxLen > 0 && finalSummary.length > maxLen) {
|
|
1275
|
-
throw new Error(
|
|
1276
|
-
`Summary too long after appending protected content (${finalSummary.length} chars, max ${maxLen}). Reduce the summary or remove protected tools from the range.`
|
|
1277
|
-
);
|
|
1278
|
-
}
|
|
1279
1316
|
const block = {
|
|
1280
1317
|
blockId,
|
|
1281
1318
|
runId: input.runId,
|
|
1282
1319
|
tier: outputTier,
|
|
1283
1320
|
topic: input.spec.topic,
|
|
1284
|
-
summary:
|
|
1321
|
+
summary: input.spec.summary,
|
|
1285
1322
|
directMessageIds: filteredIds,
|
|
1286
1323
|
effectiveMessageIds: [...effectiveMessageIds],
|
|
1287
1324
|
directBlockIds: [...consumedBlockIds],
|
|
@@ -1344,41 +1381,30 @@ function validateCompressionRange(input, directMessageIds, consumedBlockCount) {
|
|
|
1344
1381
|
}
|
|
1345
1382
|
}
|
|
1346
1383
|
function filterProtectedToolMessages(directMessageIds, messages, config) {
|
|
1347
|
-
const
|
|
1348
|
-
const allProtectedCallIds = /* @__PURE__ */ new Set();
|
|
1384
|
+
const protectedCallIds = /* @__PURE__ */ new Set();
|
|
1349
1385
|
const removedIds = /* @__PURE__ */ new Set();
|
|
1350
|
-
const appendedProtectedText = [];
|
|
1351
1386
|
for (const msg of messages) {
|
|
1352
1387
|
if (isMessageProtected(msg, config) && msg.toolCallId) {
|
|
1353
|
-
|
|
1388
|
+
protectedCallIds.add(msg.toolCallId);
|
|
1354
1389
|
}
|
|
1355
1390
|
}
|
|
1356
1391
|
for (const id of directMessageIds) {
|
|
1357
1392
|
const msg = messages.find((m) => m.id === id);
|
|
1358
1393
|
if (!msg) continue;
|
|
1359
|
-
if (
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
if (msg.text) {
|
|
1363
|
-
appendedProtectedText.push(
|
|
1364
|
-
`
|
|
1365
|
-
### Protected: ${msg.toolName ?? "tool"}
|
|
1366
|
-
${msg.text}`
|
|
1367
|
-
);
|
|
1394
|
+
if (isMessageProtected(msg, config)) {
|
|
1395
|
+
removedIds.add(id);
|
|
1396
|
+
if (msg.toolCallId) protectedCallIds.add(msg.toolCallId);
|
|
1368
1397
|
}
|
|
1369
1398
|
}
|
|
1370
1399
|
for (const id of directMessageIds) {
|
|
1371
1400
|
if (removedIds.has(id)) continue;
|
|
1372
1401
|
const msg = messages.find((m) => m.id === id);
|
|
1373
1402
|
if (!msg) continue;
|
|
1374
|
-
if (msg.contentType === "tool-result" && msg.toolCallId &&
|
|
1403
|
+
if (msg.contentType === "tool-result" && msg.toolCallId && protectedCallIds.has(msg.toolCallId)) {
|
|
1375
1404
|
removedIds.add(id);
|
|
1376
1405
|
}
|
|
1377
1406
|
}
|
|
1378
|
-
return
|
|
1379
|
-
filteredIds: directMessageIds.filter((id) => !removedIds.has(id)),
|
|
1380
|
-
appendedProtectedText
|
|
1381
|
-
};
|
|
1407
|
+
return directMessageIds.filter((id) => !removedIds.has(id));
|
|
1382
1408
|
}
|
|
1383
1409
|
function resolveTargetTier(state, nestedBlockIds, isBlockBoundary) {
|
|
1384
1410
|
if (!isBlockBoundary) return 1;
|
|
@@ -1407,8 +1433,19 @@ function resolveAdaptiveGrowth(modelContextLimit, nudge) {
|
|
|
1407
1433
|
)
|
|
1408
1434
|
);
|
|
1409
1435
|
}
|
|
1436
|
+
function pendingByTier(state, recommendation, countTokens) {
|
|
1437
|
+
const out = {};
|
|
1438
|
+
const compressible = recommendation?.contextRanges.compressible ?? [];
|
|
1439
|
+
out[1] = { pending: compressible.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };
|
|
1440
|
+
const active = activeBlocks(state);
|
|
1441
|
+
const t1 = active.filter((b) => b.tier === 1);
|
|
1442
|
+
const t2 = active.filter((b) => b.tier === 2);
|
|
1443
|
+
out[2] = { pending: t1.reduce((s, b) => s + countTokens(b.summary), 0), targetBlocks: t1 };
|
|
1444
|
+
out[3] = { pending: t2.reduce((s, b) => s + countTokens(b.summary), 0), targetBlocks: t2 };
|
|
1445
|
+
return out;
|
|
1446
|
+
}
|
|
1410
1447
|
function decideNudge(input) {
|
|
1411
|
-
const { config, state, tokenCount, recommendation } = input;
|
|
1448
|
+
const { config, state, tokenCount, recommendation, countTokens } = input;
|
|
1412
1449
|
const limit = config.modelContextLimit;
|
|
1413
1450
|
const usage = limit > 0 ? tokenCount / limit : 0;
|
|
1414
1451
|
const nudgeGrowthTokens = resolveAdaptiveGrowth(limit, config.nudge);
|
|
@@ -1423,33 +1460,39 @@ function decideNudge(input) {
|
|
|
1423
1460
|
config.nudge.minGrowthRatio * nudgeGrowthTokens
|
|
1424
1461
|
);
|
|
1425
1462
|
const growthSinceReference = tokenCount - growthReference;
|
|
1426
|
-
const growthTriggered = growthSinceReference >= effectiveThreshold;
|
|
1427
|
-
let shouldContextNudge = emergencyOverride || growthTriggered;
|
|
1428
|
-
if (shouldContextNudge && !emergencyOverride) {
|
|
1429
|
-
shouldContextNudge = growthSinceReference >= growthFloor;
|
|
1430
|
-
}
|
|
1431
|
-
let tier = null;
|
|
1432
|
-
if (config.tiers.enabled) {
|
|
1433
|
-
const active = activeBlocks(state);
|
|
1434
|
-
const activeT1 = active.filter((b) => b.tier === 1);
|
|
1435
|
-
const activeT2 = active.filter((b) => b.tier === 2);
|
|
1436
|
-
if (activeT2.length >= config.tiers.tier3Trigger) tier = 3;
|
|
1437
|
-
else if (activeT1.length >= config.tiers.tier2Trigger) tier = 2;
|
|
1438
|
-
}
|
|
1439
1463
|
const rec = recommendation;
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1464
|
+
const tiers = pendingByTier(state, rec, countTokens);
|
|
1465
|
+
let injectedTier = null;
|
|
1466
|
+
let injectedReason = "";
|
|
1467
|
+
const growthReady = growthSinceReference >= growthFloor;
|
|
1468
|
+
if (!emergencyOverride && growthReady) {
|
|
1469
|
+
for (const tier of [1, 2, 3]) {
|
|
1470
|
+
if (!config.tiers.enabled && tier > 1) break;
|
|
1471
|
+
const info = tiers[tier];
|
|
1472
|
+
if (!info || info.pending < nudgeGrowthTokens) continue;
|
|
1473
|
+
const lastShown = state.nudge.lastShownByTier[tier] ?? 0;
|
|
1474
|
+
const cadenceMet = lastShown === 0 || tokenCount - lastShown >= growthFloor;
|
|
1475
|
+
if (!cadenceMet) continue;
|
|
1476
|
+
injectedTier = tier;
|
|
1477
|
+
injectedReason = tier === 1 ? `T1 compressible ${info.pending} >= ${nudgeGrowthTokens}, growth ${growthSinceReference}, usage ${Math.round(usage * 100)}%` : `T${tier} distill ready: ${info.targetBlocks.length} tier-${tier - 1} blocks (${info.pending} tokens) >= ${nudgeGrowthTokens}, usage ${Math.round(usage * 100)}%`;
|
|
1478
|
+
break;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
const shouldInject = emergencyOverride || injectedTier !== null;
|
|
1442
1482
|
let reason;
|
|
1443
|
-
if (
|
|
1444
|
-
reason = `growth ${growthSinceReference} < threshold ${effectiveThreshold}${hasPendingNudge ? " (halved)" : ""} (floor ${growthFloor})`;
|
|
1445
|
-
} else if (emergencyOverride) {
|
|
1483
|
+
if (emergencyOverride) {
|
|
1446
1484
|
reason = `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}%`;
|
|
1447
|
-
} else if (
|
|
1448
|
-
reason =
|
|
1449
|
-
} else if (nothingToCompress) {
|
|
1450
|
-
reason = `growth ${growthSinceReference} >= ${effectiveThreshold} but no specific ranges worth compressing`;
|
|
1485
|
+
} else if (injectedTier !== null) {
|
|
1486
|
+
reason = injectedReason;
|
|
1451
1487
|
} else {
|
|
1452
|
-
|
|
1488
|
+
const tiersList = [1, 2, 3];
|
|
1489
|
+
const eligible = tiersList.filter((t) => config.tiers.enabled || t === 1);
|
|
1490
|
+
const ready = eligible.filter((t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens).map((t) => `T${t} ${tiers[t].pending}`);
|
|
1491
|
+
const readyHint = ready.length > 0 ? `, ready: ${ready.join(", ")}` : "";
|
|
1492
|
+
const blocked = eligible.filter((t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens && (state.nudge.lastShownByTier[t] ?? 0) > 0 && tokenCount - (state.nudge.lastShownByTier[t] ?? 0) < growthFloor).map((t) => `T${t} (cadence)`);
|
|
1493
|
+
const blockedHint = blocked.length > 0 ? `, blocked: ${blocked.join(", ")}` : "";
|
|
1494
|
+
const maxPending = Math.max(0, ...Object.values(tiers).map((t) => t.pending));
|
|
1495
|
+
reason = `max compressible ${maxPending} < threshold ${nudgeGrowthTokens}${readyHint}${blockedHint}, growth ${growthSinceReference} (floor ${growthFloor})`;
|
|
1453
1496
|
}
|
|
1454
1497
|
const ctxBreakdown = computeContextBreakdown(input.messages, tokenCount, growthSinceReference);
|
|
1455
1498
|
return {
|
|
@@ -1457,8 +1500,9 @@ function decideNudge(input) {
|
|
|
1457
1500
|
reason,
|
|
1458
1501
|
compressibleRanges: rec?.recommendedRanges ?? [],
|
|
1459
1502
|
protectedRanges: rec?.contextRanges.protected ?? [],
|
|
1503
|
+
tierTargetBlocks: injectedTier ? tiers[injectedTier].targetBlocks : [],
|
|
1460
1504
|
contextUsage: usage,
|
|
1461
|
-
tier,
|
|
1505
|
+
tier: injectedTier,
|
|
1462
1506
|
breakdown: {
|
|
1463
1507
|
usage,
|
|
1464
1508
|
growth: growthSinceReference,
|
|
@@ -1467,7 +1511,10 @@ function decideNudge(input) {
|
|
|
1467
1511
|
nudgeGrowthTokens,
|
|
1468
1512
|
growthFloor,
|
|
1469
1513
|
hasPendingNudge: hasPendingNudge ? 1 : 0,
|
|
1470
|
-
emergencyOverride: emergencyOverride ? 1 : 0
|
|
1514
|
+
emergencyOverride: emergencyOverride ? 1 : 0,
|
|
1515
|
+
pendingT1: tiers[1].pending,
|
|
1516
|
+
pendingT2: tiers[2].pending,
|
|
1517
|
+
pendingT3: tiers[3].pending
|
|
1471
1518
|
},
|
|
1472
1519
|
contextBreakdown: ctxBreakdown
|
|
1473
1520
|
};
|
|
@@ -1733,6 +1780,18 @@ function mergeRanges(compressible, protected_) {
|
|
|
1733
1780
|
}
|
|
1734
1781
|
return merged;
|
|
1735
1782
|
}
|
|
1783
|
+
function formatTierTargetBlocks(blocks) {
|
|
1784
|
+
if (blocks.length === 0) {
|
|
1785
|
+
return "Target blocks: (none \u2014 no tier blocks found)";
|
|
1786
|
+
}
|
|
1787
|
+
const lines = blocks.map((b) => {
|
|
1788
|
+
const summaryTokens = Math.ceil((b.summary ?? "").length / 4);
|
|
1789
|
+
const topic = b.topic ? ` "${b.topic}"` : "";
|
|
1790
|
+
return ` ${b.blockId} ${b.effectiveMessageIds.length} msgs ${formatK(b.compressedTokens)}\u2192${formatK(summaryTokens)}${topic}`;
|
|
1791
|
+
});
|
|
1792
|
+
return `Target ${blocks[0].tier === 1 ? "tier-1" : "tier-2"} blocks to distill (${blocks.length}):
|
|
1793
|
+
${lines.join("\n")}`;
|
|
1794
|
+
}
|
|
1736
1795
|
function formatRanges(compressible, protected_) {
|
|
1737
1796
|
const merged = mergeRanges(compressible, protected_);
|
|
1738
1797
|
if (merged.length === 0) {
|
|
@@ -1756,6 +1815,10 @@ function renderNudgeText(decision) {
|
|
|
1756
1815
|
const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);
|
|
1757
1816
|
if (decision.tier !== null && decision.tier >= 2) {
|
|
1758
1817
|
const isT2 = decision.tier === 2;
|
|
1818
|
+
const targets = decision.tierTargetBlocks ?? [];
|
|
1819
|
+
const blockList = formatTierTargetBlocks(targets);
|
|
1820
|
+
const startId = targets[0]?.blockId ?? "b1";
|
|
1821
|
+
const endId = targets[targets.length - 1]?.blockId ?? "b5";
|
|
1759
1822
|
return {
|
|
1760
1823
|
voice: "gentle",
|
|
1761
1824
|
text: [
|
|
@@ -1764,8 +1827,9 @@ function renderNudgeText(decision) {
|
|
|
1764
1827
|
breakdownStr,
|
|
1765
1828
|
"",
|
|
1766
1829
|
`[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`,
|
|
1767
|
-
isT2 ?
|
|
1768
|
-
|
|
1830
|
+
isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries.`,
|
|
1831
|
+
blockList,
|
|
1832
|
+
`Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
|
|
1769
1833
|
"",
|
|
1770
1834
|
HOW_TO_COMPRESS_RULES,
|
|
1771
1835
|
"",
|
|
@@ -2105,11 +2169,31 @@ function renderUncompressedRanges(visible) {
|
|
|
2105
2169
|
lines.push("");
|
|
2106
2170
|
if (visible.length === 0) {
|
|
2107
2171
|
lines.push(" (no uncompressed messages)");
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2172
|
+
return lines.join("\n");
|
|
2173
|
+
}
|
|
2174
|
+
const refNum2 = (ref) => {
|
|
2175
|
+
const m = ref.match(/\d+/);
|
|
2176
|
+
return m ? parseInt(m[0], 10) : 0;
|
|
2177
|
+
};
|
|
2178
|
+
const merged = [];
|
|
2179
|
+
for (const m of visible) {
|
|
2180
|
+
const num = refNum2(m.ref);
|
|
2181
|
+
const last = merged[merged.length - 1];
|
|
2182
|
+
if (last && num === last.startNum + last.count) {
|
|
2183
|
+
last.endRef = m.ref;
|
|
2184
|
+
last.count += 1;
|
|
2185
|
+
last.tokens += m.tokens;
|
|
2186
|
+
} else {
|
|
2187
|
+
merged.push({ startRef: m.ref, endRef: m.ref, startNum: num, count: 1, tokens: m.tokens, tool: m.tool });
|
|
2111
2188
|
}
|
|
2112
2189
|
}
|
|
2190
|
+
for (const r of merged.slice(0, 30)) {
|
|
2191
|
+
const range = r.count === 1 ? r.startRef : `${r.startRef}\u2013${r.endRef}`;
|
|
2192
|
+
lines.push(` ${range} (${r.count} msgs, ${formatTokens2(r.tokens)}${r.count > 1 ? ` (${Math.round(r.tokens / r.count)}/msg)` : ""}) ${r.tool}`);
|
|
2193
|
+
}
|
|
2194
|
+
if (merged.length > 30) {
|
|
2195
|
+
lines.push(` ... and ${merged.length - 30} more ranges`);
|
|
2196
|
+
}
|
|
2113
2197
|
return lines.join("\n");
|
|
2114
2198
|
}
|
|
2115
2199
|
function renderMessageDrilldown(visible, toolFilter, sort, limit) {
|