opencode-acp 1.16.0-pr.365.125 → 1.16.0-pr.374.129
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/index.js +390 -78
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/decompress.d.ts.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 +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/utils.d.ts +1 -0
- package/dist/lib/messages/inject/utils.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/state.d.ts +2 -0
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +6 -0
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/state/utils.d.ts +16 -0
- package/dist/lib/state/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) {
|
|
@@ -2685,6 +2752,16 @@ function resetOnCompaction(state) {
|
|
|
2685
2752
|
nextRef: 1
|
|
2686
2753
|
};
|
|
2687
2754
|
}
|
|
2755
|
+
function resolveEffectiveContextLimit(state, config) {
|
|
2756
|
+
if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
|
|
2757
|
+
return { limit: state.modelContextLimit, source: "model" };
|
|
2758
|
+
}
|
|
2759
|
+
const fallback = config.compress.contextLimitFallback;
|
|
2760
|
+
if (typeof fallback === "number" && fallback > 0) {
|
|
2761
|
+
return { limit: fallback, source: "fallback" };
|
|
2762
|
+
}
|
|
2763
|
+
return void 0;
|
|
2764
|
+
}
|
|
2688
2765
|
|
|
2689
2766
|
// lib/state/persistence.ts
|
|
2690
2767
|
function getDefaultStorageDir() {
|
|
@@ -4556,6 +4633,24 @@ var SessionStateRegistry = class {
|
|
|
4556
4633
|
hydrateModelLimitsFromClient(client) {
|
|
4557
4634
|
return this.catalog.hydrateFromClient(client);
|
|
4558
4635
|
}
|
|
4636
|
+
// [FIX #346] The init-time seed (above) is fire-and-forget and races
|
|
4637
|
+
// server readiness: in headless spawn+resume mode the provider-config
|
|
4638
|
+
// call can fail before the server is up, leaving the catalog empty for
|
|
4639
|
+
// the process's lifetime. During a request the server is guaranteed up
|
|
4640
|
+
// (we are inside its pipeline), so on a catalog miss we retry hydration
|
|
4641
|
+
// once per process before giving up (the fallback limit then applies).
|
|
4642
|
+
// The in-flight promise (not a boolean) lets concurrent callers await the
|
|
4643
|
+
// same hydration instead of skipping it.
|
|
4644
|
+
lazyHydration;
|
|
4645
|
+
async hydrateAndResolve(client, providerId, modelId) {
|
|
4646
|
+
const existing = this.catalog.resolve(providerId, modelId);
|
|
4647
|
+
if (existing !== void 0) {
|
|
4648
|
+
return existing;
|
|
4649
|
+
}
|
|
4650
|
+
this.lazyHydration ??= this.catalog.hydrateFromClient(client);
|
|
4651
|
+
await this.lazyHydration;
|
|
4652
|
+
return this.catalog.resolve(providerId, modelId);
|
|
4653
|
+
}
|
|
4559
4654
|
get(sessionId) {
|
|
4560
4655
|
return this.states.get(sessionId);
|
|
4561
4656
|
}
|
|
@@ -4649,7 +4744,8 @@ function createSessionState() {
|
|
|
4649
4744
|
modelID: void 0,
|
|
4650
4745
|
systemPromptTokens: void 0,
|
|
4651
4746
|
storageDir: void 0,
|
|
4652
|
-
qualityGateRetryPending: false
|
|
4747
|
+
qualityGateRetryPending: false,
|
|
4748
|
+
noContextLimitWarned: false
|
|
4653
4749
|
};
|
|
4654
4750
|
}
|
|
4655
4751
|
function resetSessionState(state) {
|
|
@@ -4692,6 +4788,7 @@ function resetSessionState(state) {
|
|
|
4692
4788
|
state.systemPromptTokens = void 0;
|
|
4693
4789
|
state.storageDir = void 0;
|
|
4694
4790
|
state.qualityGateRetryPending = false;
|
|
4791
|
+
state.noContextLimitWarned = false;
|
|
4695
4792
|
}
|
|
4696
4793
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
|
|
4697
4794
|
if (state.sessionId === sessionId) {
|
|
@@ -6707,6 +6804,7 @@ function getModelInfo(messages) {
|
|
|
6707
6804
|
};
|
|
6708
6805
|
}
|
|
6709
6806
|
function resolveContextTokenLimit(config, state, providerId, modelId, threshold) {
|
|
6807
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6710
6808
|
const parseLimitValue = (limit) => {
|
|
6711
6809
|
if (limit === void 0) {
|
|
6712
6810
|
return void 0;
|
|
@@ -6714,7 +6812,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6714
6812
|
if (typeof limit === "number") {
|
|
6715
6813
|
return limit;
|
|
6716
6814
|
}
|
|
6717
|
-
if (!limit.endsWith("%") ||
|
|
6815
|
+
if (!limit.endsWith("%") || effectiveLimit === void 0) {
|
|
6718
6816
|
return void 0;
|
|
6719
6817
|
}
|
|
6720
6818
|
const parsedPercent = parseFloat(limit.slice(0, -1));
|
|
@@ -6723,7 +6821,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6723
6821
|
}
|
|
6724
6822
|
const roundedPercent = Math.round(parsedPercent);
|
|
6725
6823
|
const clampedPercent = Math.max(0, Math.min(100, roundedPercent));
|
|
6726
|
-
return Math.round(clampedPercent / 100 *
|
|
6824
|
+
return Math.round(clampedPercent / 100 * effectiveLimit.limit);
|
|
6727
6825
|
};
|
|
6728
6826
|
if (threshold === "max") {
|
|
6729
6827
|
const nestedLimit = resolveCompressOverrides(config, providerId, modelId).maxContextLimit;
|
|
@@ -6774,11 +6872,12 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
|
6774
6872
|
if (!overMaxLimit) break;
|
|
6775
6873
|
}
|
|
6776
6874
|
}
|
|
6875
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6777
6876
|
return {
|
|
6778
6877
|
overMaxLimit,
|
|
6779
6878
|
overMinLimit,
|
|
6780
6879
|
currentTokens,
|
|
6781
|
-
modelContextLimit:
|
|
6880
|
+
modelContextLimit: effectiveLimit?.limit
|
|
6782
6881
|
};
|
|
6783
6882
|
}
|
|
6784
6883
|
ensureBuiltinTriggerPolicyRegistered();
|
|
@@ -6981,6 +7080,7 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
6981
7080
|
let summaryTokens = 0;
|
|
6982
7081
|
let messageTokens = 0;
|
|
6983
7082
|
let protectedTokens = 0;
|
|
7083
|
+
let reasoningTokens = 0;
|
|
6984
7084
|
const perMessage = [];
|
|
6985
7085
|
const perTool = [];
|
|
6986
7086
|
const perCode = [];
|
|
@@ -7034,6 +7134,10 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
7034
7134
|
summaryTokens += summaryPartTokens;
|
|
7035
7135
|
toolTypeMap.set(toolName, (toolTypeMap.get(toolName) || 0) + toolPartTokens);
|
|
7036
7136
|
if (!msgToolName) msgToolName = toolName;
|
|
7137
|
+
} else if (part.type === "reasoning" && typeof part.text === "string") {
|
|
7138
|
+
const tokens = Math.round(part.text.length / 4);
|
|
7139
|
+
msgTotal += tokens;
|
|
7140
|
+
reasoningTokens += tokens;
|
|
7037
7141
|
}
|
|
7038
7142
|
}
|
|
7039
7143
|
if (isProtected && !isSummary) {
|
|
@@ -7061,7 +7165,8 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
7061
7165
|
textTokens: Math.max(0, messageTokens - codeTokens),
|
|
7062
7166
|
systemTokens,
|
|
7063
7167
|
protectedTokens,
|
|
7064
|
-
|
|
7168
|
+
reasoningTokens,
|
|
7169
|
+
total: systemTokens + toolTokens + summaryTokens + messageTokens + reasoningTokens,
|
|
7065
7170
|
largestRanges: perMessage.slice(0, 15),
|
|
7066
7171
|
largestToolRanges: perTool.slice(0, 15),
|
|
7067
7172
|
largestCodeRanges: perCode.slice(0, 5),
|
|
@@ -7084,13 +7189,10 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7084
7189
|
if (!ref) continue;
|
|
7085
7190
|
const rn = parseInt(ref.slice(1), 10);
|
|
7086
7191
|
if ((protectedTools.length > 0 || protectedFilePatterns.length > 0) && messageContainsProtectedTool(msg, protectedTools, protectedFilePatterns)) {
|
|
7087
|
-
|
|
7192
|
+
const tokens2 = Math.round(countMessageCharacters(msg) / 4);
|
|
7088
7193
|
const tools = /* @__PURE__ */ new Set();
|
|
7089
7194
|
for (const part of msg.parts || []) {
|
|
7090
|
-
if (part.type
|
|
7091
|
-
tokens2 += Math.round(part.text.length / 4);
|
|
7092
|
-
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
7093
|
-
tokens2 += Math.round(JSON.stringify(part).length / 4);
|
|
7195
|
+
if (part.type !== "text" && part.type !== "reasoning") {
|
|
7094
7196
|
const toolName = part?.tool;
|
|
7095
7197
|
const callID = part?.callID;
|
|
7096
7198
|
if (toolName && callID) {
|
|
@@ -7111,15 +7213,13 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7111
7213
|
protectedMsgInfo.push({ ref, refNum: rn, tokens: tokens2, tools: [...tools] });
|
|
7112
7214
|
continue;
|
|
7113
7215
|
}
|
|
7114
|
-
|
|
7216
|
+
const tokens = Math.round(countMessageCharacters(msg) / 4);
|
|
7115
7217
|
let isTool = false;
|
|
7116
7218
|
let hasMeaningfulPart = false;
|
|
7117
7219
|
for (const part of msg.parts || []) {
|
|
7118
7220
|
if (part.type === "text" && typeof part.text === "string") {
|
|
7119
|
-
tokens += Math.round(part.text.length / 4);
|
|
7120
7221
|
if (part.text.trim().length > 0) hasMeaningfulPart = true;
|
|
7121
7222
|
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
7122
|
-
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
7123
7223
|
isTool = true;
|
|
7124
7224
|
hasMeaningfulPart = true;
|
|
7125
7225
|
}
|
|
@@ -8259,7 +8359,7 @@ This is an efficiency nudge to compress early and keep context lean \u2014 not a
|
|
|
8259
8359
|
${COMPRESS_PHILOSOPHY}` : "";
|
|
8260
8360
|
const sysPart = composition.systemTokens > 0 ? `${fmt(composition.systemTokens)} system (${pct2(composition.systemTokens)}%) | ` : "";
|
|
8261
8361
|
let breakdown = `${efficiencyNote}
|
|
8262
|
-
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}`;
|
|
8362
|
+
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}`;
|
|
8263
8363
|
const compressibleTokens = composition.total - composition.systemTokens - composition.protectedTokens - composition.summaryTokens;
|
|
8264
8364
|
if (composition.protectedTokens > 0) {
|
|
8265
8365
|
breakdown += `
|
|
@@ -8860,8 +8960,9 @@ function createDecompressTool(factoryCtx) {
|
|
|
8860
8960
|
async execute(args, toolCtx) {
|
|
8861
8961
|
const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
|
|
8862
8962
|
const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
|
|
8863
|
-
const
|
|
8864
|
-
|
|
8963
|
+
const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
8964
|
+
const contextUsageBefore = effectiveLimitBefore ? Math.round(
|
|
8965
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
|
|
8865
8966
|
) : void 0;
|
|
8866
8967
|
const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
|
|
8867
8968
|
if (!resolved.ok) {
|
|
@@ -8925,8 +9026,9 @@ function createDecompressTool(factoryCtx) {
|
|
|
8925
9026
|
0,
|
|
8926
9027
|
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
8927
9028
|
);
|
|
8928
|
-
const
|
|
8929
|
-
|
|
9029
|
+
const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
9030
|
+
const contextUsageAfter = effectiveLimitAfter ? Math.round(
|
|
9031
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
8930
9032
|
) : void 0;
|
|
8931
9033
|
await finalizeDecompressSession(ctx);
|
|
8932
9034
|
const restoredContentPreview = buildRestoredContentPreview(
|
|
@@ -9147,6 +9249,7 @@ function collectVisibleMessages(rawMessages, ctx) {
|
|
|
9147
9249
|
const ref = byRawId.get(msgId);
|
|
9148
9250
|
if (!ref) return;
|
|
9149
9251
|
let tokens = 0;
|
|
9252
|
+
let reasoning = 0;
|
|
9150
9253
|
let toolName = "";
|
|
9151
9254
|
for (const part of msg.parts || []) {
|
|
9152
9255
|
if (part.type === "text" && typeof part.text === "string") {
|
|
@@ -9157,10 +9260,12 @@ function collectVisibleMessages(rawMessages, ctx) {
|
|
|
9157
9260
|
if (!toolName) {
|
|
9158
9261
|
toolName = part?.tool || "unknown";
|
|
9159
9262
|
}
|
|
9263
|
+
} else if (part.type === "reasoning" && typeof part.text === "string") {
|
|
9264
|
+
reasoning += Math.round(part.text.length / 4);
|
|
9160
9265
|
}
|
|
9161
9266
|
}
|
|
9162
|
-
if (tokens > 0) {
|
|
9163
|
-
result.push({ ref, tokens, tool: toolName || "text", index: idx });
|
|
9267
|
+
if (tokens > 0 || reasoning > 0) {
|
|
9268
|
+
result.push({ ref, tokens, tool: toolName || "text", index: idx, reasoning });
|
|
9164
9269
|
}
|
|
9165
9270
|
});
|
|
9166
9271
|
return {
|
|
@@ -9182,14 +9287,16 @@ function renderOverview(visibleMessages, summaryTokens, systemTokens, blocks, fe
|
|
|
9182
9287
|
} else {
|
|
9183
9288
|
const totalTool = visibleMessages.filter((m) => m.tool !== "text" && m.tool !== "step-finish").reduce((s, m) => s + m.tokens, 0);
|
|
9184
9289
|
const totalText = visibleMessages.filter((m) => m.tool === "text").reduce((s, m) => s + m.tokens, 0);
|
|
9185
|
-
const
|
|
9290
|
+
const totalReasoning = visibleMessages.reduce((s, m) => s + m.reasoning, 0);
|
|
9291
|
+
const total = systemTokens + totalTool + totalText + summaryTokens + totalReasoning;
|
|
9186
9292
|
const sysPct = pct(systemTokens, total);
|
|
9187
9293
|
const toolPct = pct(totalTool, total);
|
|
9188
9294
|
const textPct = pct(totalText, total);
|
|
9189
9295
|
const summaryPct = pct(summaryTokens, total);
|
|
9296
|
+
const reasoningPct = pct(totalReasoning, total);
|
|
9190
9297
|
lines.push("CONTEXT BREAKDOWN");
|
|
9191
9298
|
lines.push(
|
|
9192
|
-
` ${formatTokens(systemTokens)} system (${sysPct}%) | ${formatTokens(totalTool)} tool (${toolPct}%) | ${formatTokens(totalText)} text (${textPct}%) | ${formatTokens(summaryTokens)} summaries (${summaryPct}%)`
|
|
9299
|
+
` ${formatTokens(systemTokens)} system (${sysPct}%) | ${formatTokens(totalTool)} tool (${toolPct}%) | ${formatTokens(totalText)} text (${textPct}%) | ${formatTokens(summaryTokens)} summaries (${summaryPct}%) | ${formatTokens(totalReasoning)} reasoning (${reasoningPct}%)`
|
|
9193
9300
|
);
|
|
9194
9301
|
const topTypes = Array.from(toolTypeMap.entries()).map(([tool6, tokens]) => ({ tool: tool6, tokens })).sort((a, b) => b.tokens - a.tokens).slice(0, 3);
|
|
9195
9302
|
if (topTypes.length > 0) {
|
|
@@ -9300,22 +9407,23 @@ function renderUncompressedDrilldown(visibleMessages, toolFilter, sort, limit) {
|
|
|
9300
9407
|
if (toolFilter) {
|
|
9301
9408
|
filtered = filtered.filter((m) => m.tool === toolFilter);
|
|
9302
9409
|
}
|
|
9410
|
+
const sizeOf = (m) => m.tokens + m.reasoning;
|
|
9303
9411
|
if (sort === "time") {
|
|
9304
9412
|
filtered.sort((a, b) => a.index - b.index);
|
|
9305
9413
|
} else if (sort === "tool") {
|
|
9306
|
-
filtered.sort((a, b) => a.tool.localeCompare(b.tool) || b
|
|
9414
|
+
filtered.sort((a, b) => a.tool.localeCompare(b.tool) || sizeOf(b) - sizeOf(a));
|
|
9307
9415
|
} else {
|
|
9308
|
-
filtered.sort((a, b) => b
|
|
9416
|
+
filtered.sort((a, b) => sizeOf(b) - sizeOf(a));
|
|
9309
9417
|
}
|
|
9310
|
-
const totalTokens = filtered.reduce((s, m) => s + m
|
|
9311
|
-
const allTokens = visibleMessages.reduce((s, m) => s + m
|
|
9418
|
+
const totalTokens = filtered.reduce((s, m) => s + sizeOf(m), 0);
|
|
9419
|
+
const allTokens = visibleMessages.reduce((s, m) => s + sizeOf(m), 0);
|
|
9312
9420
|
const header = toolFilter ? `UNCOMPRESSED \u2014 ${toolFilter}: ${formatTokens(totalTokens)} | ${filtered.length} msgs | ${pct(totalTokens, allTokens)}% of visible` : `UNCOMPRESSED \u2014 ${formatTokens(totalTokens)} | ${filtered.length} msgs`;
|
|
9313
9421
|
lines.push(header);
|
|
9314
9422
|
lines.push(`Sorted by ${sort}`);
|
|
9315
9423
|
lines.push("");
|
|
9316
9424
|
const shown = filtered.slice(0, limit);
|
|
9317
9425
|
for (const m of shown) {
|
|
9318
|
-
lines.push(` ${m.ref} (${formatTokens(m
|
|
9426
|
+
lines.push(` ${m.ref} (${formatTokens(sizeOf(m))}) ${m.tool}`);
|
|
9319
9427
|
}
|
|
9320
9428
|
if (filtered.length > shown.length) {
|
|
9321
9429
|
lines.push("");
|
|
@@ -9583,7 +9691,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
9583
9691
|
import { join as join4 } from "path";
|
|
9584
9692
|
import { existsSync as existsSync4 } from "fs";
|
|
9585
9693
|
import { homedir as homedir3 } from "os";
|
|
9586
|
-
var LOG_VERSION = true ? "1.16.0-pr.
|
|
9694
|
+
var LOG_VERSION = true ? "1.16.0-pr.374.129" : "dev";
|
|
9587
9695
|
var LEVEL_RANK = {
|
|
9588
9696
|
debug: 10,
|
|
9589
9697
|
info: 20,
|
|
@@ -9879,13 +9987,14 @@ CONTEXT BREAKDOWN
|
|
|
9879
9987
|
|
|
9880
9988
|
When context usage passes a threshold, the system appends a breakdown showing where your context tokens are spent:
|
|
9881
9989
|
|
|
9882
|
-
Breakdown:
|
|
9990
|
+
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%)
|
|
9883
9991
|
|
|
9884
9992
|
- "system" = system prompt tokens (AGENTS.md, tool definitions \u2014 not compressible)
|
|
9885
9993
|
- "tool" = tool call outputs (largest category \u2014 compress first when consumed)
|
|
9886
9994
|
- "summaries" = existing compression block summaries (already compressed; do not re-compress standalone)
|
|
9887
9995
|
- "code" = messages containing code blocks
|
|
9888
9996
|
- "text" = plain text messages
|
|
9997
|
+
- "reasoning" = model thinking blocks (counted to match real API usage; freed when their message is compressed)
|
|
9889
9998
|
|
|
9890
9999
|
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.
|
|
9891
10000
|
|
|
@@ -10407,6 +10516,8 @@ var MIN_OUTPUT_TOKENS = 1e3;
|
|
|
10407
10516
|
var KEEP_PREFIX_CHARS = 2e3;
|
|
10408
10517
|
var KEEP_SUFFIX_CHARS = 2e3;
|
|
10409
10518
|
var PROTECT_RECENT_MESSAGES = 3;
|
|
10519
|
+
var OUTPUT_RESERVE_TOKENS = 16384;
|
|
10520
|
+
var overheadErrorLogged = /* @__PURE__ */ new Set();
|
|
10410
10521
|
function parseGcThreshold(threshold, modelContextLimit) {
|
|
10411
10522
|
if (typeof threshold === "number") return threshold;
|
|
10412
10523
|
const str = threshold ?? "100%";
|
|
@@ -10415,10 +10526,26 @@ function parseGcThreshold(threshold, modelContextLimit) {
|
|
|
10415
10526
|
return modelContextLimit;
|
|
10416
10527
|
}
|
|
10417
10528
|
function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
10418
|
-
|
|
10529
|
+
const effective = resolveEffectiveContextLimit(state, config);
|
|
10530
|
+
if (!effective) return;
|
|
10419
10531
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
10420
10532
|
if (currentTokens === 0) return;
|
|
10421
|
-
const
|
|
10533
|
+
const configuredThreshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, effective.limit);
|
|
10534
|
+
const overhead = (state.systemPromptTokens ?? 0) + OUTPUT_RESERVE_TOKENS;
|
|
10535
|
+
const threshold = Math.min(configuredThreshold, effective.limit - overhead);
|
|
10536
|
+
if (threshold <= 0) {
|
|
10537
|
+
const sessionKey = state.sessionId ?? "unknown";
|
|
10538
|
+
if (!overheadErrorLogged.has(sessionKey)) {
|
|
10539
|
+
overheadErrorLogged.add(sessionKey);
|
|
10540
|
+
logger.error("ACP: model context window too small to fit overhead", {
|
|
10541
|
+
session: state.sessionId,
|
|
10542
|
+
limit: effective.limit,
|
|
10543
|
+
contextLimitSource: effective.source,
|
|
10544
|
+
overhead
|
|
10545
|
+
});
|
|
10546
|
+
}
|
|
10547
|
+
return;
|
|
10548
|
+
}
|
|
10422
10549
|
if (currentTokens < threshold) return;
|
|
10423
10550
|
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES;
|
|
10424
10551
|
const candidates = [];
|
|
@@ -10463,9 +10590,158 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
10463
10590
|
truncatedCount,
|
|
10464
10591
|
estimatedSavedTokens: Math.round(savedTokens),
|
|
10465
10592
|
currentTokens,
|
|
10466
|
-
threshold
|
|
10593
|
+
threshold,
|
|
10594
|
+
contextLimit: effective.limit,
|
|
10595
|
+
contextLimitSource: effective.source
|
|
10596
|
+
});
|
|
10597
|
+
}
|
|
10598
|
+
}
|
|
10599
|
+
|
|
10600
|
+
// lib/messages/enforce-budget.ts
|
|
10601
|
+
var DEFAULT_COMPLETION_RESERVE_TOKENS = 32768;
|
|
10602
|
+
var TRUNCATION_MARKER2 = "[truncated for context space";
|
|
10603
|
+
var KEEP_PREFIX_CHARS2 = 2e3;
|
|
10604
|
+
var KEEP_SUFFIX_CHARS2 = 2e3;
|
|
10605
|
+
var PROTECT_RECENT_MESSAGES2 = 3;
|
|
10606
|
+
var MIN_CLEAR_TOKENS = 200;
|
|
10607
|
+
function resolveContextWindow(state) {
|
|
10608
|
+
if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
|
|
10609
|
+
return state.modelContextLimit;
|
|
10610
|
+
}
|
|
10611
|
+
return void 0;
|
|
10612
|
+
}
|
|
10613
|
+
function estimateWireTokens(state, messages) {
|
|
10614
|
+
const base = getCurrentTokenUsage(state, messages);
|
|
10615
|
+
if (base > 0) {
|
|
10616
|
+
let baseAssistant = -1;
|
|
10617
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
10618
|
+
if (messages[i].info.role !== "assistant") continue;
|
|
10619
|
+
const tokens = messages[i].info.tokens;
|
|
10620
|
+
if ((tokens?.input || 0) <= 0 && (tokens?.output || 0) <= 0) continue;
|
|
10621
|
+
baseAssistant = i;
|
|
10622
|
+
break;
|
|
10623
|
+
}
|
|
10624
|
+
if (baseAssistant >= 0) {
|
|
10625
|
+
let additions = 0;
|
|
10626
|
+
for (let i = baseAssistant + 1; i < messages.length; i++) {
|
|
10627
|
+
additions += countAllMessageTokens(messages[i]);
|
|
10628
|
+
}
|
|
10629
|
+
return base + additions;
|
|
10630
|
+
}
|
|
10631
|
+
}
|
|
10632
|
+
let total = 0;
|
|
10633
|
+
for (const m of messages) total += countAllMessageTokens(m);
|
|
10634
|
+
return total + (state.systemPromptTokens ?? 0);
|
|
10635
|
+
}
|
|
10636
|
+
function enforceContextBudget(state, config, logger, messages) {
|
|
10637
|
+
const window = resolveContextWindow(state);
|
|
10638
|
+
if (window === void 0) return void 0;
|
|
10639
|
+
const configuredReserve = config.compress?.completionReserveTokens;
|
|
10640
|
+
const reserve = typeof configuredReserve === "number" && Number.isFinite(configuredReserve) && configuredReserve >= 0 ? configuredReserve : DEFAULT_COMPLETION_RESERVE_TOKENS;
|
|
10641
|
+
const budget = window - reserve;
|
|
10642
|
+
if (budget <= 0) return void 0;
|
|
10643
|
+
const estimatedTokens = estimateWireTokens(state, messages);
|
|
10644
|
+
if (estimatedTokens <= budget) {
|
|
10645
|
+
return {
|
|
10646
|
+
applied: false,
|
|
10647
|
+
window,
|
|
10648
|
+
reserve,
|
|
10649
|
+
budget,
|
|
10650
|
+
estimatedTokens,
|
|
10651
|
+
finalEstimate: estimatedTokens,
|
|
10652
|
+
truncatedCount: 0,
|
|
10653
|
+
clearedCount: 0
|
|
10654
|
+
};
|
|
10655
|
+
}
|
|
10656
|
+
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES2;
|
|
10657
|
+
const protectedTools = new Set(config.compress?.protectedTools ?? []);
|
|
10658
|
+
const candidates = [];
|
|
10659
|
+
for (let mi = 0; mi < protectedIndex; mi++) {
|
|
10660
|
+
if (mi === 0 && messages[mi].info.role === "user") continue;
|
|
10661
|
+
const msg = messages[mi];
|
|
10662
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
10663
|
+
for (const part of parts) {
|
|
10664
|
+
if (part?.type !== "tool") continue;
|
|
10665
|
+
if (part.state?.status !== "completed") continue;
|
|
10666
|
+
if (part.tool === "compress") continue;
|
|
10667
|
+
if (protectedTools.has(part.tool)) continue;
|
|
10668
|
+
const content = extractCompletedToolOutput(part);
|
|
10669
|
+
if (content === void 0) continue;
|
|
10670
|
+
if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
10671
|
+
const tokens = countTokens2(content);
|
|
10672
|
+
if (tokens <= 0) continue;
|
|
10673
|
+
candidates.push({ part, content, tokens, index: mi });
|
|
10674
|
+
}
|
|
10675
|
+
}
|
|
10676
|
+
let saved = 0;
|
|
10677
|
+
let truncatedCount = 0;
|
|
10678
|
+
let clearedCount = 0;
|
|
10679
|
+
const truncatable = candidates.filter(
|
|
10680
|
+
(c) => !c.content.includes(TRUNCATION_MARKER2) && c.content.length > KEEP_PREFIX_CHARS2 + KEEP_SUFFIX_CHARS2
|
|
10681
|
+
).sort((a, b) => b.tokens - a.tokens);
|
|
10682
|
+
for (const c of truncatable) {
|
|
10683
|
+
if (estimatedTokens - saved <= budget) break;
|
|
10684
|
+
const prefix = c.content.slice(0, KEEP_PREFIX_CHARS2);
|
|
10685
|
+
const suffix = c.content.slice(-KEEP_SUFFIX_CHARS2);
|
|
10686
|
+
const truncated = prefix + `
|
|
10687
|
+
|
|
10688
|
+
...${TRUNCATION_MARKER2} \u2014 original ~${c.tokens} tokens]...
|
|
10689
|
+
|
|
10690
|
+
` + suffix;
|
|
10691
|
+
if (truncated.length >= c.content.length) continue;
|
|
10692
|
+
c.part.state.output = truncated;
|
|
10693
|
+
saved += c.tokens - countTokens2(truncated);
|
|
10694
|
+
truncatedCount++;
|
|
10695
|
+
}
|
|
10696
|
+
if (estimatedTokens - saved > budget) {
|
|
10697
|
+
const clearable = candidates.filter((c) => {
|
|
10698
|
+
const out = extractCompletedToolOutput(c.part);
|
|
10699
|
+
return out !== void 0 && out !== COMPACTED_TOOL_OUTPUT_PLACEHOLDER && countTokens2(out) > MIN_CLEAR_TOKENS;
|
|
10700
|
+
}).sort((a, b) => a.index - b.index);
|
|
10701
|
+
for (const c of clearable) {
|
|
10702
|
+
if (estimatedTokens - saved <= budget) break;
|
|
10703
|
+
const current = extractCompletedToolOutput(c.part);
|
|
10704
|
+
if (current === void 0 || current === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
10705
|
+
c.part.state.output = COMPACTED_TOOL_OUTPUT_PLACEHOLDER;
|
|
10706
|
+
saved += countTokens2(current) - countTokens2(COMPACTED_TOOL_OUTPUT_PLACEHOLDER);
|
|
10707
|
+
clearedCount++;
|
|
10708
|
+
}
|
|
10709
|
+
}
|
|
10710
|
+
const finalEstimate = Math.max(0, estimatedTokens - saved);
|
|
10711
|
+
if (truncatedCount > 0 || clearedCount > 0) {
|
|
10712
|
+
logger.warn("Context budget guard: pruned tool outputs to fit the request", {
|
|
10713
|
+
session: state.sessionId,
|
|
10714
|
+
estimatedTokens: Math.round(estimatedTokens),
|
|
10715
|
+
budget,
|
|
10716
|
+
window,
|
|
10717
|
+
reserve,
|
|
10718
|
+
truncatedCount,
|
|
10719
|
+
clearedCount,
|
|
10720
|
+
estimatedSavedTokens: Math.round(saved),
|
|
10721
|
+
finalEstimate: Math.round(finalEstimate)
|
|
10467
10722
|
});
|
|
10468
10723
|
}
|
|
10724
|
+
if (finalEstimate > budget) {
|
|
10725
|
+
logger.warn(
|
|
10726
|
+
"Context budget guard: still over budget after pruning all compressible tool outputs; the request may be rejected by the model",
|
|
10727
|
+
{
|
|
10728
|
+
session: state.sessionId,
|
|
10729
|
+
finalEstimate: Math.round(finalEstimate),
|
|
10730
|
+
budget,
|
|
10731
|
+
window
|
|
10732
|
+
}
|
|
10733
|
+
);
|
|
10734
|
+
}
|
|
10735
|
+
return {
|
|
10736
|
+
applied: truncatedCount > 0 || clearedCount > 0,
|
|
10737
|
+
window,
|
|
10738
|
+
reserve,
|
|
10739
|
+
budget,
|
|
10740
|
+
estimatedTokens,
|
|
10741
|
+
finalEstimate,
|
|
10742
|
+
truncatedCount,
|
|
10743
|
+
clearedCount
|
|
10744
|
+
};
|
|
10469
10745
|
}
|
|
10470
10746
|
|
|
10471
10747
|
// lib/commands/context.ts
|
|
@@ -11424,11 +11700,12 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11424
11700
|
mergedCount: 0,
|
|
11425
11701
|
savedTokens: 0
|
|
11426
11702
|
};
|
|
11427
|
-
|
|
11703
|
+
const effective = resolveEffectiveContextLimit(state, config);
|
|
11704
|
+
if (!effective) {
|
|
11428
11705
|
return noop;
|
|
11429
11706
|
}
|
|
11430
11707
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
11431
|
-
if (currentTokens <
|
|
11708
|
+
if (currentTokens < effective.limit) {
|
|
11432
11709
|
return noop;
|
|
11433
11710
|
}
|
|
11434
11711
|
const maxMergedLength = config.gc.maxOldGenSummaryLength;
|
|
@@ -11445,7 +11722,8 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11445
11722
|
mergedCount: result.mergedCount,
|
|
11446
11723
|
savedTokens: result.savedTokens,
|
|
11447
11724
|
currentTokens,
|
|
11448
|
-
contextLimit:
|
|
11725
|
+
contextLimit: effective.limit,
|
|
11726
|
+
contextLimitSource: effective.source
|
|
11449
11727
|
});
|
|
11450
11728
|
return {
|
|
11451
11729
|
tier: 3,
|
|
@@ -11479,11 +11757,6 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11479
11757
|
input.model?.limit?.context
|
|
11480
11758
|
);
|
|
11481
11759
|
const state = input.sessionID ? registry4.get(input.sessionID) : void 0;
|
|
11482
|
-
if (state && input.model?.limit?.context) {
|
|
11483
|
-
state.modelContextLimit = input.model.limit.context;
|
|
11484
|
-
state.modelProviderID = input.model?.providerID;
|
|
11485
|
-
state.modelID = input.model?.id;
|
|
11486
|
-
}
|
|
11487
11760
|
if (!state || state.isSubAgent && !config.allowSubAgents) {
|
|
11488
11761
|
return;
|
|
11489
11762
|
}
|
|
@@ -11492,6 +11765,23 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11492
11765
|
logger.info("Skipping DCP system prompt injection for internal agent");
|
|
11493
11766
|
return;
|
|
11494
11767
|
}
|
|
11768
|
+
if (input.model?.limit?.context) {
|
|
11769
|
+
const limit = input.model.limit.context;
|
|
11770
|
+
const providerID = input.model?.providerID;
|
|
11771
|
+
const modelID = input.model?.id;
|
|
11772
|
+
const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
|
|
11773
|
+
state.modelContextLimit = limit;
|
|
11774
|
+
if (providerID !== void 0) {
|
|
11775
|
+
state.modelProviderID = providerID;
|
|
11776
|
+
}
|
|
11777
|
+
if (modelID !== void 0) {
|
|
11778
|
+
state.modelID = modelID;
|
|
11779
|
+
}
|
|
11780
|
+
if (changed) {
|
|
11781
|
+
saveSessionState(state, logger).catch(() => {
|
|
11782
|
+
});
|
|
11783
|
+
}
|
|
11784
|
+
}
|
|
11495
11785
|
const effectivePermission = compressPermission(state, config);
|
|
11496
11786
|
if (effectivePermission === "deny") {
|
|
11497
11787
|
return;
|
|
@@ -11536,10 +11826,17 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11536
11826
|
config
|
|
11537
11827
|
);
|
|
11538
11828
|
const requestModel = lastUserMessage.info.model;
|
|
11539
|
-
|
|
11829
|
+
let requestModelLimit = registry4.resolveModelLimit(
|
|
11540
11830
|
requestModel?.providerID,
|
|
11541
11831
|
requestModel?.modelID
|
|
11542
11832
|
);
|
|
11833
|
+
if (requestModelLimit === void 0 && requestModel?.providerID && requestModel?.modelID) {
|
|
11834
|
+
requestModelLimit = await registry4.hydrateAndResolve(
|
|
11835
|
+
client,
|
|
11836
|
+
requestModel.providerID,
|
|
11837
|
+
requestModel.modelID
|
|
11838
|
+
);
|
|
11839
|
+
}
|
|
11543
11840
|
const prevModelID = state.modelID;
|
|
11544
11841
|
if (requestModelLimit !== void 0) {
|
|
11545
11842
|
state.modelContextLimit = requestModelLimit;
|
|
@@ -11569,6 +11866,16 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11569
11866
|
});
|
|
11570
11867
|
}
|
|
11571
11868
|
await updatePerTurnState(state, logger, messages);
|
|
11869
|
+
if (state.modelContextLimit === void 0 && !state.noContextLimitWarned && requestModel?.providerID && requestModel?.modelID && registry4.resolveModelLimit(requestModel.providerID, requestModel.modelID) === void 0) {
|
|
11870
|
+
state.noContextLimitWarned = true;
|
|
11871
|
+
logger.warn(
|
|
11872
|
+
'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.',
|
|
11873
|
+
{
|
|
11874
|
+
session: state.sessionId,
|
|
11875
|
+
model: `${requestModel.providerID}/${requestModel.modelID}`
|
|
11876
|
+
}
|
|
11877
|
+
);
|
|
11878
|
+
}
|
|
11572
11879
|
}
|
|
11573
11880
|
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
11574
11881
|
if (state.isSubAgent && !config.allowSubAgents) {
|
|
@@ -11594,10 +11901,11 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11594
11901
|
}
|
|
11595
11902
|
}
|
|
11596
11903
|
ensureBuiltinFiltersRegistered();
|
|
11904
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
11597
11905
|
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
11598
11906
|
sessionId: state.sessionId ?? "",
|
|
11599
11907
|
isSubAgent: state.isSubAgent,
|
|
11600
|
-
modelContextLimit:
|
|
11908
|
+
modelContextLimit: effectiveLimit?.limit
|
|
11601
11909
|
});
|
|
11602
11910
|
cacheSystemPromptTokens(state, output.messages);
|
|
11603
11911
|
assignMessageRefs(state, output.messages);
|
|
@@ -11617,6 +11925,7 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11617
11925
|
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
11618
11926
|
prune(state, logger, config, output.messages);
|
|
11619
11927
|
truncateLargeToolOutputs(state, config, logger, output.messages);
|
|
11928
|
+
enforceContextBudget(state, config, logger, output.messages);
|
|
11620
11929
|
hideConsumedCompressCalls(state, output.messages);
|
|
11621
11930
|
assignMessageRefs(state, output.messages);
|
|
11622
11931
|
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
@@ -11648,14 +11957,31 @@ ${text}`);
|
|
|
11648
11957
|
stripStaleMetadata(output.messages);
|
|
11649
11958
|
dropEmptyMessages(output.messages);
|
|
11650
11959
|
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
11960
|
+
if (postTokens !== void 0 && effectiveLimit) {
|
|
11961
|
+
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
11962
|
+
if (postTokens > budget) {
|
|
11963
|
+
logger.error(
|
|
11964
|
+
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
11965
|
+
{
|
|
11966
|
+
session: state.sessionId,
|
|
11967
|
+
postTokens,
|
|
11968
|
+
budget,
|
|
11969
|
+
contextLimit: effectiveLimit.limit,
|
|
11970
|
+
contextLimitSource: effectiveLimit.source,
|
|
11971
|
+
hint: "request will likely be rejected; run /compact or start a new session"
|
|
11972
|
+
}
|
|
11973
|
+
);
|
|
11974
|
+
}
|
|
11975
|
+
}
|
|
11651
11976
|
logger.info("Chat transform complete", {
|
|
11652
11977
|
session: state.sessionId,
|
|
11653
11978
|
model: state.modelID,
|
|
11654
11979
|
messages: output.messages.length,
|
|
11655
11980
|
prePruneTokens,
|
|
11656
11981
|
postTokens,
|
|
11657
|
-
contextLimit:
|
|
11658
|
-
|
|
11982
|
+
contextLimit: effectiveLimit?.limit,
|
|
11983
|
+
contextLimitSource: effectiveLimit?.source,
|
|
11984
|
+
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
11659
11985
|
nudged: state.nudges.shouldInjectThisTurn
|
|
11660
11986
|
});
|
|
11661
11987
|
if (state.sessionId) {
|
|
@@ -11687,12 +12013,7 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
|
|
|
11687
12013
|
path: { id: input.sessionID }
|
|
11688
12014
|
});
|
|
11689
12015
|
const messages = filterMessages(messagesResponse.data || messagesResponse);
|
|
11690
|
-
const state = await registry4.getOrCreate(
|
|
11691
|
-
client,
|
|
11692
|
-
input.sessionID,
|
|
11693
|
-
messages,
|
|
11694
|
-
config
|
|
11695
|
-
);
|
|
12016
|
+
const state = await registry4.getOrCreate(client, input.sessionID, messages, config);
|
|
11696
12017
|
syncCompressPermissionState(state, config, hostPermissions, messages);
|
|
11697
12018
|
const commandCtx = {
|
|
11698
12019
|
client,
|
|
@@ -11706,7 +12027,7 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
|
|
|
11706
12027
|
const sub = input.arguments?.trim().toLowerCase();
|
|
11707
12028
|
if (sub === "stats" || sub === "status" || sub === "") {
|
|
11708
12029
|
await handleStatsCommand(commandCtx);
|
|
11709
|
-
|
|
12030
|
+
return;
|
|
11710
12031
|
}
|
|
11711
12032
|
if (sub === "export" || sub.startsWith("export ")) {
|
|
11712
12033
|
const exportArgs = input.arguments?.trim().slice("export".length).trim() || "";
|
|
@@ -11714,17 +12035,10 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
|
|
|
11714
12035
|
throw new Error("__DCP_CONTEXT_HANDLED__");
|
|
11715
12036
|
}
|
|
11716
12037
|
if (sub === "help") {
|
|
11717
|
-
await sendIgnoredMessage(
|
|
11718
|
-
client,
|
|
11719
|
-
input.sessionID,
|
|
11720
|
-
buildHelpText(),
|
|
11721
|
-
{},
|
|
11722
|
-
logger
|
|
11723
|
-
);
|
|
12038
|
+
await sendIgnoredMessage(client, input.sessionID, buildHelpText(), {}, logger);
|
|
11724
12039
|
throw new Error("__DCP_CONTEXT_HANDLED__");
|
|
11725
12040
|
}
|
|
11726
12041
|
await handleContextCommand(commandCtx);
|
|
11727
|
-
throw new Error("__DCP_CONTEXT_HANDLED__");
|
|
11728
12042
|
}
|
|
11729
12043
|
};
|
|
11730
12044
|
}
|
|
@@ -11795,9 +12109,7 @@ function createEventHandler(registry4, logger) {
|
|
|
11795
12109
|
return;
|
|
11796
12110
|
}
|
|
11797
12111
|
if (typeof part.callID === "string" && typeof part.messageID === "string") {
|
|
11798
|
-
timing.startsByCallId.delete(
|
|
11799
|
-
buildCompressionTimingKey(part.messageID, part.callID)
|
|
11800
|
-
);
|
|
12112
|
+
timing.startsByCallId.delete(buildCompressionTimingKey(part.messageID, part.callID));
|
|
11801
12113
|
}
|
|
11802
12114
|
};
|
|
11803
12115
|
}
|
|
@@ -12074,7 +12386,7 @@ var server = (async (ctx) => {
|
|
|
12074
12386
|
}
|
|
12075
12387
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
12076
12388
|
logger.info("ACP plugin initialized", {
|
|
12077
|
-
version: true ? "1.16.0-pr.
|
|
12389
|
+
version: true ? "1.16.0-pr.374.129" : "dev",
|
|
12078
12390
|
workspace: ctx.directory,
|
|
12079
12391
|
logLevel: logger.level,
|
|
12080
12392
|
debug: config.debug,
|