opencode-acp 1.14.25-pr.349.71 → 1.14.25-pr.350.73
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 +182 -125
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/decompress.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +6 -7
- 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 +61 -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",
|
|
@@ -1312,20 +1311,6 @@ function validateConfigTypes(config) {
|
|
|
1312
1311
|
}
|
|
1313
1312
|
validateModelLimits("compress.modelMaxLimits", compress.modelMaxLimits);
|
|
1314
1313
|
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
1314
|
const validValues = ["ask", "allow", "deny"];
|
|
1330
1315
|
if (compress.permission !== void 0 && !validValues.includes(compress.permission)) {
|
|
1331
1316
|
errors.push({
|
|
@@ -1503,7 +1488,6 @@ var defaultConfig = {
|
|
|
1503
1488
|
summaryBuffer: true,
|
|
1504
1489
|
maxContextLimit: "80%",
|
|
1505
1490
|
minContextLimit: "80%",
|
|
1506
|
-
contextLimitFallback: 128e3,
|
|
1507
1491
|
nudgeFrequency: 5,
|
|
1508
1492
|
minNudgeContextPercent: 15,
|
|
1509
1493
|
iterationNudgeThreshold: 15,
|
|
@@ -1639,7 +1623,6 @@ function mergeCompress(base, override) {
|
|
|
1639
1623
|
minContextLimit: override.minContextLimit ?? base.minContextLimit,
|
|
1640
1624
|
modelMaxLimits: override.modelMaxLimits ?? base.modelMaxLimits,
|
|
1641
1625
|
modelMinLimits: override.modelMinLimits ?? base.modelMinLimits,
|
|
1642
|
-
contextLimitFallback: override.contextLimitFallback ?? base.contextLimitFallback,
|
|
1643
1626
|
nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
|
|
1644
1627
|
minNudgeContextPercent: override.minNudgeContextPercent ?? base.minNudgeContextPercent,
|
|
1645
1628
|
nudgeGrowthTokens: override.nudgeGrowthTokens,
|
|
@@ -1659,7 +1642,8 @@ function mergeCompress(base, override) {
|
|
|
1659
1642
|
lastSegmentSoftBlock: override.lastSegmentSoftBlock ?? base.lastSegmentSoftBlock,
|
|
1660
1643
|
preserveRecentMessages: override.preserveRecentMessages ?? base.preserveRecentMessages,
|
|
1661
1644
|
preserveRecentTokens: override.preserveRecentTokens ?? base.preserveRecentTokens,
|
|
1662
|
-
preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage
|
|
1645
|
+
preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage,
|
|
1646
|
+
completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens
|
|
1663
1647
|
};
|
|
1664
1648
|
}
|
|
1665
1649
|
function mergeCommands(base, override) {
|
|
@@ -2382,16 +2366,6 @@ function resetOnCompaction(state) {
|
|
|
2382
2366
|
nextRef: 1
|
|
2383
2367
|
};
|
|
2384
2368
|
}
|
|
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
2369
|
|
|
2396
2370
|
// lib/state/persistence.ts
|
|
2397
2371
|
function getStorageDir() {
|
|
@@ -4237,24 +4211,6 @@ var SessionStateRegistry = class {
|
|
|
4237
4211
|
hydrateModelLimitsFromClient(client) {
|
|
4238
4212
|
return this.catalog.hydrateFromClient(client);
|
|
4239
4213
|
}
|
|
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
|
-
lazyHydrated = false;
|
|
4247
|
-
async hydrateAndResolve(client, providerId, modelId) {
|
|
4248
|
-
const existing = this.catalog.resolve(providerId, modelId);
|
|
4249
|
-
if (existing !== void 0) {
|
|
4250
|
-
return existing;
|
|
4251
|
-
}
|
|
4252
|
-
if (!this.lazyHydrated) {
|
|
4253
|
-
this.lazyHydrated = true;
|
|
4254
|
-
await this.catalog.hydrateFromClient(client);
|
|
4255
|
-
}
|
|
4256
|
-
return this.catalog.resolve(providerId, modelId);
|
|
4257
|
-
}
|
|
4258
4214
|
get(sessionId) {
|
|
4259
4215
|
return this.states.get(sessionId);
|
|
4260
4216
|
}
|
|
@@ -4346,7 +4302,8 @@ function createSessionState() {
|
|
|
4346
4302
|
modelProviderID: void 0,
|
|
4347
4303
|
modelID: void 0,
|
|
4348
4304
|
systemPromptTokens: void 0,
|
|
4349
|
-
qualityGateRetryPending: false
|
|
4305
|
+
qualityGateRetryPending: false,
|
|
4306
|
+
noContextLimitWarned: false
|
|
4350
4307
|
};
|
|
4351
4308
|
}
|
|
4352
4309
|
function resetSessionState(state) {
|
|
@@ -4388,6 +4345,7 @@ function resetSessionState(state) {
|
|
|
4388
4345
|
state.modelID = void 0;
|
|
4389
4346
|
state.systemPromptTokens = void 0;
|
|
4390
4347
|
state.qualityGateRetryPending = false;
|
|
4348
|
+
state.noContextLimitWarned = false;
|
|
4391
4349
|
}
|
|
4392
4350
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config) {
|
|
4393
4351
|
if (state.sessionId === sessionId) {
|
|
@@ -6925,7 +6883,6 @@ function getModelInfo(messages) {
|
|
|
6925
6883
|
};
|
|
6926
6884
|
}
|
|
6927
6885
|
function resolveContextTokenLimit(config, state, providerId, modelId, threshold) {
|
|
6928
|
-
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6929
6886
|
const parseLimitValue = (limit) => {
|
|
6930
6887
|
if (limit === void 0) {
|
|
6931
6888
|
return void 0;
|
|
@@ -6933,7 +6890,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6933
6890
|
if (typeof limit === "number") {
|
|
6934
6891
|
return limit;
|
|
6935
6892
|
}
|
|
6936
|
-
if (!limit.endsWith("%") ||
|
|
6893
|
+
if (!limit.endsWith("%") || state.modelContextLimit === void 0) {
|
|
6937
6894
|
return void 0;
|
|
6938
6895
|
}
|
|
6939
6896
|
const parsedPercent = parseFloat(limit.slice(0, -1));
|
|
@@ -6942,7 +6899,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6942
6899
|
}
|
|
6943
6900
|
const roundedPercent = Math.round(parsedPercent);
|
|
6944
6901
|
const clampedPercent = Math.max(0, Math.min(100, roundedPercent));
|
|
6945
|
-
return Math.round(clampedPercent / 100 *
|
|
6902
|
+
return Math.round(clampedPercent / 100 * state.modelContextLimit);
|
|
6946
6903
|
};
|
|
6947
6904
|
const modelLimits = threshold === "max" ? config.compress.modelMaxLimits : config.compress.modelMinLimits;
|
|
6948
6905
|
if (modelLimits && providerId !== void 0 && modelId !== void 0) {
|
|
@@ -6987,12 +6944,11 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
|
6987
6944
|
if (!overMaxLimit) break;
|
|
6988
6945
|
}
|
|
6989
6946
|
}
|
|
6990
|
-
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6991
6947
|
return {
|
|
6992
6948
|
overMaxLimit,
|
|
6993
6949
|
overMinLimit,
|
|
6994
6950
|
currentTokens,
|
|
6995
|
-
modelContextLimit:
|
|
6951
|
+
modelContextLimit: state.modelContextLimit
|
|
6996
6952
|
};
|
|
6997
6953
|
}
|
|
6998
6954
|
ensureBuiltinTriggerPolicyRegistered();
|
|
@@ -8434,9 +8390,8 @@ function createDecompressTool(factoryCtx) {
|
|
|
8434
8390
|
async execute(args, toolCtx) {
|
|
8435
8391
|
const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
|
|
8436
8392
|
const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
|
|
8437
|
-
const
|
|
8438
|
-
|
|
8439
|
-
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
|
|
8393
|
+
const contextUsageBefore = ctx.state.modelContextLimit ? Math.round(
|
|
8394
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / ctx.state.modelContextLimit * 100
|
|
8440
8395
|
) : void 0;
|
|
8441
8396
|
const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
|
|
8442
8397
|
if (!resolved.ok) {
|
|
@@ -8500,9 +8455,8 @@ function createDecompressTool(factoryCtx) {
|
|
|
8500
8455
|
0,
|
|
8501
8456
|
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
8502
8457
|
);
|
|
8503
|
-
const
|
|
8504
|
-
|
|
8505
|
-
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
8458
|
+
const contextUsageAfter = ctx.state.modelContextLimit ? Math.round(
|
|
8459
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / ctx.state.modelContextLimit * 100
|
|
8506
8460
|
) : void 0;
|
|
8507
8461
|
await finalizeDecompressSession(ctx);
|
|
8508
8462
|
const restoredContentPreview = buildRestoredContentPreview(
|
|
@@ -9159,7 +9113,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
9159
9113
|
import { join as join3 } from "path";
|
|
9160
9114
|
import { existsSync as existsSync3 } from "fs";
|
|
9161
9115
|
import { homedir as homedir3 } from "os";
|
|
9162
|
-
var LOG_VERSION = true ? "1.14.25-pr.
|
|
9116
|
+
var LOG_VERSION = true ? "1.14.25-pr.350.73" : "dev";
|
|
9163
9117
|
var LEVEL_RANK = {
|
|
9164
9118
|
debug: 10,
|
|
9165
9119
|
info: 20,
|
|
@@ -9983,7 +9937,6 @@ var MIN_OUTPUT_TOKENS = 1e3;
|
|
|
9983
9937
|
var KEEP_PREFIX_CHARS = 2e3;
|
|
9984
9938
|
var KEEP_SUFFIX_CHARS = 2e3;
|
|
9985
9939
|
var PROTECT_RECENT_MESSAGES = 3;
|
|
9986
|
-
var OUTPUT_RESERVE_TOKENS = 16384;
|
|
9987
9940
|
function parseGcThreshold(threshold, modelContextLimit) {
|
|
9988
9941
|
if (typeof threshold === "number") return threshold;
|
|
9989
9942
|
const str = threshold ?? "100%";
|
|
@@ -9992,22 +9945,10 @@ function parseGcThreshold(threshold, modelContextLimit) {
|
|
|
9992
9945
|
return modelContextLimit;
|
|
9993
9946
|
}
|
|
9994
9947
|
function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
9995
|
-
|
|
9996
|
-
if (!effective) return;
|
|
9948
|
+
if (!state.modelContextLimit) return;
|
|
9997
9949
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
9998
9950
|
if (currentTokens === 0) return;
|
|
9999
|
-
const
|
|
10000
|
-
const overhead = (state.systemPromptTokens ?? 0) + OUTPUT_RESERVE_TOKENS;
|
|
10001
|
-
const threshold = Math.min(configuredThreshold, effective.limit - overhead);
|
|
10002
|
-
if (threshold <= 0) {
|
|
10003
|
-
logger.error("ACP: model context window too small to fit overhead", {
|
|
10004
|
-
session: state.sessionId,
|
|
10005
|
-
limit: effective.limit,
|
|
10006
|
-
contextLimitSource: effective.source,
|
|
10007
|
-
overhead
|
|
10008
|
-
});
|
|
10009
|
-
return;
|
|
10010
|
-
}
|
|
9951
|
+
const threshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, state.modelContextLimit);
|
|
10011
9952
|
if (currentTokens < threshold) return;
|
|
10012
9953
|
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES;
|
|
10013
9954
|
const candidates = [];
|
|
@@ -10052,13 +9993,153 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
10052
9993
|
truncatedCount,
|
|
10053
9994
|
estimatedSavedTokens: Math.round(savedTokens),
|
|
10054
9995
|
currentTokens,
|
|
10055
|
-
threshold
|
|
10056
|
-
contextLimit: effective.limit,
|
|
10057
|
-
contextLimitSource: effective.source
|
|
9996
|
+
threshold
|
|
10058
9997
|
});
|
|
10059
9998
|
}
|
|
10060
9999
|
}
|
|
10061
10000
|
|
|
10001
|
+
// lib/messages/enforce-budget.ts
|
|
10002
|
+
var DEFAULT_COMPLETION_RESERVE_TOKENS = 32768;
|
|
10003
|
+
var TRUNCATION_MARKER2 = "[truncated for context space";
|
|
10004
|
+
var KEEP_PREFIX_CHARS2 = 2e3;
|
|
10005
|
+
var KEEP_SUFFIX_CHARS2 = 2e3;
|
|
10006
|
+
var PROTECT_RECENT_MESSAGES2 = 3;
|
|
10007
|
+
var MIN_CLEAR_TOKENS = 200;
|
|
10008
|
+
function resolveContextWindow(state) {
|
|
10009
|
+
if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
|
|
10010
|
+
return state.modelContextLimit;
|
|
10011
|
+
}
|
|
10012
|
+
return void 0;
|
|
10013
|
+
}
|
|
10014
|
+
function estimateWireTokens(state, messages) {
|
|
10015
|
+
const base = getCurrentTokenUsage(state, messages);
|
|
10016
|
+
if (base > 0) {
|
|
10017
|
+
let lastAssistant = -1;
|
|
10018
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
10019
|
+
if (messages[i].info.role === "assistant") {
|
|
10020
|
+
lastAssistant = i;
|
|
10021
|
+
break;
|
|
10022
|
+
}
|
|
10023
|
+
}
|
|
10024
|
+
let additions = 0;
|
|
10025
|
+
for (let i = lastAssistant + 1; i < messages.length; i++) {
|
|
10026
|
+
additions += countAllMessageTokens(messages[i]);
|
|
10027
|
+
}
|
|
10028
|
+
return base + additions;
|
|
10029
|
+
}
|
|
10030
|
+
let total = 0;
|
|
10031
|
+
for (const m of messages) total += countAllMessageTokens(m);
|
|
10032
|
+
return total + (state.systemPromptTokens ?? 0);
|
|
10033
|
+
}
|
|
10034
|
+
function enforceContextBudget(state, config, logger, messages) {
|
|
10035
|
+
const window = resolveContextWindow(state);
|
|
10036
|
+
if (window === void 0) return void 0;
|
|
10037
|
+
const reserve = config.compress?.completionReserveTokens ?? DEFAULT_COMPLETION_RESERVE_TOKENS;
|
|
10038
|
+
const budget = window - reserve;
|
|
10039
|
+
if (budget <= 0) return void 0;
|
|
10040
|
+
const estimatedTokens = estimateWireTokens(state, messages);
|
|
10041
|
+
if (estimatedTokens <= budget) {
|
|
10042
|
+
return {
|
|
10043
|
+
applied: false,
|
|
10044
|
+
window,
|
|
10045
|
+
reserve,
|
|
10046
|
+
budget,
|
|
10047
|
+
estimatedTokens,
|
|
10048
|
+
finalEstimate: estimatedTokens,
|
|
10049
|
+
truncatedCount: 0,
|
|
10050
|
+
clearedCount: 0
|
|
10051
|
+
};
|
|
10052
|
+
}
|
|
10053
|
+
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES2;
|
|
10054
|
+
const protectedTools = new Set(config.compress?.protectedTools ?? []);
|
|
10055
|
+
const candidates = [];
|
|
10056
|
+
for (let mi = 0; mi < protectedIndex; mi++) {
|
|
10057
|
+
if (mi === 0 && messages[mi].info.role === "user") continue;
|
|
10058
|
+
const msg = messages[mi];
|
|
10059
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
10060
|
+
for (const part of parts) {
|
|
10061
|
+
if (part?.type !== "tool") continue;
|
|
10062
|
+
if (part.state?.status !== "completed") continue;
|
|
10063
|
+
if (part.tool === "compress") continue;
|
|
10064
|
+
if (protectedTools.has(part.tool)) continue;
|
|
10065
|
+
const content = extractCompletedToolOutput(part);
|
|
10066
|
+
if (content === void 0) continue;
|
|
10067
|
+
if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
10068
|
+
const tokens = countTokens2(content);
|
|
10069
|
+
if (tokens <= 0) continue;
|
|
10070
|
+
candidates.push({ part, content, tokens, index: mi });
|
|
10071
|
+
}
|
|
10072
|
+
}
|
|
10073
|
+
let saved = 0;
|
|
10074
|
+
let truncatedCount = 0;
|
|
10075
|
+
let clearedCount = 0;
|
|
10076
|
+
const truncatable = candidates.filter(
|
|
10077
|
+
(c) => !c.content.includes(TRUNCATION_MARKER2) && c.content.length > KEEP_PREFIX_CHARS2 + KEEP_SUFFIX_CHARS2
|
|
10078
|
+
).sort((a, b) => b.tokens - a.tokens);
|
|
10079
|
+
for (const c of truncatable) {
|
|
10080
|
+
if (estimatedTokens - saved <= budget) break;
|
|
10081
|
+
const prefix = c.content.slice(0, KEEP_PREFIX_CHARS2);
|
|
10082
|
+
const suffix = c.content.slice(-KEEP_SUFFIX_CHARS2);
|
|
10083
|
+
const truncated = prefix + `
|
|
10084
|
+
|
|
10085
|
+
...${TRUNCATION_MARKER2} \u2014 original ~${c.tokens} tokens]...
|
|
10086
|
+
|
|
10087
|
+
` + suffix;
|
|
10088
|
+
c.part.state.output = truncated;
|
|
10089
|
+
saved += c.tokens - countTokens2(truncated);
|
|
10090
|
+
truncatedCount++;
|
|
10091
|
+
}
|
|
10092
|
+
if (estimatedTokens - saved > budget) {
|
|
10093
|
+
const clearable = candidates.filter((c) => {
|
|
10094
|
+
const out = extractCompletedToolOutput(c.part);
|
|
10095
|
+
return out !== void 0 && out !== COMPACTED_TOOL_OUTPUT_PLACEHOLDER && countTokens2(out) > MIN_CLEAR_TOKENS;
|
|
10096
|
+
}).sort((a, b) => a.index - b.index);
|
|
10097
|
+
for (const c of clearable) {
|
|
10098
|
+
if (estimatedTokens - saved <= budget) break;
|
|
10099
|
+
const current = extractCompletedToolOutput(c.part);
|
|
10100
|
+
if (current === void 0 || current === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
10101
|
+
c.part.state.output = COMPACTED_TOOL_OUTPUT_PLACEHOLDER;
|
|
10102
|
+
saved += countTokens2(current) - countTokens2(COMPACTED_TOOL_OUTPUT_PLACEHOLDER);
|
|
10103
|
+
clearedCount++;
|
|
10104
|
+
}
|
|
10105
|
+
}
|
|
10106
|
+
const finalEstimate = Math.max(0, estimatedTokens - saved);
|
|
10107
|
+
if (truncatedCount > 0 || clearedCount > 0) {
|
|
10108
|
+
logger.warn("Context budget guard: pruned tool outputs to fit the request", {
|
|
10109
|
+
session: state.sessionId,
|
|
10110
|
+
estimatedTokens: Math.round(estimatedTokens),
|
|
10111
|
+
budget,
|
|
10112
|
+
window,
|
|
10113
|
+
reserve,
|
|
10114
|
+
truncatedCount,
|
|
10115
|
+
clearedCount,
|
|
10116
|
+
estimatedSavedTokens: Math.round(saved),
|
|
10117
|
+
finalEstimate: Math.round(finalEstimate)
|
|
10118
|
+
});
|
|
10119
|
+
}
|
|
10120
|
+
if (finalEstimate > budget) {
|
|
10121
|
+
logger.warn(
|
|
10122
|
+
"Context budget guard: still over budget after pruning all compressible tool outputs; the request may be rejected by the model",
|
|
10123
|
+
{
|
|
10124
|
+
session: state.sessionId,
|
|
10125
|
+
finalEstimate: Math.round(finalEstimate),
|
|
10126
|
+
budget,
|
|
10127
|
+
window
|
|
10128
|
+
}
|
|
10129
|
+
);
|
|
10130
|
+
}
|
|
10131
|
+
return {
|
|
10132
|
+
applied: truncatedCount > 0 || clearedCount > 0,
|
|
10133
|
+
window,
|
|
10134
|
+
reserve,
|
|
10135
|
+
budget,
|
|
10136
|
+
estimatedTokens,
|
|
10137
|
+
finalEstimate,
|
|
10138
|
+
truncatedCount,
|
|
10139
|
+
clearedCount
|
|
10140
|
+
};
|
|
10141
|
+
}
|
|
10142
|
+
|
|
10062
10143
|
// lib/commands/context.ts
|
|
10063
10144
|
function analyzeTokens(state, messages) {
|
|
10064
10145
|
const breakdown = {
|
|
@@ -11015,12 +11096,11 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11015
11096
|
mergedCount: 0,
|
|
11016
11097
|
savedTokens: 0
|
|
11017
11098
|
};
|
|
11018
|
-
|
|
11019
|
-
if (!effective) {
|
|
11099
|
+
if (!state.modelContextLimit || state.modelContextLimit <= 0) {
|
|
11020
11100
|
return noop;
|
|
11021
11101
|
}
|
|
11022
11102
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
11023
|
-
if (currentTokens <
|
|
11103
|
+
if (currentTokens < state.modelContextLimit) {
|
|
11024
11104
|
return noop;
|
|
11025
11105
|
}
|
|
11026
11106
|
const maxMergedLength = config.gc.maxOldGenSummaryLength;
|
|
@@ -11037,8 +11117,7 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11037
11117
|
mergedCount: result.mergedCount,
|
|
11038
11118
|
savedTokens: result.savedTokens,
|
|
11039
11119
|
currentTokens,
|
|
11040
|
-
contextLimit:
|
|
11041
|
-
contextLimitSource: effective.source
|
|
11120
|
+
contextLimit: state.modelContextLimit
|
|
11042
11121
|
});
|
|
11043
11122
|
return {
|
|
11044
11123
|
tier: 3,
|
|
@@ -11072,6 +11151,11 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11072
11151
|
input.model?.limit?.context
|
|
11073
11152
|
);
|
|
11074
11153
|
const state = input.sessionID ? registry4.get(input.sessionID) : void 0;
|
|
11154
|
+
if (state && input.model?.limit?.context) {
|
|
11155
|
+
state.modelContextLimit = input.model.limit.context;
|
|
11156
|
+
state.modelProviderID = input.model?.providerID;
|
|
11157
|
+
state.modelID = input.model?.id;
|
|
11158
|
+
}
|
|
11075
11159
|
if (!state || state.isSubAgent && !config.allowSubAgents) {
|
|
11076
11160
|
return;
|
|
11077
11161
|
}
|
|
@@ -11080,19 +11164,6 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11080
11164
|
logger.info("Skipping DCP system prompt injection for internal agent");
|
|
11081
11165
|
return;
|
|
11082
11166
|
}
|
|
11083
|
-
if (input.model?.limit?.context) {
|
|
11084
|
-
const limit = input.model.limit.context;
|
|
11085
|
-
const providerID = input.model?.providerID;
|
|
11086
|
-
const modelID = input.model?.id;
|
|
11087
|
-
const changed = state.modelContextLimit !== limit || state.modelProviderID !== providerID || state.modelID !== modelID;
|
|
11088
|
-
state.modelContextLimit = limit;
|
|
11089
|
-
state.modelProviderID = providerID;
|
|
11090
|
-
state.modelID = modelID;
|
|
11091
|
-
if (changed) {
|
|
11092
|
-
saveSessionState(state, logger).catch(() => {
|
|
11093
|
-
});
|
|
11094
|
-
}
|
|
11095
|
-
}
|
|
11096
11167
|
const effectivePermission = compressPermission(state, config);
|
|
11097
11168
|
if (effectivePermission === "deny") {
|
|
11098
11169
|
return;
|
|
@@ -11137,17 +11208,10 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11137
11208
|
config
|
|
11138
11209
|
);
|
|
11139
11210
|
const requestModel = lastUserMessage.info.model;
|
|
11140
|
-
|
|
11211
|
+
const requestModelLimit = registry4.resolveModelLimit(
|
|
11141
11212
|
requestModel?.providerID,
|
|
11142
11213
|
requestModel?.modelID
|
|
11143
11214
|
);
|
|
11144
|
-
if (requestModelLimit === void 0 && requestModel?.providerID && requestModel?.modelID) {
|
|
11145
|
-
requestModelLimit = await registry4.hydrateAndResolve(
|
|
11146
|
-
client,
|
|
11147
|
-
requestModel.providerID,
|
|
11148
|
-
requestModel.modelID
|
|
11149
|
-
);
|
|
11150
|
-
}
|
|
11151
11215
|
const prevModelID = state.modelID;
|
|
11152
11216
|
if (requestModelLimit !== void 0) {
|
|
11153
11217
|
state.modelContextLimit = requestModelLimit;
|
|
@@ -11177,6 +11241,16 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11177
11241
|
});
|
|
11178
11242
|
}
|
|
11179
11243
|
await updatePerTurnState(state, logger, messages);
|
|
11244
|
+
if (state.modelContextLimit === void 0 && !state.noContextLimitWarned && requestModel?.providerID && requestModel?.modelID && registry4.resolveModelLimit(requestModel.providerID, requestModel.modelID) === void 0) {
|
|
11245
|
+
state.noContextLimitWarned = true;
|
|
11246
|
+
logger.warn(
|
|
11247
|
+
'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}) or set an absolute compress.maxContextLimit in acp.jsonc.',
|
|
11248
|
+
{
|
|
11249
|
+
session: state.sessionId,
|
|
11250
|
+
model: `${requestModel.providerID}/${requestModel.modelID}`
|
|
11251
|
+
}
|
|
11252
|
+
);
|
|
11253
|
+
}
|
|
11180
11254
|
}
|
|
11181
11255
|
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
11182
11256
|
if (state.isSubAgent && !config.allowSubAgents) {
|
|
@@ -11184,11 +11258,10 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11184
11258
|
}
|
|
11185
11259
|
stripHallucinations(output.messages);
|
|
11186
11260
|
ensureBuiltinFiltersRegistered();
|
|
11187
|
-
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
11188
11261
|
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
11189
11262
|
sessionId: state.sessionId ?? "",
|
|
11190
11263
|
isSubAgent: state.isSubAgent,
|
|
11191
|
-
modelContextLimit:
|
|
11264
|
+
modelContextLimit: state.modelContextLimit
|
|
11192
11265
|
});
|
|
11193
11266
|
cacheSystemPromptTokens(state, output.messages);
|
|
11194
11267
|
assignMessageRefs(state, output.messages);
|
|
@@ -11208,6 +11281,7 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11208
11281
|
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
11209
11282
|
prune(state, logger, config, output.messages);
|
|
11210
11283
|
truncateLargeToolOutputs(state, config, logger, output.messages);
|
|
11284
|
+
enforceContextBudget(state, config, logger, output.messages);
|
|
11211
11285
|
hideConsumedCompressCalls(state, output.messages);
|
|
11212
11286
|
assignMessageRefs(state, output.messages);
|
|
11213
11287
|
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
@@ -11239,31 +11313,14 @@ ${text}`);
|
|
|
11239
11313
|
stripStaleMetadata(output.messages);
|
|
11240
11314
|
dropEmptyMessages(output.messages);
|
|
11241
11315
|
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
11242
|
-
if (postTokens !== void 0 && effectiveLimit) {
|
|
11243
|
-
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
11244
|
-
if (postTokens > budget) {
|
|
11245
|
-
logger.error(
|
|
11246
|
-
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
11247
|
-
{
|
|
11248
|
-
session: state.sessionId,
|
|
11249
|
-
postTokens,
|
|
11250
|
-
budget,
|
|
11251
|
-
contextLimit: effectiveLimit.limit,
|
|
11252
|
-
contextLimitSource: effectiveLimit.source,
|
|
11253
|
-
hint: "request will likely be rejected; run /compact or start a new session"
|
|
11254
|
-
}
|
|
11255
|
-
);
|
|
11256
|
-
}
|
|
11257
|
-
}
|
|
11258
11316
|
logger.info("Chat transform complete", {
|
|
11259
11317
|
session: state.sessionId,
|
|
11260
11318
|
model: state.modelID,
|
|
11261
11319
|
messages: output.messages.length,
|
|
11262
11320
|
prePruneTokens,
|
|
11263
11321
|
postTokens,
|
|
11264
|
-
contextLimit:
|
|
11265
|
-
|
|
11266
|
-
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
11322
|
+
contextLimit: state.modelContextLimit,
|
|
11323
|
+
usagePct: postTokens !== void 0 && state.modelContextLimit ? `${(postTokens / state.modelContextLimit * 100).toFixed(1)}%` : void 0,
|
|
11267
11324
|
nudged: state.nudges.shouldInjectThisTurn
|
|
11268
11325
|
});
|
|
11269
11326
|
if (state.sessionId) {
|
|
@@ -11650,7 +11707,7 @@ var server = (async (ctx) => {
|
|
|
11650
11707
|
}
|
|
11651
11708
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
11652
11709
|
logger.info("ACP plugin initialized", {
|
|
11653
|
-
version: true ? "1.14.25-pr.
|
|
11710
|
+
version: true ? "1.14.25-pr.350.73" : "dev",
|
|
11654
11711
|
workspace: ctx.directory,
|
|
11655
11712
|
logLevel: logger.level,
|
|
11656
11713
|
debug: config.debug,
|