opencode-acp 1.16.0 → 1.17.0-pr.385.132
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 +28 -0
- package/README.zh-CN.md +27 -0
- package/dist/index.js +565 -108
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/decompress-logic.d.ts.map +1 -1
- package/dist/lib/compress/decompress.d.ts.map +1 -1
- package/dist/lib/compress/hide-consumed.d.ts.map +1 -1
- package/dist/lib/compress/search.d.ts.map +1 -1
- package/dist/lib/compress/state.d.ts.map +1 -1
- package/dist/lib/compress/status.d.ts.map +1 -1
- package/dist/lib/compress/types.d.ts +6 -0
- package/dist/lib/compress/types.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +16 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/gc/merge.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/enforce-budget.d.ts +65 -0
- package/dist/lib/messages/enforce-budget.d.ts.map +1 -0
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +1 -0
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/messages/query.d.ts +16 -0
- package/dist/lib/messages/query.d.ts.map +1 -1
- package/dist/lib/messages/sync.d.ts.map +1 -1
- package/dist/lib/messages/truncate-tools.d.ts +1 -0
- package/dist/lib/messages/truncate-tools.d.ts.map +1 -1
- package/dist/lib/prompts/system.d.ts +1 -1
- package/dist/lib/prompts/system.d.ts.map +1 -1
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts +2 -0
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +29 -0
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/state/utils.d.ts +23 -0
- package/dist/lib/state/utils.d.ts.map +1 -1
- package/dist/lib/token-utils.d.ts +8 -0
- package/dist/lib/token-utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -890,6 +890,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
890
890
|
"compress.modelMaxLimits",
|
|
891
891
|
"compress.modelMinLimits",
|
|
892
892
|
"compress.providers",
|
|
893
|
+
"compress.contextLimitFallback",
|
|
893
894
|
"compress.nudgeFrequency",
|
|
894
895
|
"compress.minNudgeContextPercent",
|
|
895
896
|
"compress.nudgeGrowthTokens",
|
|
@@ -913,6 +914,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
913
914
|
"compress.reasoning",
|
|
914
915
|
"compress.reasoning.drop",
|
|
915
916
|
"compress.reasoning.threshold",
|
|
917
|
+
"compress.completionReserveTokens",
|
|
916
918
|
"gc",
|
|
917
919
|
"gc.algorithm",
|
|
918
920
|
"gc.promotionThreshold",
|
|
@@ -964,7 +966,11 @@ function validateConfigTypes(config) {
|
|
|
964
966
|
errors.push({ key: "storagePath", expected: "string", actual: typeof config.storagePath });
|
|
965
967
|
}
|
|
966
968
|
if (config.allowSubAgents !== void 0 && typeof config.allowSubAgents !== "boolean") {
|
|
967
|
-
errors.push({
|
|
969
|
+
errors.push({
|
|
970
|
+
key: "allowSubAgents",
|
|
971
|
+
expected: "boolean",
|
|
972
|
+
actual: typeof config.allowSubAgents
|
|
973
|
+
});
|
|
968
974
|
}
|
|
969
975
|
if (config.pruneNotification !== void 0) {
|
|
970
976
|
const validValues = ["off", "minimal", "detailed"];
|
|
@@ -1293,6 +1299,20 @@ function validateConfigTypes(config) {
|
|
|
1293
1299
|
}
|
|
1294
1300
|
}
|
|
1295
1301
|
}
|
|
1302
|
+
if (compress.completionReserveTokens !== void 0 && typeof compress.completionReserveTokens !== "number") {
|
|
1303
|
+
errors.push({
|
|
1304
|
+
key: "compress.completionReserveTokens",
|
|
1305
|
+
expected: "number",
|
|
1306
|
+
actual: typeof compress.completionReserveTokens
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
if (typeof compress.completionReserveTokens === "number" && compress.completionReserveTokens < 0) {
|
|
1310
|
+
errors.push({
|
|
1311
|
+
key: "compress.completionReserveTokens",
|
|
1312
|
+
expected: "non-negative number (>= 0)",
|
|
1313
|
+
actual: `${compress.completionReserveTokens}`
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1296
1316
|
if (typeof compress.iterationNudgeThreshold === "number" && compress.iterationNudgeThreshold < 1) {
|
|
1297
1317
|
errors.push({
|
|
1298
1318
|
key: "compress.iterationNudgeThreshold",
|
|
@@ -1411,12 +1431,20 @@ function validateConfigTypes(config) {
|
|
|
1411
1431
|
break;
|
|
1412
1432
|
case "nudgeForce":
|
|
1413
1433
|
if (value !== "strong" && value !== "soft") {
|
|
1414
|
-
errors.push({
|
|
1434
|
+
errors.push({
|
|
1435
|
+
key,
|
|
1436
|
+
expected: "'strong' | 'soft'",
|
|
1437
|
+
actual: JSON.stringify(value)
|
|
1438
|
+
});
|
|
1415
1439
|
}
|
|
1416
1440
|
break;
|
|
1417
1441
|
case "stringArray":
|
|
1418
1442
|
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
|
|
1419
|
-
errors.push({
|
|
1443
|
+
errors.push({
|
|
1444
|
+
key,
|
|
1445
|
+
expected: "string[]",
|
|
1446
|
+
actual: JSON.stringify(value)
|
|
1447
|
+
});
|
|
1420
1448
|
}
|
|
1421
1449
|
break;
|
|
1422
1450
|
case "reasoningConfig":
|
|
@@ -1451,7 +1479,11 @@ function validateConfigTypes(config) {
|
|
|
1451
1479
|
return;
|
|
1452
1480
|
}
|
|
1453
1481
|
if (typeof overrides !== "object" || overrides === null || Array.isArray(overrides)) {
|
|
1454
|
-
errors.push({
|
|
1482
|
+
errors.push({
|
|
1483
|
+
key: prefix,
|
|
1484
|
+
expected: "CompressModelOverrides",
|
|
1485
|
+
actual: typeof overrides
|
|
1486
|
+
});
|
|
1455
1487
|
return;
|
|
1456
1488
|
}
|
|
1457
1489
|
const model = overrides;
|
|
@@ -1484,7 +1516,11 @@ function validateConfigTypes(config) {
|
|
|
1484
1516
|
for (const [providerId, providerValue] of Object.entries(providers)) {
|
|
1485
1517
|
const prefix = `compress.providers.${providerId}`;
|
|
1486
1518
|
if (typeof providerValue !== "object" || providerValue === null || Array.isArray(providerValue)) {
|
|
1487
|
-
errors.push({
|
|
1519
|
+
errors.push({
|
|
1520
|
+
key: prefix,
|
|
1521
|
+
expected: "ProviderOverrides",
|
|
1522
|
+
actual: typeof providerValue
|
|
1523
|
+
});
|
|
1488
1524
|
continue;
|
|
1489
1525
|
}
|
|
1490
1526
|
const provider = providerValue;
|
|
@@ -1521,6 +1557,20 @@ function validateConfigTypes(config) {
|
|
|
1521
1557
|
}
|
|
1522
1558
|
};
|
|
1523
1559
|
validateProviderOverrides(compress.providers);
|
|
1560
|
+
if (compress.contextLimitFallback !== void 0 && typeof compress.contextLimitFallback !== "number") {
|
|
1561
|
+
errors.push({
|
|
1562
|
+
key: "compress.contextLimitFallback",
|
|
1563
|
+
expected: "number",
|
|
1564
|
+
actual: typeof compress.contextLimitFallback
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
if (typeof compress.contextLimitFallback === "number" && compress.contextLimitFallback < 0) {
|
|
1568
|
+
errors.push({
|
|
1569
|
+
key: "compress.contextLimitFallback",
|
|
1570
|
+
expected: "non-negative number (0 disables the fallback)",
|
|
1571
|
+
actual: `${compress.contextLimitFallback}`
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1524
1574
|
const validValues = ["ask", "allow", "deny"];
|
|
1525
1575
|
if (compress.permission !== void 0 && !validValues.includes(compress.permission)) {
|
|
1526
1576
|
errors.push({
|
|
@@ -1606,13 +1656,22 @@ function validateConfigTypes(config) {
|
|
|
1606
1656
|
});
|
|
1607
1657
|
} else {
|
|
1608
1658
|
if (gc.batchCleanup.lowThreshold !== void 0) {
|
|
1609
|
-
validateBatchThreshold(
|
|
1659
|
+
validateBatchThreshold(
|
|
1660
|
+
"gc.batchCleanup.lowThreshold",
|
|
1661
|
+
gc.batchCleanup.lowThreshold
|
|
1662
|
+
);
|
|
1610
1663
|
}
|
|
1611
1664
|
if (gc.batchCleanup.highThreshold !== void 0) {
|
|
1612
|
-
validateBatchThreshold(
|
|
1665
|
+
validateBatchThreshold(
|
|
1666
|
+
"gc.batchCleanup.highThreshold",
|
|
1667
|
+
gc.batchCleanup.highThreshold
|
|
1668
|
+
);
|
|
1613
1669
|
}
|
|
1614
1670
|
if (gc.batchCleanup.forceThreshold !== void 0) {
|
|
1615
|
-
validateBatchThreshold(
|
|
1671
|
+
validateBatchThreshold(
|
|
1672
|
+
"gc.batchCleanup.forceThreshold",
|
|
1673
|
+
gc.batchCleanup.forceThreshold
|
|
1674
|
+
);
|
|
1616
1675
|
}
|
|
1617
1676
|
}
|
|
1618
1677
|
}
|
|
@@ -1702,6 +1761,7 @@ var defaultConfig = {
|
|
|
1702
1761
|
summaryBuffer: true,
|
|
1703
1762
|
maxContextLimit: "80%",
|
|
1704
1763
|
minContextLimit: "80%",
|
|
1764
|
+
contextLimitFallback: 128e3,
|
|
1705
1765
|
nudgeFrequency: 5,
|
|
1706
1766
|
minNudgeContextPercent: 5,
|
|
1707
1767
|
iterationNudgeThreshold: 15,
|
|
@@ -1872,6 +1932,7 @@ function mergeCompress(base, override) {
|
|
|
1872
1932
|
modelMaxLimits: override.modelMaxLimits ?? base.modelMaxLimits,
|
|
1873
1933
|
modelMinLimits: override.modelMinLimits ?? base.modelMinLimits,
|
|
1874
1934
|
providers: mergeProviderOverrides(base.providers, override.providers),
|
|
1935
|
+
contextLimitFallback: override.contextLimitFallback ?? base.contextLimitFallback,
|
|
1875
1936
|
nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
|
|
1876
1937
|
minNudgeContextPercent: override.minNudgeContextPercent ?? base.minNudgeContextPercent,
|
|
1877
1938
|
nudgeGrowthTokens: override.nudgeGrowthTokens,
|
|
@@ -1895,7 +1956,8 @@ function mergeCompress(base, override) {
|
|
|
1895
1956
|
reasoning: {
|
|
1896
1957
|
drop: override.reasoning?.drop ?? base.reasoning?.drop ?? DEFAULT_COMPRESS_REASONING.drop,
|
|
1897
1958
|
threshold: override.reasoning?.threshold ?? base.reasoning?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
1898
|
-
}
|
|
1959
|
+
},
|
|
1960
|
+
completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens
|
|
1899
1961
|
};
|
|
1900
1962
|
}
|
|
1901
1963
|
function mergeCommands(base, override) {
|
|
@@ -1933,10 +1995,9 @@ function deepCloneConfig(config) {
|
|
|
1933
1995
|
...provider,
|
|
1934
1996
|
...provider.models ? {
|
|
1935
1997
|
models: Object.fromEntries(
|
|
1936
|
-
Object.entries(provider.models).map(
|
|
1937
|
-
modelId,
|
|
1938
|
-
|
|
1939
|
-
])
|
|
1998
|
+
Object.entries(provider.models).map(
|
|
1999
|
+
([modelId, model]) => [modelId, { ...model }]
|
|
2000
|
+
)
|
|
1940
2001
|
)
|
|
1941
2002
|
} : {}
|
|
1942
2003
|
}
|
|
@@ -2010,8 +2071,14 @@ function mergeLayer(config, data) {
|
|
|
2010
2071
|
],
|
|
2011
2072
|
compress: mergeCompress(config.compress, data.compress),
|
|
2012
2073
|
gc: mergeGC(config.gc, data.gc),
|
|
2013
|
-
qualityGate: mergeQualityGate(
|
|
2014
|
-
|
|
2074
|
+
qualityGate: mergeQualityGate(
|
|
2075
|
+
config.qualityGate,
|
|
2076
|
+
data.qualityGate
|
|
2077
|
+
),
|
|
2078
|
+
messageFilters: mergeMessageFilters(
|
|
2079
|
+
config.messageFilters,
|
|
2080
|
+
data.messageFilters
|
|
2081
|
+
)
|
|
2015
2082
|
};
|
|
2016
2083
|
}
|
|
2017
2084
|
function scheduleParseWarning(ctx, title, message) {
|
|
@@ -2159,6 +2226,56 @@ var messageHasCompressAttempt = (message) => {
|
|
|
2159
2226
|
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
2160
2227
|
return parts.some((part) => part.type === "tool" && part.tool === "compress");
|
|
2161
2228
|
};
|
|
2229
|
+
var isCaptureOnlyCompress = (message) => {
|
|
2230
|
+
if (!isMessageWithInfo(message)) {
|
|
2231
|
+
return false;
|
|
2232
|
+
}
|
|
2233
|
+
if (message.info.role !== "assistant") {
|
|
2234
|
+
return false;
|
|
2235
|
+
}
|
|
2236
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
2237
|
+
let sawBoundary = false;
|
|
2238
|
+
for (const part of parts) {
|
|
2239
|
+
if (!(part.type === "tool" && part.tool === "compress")) {
|
|
2240
|
+
continue;
|
|
2241
|
+
}
|
|
2242
|
+
for (const startId of extractCompressBoundaryIds(part.state?.input)) {
|
|
2243
|
+
sawBoundary = true;
|
|
2244
|
+
if (/^b\d+$/i.test(startId)) {
|
|
2245
|
+
return false;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return sawBoundary;
|
|
2250
|
+
};
|
|
2251
|
+
function extractCompressBoundaryIds(rawInput) {
|
|
2252
|
+
let content = [];
|
|
2253
|
+
if (typeof rawInput === "string") {
|
|
2254
|
+
try {
|
|
2255
|
+
const parsed = JSON.parse(rawInput);
|
|
2256
|
+
const c = parsed?.content;
|
|
2257
|
+
content = Array.isArray(c) ? c : [];
|
|
2258
|
+
} catch {
|
|
2259
|
+
return [];
|
|
2260
|
+
}
|
|
2261
|
+
} else if (rawInput && typeof rawInput === "object") {
|
|
2262
|
+
const c = rawInput.content;
|
|
2263
|
+
content = Array.isArray(c) ? c : [];
|
|
2264
|
+
}
|
|
2265
|
+
const ids = [];
|
|
2266
|
+
for (const entry of content) {
|
|
2267
|
+
if (!entry || typeof entry !== "object") {
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
const { startId, endId } = entry;
|
|
2271
|
+
for (const sid of [startId, endId]) {
|
|
2272
|
+
if (typeof sid === "string" && sid.trim() !== "") {
|
|
2273
|
+
ids.push(sid.trim());
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return ids;
|
|
2278
|
+
}
|
|
2162
2279
|
var isIgnoredUserMessage = (message) => {
|
|
2163
2280
|
if (!isMessageWithInfo(message)) {
|
|
2164
2281
|
return false;
|
|
@@ -2325,6 +2442,9 @@ function countMessageCharacters(msg) {
|
|
|
2325
2442
|
}
|
|
2326
2443
|
return total;
|
|
2327
2444
|
}
|
|
2445
|
+
function estimateAllMessageTokensFast(msg) {
|
|
2446
|
+
return Math.round(countMessageCharacters(msg) / 4);
|
|
2447
|
+
}
|
|
2328
2448
|
|
|
2329
2449
|
// lib/prompts/extensions/tool.ts
|
|
2330
2450
|
var RANGE_FORMAT_EXTENSION = `
|
|
@@ -2374,6 +2494,9 @@ var isMessageCompacted = (state, msg) => {
|
|
|
2374
2494
|
}
|
|
2375
2495
|
return false;
|
|
2376
2496
|
};
|
|
2497
|
+
function bumpPruneStructureVersion(messagesState) {
|
|
2498
|
+
messagesState.structureVersion = (messagesState.structureVersion ?? 0) + 1;
|
|
2499
|
+
}
|
|
2377
2500
|
function serializePruneMessagesState(messagesState) {
|
|
2378
2501
|
return {
|
|
2379
2502
|
byMessageId: Object.fromEntries(messagesState.byMessageId),
|
|
@@ -2635,6 +2758,16 @@ function resetOnCompaction(state) {
|
|
|
2635
2758
|
nextRef: 1
|
|
2636
2759
|
};
|
|
2637
2760
|
}
|
|
2761
|
+
function resolveEffectiveContextLimit(state, config) {
|
|
2762
|
+
if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
|
|
2763
|
+
return { limit: state.modelContextLimit, source: "model" };
|
|
2764
|
+
}
|
|
2765
|
+
const fallback = config.compress.contextLimitFallback;
|
|
2766
|
+
if (typeof fallback === "number" && fallback > 0) {
|
|
2767
|
+
return { limit: fallback, source: "fallback" };
|
|
2768
|
+
}
|
|
2769
|
+
return void 0;
|
|
2770
|
+
}
|
|
2638
2771
|
|
|
2639
2772
|
// lib/state/persistence.ts
|
|
2640
2773
|
function getDefaultStorageDir() {
|
|
@@ -2681,9 +2814,13 @@ async function writePersistedSessionState(sessionId, state, logger, storageDir)
|
|
|
2681
2814
|
totalTokensSaved: state.stats.totalPruneTokens
|
|
2682
2815
|
});
|
|
2683
2816
|
}
|
|
2684
|
-
|
|
2817
|
+
var saveQueues = /* @__PURE__ */ new Map();
|
|
2818
|
+
function saveQueueKey(sessionId, storageDir) {
|
|
2819
|
+
return `${sessionId}\0${storageDir ?? ""}`;
|
|
2820
|
+
}
|
|
2821
|
+
function saveSessionState(sessionState, logger, sessionName) {
|
|
2685
2822
|
if (!sessionState.sessionId) {
|
|
2686
|
-
return;
|
|
2823
|
+
return Promise.resolve();
|
|
2687
2824
|
}
|
|
2688
2825
|
const state = {
|
|
2689
2826
|
sessionName,
|
|
@@ -2714,7 +2851,53 @@ async function saveSessionState(sessionState, logger, sessionName) {
|
|
|
2714
2851
|
modelProviderID: sessionState.modelProviderID,
|
|
2715
2852
|
modelID: sessionState.modelID
|
|
2716
2853
|
};
|
|
2717
|
-
|
|
2854
|
+
const key = saveQueueKey(sessionState.sessionId, sessionState.storageDir);
|
|
2855
|
+
let queue = saveQueues.get(key);
|
|
2856
|
+
if (!queue) {
|
|
2857
|
+
queue = { pending: [], waiters: [], draining: false };
|
|
2858
|
+
saveQueues.set(key, queue);
|
|
2859
|
+
}
|
|
2860
|
+
const promise = new Promise((resolve2, reject) => {
|
|
2861
|
+
queue.waiters.push({ resolve: resolve2, reject });
|
|
2862
|
+
});
|
|
2863
|
+
queue.pending.push({
|
|
2864
|
+
sessionId: sessionState.sessionId,
|
|
2865
|
+
state,
|
|
2866
|
+
storageDir: sessionState.storageDir,
|
|
2867
|
+
logger
|
|
2868
|
+
});
|
|
2869
|
+
if (!queue.draining) {
|
|
2870
|
+
queue.draining = true;
|
|
2871
|
+
setImmediate(() => drainSaveQueue(key));
|
|
2872
|
+
}
|
|
2873
|
+
return promise;
|
|
2874
|
+
}
|
|
2875
|
+
function drainSaveQueue(key) {
|
|
2876
|
+
const queue = saveQueues.get(key);
|
|
2877
|
+
if (!queue || queue.pending.length === 0) {
|
|
2878
|
+
if (queue) {
|
|
2879
|
+
queue.draining = false;
|
|
2880
|
+
if (queue.waiters.length === 0) saveQueues.delete(key);
|
|
2881
|
+
}
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
const batch = queue.pending.splice(0, queue.pending.length);
|
|
2885
|
+
const waiters = queue.waiters.splice(0, batch.length);
|
|
2886
|
+
const latest = batch[batch.length - 1];
|
|
2887
|
+
writePersistedSessionState(latest.sessionId, latest.state, latest.logger, latest.storageDir).then(() => {
|
|
2888
|
+
for (const waiter of waiters) waiter.resolve();
|
|
2889
|
+
}).catch((err) => {
|
|
2890
|
+
for (const waiter of waiters) waiter.reject(err);
|
|
2891
|
+
}).finally(() => {
|
|
2892
|
+
const q = saveQueues.get(key);
|
|
2893
|
+
if (!q) return;
|
|
2894
|
+
if (q.pending.length > 0) {
|
|
2895
|
+
drainSaveQueue(key);
|
|
2896
|
+
} else {
|
|
2897
|
+
q.draining = false;
|
|
2898
|
+
if (q.waiters.length === 0) saveQueues.delete(key);
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2718
2901
|
}
|
|
2719
2902
|
async function loadSessionState(sessionId, logger, storageDir) {
|
|
2720
2903
|
try {
|
|
@@ -3208,6 +3391,7 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3208
3391
|
state.stats.pruneTokenCounter += compressedTokens;
|
|
3209
3392
|
state.stats.totalPruneTokens += state.stats.pruneTokenCounter;
|
|
3210
3393
|
state.stats.pruneTokenCounter = 0;
|
|
3394
|
+
bumpPruneStructureVersion(messagesState);
|
|
3211
3395
|
return {
|
|
3212
3396
|
compressedTokens,
|
|
3213
3397
|
messageIds: selection.messageIds,
|
|
@@ -3321,15 +3505,17 @@ function buildSearchContext(state, rawMessages) {
|
|
|
3321
3505
|
}
|
|
3322
3506
|
summaryByBlockId.set(blockId, block);
|
|
3323
3507
|
}
|
|
3324
|
-
|
|
3508
|
+
const context = {
|
|
3325
3509
|
rawMessages,
|
|
3326
3510
|
rawMessagesById,
|
|
3327
3511
|
rawIndexById,
|
|
3328
3512
|
summaryByBlockId
|
|
3329
3513
|
};
|
|
3514
|
+
context.boundaryLookup = buildBoundaryLookup(context, state);
|
|
3515
|
+
return context;
|
|
3330
3516
|
}
|
|
3331
3517
|
function resolveBoundaryIds(context, state, startId, endId, logger) {
|
|
3332
|
-
const lookup = buildBoundaryLookup(context, state);
|
|
3518
|
+
const lookup = context.boundaryLookup ?? (context.boundaryLookup = buildBoundaryLookup(context, state));
|
|
3333
3519
|
const issues = [];
|
|
3334
3520
|
const parsedStartId = parseBoundaryId(startId);
|
|
3335
3521
|
const parsedEndId = parseBoundaryId(endId);
|
|
@@ -3529,7 +3715,7 @@ function resolveSelection(context, startReference, endReference) {
|
|
|
3529
3715
|
messageIds.push(messageId);
|
|
3530
3716
|
}
|
|
3531
3717
|
if (!messageTokenById.has(messageId)) {
|
|
3532
|
-
messageTokenById.set(messageId,
|
|
3718
|
+
messageTokenById.set(messageId, estimateAllMessageTokensFast(rawMessage));
|
|
3533
3719
|
}
|
|
3534
3720
|
const parts = Array.isArray(rawMessage.parts) ? rawMessage.parts : [];
|
|
3535
3721
|
for (const part of parts) {
|
|
@@ -4506,6 +4692,24 @@ var SessionStateRegistry = class {
|
|
|
4506
4692
|
hydrateModelLimitsFromClient(client) {
|
|
4507
4693
|
return this.catalog.hydrateFromClient(client);
|
|
4508
4694
|
}
|
|
4695
|
+
// [FIX #346] The init-time seed (above) is fire-and-forget and races
|
|
4696
|
+
// server readiness: in headless spawn+resume mode the provider-config
|
|
4697
|
+
// call can fail before the server is up, leaving the catalog empty for
|
|
4698
|
+
// the process's lifetime. During a request the server is guaranteed up
|
|
4699
|
+
// (we are inside its pipeline), so on a catalog miss we retry hydration
|
|
4700
|
+
// once per process before giving up (the fallback limit then applies).
|
|
4701
|
+
// The in-flight promise (not a boolean) lets concurrent callers await the
|
|
4702
|
+
// same hydration instead of skipping it.
|
|
4703
|
+
lazyHydration;
|
|
4704
|
+
async hydrateAndResolve(client, providerId, modelId) {
|
|
4705
|
+
const existing = this.catalog.resolve(providerId, modelId);
|
|
4706
|
+
if (existing !== void 0) {
|
|
4707
|
+
return existing;
|
|
4708
|
+
}
|
|
4709
|
+
this.lazyHydration ??= this.catalog.hydrateFromClient(client);
|
|
4710
|
+
await this.lazyHydration;
|
|
4711
|
+
return this.catalog.resolve(providerId, modelId);
|
|
4712
|
+
}
|
|
4509
4713
|
get(sessionId) {
|
|
4510
4714
|
return this.states.get(sessionId);
|
|
4511
4715
|
}
|
|
@@ -4599,7 +4803,8 @@ function createSessionState() {
|
|
|
4599
4803
|
modelID: void 0,
|
|
4600
4804
|
systemPromptTokens: void 0,
|
|
4601
4805
|
storageDir: void 0,
|
|
4602
|
-
qualityGateRetryPending: false
|
|
4806
|
+
qualityGateRetryPending: false,
|
|
4807
|
+
noContextLimitWarned: false
|
|
4603
4808
|
};
|
|
4604
4809
|
}
|
|
4605
4810
|
function resetSessionState(state) {
|
|
@@ -4642,6 +4847,7 @@ function resetSessionState(state) {
|
|
|
4642
4847
|
state.systemPromptTokens = void 0;
|
|
4643
4848
|
state.storageDir = void 0;
|
|
4644
4849
|
state.qualityGateRetryPending = false;
|
|
4850
|
+
state.noContextLimitWarned = false;
|
|
4645
4851
|
}
|
|
4646
4852
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
|
|
4647
4853
|
if (state.sessionId === sessionId) {
|
|
@@ -6657,6 +6863,7 @@ function getModelInfo(messages) {
|
|
|
6657
6863
|
};
|
|
6658
6864
|
}
|
|
6659
6865
|
function resolveContextTokenLimit(config, state, providerId, modelId, threshold) {
|
|
6866
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6660
6867
|
const parseLimitValue = (limit) => {
|
|
6661
6868
|
if (limit === void 0) {
|
|
6662
6869
|
return void 0;
|
|
@@ -6664,7 +6871,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6664
6871
|
if (typeof limit === "number") {
|
|
6665
6872
|
return limit;
|
|
6666
6873
|
}
|
|
6667
|
-
if (!limit.endsWith("%") ||
|
|
6874
|
+
if (!limit.endsWith("%") || effectiveLimit === void 0) {
|
|
6668
6875
|
return void 0;
|
|
6669
6876
|
}
|
|
6670
6877
|
const parsedPercent = parseFloat(limit.slice(0, -1));
|
|
@@ -6673,7 +6880,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6673
6880
|
}
|
|
6674
6881
|
const roundedPercent = Math.round(parsedPercent);
|
|
6675
6882
|
const clampedPercent = Math.max(0, Math.min(100, roundedPercent));
|
|
6676
|
-
return Math.round(clampedPercent / 100 *
|
|
6883
|
+
return Math.round(clampedPercent / 100 * effectiveLimit.limit);
|
|
6677
6884
|
};
|
|
6678
6885
|
if (threshold === "max") {
|
|
6679
6886
|
const nestedLimit = resolveCompressOverrides(config, providerId, modelId).maxContextLimit;
|
|
@@ -6724,11 +6931,12 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
|
6724
6931
|
if (!overMaxLimit) break;
|
|
6725
6932
|
}
|
|
6726
6933
|
}
|
|
6934
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6727
6935
|
return {
|
|
6728
6936
|
overMaxLimit,
|
|
6729
6937
|
overMinLimit,
|
|
6730
6938
|
currentTokens,
|
|
6731
|
-
modelContextLimit:
|
|
6939
|
+
modelContextLimit: effectiveLimit?.limit
|
|
6732
6940
|
};
|
|
6733
6941
|
}
|
|
6734
6942
|
ensureBuiltinTriggerPolicyRegistered();
|
|
@@ -6931,6 +7139,7 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
6931
7139
|
let summaryTokens = 0;
|
|
6932
7140
|
let messageTokens = 0;
|
|
6933
7141
|
let protectedTokens = 0;
|
|
7142
|
+
let reasoningTokens = 0;
|
|
6934
7143
|
const perMessage = [];
|
|
6935
7144
|
const perTool = [];
|
|
6936
7145
|
const perCode = [];
|
|
@@ -6984,6 +7193,10 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
6984
7193
|
summaryTokens += summaryPartTokens;
|
|
6985
7194
|
toolTypeMap.set(toolName, (toolTypeMap.get(toolName) || 0) + toolPartTokens);
|
|
6986
7195
|
if (!msgToolName) msgToolName = toolName;
|
|
7196
|
+
} else if (part.type === "reasoning" && typeof part.text === "string") {
|
|
7197
|
+
const tokens = Math.round(part.text.length / 4);
|
|
7198
|
+
msgTotal += tokens;
|
|
7199
|
+
reasoningTokens += tokens;
|
|
6987
7200
|
}
|
|
6988
7201
|
}
|
|
6989
7202
|
if (isProtected && !isSummary) {
|
|
@@ -7011,7 +7224,8 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
7011
7224
|
textTokens: Math.max(0, messageTokens - codeTokens),
|
|
7012
7225
|
systemTokens,
|
|
7013
7226
|
protectedTokens,
|
|
7014
|
-
|
|
7227
|
+
reasoningTokens,
|
|
7228
|
+
total: systemTokens + toolTokens + summaryTokens + messageTokens + reasoningTokens,
|
|
7015
7229
|
largestRanges: perMessage.slice(0, 15),
|
|
7016
7230
|
largestToolRanges: perTool.slice(0, 15),
|
|
7017
7231
|
largestCodeRanges: perCode.slice(0, 5),
|
|
@@ -7034,13 +7248,10 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7034
7248
|
if (!ref) continue;
|
|
7035
7249
|
const rn = parseInt(ref.slice(1), 10);
|
|
7036
7250
|
if ((protectedTools.length > 0 || protectedFilePatterns.length > 0) && messageContainsProtectedTool(msg, protectedTools, protectedFilePatterns)) {
|
|
7037
|
-
|
|
7251
|
+
const tokens2 = Math.round(countMessageCharacters(msg) / 4);
|
|
7038
7252
|
const tools = /* @__PURE__ */ new Set();
|
|
7039
7253
|
for (const part of msg.parts || []) {
|
|
7040
|
-
if (part.type
|
|
7041
|
-
tokens2 += Math.round(part.text.length / 4);
|
|
7042
|
-
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
7043
|
-
tokens2 += Math.round(JSON.stringify(part).length / 4);
|
|
7254
|
+
if (part.type !== "text" && part.type !== "reasoning") {
|
|
7044
7255
|
const toolName = part?.tool;
|
|
7045
7256
|
const callID = part?.callID;
|
|
7046
7257
|
if (toolName && callID) {
|
|
@@ -7061,15 +7272,13 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7061
7272
|
protectedMsgInfo.push({ ref, refNum: rn, tokens: tokens2, tools: [...tools] });
|
|
7062
7273
|
continue;
|
|
7063
7274
|
}
|
|
7064
|
-
|
|
7275
|
+
const tokens = Math.round(countMessageCharacters(msg) / 4);
|
|
7065
7276
|
let isTool = false;
|
|
7066
7277
|
let hasMeaningfulPart = false;
|
|
7067
7278
|
for (const part of msg.parts || []) {
|
|
7068
7279
|
if (part.type === "text" && typeof part.text === "string") {
|
|
7069
|
-
tokens += Math.round(part.text.length / 4);
|
|
7070
7280
|
if (part.text.trim().length > 0) hasMeaningfulPart = true;
|
|
7071
7281
|
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
7072
|
-
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
7073
7282
|
isTool = true;
|
|
7074
7283
|
hasMeaningfulPart = true;
|
|
7075
7284
|
}
|
|
@@ -7713,6 +7922,23 @@ var syncCompressionBlocks = (state, logger, messages) => {
|
|
|
7713
7922
|
return;
|
|
7714
7923
|
}
|
|
7715
7924
|
const messageIds = new Set(messages.map((msg) => msg.info.id));
|
|
7925
|
+
const structureVersion = messagesState.structureVersion ?? 0;
|
|
7926
|
+
if (messagesState.lastSyncedStructureVersion === structureVersion) {
|
|
7927
|
+
const activeBlocks = [...messagesState.activeBlockIds].map((id) => messagesState.blocksById.get(id)).filter((b) => b !== void 0).sort(sortBlocksByCreation);
|
|
7928
|
+
const nextAnchorMap = /* @__PURE__ */ new Map();
|
|
7929
|
+
for (const block of activeBlocks) {
|
|
7930
|
+
if (!messageIds.has(block.anchorMessageId)) continue;
|
|
7931
|
+
nextAnchorMap.set(block.anchorMessageId, block.blockId);
|
|
7932
|
+
}
|
|
7933
|
+
const previous = messagesState.activeByAnchorMessageId;
|
|
7934
|
+
if (previous.size !== nextAnchorMap.size || ![...nextAnchorMap.entries()].every(([anchor, id]) => previous.get(anchor) === id)) {
|
|
7935
|
+
previous.clear();
|
|
7936
|
+
for (const [anchor, id] of nextAnchorMap) {
|
|
7937
|
+
previous.set(anchor, id);
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7940
|
+
return;
|
|
7941
|
+
}
|
|
7716
7942
|
const previousActiveBlockIds = new Set(
|
|
7717
7943
|
Array.from(messagesState.blocksById.values()).filter((block) => block.active).map((block) => block.blockId)
|
|
7718
7944
|
);
|
|
@@ -7779,6 +8005,7 @@ var syncCompressionBlocks = (state, logger, messages) => {
|
|
|
7779
8005
|
reactivatedCount
|
|
7780
8006
|
});
|
|
7781
8007
|
}
|
|
8008
|
+
messagesState.lastSyncedStructureVersion = structureVersion;
|
|
7782
8009
|
};
|
|
7783
8010
|
|
|
7784
8011
|
// lib/host-permissions.ts
|
|
@@ -7893,8 +8120,11 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
7893
8120
|
state.nudges.iterationNudgeAnchors.clear();
|
|
7894
8121
|
state.nudges.lastNudgeShownTokens = void 0;
|
|
7895
8122
|
state.nudges.lastToolOutputNudgeTokens = void 0;
|
|
7896
|
-
|
|
7897
|
-
|
|
8123
|
+
const captureOnly = isCaptureOnlyCompress(lastCompressMsg);
|
|
8124
|
+
if (!captureOnly) {
|
|
8125
|
+
state.nudges.lastTier2NudgeTokens = currentTokens;
|
|
8126
|
+
state.nudges.lastTier3NudgeTokens = currentTokens;
|
|
8127
|
+
}
|
|
7898
8128
|
const currentTurnHasSuccessfulCompress = messages.slice(currentTurnStart).some((m) => m.info.role === "assistant" && messageHasCompress(m));
|
|
7899
8129
|
if (currentTurnHasSuccessfulCompress && wasNudgeTriggered && !state.nudges.compressBaselineSet) {
|
|
7900
8130
|
const baseline = state.nudges.lastPerMessageNudgeTokens;
|
|
@@ -8029,20 +8259,23 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8029
8259
|
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
8030
8260
|
baselineReEstablished = true;
|
|
8031
8261
|
}
|
|
8032
|
-
const
|
|
8262
|
+
const tierUsageEarly = getTierTokenUsage(state);
|
|
8263
|
+
const tierTriggerPossible = !!suffixMessage && (tierUsageEarly.tier1Tokens >= nudgeGrowthTokens || tierUsageEarly.tier2Tokens >= nudgeGrowthTokens);
|
|
8264
|
+
const needsNudgeAnalysis = nudgeAllowed || emergencyOverride || tierTriggerPossible;
|
|
8265
|
+
const composition = needsNudgeAnalysis ? estimateContextComposition(
|
|
8033
8266
|
messages,
|
|
8034
8267
|
state,
|
|
8035
8268
|
config.compress.protectedTools,
|
|
8036
8269
|
config.protectedFilePatterns
|
|
8037
|
-
);
|
|
8038
|
-
const protectedRefs = computeProtectedRefs(messages, state, config.compress);
|
|
8039
|
-
const contextRanges = buildCompressibleRanges(
|
|
8270
|
+
) : null;
|
|
8271
|
+
const protectedRefs = needsNudgeAnalysis ? computeProtectedRefs(messages, state, config.compress) : /* @__PURE__ */ new Set();
|
|
8272
|
+
const contextRanges = needsNudgeAnalysis ? buildCompressibleRanges(
|
|
8040
8273
|
messages,
|
|
8041
8274
|
state,
|
|
8042
8275
|
config.compress.protectedTools,
|
|
8043
8276
|
config.protectedFilePatterns,
|
|
8044
8277
|
protectedRefs
|
|
8045
|
-
);
|
|
8278
|
+
) : { compressible: [], protected: [] };
|
|
8046
8279
|
const unprotectedCompressible = excludeProtectedRanges(contextRanges.compressible, protectedRefs);
|
|
8047
8280
|
const recommendedRanges = filterRecommendedRanges(
|
|
8048
8281
|
unprotectedCompressible,
|
|
@@ -8067,7 +8300,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8067
8300
|
baselineReEstablished = true;
|
|
8068
8301
|
}
|
|
8069
8302
|
if (suffixMessage && !shouldInject) {
|
|
8070
|
-
const tierUsage =
|
|
8303
|
+
const tierUsage = tierUsageEarly;
|
|
8071
8304
|
const tierChecks = [
|
|
8072
8305
|
{ triggerTier: 2, targetTier: 1, tokens: tierUsage.tier1Tokens, lastNudge: state.nudges.lastTier2NudgeTokens },
|
|
8073
8306
|
{ triggerTier: 3, targetTier: 2, tokens: tierUsage.tier2Tokens, lastNudge: state.nudges.lastTier3NudgeTokens }
|
|
@@ -8194,7 +8427,7 @@ ${rules}`;
|
|
|
8194
8427
|
}
|
|
8195
8428
|
let tipsText = null;
|
|
8196
8429
|
if (shouldInject) {
|
|
8197
|
-
if (suffixMessage && composition.total > 0) {
|
|
8430
|
+
if (suffixMessage && composition !== null && composition.total > 0) {
|
|
8198
8431
|
const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
8199
8432
|
const pct2 = (n) => n > 0 ? Math.max(1, Math.round(n / composition.total * 100)) : 0;
|
|
8200
8433
|
const growth = currentTokens !== void 0 && (state.nudges.lastNudgeShownTokens ?? state.nudges.lastPerMessageNudgeTokens) !== void 0 ? currentTokens - (state.nudges.lastNudgeShownTokens ?? state.nudges.lastPerMessageNudgeTokens) : 0;
|
|
@@ -8206,7 +8439,7 @@ This is an efficiency nudge to compress early and keep context lean \u2014 not a
|
|
|
8206
8439
|
${COMPRESS_PHILOSOPHY}` : "";
|
|
8207
8440
|
const sysPart = composition.systemTokens > 0 ? `${fmt(composition.systemTokens)} system (${pct2(composition.systemTokens)}%) | ` : "";
|
|
8208
8441
|
let breakdown = `${efficiencyNote}
|
|
8209
|
-
Breakdown: ${sysPart}${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}`;
|
|
8442
|
+
Breakdown: ${sysPart}${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)}%) | ${fmt(composition.reasoningTokens)} reasoning (${pct2(composition.reasoningTokens)}%)${growthStr}`;
|
|
8210
8443
|
const compressibleTokens = composition.total - composition.systemTokens - composition.protectedTokens - composition.summaryTokens;
|
|
8211
8444
|
if (composition.protectedTokens > 0) {
|
|
8212
8445
|
breakdown += `
|
|
@@ -8569,6 +8802,9 @@ function deactivateCompressionTarget(messagesState, target, options) {
|
|
|
8569
8802
|
}
|
|
8570
8803
|
}
|
|
8571
8804
|
}
|
|
8805
|
+
if (target.blocks.length > 0) {
|
|
8806
|
+
bumpPruneStructureVersion(messagesState);
|
|
8807
|
+
}
|
|
8572
8808
|
}
|
|
8573
8809
|
function computeRestoredMessages(messagesState, activeMessagesBefore) {
|
|
8574
8810
|
let restoredMessageCount = 0;
|
|
@@ -8807,8 +9043,9 @@ function createDecompressTool(factoryCtx) {
|
|
|
8807
9043
|
async execute(args, toolCtx) {
|
|
8808
9044
|
const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
|
|
8809
9045
|
const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
|
|
8810
|
-
const
|
|
8811
|
-
|
|
9046
|
+
const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
9047
|
+
const contextUsageBefore = effectiveLimitBefore ? Math.round(
|
|
9048
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
|
|
8812
9049
|
) : void 0;
|
|
8813
9050
|
const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
|
|
8814
9051
|
if (!resolved.ok) {
|
|
@@ -8872,8 +9109,9 @@ function createDecompressTool(factoryCtx) {
|
|
|
8872
9109
|
0,
|
|
8873
9110
|
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
8874
9111
|
);
|
|
8875
|
-
const
|
|
8876
|
-
|
|
9112
|
+
const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
9113
|
+
const contextUsageAfter = effectiveLimitAfter ? Math.round(
|
|
9114
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
8877
9115
|
) : void 0;
|
|
8878
9116
|
await finalizeDecompressSession(ctx);
|
|
8879
9117
|
const restoredContentPreview = buildRestoredContentPreview(
|
|
@@ -8948,21 +9186,29 @@ function rewriteCompressInput(part, liveKeys) {
|
|
|
8948
9186
|
};
|
|
8949
9187
|
}
|
|
8950
9188
|
function hideConsumedCompressCalls(state, messages) {
|
|
8951
|
-
const
|
|
8952
|
-
const
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
9189
|
+
const messagesState = state.prune.messages;
|
|
9190
|
+
const version = messagesState.structureVersion ?? 0;
|
|
9191
|
+
let cached = messagesState.hideConsumedIndex;
|
|
9192
|
+
if (!cached || cached.version !== version) {
|
|
9193
|
+
const allBlockCallIds2 = /* @__PURE__ */ new Set();
|
|
9194
|
+
const liveRangeKeysByCallId2 = /* @__PURE__ */ new Map();
|
|
9195
|
+
const activeCallIds2 = /* @__PURE__ */ new Set();
|
|
9196
|
+
for (const block of messagesState.blocksById.values()) {
|
|
9197
|
+
if (!block.compressCallId) continue;
|
|
9198
|
+
allBlockCallIds2.add(block.compressCallId);
|
|
9199
|
+
if (!isLiveBlock(block)) continue;
|
|
9200
|
+
activeCallIds2.add(block.compressCallId);
|
|
9201
|
+
let keys = liveRangeKeysByCallId2.get(block.compressCallId);
|
|
9202
|
+
if (!keys) {
|
|
9203
|
+
keys = /* @__PURE__ */ new Set();
|
|
9204
|
+
liveRangeKeysByCallId2.set(block.compressCallId, keys);
|
|
9205
|
+
}
|
|
9206
|
+
keys.add(rangeKey(block.startId, block.endId));
|
|
9207
|
+
}
|
|
9208
|
+
cached = { version, allBlockCallIds: allBlockCallIds2, liveRangeKeysByCallId: liveRangeKeysByCallId2, activeCallIds: activeCallIds2 };
|
|
9209
|
+
messagesState.hideConsumedIndex = cached;
|
|
9210
|
+
}
|
|
9211
|
+
const { allBlockCallIds, liveRangeKeysByCallId, activeCallIds } = cached;
|
|
8966
9212
|
const lastOrphanedCallIds = [];
|
|
8967
9213
|
for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {
|
|
8968
9214
|
const parts = Array.isArray(messages[i]?.parts) ? messages[i].parts : [];
|
|
@@ -9094,6 +9340,7 @@ function collectVisibleMessages(rawMessages, ctx) {
|
|
|
9094
9340
|
const ref = byRawId.get(msgId);
|
|
9095
9341
|
if (!ref) return;
|
|
9096
9342
|
let tokens = 0;
|
|
9343
|
+
let reasoning = 0;
|
|
9097
9344
|
let toolName = "";
|
|
9098
9345
|
for (const part of msg.parts || []) {
|
|
9099
9346
|
if (part.type === "text" && typeof part.text === "string") {
|
|
@@ -9104,10 +9351,12 @@ function collectVisibleMessages(rawMessages, ctx) {
|
|
|
9104
9351
|
if (!toolName) {
|
|
9105
9352
|
toolName = part?.tool || "unknown";
|
|
9106
9353
|
}
|
|
9354
|
+
} else if (part.type === "reasoning" && typeof part.text === "string") {
|
|
9355
|
+
reasoning += Math.round(part.text.length / 4);
|
|
9107
9356
|
}
|
|
9108
9357
|
}
|
|
9109
|
-
if (tokens > 0) {
|
|
9110
|
-
result.push({ ref, tokens, tool: toolName || "text", index: idx });
|
|
9358
|
+
if (tokens > 0 || reasoning > 0) {
|
|
9359
|
+
result.push({ ref, tokens, tool: toolName || "text", index: idx, reasoning });
|
|
9111
9360
|
}
|
|
9112
9361
|
});
|
|
9113
9362
|
return {
|
|
@@ -9129,14 +9378,16 @@ function renderOverview(visibleMessages, summaryTokens, systemTokens, blocks, fe
|
|
|
9129
9378
|
} else {
|
|
9130
9379
|
const totalTool = visibleMessages.filter((m) => m.tool !== "text" && m.tool !== "step-finish").reduce((s, m) => s + m.tokens, 0);
|
|
9131
9380
|
const totalText = visibleMessages.filter((m) => m.tool === "text").reduce((s, m) => s + m.tokens, 0);
|
|
9132
|
-
const
|
|
9381
|
+
const totalReasoning = visibleMessages.reduce((s, m) => s + m.reasoning, 0);
|
|
9382
|
+
const total = systemTokens + totalTool + totalText + summaryTokens + totalReasoning;
|
|
9133
9383
|
const sysPct = pct(systemTokens, total);
|
|
9134
9384
|
const toolPct = pct(totalTool, total);
|
|
9135
9385
|
const textPct = pct(totalText, total);
|
|
9136
9386
|
const summaryPct = pct(summaryTokens, total);
|
|
9387
|
+
const reasoningPct = pct(totalReasoning, total);
|
|
9137
9388
|
lines.push("CONTEXT BREAKDOWN");
|
|
9138
9389
|
lines.push(
|
|
9139
|
-
` ${formatTokens(systemTokens)} system (${sysPct}%) | ${formatTokens(totalTool)} tool (${toolPct}%) | ${formatTokens(totalText)} text (${textPct}%) | ${formatTokens(summaryTokens)} summaries (${summaryPct}%)`
|
|
9390
|
+
` ${formatTokens(systemTokens)} system (${sysPct}%) | ${formatTokens(totalTool)} tool (${toolPct}%) | ${formatTokens(totalText)} text (${textPct}%) | ${formatTokens(summaryTokens)} summaries (${summaryPct}%) | ${formatTokens(totalReasoning)} reasoning (${reasoningPct}%)`
|
|
9140
9391
|
);
|
|
9141
9392
|
const topTypes = Array.from(toolTypeMap.entries()).map(([tool6, tokens]) => ({ tool: tool6, tokens })).sort((a, b) => b.tokens - a.tokens).slice(0, 3);
|
|
9142
9393
|
if (topTypes.length > 0) {
|
|
@@ -9247,22 +9498,23 @@ function renderUncompressedDrilldown(visibleMessages, toolFilter, sort, limit) {
|
|
|
9247
9498
|
if (toolFilter) {
|
|
9248
9499
|
filtered = filtered.filter((m) => m.tool === toolFilter);
|
|
9249
9500
|
}
|
|
9501
|
+
const sizeOf = (m) => m.tokens + m.reasoning;
|
|
9250
9502
|
if (sort === "time") {
|
|
9251
9503
|
filtered.sort((a, b) => a.index - b.index);
|
|
9252
9504
|
} else if (sort === "tool") {
|
|
9253
|
-
filtered.sort((a, b) => a.tool.localeCompare(b.tool) || b
|
|
9505
|
+
filtered.sort((a, b) => a.tool.localeCompare(b.tool) || sizeOf(b) - sizeOf(a));
|
|
9254
9506
|
} else {
|
|
9255
|
-
filtered.sort((a, b) => b
|
|
9507
|
+
filtered.sort((a, b) => sizeOf(b) - sizeOf(a));
|
|
9256
9508
|
}
|
|
9257
|
-
const totalTokens = filtered.reduce((s, m) => s + m
|
|
9258
|
-
const allTokens = visibleMessages.reduce((s, m) => s + m
|
|
9509
|
+
const totalTokens = filtered.reduce((s, m) => s + sizeOf(m), 0);
|
|
9510
|
+
const allTokens = visibleMessages.reduce((s, m) => s + sizeOf(m), 0);
|
|
9259
9511
|
const header = toolFilter ? `UNCOMPRESSED \u2014 ${toolFilter}: ${formatTokens(totalTokens)} | ${filtered.length} msgs | ${pct(totalTokens, allTokens)}% of visible` : `UNCOMPRESSED \u2014 ${formatTokens(totalTokens)} | ${filtered.length} msgs`;
|
|
9260
9512
|
lines.push(header);
|
|
9261
9513
|
lines.push(`Sorted by ${sort}`);
|
|
9262
9514
|
lines.push("");
|
|
9263
9515
|
const shown = filtered.slice(0, limit);
|
|
9264
9516
|
for (const m of shown) {
|
|
9265
|
-
lines.push(` ${m.ref} (${formatTokens(m
|
|
9517
|
+
lines.push(` ${m.ref} (${formatTokens(sizeOf(m))}) ${m.tool}`);
|
|
9266
9518
|
}
|
|
9267
9519
|
if (filtered.length > shown.length) {
|
|
9268
9520
|
lines.push("");
|
|
@@ -9530,7 +9782,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
9530
9782
|
import { join as join4 } from "path";
|
|
9531
9783
|
import { existsSync as existsSync4 } from "fs";
|
|
9532
9784
|
import { homedir as homedir3 } from "os";
|
|
9533
|
-
var LOG_VERSION = true ? "1.
|
|
9785
|
+
var LOG_VERSION = true ? "1.17.0-pr.385.132" : "dev";
|
|
9534
9786
|
var LEVEL_RANK = {
|
|
9535
9787
|
debug: 10,
|
|
9536
9788
|
info: 20,
|
|
@@ -9826,13 +10078,14 @@ CONTEXT BREAKDOWN
|
|
|
9826
10078
|
|
|
9827
10079
|
When context usage passes a threshold, the system appends a breakdown showing where your context tokens are spent:
|
|
9828
10080
|
|
|
9829
|
-
Breakdown:
|
|
10081
|
+
Breakdown: 4.2K system (21%) | 8.0K tool (40%) | 2.0K summaries (10%) | 2.6K code (13%) | 2.2K text (11%) | 1.0K reasoning (5%)
|
|
9830
10082
|
|
|
9831
10083
|
- "system" = system prompt tokens (AGENTS.md, tool definitions \u2014 not compressible)
|
|
9832
10084
|
- "tool" = tool call outputs (largest category \u2014 compress first when consumed)
|
|
9833
10085
|
- "summaries" = existing compression block summaries (already compressed; do not re-compress standalone)
|
|
9834
10086
|
- "code" = messages containing code blocks
|
|
9835
10087
|
- "text" = plain text messages
|
|
10088
|
+
- "reasoning" = model thinking blocks (counted to match real API usage; freed when their message is compressed)
|
|
9836
10089
|
|
|
9837
10090
|
Below the breakdown, the system lists compressible ranges grouped by conversation turn. All listed ranges 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. Compress the largest ranges first when the current step no longer needs them.
|
|
9838
10091
|
|
|
@@ -10354,6 +10607,8 @@ var MIN_OUTPUT_TOKENS = 1e3;
|
|
|
10354
10607
|
var KEEP_PREFIX_CHARS = 2e3;
|
|
10355
10608
|
var KEEP_SUFFIX_CHARS = 2e3;
|
|
10356
10609
|
var PROTECT_RECENT_MESSAGES = 3;
|
|
10610
|
+
var OUTPUT_RESERVE_TOKENS = 16384;
|
|
10611
|
+
var overheadErrorLogged = /* @__PURE__ */ new Set();
|
|
10357
10612
|
function parseGcThreshold(threshold, modelContextLimit) {
|
|
10358
10613
|
if (typeof threshold === "number") return threshold;
|
|
10359
10614
|
const str = threshold ?? "100%";
|
|
@@ -10362,10 +10617,26 @@ function parseGcThreshold(threshold, modelContextLimit) {
|
|
|
10362
10617
|
return modelContextLimit;
|
|
10363
10618
|
}
|
|
10364
10619
|
function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
10365
|
-
|
|
10620
|
+
const effective = resolveEffectiveContextLimit(state, config);
|
|
10621
|
+
if (!effective) return;
|
|
10366
10622
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
10367
10623
|
if (currentTokens === 0) return;
|
|
10368
|
-
const
|
|
10624
|
+
const configuredThreshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, effective.limit);
|
|
10625
|
+
const overhead = (state.systemPromptTokens ?? 0) + OUTPUT_RESERVE_TOKENS;
|
|
10626
|
+
const threshold = Math.min(configuredThreshold, effective.limit - overhead);
|
|
10627
|
+
if (threshold <= 0) {
|
|
10628
|
+
const sessionKey = state.sessionId ?? "unknown";
|
|
10629
|
+
if (!overheadErrorLogged.has(sessionKey)) {
|
|
10630
|
+
overheadErrorLogged.add(sessionKey);
|
|
10631
|
+
logger.error("ACP: model context window too small to fit overhead", {
|
|
10632
|
+
session: state.sessionId,
|
|
10633
|
+
limit: effective.limit,
|
|
10634
|
+
contextLimitSource: effective.source,
|
|
10635
|
+
overhead
|
|
10636
|
+
});
|
|
10637
|
+
}
|
|
10638
|
+
return;
|
|
10639
|
+
}
|
|
10369
10640
|
if (currentTokens < threshold) return;
|
|
10370
10641
|
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES;
|
|
10371
10642
|
const candidates = [];
|
|
@@ -10410,11 +10681,160 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
10410
10681
|
truncatedCount,
|
|
10411
10682
|
estimatedSavedTokens: Math.round(savedTokens),
|
|
10412
10683
|
currentTokens,
|
|
10413
|
-
threshold
|
|
10684
|
+
threshold,
|
|
10685
|
+
contextLimit: effective.limit,
|
|
10686
|
+
contextLimitSource: effective.source
|
|
10414
10687
|
});
|
|
10415
10688
|
}
|
|
10416
10689
|
}
|
|
10417
10690
|
|
|
10691
|
+
// lib/messages/enforce-budget.ts
|
|
10692
|
+
var DEFAULT_COMPLETION_RESERVE_TOKENS = 32768;
|
|
10693
|
+
var TRUNCATION_MARKER2 = "[truncated for context space";
|
|
10694
|
+
var KEEP_PREFIX_CHARS2 = 2e3;
|
|
10695
|
+
var KEEP_SUFFIX_CHARS2 = 2e3;
|
|
10696
|
+
var PROTECT_RECENT_MESSAGES2 = 3;
|
|
10697
|
+
var MIN_CLEAR_TOKENS = 200;
|
|
10698
|
+
function resolveContextWindow(state) {
|
|
10699
|
+
if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
|
|
10700
|
+
return state.modelContextLimit;
|
|
10701
|
+
}
|
|
10702
|
+
return void 0;
|
|
10703
|
+
}
|
|
10704
|
+
function estimateWireTokens(state, messages) {
|
|
10705
|
+
const base = getCurrentTokenUsage(state, messages);
|
|
10706
|
+
if (base > 0) {
|
|
10707
|
+
let baseAssistant = -1;
|
|
10708
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
10709
|
+
if (messages[i].info.role !== "assistant") continue;
|
|
10710
|
+
const tokens = messages[i].info.tokens;
|
|
10711
|
+
if ((tokens?.input || 0) <= 0 && (tokens?.output || 0) <= 0) continue;
|
|
10712
|
+
baseAssistant = i;
|
|
10713
|
+
break;
|
|
10714
|
+
}
|
|
10715
|
+
if (baseAssistant >= 0) {
|
|
10716
|
+
let additions = 0;
|
|
10717
|
+
for (let i = baseAssistant + 1; i < messages.length; i++) {
|
|
10718
|
+
additions += countAllMessageTokens(messages[i]);
|
|
10719
|
+
}
|
|
10720
|
+
return base + additions;
|
|
10721
|
+
}
|
|
10722
|
+
}
|
|
10723
|
+
let total = 0;
|
|
10724
|
+
for (const m of messages) total += countAllMessageTokens(m);
|
|
10725
|
+
return total + (state.systemPromptTokens ?? 0);
|
|
10726
|
+
}
|
|
10727
|
+
function enforceContextBudget(state, config, logger, messages) {
|
|
10728
|
+
const window = resolveContextWindow(state);
|
|
10729
|
+
if (window === void 0) return void 0;
|
|
10730
|
+
const configuredReserve = config.compress?.completionReserveTokens;
|
|
10731
|
+
const reserve = typeof configuredReserve === "number" && Number.isFinite(configuredReserve) && configuredReserve >= 0 ? configuredReserve : DEFAULT_COMPLETION_RESERVE_TOKENS;
|
|
10732
|
+
const budget = window - reserve;
|
|
10733
|
+
if (budget <= 0) return void 0;
|
|
10734
|
+
const estimatedTokens = estimateWireTokens(state, messages);
|
|
10735
|
+
if (estimatedTokens <= budget) {
|
|
10736
|
+
return {
|
|
10737
|
+
applied: false,
|
|
10738
|
+
window,
|
|
10739
|
+
reserve,
|
|
10740
|
+
budget,
|
|
10741
|
+
estimatedTokens,
|
|
10742
|
+
finalEstimate: estimatedTokens,
|
|
10743
|
+
truncatedCount: 0,
|
|
10744
|
+
clearedCount: 0
|
|
10745
|
+
};
|
|
10746
|
+
}
|
|
10747
|
+
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES2;
|
|
10748
|
+
const protectedTools = new Set(config.compress?.protectedTools ?? []);
|
|
10749
|
+
const candidates = [];
|
|
10750
|
+
for (let mi = 0; mi < protectedIndex; mi++) {
|
|
10751
|
+
if (mi === 0 && messages[mi].info.role === "user") continue;
|
|
10752
|
+
const msg = messages[mi];
|
|
10753
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
10754
|
+
for (const part of parts) {
|
|
10755
|
+
if (part?.type !== "tool") continue;
|
|
10756
|
+
if (part.state?.status !== "completed") continue;
|
|
10757
|
+
if (part.tool === "compress") continue;
|
|
10758
|
+
if (protectedTools.has(part.tool)) continue;
|
|
10759
|
+
const content = extractCompletedToolOutput(part);
|
|
10760
|
+
if (content === void 0) continue;
|
|
10761
|
+
if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
10762
|
+
const tokens = countTokens2(content);
|
|
10763
|
+
if (tokens <= 0) continue;
|
|
10764
|
+
candidates.push({ part, content, tokens, index: mi });
|
|
10765
|
+
}
|
|
10766
|
+
}
|
|
10767
|
+
let saved = 0;
|
|
10768
|
+
let truncatedCount = 0;
|
|
10769
|
+
let clearedCount = 0;
|
|
10770
|
+
const truncatable = candidates.filter(
|
|
10771
|
+
(c) => !c.content.includes(TRUNCATION_MARKER2) && c.content.length > KEEP_PREFIX_CHARS2 + KEEP_SUFFIX_CHARS2
|
|
10772
|
+
).sort((a, b) => b.tokens - a.tokens);
|
|
10773
|
+
for (const c of truncatable) {
|
|
10774
|
+
if (estimatedTokens - saved <= budget) break;
|
|
10775
|
+
const prefix = c.content.slice(0, KEEP_PREFIX_CHARS2);
|
|
10776
|
+
const suffix = c.content.slice(-KEEP_SUFFIX_CHARS2);
|
|
10777
|
+
const truncated = prefix + `
|
|
10778
|
+
|
|
10779
|
+
...${TRUNCATION_MARKER2} \u2014 original ~${c.tokens} tokens]...
|
|
10780
|
+
|
|
10781
|
+
` + suffix;
|
|
10782
|
+
if (truncated.length >= c.content.length) continue;
|
|
10783
|
+
c.part.state.output = truncated;
|
|
10784
|
+
saved += c.tokens - countTokens2(truncated);
|
|
10785
|
+
truncatedCount++;
|
|
10786
|
+
}
|
|
10787
|
+
if (estimatedTokens - saved > budget) {
|
|
10788
|
+
const clearable = candidates.filter((c) => {
|
|
10789
|
+
const out = extractCompletedToolOutput(c.part);
|
|
10790
|
+
return out !== void 0 && out !== COMPACTED_TOOL_OUTPUT_PLACEHOLDER && countTokens2(out) > MIN_CLEAR_TOKENS;
|
|
10791
|
+
}).sort((a, b) => a.index - b.index);
|
|
10792
|
+
for (const c of clearable) {
|
|
10793
|
+
if (estimatedTokens - saved <= budget) break;
|
|
10794
|
+
const current = extractCompletedToolOutput(c.part);
|
|
10795
|
+
if (current === void 0 || current === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
10796
|
+
c.part.state.output = COMPACTED_TOOL_OUTPUT_PLACEHOLDER;
|
|
10797
|
+
saved += countTokens2(current) - countTokens2(COMPACTED_TOOL_OUTPUT_PLACEHOLDER);
|
|
10798
|
+
clearedCount++;
|
|
10799
|
+
}
|
|
10800
|
+
}
|
|
10801
|
+
const finalEstimate = Math.max(0, estimatedTokens - saved);
|
|
10802
|
+
if (truncatedCount > 0 || clearedCount > 0) {
|
|
10803
|
+
logger.warn("Context budget guard: pruned tool outputs to fit the request", {
|
|
10804
|
+
session: state.sessionId,
|
|
10805
|
+
estimatedTokens: Math.round(estimatedTokens),
|
|
10806
|
+
budget,
|
|
10807
|
+
window,
|
|
10808
|
+
reserve,
|
|
10809
|
+
truncatedCount,
|
|
10810
|
+
clearedCount,
|
|
10811
|
+
estimatedSavedTokens: Math.round(saved),
|
|
10812
|
+
finalEstimate: Math.round(finalEstimate)
|
|
10813
|
+
});
|
|
10814
|
+
}
|
|
10815
|
+
if (finalEstimate > budget) {
|
|
10816
|
+
logger.warn(
|
|
10817
|
+
"Context budget guard: still over budget after pruning all compressible tool outputs; the request may be rejected by the model",
|
|
10818
|
+
{
|
|
10819
|
+
session: state.sessionId,
|
|
10820
|
+
finalEstimate: Math.round(finalEstimate),
|
|
10821
|
+
budget,
|
|
10822
|
+
window
|
|
10823
|
+
}
|
|
10824
|
+
);
|
|
10825
|
+
}
|
|
10826
|
+
return {
|
|
10827
|
+
applied: truncatedCount > 0 || clearedCount > 0,
|
|
10828
|
+
window,
|
|
10829
|
+
reserve,
|
|
10830
|
+
budget,
|
|
10831
|
+
estimatedTokens,
|
|
10832
|
+
finalEstimate,
|
|
10833
|
+
truncatedCount,
|
|
10834
|
+
clearedCount
|
|
10835
|
+
};
|
|
10836
|
+
}
|
|
10837
|
+
|
|
10418
10838
|
// lib/commands/context.ts
|
|
10419
10839
|
function analyzeTokens(state, messages) {
|
|
10420
10840
|
const breakdown = {
|
|
@@ -11362,6 +11782,7 @@ function mergeMarkedBlocks(state, markedIds, maxMergedLength) {
|
|
|
11362
11782
|
0
|
|
11363
11783
|
);
|
|
11364
11784
|
const savedTokens = Math.max(0, sourceTokens - newSummaryTokens);
|
|
11785
|
+
bumpPruneStructureVersion(messagesState);
|
|
11365
11786
|
return { mergedCount: sourceBlocks.length, savedTokens };
|
|
11366
11787
|
}
|
|
11367
11788
|
function runBatchCleanup(state, config, logger, messages) {
|
|
@@ -11371,11 +11792,12 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11371
11792
|
mergedCount: 0,
|
|
11372
11793
|
savedTokens: 0
|
|
11373
11794
|
};
|
|
11374
|
-
|
|
11795
|
+
const effective = resolveEffectiveContextLimit(state, config);
|
|
11796
|
+
if (!effective) {
|
|
11375
11797
|
return noop;
|
|
11376
11798
|
}
|
|
11377
11799
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
11378
|
-
if (currentTokens <
|
|
11800
|
+
if (currentTokens < effective.limit) {
|
|
11379
11801
|
return noop;
|
|
11380
11802
|
}
|
|
11381
11803
|
const maxMergedLength = config.gc.maxOldGenSummaryLength;
|
|
@@ -11392,7 +11814,8 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11392
11814
|
mergedCount: result.mergedCount,
|
|
11393
11815
|
savedTokens: result.savedTokens,
|
|
11394
11816
|
currentTokens,
|
|
11395
|
-
contextLimit:
|
|
11817
|
+
contextLimit: effective.limit,
|
|
11818
|
+
contextLimitSource: effective.source
|
|
11396
11819
|
});
|
|
11397
11820
|
return {
|
|
11398
11821
|
tier: 3,
|
|
@@ -11426,11 +11849,6 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11426
11849
|
input.model?.limit?.context
|
|
11427
11850
|
);
|
|
11428
11851
|
const state = input.sessionID ? registry4.get(input.sessionID) : void 0;
|
|
11429
|
-
if (state && input.model?.limit?.context) {
|
|
11430
|
-
state.modelContextLimit = input.model.limit.context;
|
|
11431
|
-
state.modelProviderID = input.model?.providerID;
|
|
11432
|
-
state.modelID = input.model?.id;
|
|
11433
|
-
}
|
|
11434
11852
|
if (!state || state.isSubAgent && !config.allowSubAgents) {
|
|
11435
11853
|
return;
|
|
11436
11854
|
}
|
|
@@ -11439,6 +11857,23 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11439
11857
|
logger.info("Skipping DCP system prompt injection for internal agent");
|
|
11440
11858
|
return;
|
|
11441
11859
|
}
|
|
11860
|
+
if (input.model?.limit?.context) {
|
|
11861
|
+
const limit = input.model.limit.context;
|
|
11862
|
+
const providerID = input.model?.providerID;
|
|
11863
|
+
const modelID = input.model?.id;
|
|
11864
|
+
const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
|
|
11865
|
+
state.modelContextLimit = limit;
|
|
11866
|
+
if (providerID !== void 0) {
|
|
11867
|
+
state.modelProviderID = providerID;
|
|
11868
|
+
}
|
|
11869
|
+
if (modelID !== void 0) {
|
|
11870
|
+
state.modelID = modelID;
|
|
11871
|
+
}
|
|
11872
|
+
if (changed) {
|
|
11873
|
+
saveSessionState(state, logger).catch(() => {
|
|
11874
|
+
});
|
|
11875
|
+
}
|
|
11876
|
+
}
|
|
11442
11877
|
const effectivePermission = compressPermission(state, config);
|
|
11443
11878
|
if (effectivePermission === "deny") {
|
|
11444
11879
|
return;
|
|
@@ -11483,10 +11918,17 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11483
11918
|
config
|
|
11484
11919
|
);
|
|
11485
11920
|
const requestModel = lastUserMessage.info.model;
|
|
11486
|
-
|
|
11921
|
+
let requestModelLimit = registry4.resolveModelLimit(
|
|
11487
11922
|
requestModel?.providerID,
|
|
11488
11923
|
requestModel?.modelID
|
|
11489
11924
|
);
|
|
11925
|
+
if (requestModelLimit === void 0 && requestModel?.providerID && requestModel?.modelID) {
|
|
11926
|
+
requestModelLimit = await registry4.hydrateAndResolve(
|
|
11927
|
+
client,
|
|
11928
|
+
requestModel.providerID,
|
|
11929
|
+
requestModel.modelID
|
|
11930
|
+
);
|
|
11931
|
+
}
|
|
11490
11932
|
const prevModelID = state.modelID;
|
|
11491
11933
|
if (requestModelLimit !== void 0) {
|
|
11492
11934
|
state.modelContextLimit = requestModelLimit;
|
|
@@ -11516,6 +11958,16 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11516
11958
|
});
|
|
11517
11959
|
}
|
|
11518
11960
|
await updatePerTurnState(state, logger, messages);
|
|
11961
|
+
if (state.modelContextLimit === void 0 && !state.noContextLimitWarned && requestModel?.providerID && requestModel?.modelID && registry4.resolveModelLimit(requestModel.providerID, requestModel.modelID) === void 0) {
|
|
11962
|
+
state.noContextLimitWarned = true;
|
|
11963
|
+
logger.warn(
|
|
11964
|
+
'Model reports no context window and the catalog has no entry for it; all percentage thresholds (min/max/emergency, GC) and the context-budget guard are disabled. Set the model limit in opencode.json (e.g. "limit": {"context": 262144, "output": 16384}) to enable them (also fixes the 32000 max_tokens fallback); an absolute compress.maxContextLimit in acp.jsonc only enables proactive nudges, not the guard.',
|
|
11965
|
+
{
|
|
11966
|
+
session: state.sessionId,
|
|
11967
|
+
model: `${requestModel.providerID}/${requestModel.modelID}`
|
|
11968
|
+
}
|
|
11969
|
+
);
|
|
11970
|
+
}
|
|
11519
11971
|
}
|
|
11520
11972
|
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
11521
11973
|
if (state.isSubAgent && !config.allowSubAgents) {
|
|
@@ -11541,10 +11993,11 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11541
11993
|
}
|
|
11542
11994
|
}
|
|
11543
11995
|
ensureBuiltinFiltersRegistered();
|
|
11996
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
11544
11997
|
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
11545
11998
|
sessionId: state.sessionId ?? "",
|
|
11546
11999
|
isSubAgent: state.isSubAgent,
|
|
11547
|
-
modelContextLimit:
|
|
12000
|
+
modelContextLimit: effectiveLimit?.limit
|
|
11548
12001
|
});
|
|
11549
12002
|
cacheSystemPromptTokens(state, output.messages);
|
|
11550
12003
|
assignMessageRefs(state, output.messages);
|
|
@@ -11564,6 +12017,7 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11564
12017
|
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
11565
12018
|
prune(state, logger, config, output.messages);
|
|
11566
12019
|
truncateLargeToolOutputs(state, config, logger, output.messages);
|
|
12020
|
+
enforceContextBudget(state, config, logger, output.messages);
|
|
11567
12021
|
hideConsumedCompressCalls(state, output.messages);
|
|
11568
12022
|
assignMessageRefs(state, output.messages);
|
|
11569
12023
|
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
@@ -11595,14 +12049,31 @@ ${text}`);
|
|
|
11595
12049
|
stripStaleMetadata(output.messages);
|
|
11596
12050
|
dropEmptyMessages(output.messages);
|
|
11597
12051
|
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
12052
|
+
if (postTokens !== void 0 && effectiveLimit) {
|
|
12053
|
+
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
12054
|
+
if (postTokens > budget) {
|
|
12055
|
+
logger.error(
|
|
12056
|
+
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
12057
|
+
{
|
|
12058
|
+
session: state.sessionId,
|
|
12059
|
+
postTokens,
|
|
12060
|
+
budget,
|
|
12061
|
+
contextLimit: effectiveLimit.limit,
|
|
12062
|
+
contextLimitSource: effectiveLimit.source,
|
|
12063
|
+
hint: "request will likely be rejected; run /compact or start a new session"
|
|
12064
|
+
}
|
|
12065
|
+
);
|
|
12066
|
+
}
|
|
12067
|
+
}
|
|
11598
12068
|
logger.info("Chat transform complete", {
|
|
11599
12069
|
session: state.sessionId,
|
|
11600
12070
|
model: state.modelID,
|
|
11601
12071
|
messages: output.messages.length,
|
|
11602
12072
|
prePruneTokens,
|
|
11603
12073
|
postTokens,
|
|
11604
|
-
contextLimit:
|
|
11605
|
-
|
|
12074
|
+
contextLimit: effectiveLimit?.limit,
|
|
12075
|
+
contextLimitSource: effectiveLimit?.source,
|
|
12076
|
+
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
11606
12077
|
nudged: state.nudges.shouldInjectThisTurn
|
|
11607
12078
|
});
|
|
11608
12079
|
if (state.sessionId) {
|
|
@@ -11634,12 +12105,7 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
|
|
|
11634
12105
|
path: { id: input.sessionID }
|
|
11635
12106
|
});
|
|
11636
12107
|
const messages = filterMessages(messagesResponse.data || messagesResponse);
|
|
11637
|
-
const state = await registry4.getOrCreate(
|
|
11638
|
-
client,
|
|
11639
|
-
input.sessionID,
|
|
11640
|
-
messages,
|
|
11641
|
-
config
|
|
11642
|
-
);
|
|
12108
|
+
const state = await registry4.getOrCreate(client, input.sessionID, messages, config);
|
|
11643
12109
|
syncCompressPermissionState(state, config, hostPermissions, messages);
|
|
11644
12110
|
const commandCtx = {
|
|
11645
12111
|
client,
|
|
@@ -11653,7 +12119,7 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
|
|
|
11653
12119
|
const sub = input.arguments?.trim().toLowerCase();
|
|
11654
12120
|
if (sub === "stats" || sub === "status" || sub === "") {
|
|
11655
12121
|
await handleStatsCommand(commandCtx);
|
|
11656
|
-
|
|
12122
|
+
return;
|
|
11657
12123
|
}
|
|
11658
12124
|
if (sub === "export" || sub.startsWith("export ")) {
|
|
11659
12125
|
const exportArgs = input.arguments?.trim().slice("export".length).trim() || "";
|
|
@@ -11661,17 +12127,10 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
|
|
|
11661
12127
|
throw new Error("__DCP_CONTEXT_HANDLED__");
|
|
11662
12128
|
}
|
|
11663
12129
|
if (sub === "help") {
|
|
11664
|
-
await sendIgnoredMessage(
|
|
11665
|
-
client,
|
|
11666
|
-
input.sessionID,
|
|
11667
|
-
buildHelpText(),
|
|
11668
|
-
{},
|
|
11669
|
-
logger
|
|
11670
|
-
);
|
|
12130
|
+
await sendIgnoredMessage(client, input.sessionID, buildHelpText(), {}, logger);
|
|
11671
12131
|
throw new Error("__DCP_CONTEXT_HANDLED__");
|
|
11672
12132
|
}
|
|
11673
12133
|
await handleContextCommand(commandCtx);
|
|
11674
|
-
throw new Error("__DCP_CONTEXT_HANDLED__");
|
|
11675
12134
|
}
|
|
11676
12135
|
};
|
|
11677
12136
|
}
|
|
@@ -11742,9 +12201,7 @@ function createEventHandler(registry4, logger) {
|
|
|
11742
12201
|
return;
|
|
11743
12202
|
}
|
|
11744
12203
|
if (typeof part.callID === "string" && typeof part.messageID === "string") {
|
|
11745
|
-
timing.startsByCallId.delete(
|
|
11746
|
-
buildCompressionTimingKey(part.messageID, part.callID)
|
|
11747
|
-
);
|
|
12204
|
+
timing.startsByCallId.delete(buildCompressionTimingKey(part.messageID, part.callID));
|
|
11748
12205
|
}
|
|
11749
12206
|
};
|
|
11750
12207
|
}
|
|
@@ -12021,7 +12478,7 @@ var server = (async (ctx) => {
|
|
|
12021
12478
|
}
|
|
12022
12479
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
12023
12480
|
logger.info("ACP plugin initialized", {
|
|
12024
|
-
version: true ? "1.
|
|
12481
|
+
version: true ? "1.17.0-pr.385.132" : "dev",
|
|
12025
12482
|
workspace: ctx.directory,
|
|
12026
12483
|
logLevel: logger.level,
|
|
12027
12484
|
debug: config.debug,
|