opencode-acp 1.14.25-pr.349.75 → 1.14.25-pr.350.76
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 +202 -134
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/decompress.d.ts.map +1 -1
- package/dist/lib/config.d.ts +6 -9
- 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.map +1 -1
- package/dist/lib/messages/truncate-tools.d.ts +0 -1
- package/dist/lib/messages/truncate-tools.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts +0 -2
- 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 +0 -16
- package/dist/lib/state/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -888,7 +888,6 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
888
888
|
"compress.minContextLimit",
|
|
889
889
|
"compress.modelMaxLimits",
|
|
890
890
|
"compress.modelMinLimits",
|
|
891
|
-
"compress.contextLimitFallback",
|
|
892
891
|
"compress.nudgeFrequency",
|
|
893
892
|
"compress.minNudgeContextPercent",
|
|
894
893
|
"compress.nudgeGrowthTokens",
|
|
@@ -909,6 +908,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
909
908
|
"compress.preserveRecentMessages",
|
|
910
909
|
"compress.preserveRecentTokens",
|
|
911
910
|
"compress.preserveLastUserMessage",
|
|
911
|
+
"compress.completionReserveTokens",
|
|
912
912
|
"gc",
|
|
913
913
|
"gc.algorithm",
|
|
914
914
|
"gc.promotionThreshold",
|
|
@@ -1262,6 +1262,20 @@ function validateConfigTypes(config) {
|
|
|
1262
1262
|
actual: typeof compress.preserveLastUserMessage
|
|
1263
1263
|
});
|
|
1264
1264
|
}
|
|
1265
|
+
if (compress.completionReserveTokens !== void 0 && typeof compress.completionReserveTokens !== "number") {
|
|
1266
|
+
errors.push({
|
|
1267
|
+
key: "compress.completionReserveTokens",
|
|
1268
|
+
expected: "number",
|
|
1269
|
+
actual: typeof compress.completionReserveTokens
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
if (typeof compress.completionReserveTokens === "number" && compress.completionReserveTokens < 0) {
|
|
1273
|
+
errors.push({
|
|
1274
|
+
key: "compress.completionReserveTokens",
|
|
1275
|
+
expected: "non-negative number (>= 0)",
|
|
1276
|
+
actual: `${compress.completionReserveTokens}`
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1265
1279
|
if (typeof compress.iterationNudgeThreshold === "number" && compress.iterationNudgeThreshold < 1) {
|
|
1266
1280
|
errors.push({
|
|
1267
1281
|
key: "compress.iterationNudgeThreshold",
|
|
@@ -1312,20 +1326,6 @@ function validateConfigTypes(config) {
|
|
|
1312
1326
|
}
|
|
1313
1327
|
validateModelLimits("compress.modelMaxLimits", compress.modelMaxLimits);
|
|
1314
1328
|
validateModelLimits("compress.modelMinLimits", compress.modelMinLimits);
|
|
1315
|
-
if (compress.contextLimitFallback !== void 0 && typeof compress.contextLimitFallback !== "number") {
|
|
1316
|
-
errors.push({
|
|
1317
|
-
key: "compress.contextLimitFallback",
|
|
1318
|
-
expected: "number",
|
|
1319
|
-
actual: typeof compress.contextLimitFallback
|
|
1320
|
-
});
|
|
1321
|
-
}
|
|
1322
|
-
if (typeof compress.contextLimitFallback === "number" && compress.contextLimitFallback < 0) {
|
|
1323
|
-
errors.push({
|
|
1324
|
-
key: "compress.contextLimitFallback",
|
|
1325
|
-
expected: "non-negative number (0 disables the fallback)",
|
|
1326
|
-
actual: `${compress.contextLimitFallback}`
|
|
1327
|
-
});
|
|
1328
|
-
}
|
|
1329
1329
|
const validValues = ["ask", "allow", "deny"];
|
|
1330
1330
|
if (compress.permission !== void 0 && !validValues.includes(compress.permission)) {
|
|
1331
1331
|
errors.push({
|
|
@@ -1503,7 +1503,6 @@ var defaultConfig = {
|
|
|
1503
1503
|
summaryBuffer: true,
|
|
1504
1504
|
maxContextLimit: "80%",
|
|
1505
1505
|
minContextLimit: "80%",
|
|
1506
|
-
contextLimitFallback: 128e3,
|
|
1507
1506
|
nudgeFrequency: 5,
|
|
1508
1507
|
minNudgeContextPercent: 15,
|
|
1509
1508
|
iterationNudgeThreshold: 15,
|
|
@@ -1639,7 +1638,6 @@ function mergeCompress(base, override) {
|
|
|
1639
1638
|
minContextLimit: override.minContextLimit ?? base.minContextLimit,
|
|
1640
1639
|
modelMaxLimits: override.modelMaxLimits ?? base.modelMaxLimits,
|
|
1641
1640
|
modelMinLimits: override.modelMinLimits ?? base.modelMinLimits,
|
|
1642
|
-
contextLimitFallback: override.contextLimitFallback ?? base.contextLimitFallback,
|
|
1643
1641
|
nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
|
|
1644
1642
|
minNudgeContextPercent: override.minNudgeContextPercent ?? base.minNudgeContextPercent,
|
|
1645
1643
|
nudgeGrowthTokens: override.nudgeGrowthTokens,
|
|
@@ -1659,7 +1657,8 @@ function mergeCompress(base, override) {
|
|
|
1659
1657
|
lastSegmentSoftBlock: override.lastSegmentSoftBlock ?? base.lastSegmentSoftBlock,
|
|
1660
1658
|
preserveRecentMessages: override.preserveRecentMessages ?? base.preserveRecentMessages,
|
|
1661
1659
|
preserveRecentTokens: override.preserveRecentTokens ?? base.preserveRecentTokens,
|
|
1662
|
-
preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage
|
|
1660
|
+
preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage,
|
|
1661
|
+
completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens
|
|
1663
1662
|
};
|
|
1664
1663
|
}
|
|
1665
1664
|
function mergeCommands(base, override) {
|
|
@@ -2382,16 +2381,6 @@ function resetOnCompaction(state) {
|
|
|
2382
2381
|
nextRef: 1
|
|
2383
2382
|
};
|
|
2384
2383
|
}
|
|
2385
|
-
function resolveEffectiveContextLimit(state, config) {
|
|
2386
|
-
if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
|
|
2387
|
-
return { limit: state.modelContextLimit, source: "model" };
|
|
2388
|
-
}
|
|
2389
|
-
const fallback = config.compress.contextLimitFallback;
|
|
2390
|
-
if (typeof fallback === "number" && fallback > 0) {
|
|
2391
|
-
return { limit: fallback, source: "fallback" };
|
|
2392
|
-
}
|
|
2393
|
-
return void 0;
|
|
2394
|
-
}
|
|
2395
2384
|
|
|
2396
2385
|
// lib/state/persistence.ts
|
|
2397
2386
|
function getStorageDir() {
|
|
@@ -4237,24 +4226,6 @@ var SessionStateRegistry = class {
|
|
|
4237
4226
|
hydrateModelLimitsFromClient(client) {
|
|
4238
4227
|
return this.catalog.hydrateFromClient(client);
|
|
4239
4228
|
}
|
|
4240
|
-
// [FIX #346] The init-time seed (above) is fire-and-forget and races
|
|
4241
|
-
// server readiness: in headless spawn+resume mode the provider-config
|
|
4242
|
-
// call can fail before the server is up, leaving the catalog empty for
|
|
4243
|
-
// the process's lifetime. During a request the server is guaranteed up
|
|
4244
|
-
// (we are inside its pipeline), so on a catalog miss we retry hydration
|
|
4245
|
-
// once per process before giving up (the fallback limit then applies).
|
|
4246
|
-
// The in-flight promise (not a boolean) lets concurrent callers await the
|
|
4247
|
-
// same hydration instead of skipping it.
|
|
4248
|
-
lazyHydration;
|
|
4249
|
-
async hydrateAndResolve(client, providerId, modelId) {
|
|
4250
|
-
const existing = this.catalog.resolve(providerId, modelId);
|
|
4251
|
-
if (existing !== void 0) {
|
|
4252
|
-
return existing;
|
|
4253
|
-
}
|
|
4254
|
-
this.lazyHydration ??= this.catalog.hydrateFromClient(client);
|
|
4255
|
-
await this.lazyHydration;
|
|
4256
|
-
return this.catalog.resolve(providerId, modelId);
|
|
4257
|
-
}
|
|
4258
4229
|
get(sessionId) {
|
|
4259
4230
|
return this.states.get(sessionId);
|
|
4260
4231
|
}
|
|
@@ -4346,7 +4317,8 @@ function createSessionState() {
|
|
|
4346
4317
|
modelProviderID: void 0,
|
|
4347
4318
|
modelID: void 0,
|
|
4348
4319
|
systemPromptTokens: void 0,
|
|
4349
|
-
qualityGateRetryPending: false
|
|
4320
|
+
qualityGateRetryPending: false,
|
|
4321
|
+
noContextLimitWarned: false
|
|
4350
4322
|
};
|
|
4351
4323
|
}
|
|
4352
4324
|
function resetSessionState(state) {
|
|
@@ -4388,6 +4360,7 @@ function resetSessionState(state) {
|
|
|
4388
4360
|
state.modelID = void 0;
|
|
4389
4361
|
state.systemPromptTokens = void 0;
|
|
4390
4362
|
state.qualityGateRetryPending = false;
|
|
4363
|
+
state.noContextLimitWarned = false;
|
|
4391
4364
|
}
|
|
4392
4365
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config) {
|
|
4393
4366
|
if (state.sessionId === sessionId) {
|
|
@@ -6925,7 +6898,6 @@ function getModelInfo(messages) {
|
|
|
6925
6898
|
};
|
|
6926
6899
|
}
|
|
6927
6900
|
function resolveContextTokenLimit(config, state, providerId, modelId, threshold) {
|
|
6928
|
-
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6929
6901
|
const parseLimitValue = (limit) => {
|
|
6930
6902
|
if (limit === void 0) {
|
|
6931
6903
|
return void 0;
|
|
@@ -6933,7 +6905,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6933
6905
|
if (typeof limit === "number") {
|
|
6934
6906
|
return limit;
|
|
6935
6907
|
}
|
|
6936
|
-
if (!limit.endsWith("%") ||
|
|
6908
|
+
if (!limit.endsWith("%") || state.modelContextLimit === void 0) {
|
|
6937
6909
|
return void 0;
|
|
6938
6910
|
}
|
|
6939
6911
|
const parsedPercent = parseFloat(limit.slice(0, -1));
|
|
@@ -6942,7 +6914,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6942
6914
|
}
|
|
6943
6915
|
const roundedPercent = Math.round(parsedPercent);
|
|
6944
6916
|
const clampedPercent = Math.max(0, Math.min(100, roundedPercent));
|
|
6945
|
-
return Math.round(clampedPercent / 100 *
|
|
6917
|
+
return Math.round(clampedPercent / 100 * state.modelContextLimit);
|
|
6946
6918
|
};
|
|
6947
6919
|
const modelLimits = threshold === "max" ? config.compress.modelMaxLimits : config.compress.modelMinLimits;
|
|
6948
6920
|
if (modelLimits && providerId !== void 0 && modelId !== void 0) {
|
|
@@ -6987,12 +6959,11 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
|
6987
6959
|
if (!overMaxLimit) break;
|
|
6988
6960
|
}
|
|
6989
6961
|
}
|
|
6990
|
-
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6991
6962
|
return {
|
|
6992
6963
|
overMaxLimit,
|
|
6993
6964
|
overMinLimit,
|
|
6994
6965
|
currentTokens,
|
|
6995
|
-
modelContextLimit:
|
|
6966
|
+
modelContextLimit: state.modelContextLimit
|
|
6996
6967
|
};
|
|
6997
6968
|
}
|
|
6998
6969
|
ensureBuiltinTriggerPolicyRegistered();
|
|
@@ -8434,9 +8405,8 @@ function createDecompressTool(factoryCtx) {
|
|
|
8434
8405
|
async execute(args, toolCtx) {
|
|
8435
8406
|
const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
|
|
8436
8407
|
const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
|
|
8437
|
-
const
|
|
8438
|
-
|
|
8439
|
-
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
|
|
8408
|
+
const contextUsageBefore = ctx.state.modelContextLimit ? Math.round(
|
|
8409
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / ctx.state.modelContextLimit * 100
|
|
8440
8410
|
) : void 0;
|
|
8441
8411
|
const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
|
|
8442
8412
|
if (!resolved.ok) {
|
|
@@ -8500,9 +8470,8 @@ function createDecompressTool(factoryCtx) {
|
|
|
8500
8470
|
0,
|
|
8501
8471
|
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
8502
8472
|
);
|
|
8503
|
-
const
|
|
8504
|
-
|
|
8505
|
-
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
8473
|
+
const contextUsageAfter = ctx.state.modelContextLimit ? Math.round(
|
|
8474
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / ctx.state.modelContextLimit * 100
|
|
8506
8475
|
) : void 0;
|
|
8507
8476
|
await finalizeDecompressSession(ctx);
|
|
8508
8477
|
const restoredContentPreview = buildRestoredContentPreview(
|
|
@@ -9159,7 +9128,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
9159
9128
|
import { join as join3 } from "path";
|
|
9160
9129
|
import { existsSync as existsSync3 } from "fs";
|
|
9161
9130
|
import { homedir as homedir3 } from "os";
|
|
9162
|
-
var LOG_VERSION = true ? "1.14.25-pr.
|
|
9131
|
+
var LOG_VERSION = true ? "1.14.25-pr.350.76" : "dev";
|
|
9163
9132
|
var LEVEL_RANK = {
|
|
9164
9133
|
debug: 10,
|
|
9165
9134
|
info: 20,
|
|
@@ -9983,8 +9952,6 @@ var MIN_OUTPUT_TOKENS = 1e3;
|
|
|
9983
9952
|
var KEEP_PREFIX_CHARS = 2e3;
|
|
9984
9953
|
var KEEP_SUFFIX_CHARS = 2e3;
|
|
9985
9954
|
var PROTECT_RECENT_MESSAGES = 3;
|
|
9986
|
-
var OUTPUT_RESERVE_TOKENS = 16384;
|
|
9987
|
-
var overheadErrorLogged = /* @__PURE__ */ new Set();
|
|
9988
9955
|
function parseGcThreshold(threshold, modelContextLimit) {
|
|
9989
9956
|
if (typeof threshold === "number") return threshold;
|
|
9990
9957
|
const str = threshold ?? "100%";
|
|
@@ -9993,26 +9960,10 @@ function parseGcThreshold(threshold, modelContextLimit) {
|
|
|
9993
9960
|
return modelContextLimit;
|
|
9994
9961
|
}
|
|
9995
9962
|
function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
9996
|
-
|
|
9997
|
-
if (!effective) return;
|
|
9963
|
+
if (!state.modelContextLimit) return;
|
|
9998
9964
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
9999
9965
|
if (currentTokens === 0) return;
|
|
10000
|
-
const
|
|
10001
|
-
const overhead = (state.systemPromptTokens ?? 0) + OUTPUT_RESERVE_TOKENS;
|
|
10002
|
-
const threshold = Math.min(configuredThreshold, effective.limit - overhead);
|
|
10003
|
-
if (threshold <= 0) {
|
|
10004
|
-
const sessionKey = state.sessionId ?? "unknown";
|
|
10005
|
-
if (!overheadErrorLogged.has(sessionKey)) {
|
|
10006
|
-
overheadErrorLogged.add(sessionKey);
|
|
10007
|
-
logger.error("ACP: model context window too small to fit overhead", {
|
|
10008
|
-
session: state.sessionId,
|
|
10009
|
-
limit: effective.limit,
|
|
10010
|
-
contextLimitSource: effective.source,
|
|
10011
|
-
overhead
|
|
10012
|
-
});
|
|
10013
|
-
}
|
|
10014
|
-
return;
|
|
10015
|
-
}
|
|
9966
|
+
const threshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, state.modelContextLimit);
|
|
10016
9967
|
if (currentTokens < threshold) return;
|
|
10017
9968
|
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES;
|
|
10018
9969
|
const candidates = [];
|
|
@@ -10057,13 +10008,158 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
10057
10008
|
truncatedCount,
|
|
10058
10009
|
estimatedSavedTokens: Math.round(savedTokens),
|
|
10059
10010
|
currentTokens,
|
|
10060
|
-
threshold
|
|
10061
|
-
contextLimit: effective.limit,
|
|
10062
|
-
contextLimitSource: effective.source
|
|
10011
|
+
threshold
|
|
10063
10012
|
});
|
|
10064
10013
|
}
|
|
10065
10014
|
}
|
|
10066
10015
|
|
|
10016
|
+
// lib/messages/enforce-budget.ts
|
|
10017
|
+
var DEFAULT_COMPLETION_RESERVE_TOKENS = 32768;
|
|
10018
|
+
var TRUNCATION_MARKER2 = "[truncated for context space";
|
|
10019
|
+
var KEEP_PREFIX_CHARS2 = 2e3;
|
|
10020
|
+
var KEEP_SUFFIX_CHARS2 = 2e3;
|
|
10021
|
+
var PROTECT_RECENT_MESSAGES2 = 3;
|
|
10022
|
+
var MIN_CLEAR_TOKENS = 200;
|
|
10023
|
+
function resolveContextWindow(state) {
|
|
10024
|
+
if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
|
|
10025
|
+
return state.modelContextLimit;
|
|
10026
|
+
}
|
|
10027
|
+
return void 0;
|
|
10028
|
+
}
|
|
10029
|
+
function estimateWireTokens(state, messages) {
|
|
10030
|
+
const base = getCurrentTokenUsage(state, messages);
|
|
10031
|
+
if (base > 0) {
|
|
10032
|
+
let baseAssistant = -1;
|
|
10033
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
10034
|
+
if (messages[i].info.role !== "assistant") continue;
|
|
10035
|
+
const tokens = messages[i].info.tokens;
|
|
10036
|
+
if ((tokens?.input || 0) <= 0 && (tokens?.output || 0) <= 0) continue;
|
|
10037
|
+
baseAssistant = i;
|
|
10038
|
+
break;
|
|
10039
|
+
}
|
|
10040
|
+
if (baseAssistant >= 0) {
|
|
10041
|
+
let additions = 0;
|
|
10042
|
+
for (let i = baseAssistant + 1; i < messages.length; i++) {
|
|
10043
|
+
additions += countAllMessageTokens(messages[i]);
|
|
10044
|
+
}
|
|
10045
|
+
return base + additions;
|
|
10046
|
+
}
|
|
10047
|
+
}
|
|
10048
|
+
let total = 0;
|
|
10049
|
+
for (const m of messages) total += countAllMessageTokens(m);
|
|
10050
|
+
return total + (state.systemPromptTokens ?? 0);
|
|
10051
|
+
}
|
|
10052
|
+
function enforceContextBudget(state, config, logger, messages) {
|
|
10053
|
+
const window = resolveContextWindow(state);
|
|
10054
|
+
if (window === void 0) return void 0;
|
|
10055
|
+
const configuredReserve = config.compress?.completionReserveTokens;
|
|
10056
|
+
const reserve = typeof configuredReserve === "number" && Number.isFinite(configuredReserve) && configuredReserve >= 0 ? configuredReserve : DEFAULT_COMPLETION_RESERVE_TOKENS;
|
|
10057
|
+
const budget = window - reserve;
|
|
10058
|
+
if (budget <= 0) return void 0;
|
|
10059
|
+
const estimatedTokens = estimateWireTokens(state, messages);
|
|
10060
|
+
if (estimatedTokens <= budget) {
|
|
10061
|
+
return {
|
|
10062
|
+
applied: false,
|
|
10063
|
+
window,
|
|
10064
|
+
reserve,
|
|
10065
|
+
budget,
|
|
10066
|
+
estimatedTokens,
|
|
10067
|
+
finalEstimate: estimatedTokens,
|
|
10068
|
+
truncatedCount: 0,
|
|
10069
|
+
clearedCount: 0
|
|
10070
|
+
};
|
|
10071
|
+
}
|
|
10072
|
+
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES2;
|
|
10073
|
+
const protectedTools = new Set(config.compress?.protectedTools ?? []);
|
|
10074
|
+
const candidates = [];
|
|
10075
|
+
for (let mi = 0; mi < protectedIndex; mi++) {
|
|
10076
|
+
if (mi === 0 && messages[mi].info.role === "user") continue;
|
|
10077
|
+
const msg = messages[mi];
|
|
10078
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
10079
|
+
for (const part of parts) {
|
|
10080
|
+
if (part?.type !== "tool") continue;
|
|
10081
|
+
if (part.state?.status !== "completed") continue;
|
|
10082
|
+
if (part.tool === "compress") continue;
|
|
10083
|
+
if (protectedTools.has(part.tool)) continue;
|
|
10084
|
+
const content = extractCompletedToolOutput(part);
|
|
10085
|
+
if (content === void 0) continue;
|
|
10086
|
+
if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
10087
|
+
const tokens = countTokens2(content);
|
|
10088
|
+
if (tokens <= 0) continue;
|
|
10089
|
+
candidates.push({ part, content, tokens, index: mi });
|
|
10090
|
+
}
|
|
10091
|
+
}
|
|
10092
|
+
let saved = 0;
|
|
10093
|
+
let truncatedCount = 0;
|
|
10094
|
+
let clearedCount = 0;
|
|
10095
|
+
const truncatable = candidates.filter(
|
|
10096
|
+
(c) => !c.content.includes(TRUNCATION_MARKER2) && c.content.length > KEEP_PREFIX_CHARS2 + KEEP_SUFFIX_CHARS2
|
|
10097
|
+
).sort((a, b) => b.tokens - a.tokens);
|
|
10098
|
+
for (const c of truncatable) {
|
|
10099
|
+
if (estimatedTokens - saved <= budget) break;
|
|
10100
|
+
const prefix = c.content.slice(0, KEEP_PREFIX_CHARS2);
|
|
10101
|
+
const suffix = c.content.slice(-KEEP_SUFFIX_CHARS2);
|
|
10102
|
+
const truncated = prefix + `
|
|
10103
|
+
|
|
10104
|
+
...${TRUNCATION_MARKER2} \u2014 original ~${c.tokens} tokens]...
|
|
10105
|
+
|
|
10106
|
+
` + suffix;
|
|
10107
|
+
if (truncated.length >= c.content.length) continue;
|
|
10108
|
+
c.part.state.output = truncated;
|
|
10109
|
+
saved += c.tokens - countTokens2(truncated);
|
|
10110
|
+
truncatedCount++;
|
|
10111
|
+
}
|
|
10112
|
+
if (estimatedTokens - saved > budget) {
|
|
10113
|
+
const clearable = candidates.filter((c) => {
|
|
10114
|
+
const out = extractCompletedToolOutput(c.part);
|
|
10115
|
+
return out !== void 0 && out !== COMPACTED_TOOL_OUTPUT_PLACEHOLDER && countTokens2(out) > MIN_CLEAR_TOKENS;
|
|
10116
|
+
}).sort((a, b) => a.index - b.index);
|
|
10117
|
+
for (const c of clearable) {
|
|
10118
|
+
if (estimatedTokens - saved <= budget) break;
|
|
10119
|
+
const current = extractCompletedToolOutput(c.part);
|
|
10120
|
+
if (current === void 0 || current === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
10121
|
+
c.part.state.output = COMPACTED_TOOL_OUTPUT_PLACEHOLDER;
|
|
10122
|
+
saved += countTokens2(current) - countTokens2(COMPACTED_TOOL_OUTPUT_PLACEHOLDER);
|
|
10123
|
+
clearedCount++;
|
|
10124
|
+
}
|
|
10125
|
+
}
|
|
10126
|
+
const finalEstimate = Math.max(0, estimatedTokens - saved);
|
|
10127
|
+
if (truncatedCount > 0 || clearedCount > 0) {
|
|
10128
|
+
logger.warn("Context budget guard: pruned tool outputs to fit the request", {
|
|
10129
|
+
session: state.sessionId,
|
|
10130
|
+
estimatedTokens: Math.round(estimatedTokens),
|
|
10131
|
+
budget,
|
|
10132
|
+
window,
|
|
10133
|
+
reserve,
|
|
10134
|
+
truncatedCount,
|
|
10135
|
+
clearedCount,
|
|
10136
|
+
estimatedSavedTokens: Math.round(saved),
|
|
10137
|
+
finalEstimate: Math.round(finalEstimate)
|
|
10138
|
+
});
|
|
10139
|
+
}
|
|
10140
|
+
if (finalEstimate > budget) {
|
|
10141
|
+
logger.warn(
|
|
10142
|
+
"Context budget guard: still over budget after pruning all compressible tool outputs; the request may be rejected by the model",
|
|
10143
|
+
{
|
|
10144
|
+
session: state.sessionId,
|
|
10145
|
+
finalEstimate: Math.round(finalEstimate),
|
|
10146
|
+
budget,
|
|
10147
|
+
window
|
|
10148
|
+
}
|
|
10149
|
+
);
|
|
10150
|
+
}
|
|
10151
|
+
return {
|
|
10152
|
+
applied: truncatedCount > 0 || clearedCount > 0,
|
|
10153
|
+
window,
|
|
10154
|
+
reserve,
|
|
10155
|
+
budget,
|
|
10156
|
+
estimatedTokens,
|
|
10157
|
+
finalEstimate,
|
|
10158
|
+
truncatedCount,
|
|
10159
|
+
clearedCount
|
|
10160
|
+
};
|
|
10161
|
+
}
|
|
10162
|
+
|
|
10067
10163
|
// lib/commands/context.ts
|
|
10068
10164
|
function analyzeTokens(state, messages) {
|
|
10069
10165
|
const breakdown = {
|
|
@@ -11020,12 +11116,11 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11020
11116
|
mergedCount: 0,
|
|
11021
11117
|
savedTokens: 0
|
|
11022
11118
|
};
|
|
11023
|
-
|
|
11024
|
-
if (!effective) {
|
|
11119
|
+
if (!state.modelContextLimit || state.modelContextLimit <= 0) {
|
|
11025
11120
|
return noop;
|
|
11026
11121
|
}
|
|
11027
11122
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
11028
|
-
if (currentTokens <
|
|
11123
|
+
if (currentTokens < state.modelContextLimit) {
|
|
11029
11124
|
return noop;
|
|
11030
11125
|
}
|
|
11031
11126
|
const maxMergedLength = config.gc.maxOldGenSummaryLength;
|
|
@@ -11042,8 +11137,7 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11042
11137
|
mergedCount: result.mergedCount,
|
|
11043
11138
|
savedTokens: result.savedTokens,
|
|
11044
11139
|
currentTokens,
|
|
11045
|
-
contextLimit:
|
|
11046
|
-
contextLimitSource: effective.source
|
|
11140
|
+
contextLimit: state.modelContextLimit
|
|
11047
11141
|
});
|
|
11048
11142
|
return {
|
|
11049
11143
|
tier: 3,
|
|
@@ -11077,6 +11171,11 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11077
11171
|
input.model?.limit?.context
|
|
11078
11172
|
);
|
|
11079
11173
|
const state = input.sessionID ? registry4.get(input.sessionID) : void 0;
|
|
11174
|
+
if (state && input.model?.limit?.context) {
|
|
11175
|
+
state.modelContextLimit = input.model.limit.context;
|
|
11176
|
+
state.modelProviderID = input.model?.providerID;
|
|
11177
|
+
state.modelID = input.model?.id;
|
|
11178
|
+
}
|
|
11080
11179
|
if (!state || state.isSubAgent && !config.allowSubAgents) {
|
|
11081
11180
|
return;
|
|
11082
11181
|
}
|
|
@@ -11085,23 +11184,6 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11085
11184
|
logger.info("Skipping DCP system prompt injection for internal agent");
|
|
11086
11185
|
return;
|
|
11087
11186
|
}
|
|
11088
|
-
if (input.model?.limit?.context) {
|
|
11089
|
-
const limit = input.model.limit.context;
|
|
11090
|
-
const providerID = input.model?.providerID;
|
|
11091
|
-
const modelID = input.model?.id;
|
|
11092
|
-
const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
|
|
11093
|
-
state.modelContextLimit = limit;
|
|
11094
|
-
if (providerID !== void 0) {
|
|
11095
|
-
state.modelProviderID = providerID;
|
|
11096
|
-
}
|
|
11097
|
-
if (modelID !== void 0) {
|
|
11098
|
-
state.modelID = modelID;
|
|
11099
|
-
}
|
|
11100
|
-
if (changed) {
|
|
11101
|
-
saveSessionState(state, logger).catch(() => {
|
|
11102
|
-
});
|
|
11103
|
-
}
|
|
11104
|
-
}
|
|
11105
11187
|
const effectivePermission = compressPermission(state, config);
|
|
11106
11188
|
if (effectivePermission === "deny") {
|
|
11107
11189
|
return;
|
|
@@ -11146,17 +11228,10 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11146
11228
|
config
|
|
11147
11229
|
);
|
|
11148
11230
|
const requestModel = lastUserMessage.info.model;
|
|
11149
|
-
|
|
11231
|
+
const requestModelLimit = registry4.resolveModelLimit(
|
|
11150
11232
|
requestModel?.providerID,
|
|
11151
11233
|
requestModel?.modelID
|
|
11152
11234
|
);
|
|
11153
|
-
if (requestModelLimit === void 0 && requestModel?.providerID && requestModel?.modelID) {
|
|
11154
|
-
requestModelLimit = await registry4.hydrateAndResolve(
|
|
11155
|
-
client,
|
|
11156
|
-
requestModel.providerID,
|
|
11157
|
-
requestModel.modelID
|
|
11158
|
-
);
|
|
11159
|
-
}
|
|
11160
11235
|
const prevModelID = state.modelID;
|
|
11161
11236
|
if (requestModelLimit !== void 0) {
|
|
11162
11237
|
state.modelContextLimit = requestModelLimit;
|
|
@@ -11186,6 +11261,16 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11186
11261
|
});
|
|
11187
11262
|
}
|
|
11188
11263
|
await updatePerTurnState(state, logger, messages);
|
|
11264
|
+
if (state.modelContextLimit === void 0 && !state.noContextLimitWarned && requestModel?.providerID && requestModel?.modelID && registry4.resolveModelLimit(requestModel.providerID, requestModel.modelID) === void 0) {
|
|
11265
|
+
state.noContextLimitWarned = true;
|
|
11266
|
+
logger.warn(
|
|
11267
|
+
'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.',
|
|
11268
|
+
{
|
|
11269
|
+
session: state.sessionId,
|
|
11270
|
+
model: `${requestModel.providerID}/${requestModel.modelID}`
|
|
11271
|
+
}
|
|
11272
|
+
);
|
|
11273
|
+
}
|
|
11189
11274
|
}
|
|
11190
11275
|
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
11191
11276
|
if (state.isSubAgent && !config.allowSubAgents) {
|
|
@@ -11193,11 +11278,10 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11193
11278
|
}
|
|
11194
11279
|
stripHallucinations(output.messages);
|
|
11195
11280
|
ensureBuiltinFiltersRegistered();
|
|
11196
|
-
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
11197
11281
|
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
11198
11282
|
sessionId: state.sessionId ?? "",
|
|
11199
11283
|
isSubAgent: state.isSubAgent,
|
|
11200
|
-
modelContextLimit:
|
|
11284
|
+
modelContextLimit: state.modelContextLimit
|
|
11201
11285
|
});
|
|
11202
11286
|
cacheSystemPromptTokens(state, output.messages);
|
|
11203
11287
|
assignMessageRefs(state, output.messages);
|
|
@@ -11217,6 +11301,7 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11217
11301
|
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
11218
11302
|
prune(state, logger, config, output.messages);
|
|
11219
11303
|
truncateLargeToolOutputs(state, config, logger, output.messages);
|
|
11304
|
+
enforceContextBudget(state, config, logger, output.messages);
|
|
11220
11305
|
hideConsumedCompressCalls(state, output.messages);
|
|
11221
11306
|
assignMessageRefs(state, output.messages);
|
|
11222
11307
|
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
@@ -11248,31 +11333,14 @@ ${text}`);
|
|
|
11248
11333
|
stripStaleMetadata(output.messages);
|
|
11249
11334
|
dropEmptyMessages(output.messages);
|
|
11250
11335
|
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
11251
|
-
if (postTokens !== void 0 && effectiveLimit) {
|
|
11252
|
-
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
11253
|
-
if (postTokens > budget) {
|
|
11254
|
-
logger.error(
|
|
11255
|
-
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
11256
|
-
{
|
|
11257
|
-
session: state.sessionId,
|
|
11258
|
-
postTokens,
|
|
11259
|
-
budget,
|
|
11260
|
-
contextLimit: effectiveLimit.limit,
|
|
11261
|
-
contextLimitSource: effectiveLimit.source,
|
|
11262
|
-
hint: "request will likely be rejected; run /compact or start a new session"
|
|
11263
|
-
}
|
|
11264
|
-
);
|
|
11265
|
-
}
|
|
11266
|
-
}
|
|
11267
11336
|
logger.info("Chat transform complete", {
|
|
11268
11337
|
session: state.sessionId,
|
|
11269
11338
|
model: state.modelID,
|
|
11270
11339
|
messages: output.messages.length,
|
|
11271
11340
|
prePruneTokens,
|
|
11272
11341
|
postTokens,
|
|
11273
|
-
contextLimit:
|
|
11274
|
-
|
|
11275
|
-
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
11342
|
+
contextLimit: state.modelContextLimit,
|
|
11343
|
+
usagePct: postTokens !== void 0 && state.modelContextLimit ? `${(postTokens / state.modelContextLimit * 100).toFixed(1)}%` : void 0,
|
|
11276
11344
|
nudged: state.nudges.shouldInjectThisTurn
|
|
11277
11345
|
});
|
|
11278
11346
|
if (state.sessionId) {
|
|
@@ -11659,7 +11727,7 @@ var server = (async (ctx) => {
|
|
|
11659
11727
|
}
|
|
11660
11728
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
11661
11729
|
logger.info("ACP plugin initialized", {
|
|
11662
|
-
version: true ? "1.14.25-pr.
|
|
11730
|
+
version: true ? "1.14.25-pr.350.76" : "dev",
|
|
11663
11731
|
workspace: ctx.directory,
|
|
11664
11732
|
logLevel: logger.level,
|
|
11665
11733
|
debug: config.debug,
|