opencode-acp 1.14.25-pr.348.72 → 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 +411 -419
- package/dist/index.js.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +4 -13
- package/dist/lib/config.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/index.d.ts +0 -2
- package/dist/lib/messages/index.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +4 -11
- package/dist/lib/state/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/lib/messages/prune-to-fit.d.ts +0 -25
- package/dist/lib/messages/prune-to-fit.d.ts.map +0 -1
- package/dist/lib/messages/uncalibrated-window.d.ts +0 -31
- package/dist/lib/messages/uncalibrated-window.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -908,8 +908,6 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
908
908
|
"compress.preserveRecentMessages",
|
|
909
909
|
"compress.preserveRecentTokens",
|
|
910
910
|
"compress.preserveLastUserMessage",
|
|
911
|
-
"compress.overflowGuard",
|
|
912
|
-
"compress.overflowGuardReserve",
|
|
913
911
|
"gc",
|
|
914
912
|
"gc.algorithm",
|
|
915
913
|
"gc.promotionThreshold",
|
|
@@ -1263,27 +1261,6 @@ function validateConfigTypes(config) {
|
|
|
1263
1261
|
actual: typeof compress.preserveLastUserMessage
|
|
1264
1262
|
});
|
|
1265
1263
|
}
|
|
1266
|
-
if (compress.overflowGuard !== void 0 && typeof compress.overflowGuard !== "boolean") {
|
|
1267
|
-
errors.push({
|
|
1268
|
-
key: "compress.overflowGuard",
|
|
1269
|
-
expected: "boolean",
|
|
1270
|
-
actual: typeof compress.overflowGuard
|
|
1271
|
-
});
|
|
1272
|
-
}
|
|
1273
|
-
if (compress.overflowGuardReserve !== void 0 && typeof compress.overflowGuardReserve !== "number") {
|
|
1274
|
-
errors.push({
|
|
1275
|
-
key: "compress.overflowGuardReserve",
|
|
1276
|
-
expected: "number",
|
|
1277
|
-
actual: typeof compress.overflowGuardReserve
|
|
1278
|
-
});
|
|
1279
|
-
}
|
|
1280
|
-
if (typeof compress.overflowGuardReserve === "number" && compress.overflowGuardReserve < 0) {
|
|
1281
|
-
errors.push({
|
|
1282
|
-
key: "compress.overflowGuardReserve",
|
|
1283
|
-
expected: "non-negative number (>= 0)",
|
|
1284
|
-
actual: `${compress.overflowGuardReserve}`
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
1264
|
if (typeof compress.iterationNudgeThreshold === "number" && compress.iterationNudgeThreshold < 1) {
|
|
1288
1265
|
errors.push({
|
|
1289
1266
|
key: "compress.iterationNudgeThreshold",
|
|
@@ -1529,9 +1506,7 @@ var defaultConfig = {
|
|
|
1529
1506
|
lastSegmentSoftBlock: true,
|
|
1530
1507
|
preserveRecentMessages: 5,
|
|
1531
1508
|
preserveRecentTokens: 5e3,
|
|
1532
|
-
preserveLastUserMessage: true
|
|
1533
|
-
overflowGuard: true,
|
|
1534
|
-
overflowGuardReserve: 32768
|
|
1509
|
+
preserveLastUserMessage: true
|
|
1535
1510
|
},
|
|
1536
1511
|
gc: {
|
|
1537
1512
|
algorithm: "truncate",
|
|
@@ -1668,8 +1643,7 @@ function mergeCompress(base, override) {
|
|
|
1668
1643
|
preserveRecentMessages: override.preserveRecentMessages ?? base.preserveRecentMessages,
|
|
1669
1644
|
preserveRecentTokens: override.preserveRecentTokens ?? base.preserveRecentTokens,
|
|
1670
1645
|
preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage,
|
|
1671
|
-
|
|
1672
|
-
overflowGuardReserve: override.overflowGuardReserve ?? base.overflowGuardReserve
|
|
1646
|
+
completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens
|
|
1673
1647
|
};
|
|
1674
1648
|
}
|
|
1675
1649
|
function mergeCommands(base, override) {
|
|
@@ -4329,8 +4303,7 @@ function createSessionState() {
|
|
|
4329
4303
|
modelID: void 0,
|
|
4330
4304
|
systemPromptTokens: void 0,
|
|
4331
4305
|
qualityGateRetryPending: false,
|
|
4332
|
-
|
|
4333
|
-
uncalibratedWindowWarned: false
|
|
4306
|
+
noContextLimitWarned: false
|
|
4334
4307
|
};
|
|
4335
4308
|
}
|
|
4336
4309
|
function resetSessionState(state) {
|
|
@@ -4372,8 +4345,7 @@ function resetSessionState(state) {
|
|
|
4372
4345
|
state.modelID = void 0;
|
|
4373
4346
|
state.systemPromptTokens = void 0;
|
|
4374
4347
|
state.qualityGateRetryPending = false;
|
|
4375
|
-
state.
|
|
4376
|
-
state.uncalibratedWindowWarned = false;
|
|
4348
|
+
state.noContextLimitWarned = false;
|
|
4377
4349
|
}
|
|
4378
4350
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config) {
|
|
4379
4351
|
if (state.sessionId === sessionId) {
|
|
@@ -6420,66 +6392,159 @@ var filterCompressedRanges = (state, messages) => {
|
|
|
6420
6392
|
messages.push(...result);
|
|
6421
6393
|
};
|
|
6422
6394
|
|
|
6423
|
-
// lib/
|
|
6424
|
-
function
|
|
6425
|
-
const
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
const
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
for (let i = 0; i < 3 && i * chunkSize < blocksWithRef.length; i++) {
|
|
6450
|
-
const chunk = blocksWithRef.slice(i * chunkSize, (i + 1) * chunkSize);
|
|
6451
|
-
if (chunk.length < 2) continue;
|
|
6452
|
-
const startRef = chunk[0].ref;
|
|
6453
|
-
const endRef = chunk[chunk.length - 1].ref;
|
|
6454
|
-
const chunkTokens = chunk.reduce((s, x) => s + (x.block.summaryTokens ?? 0), 0);
|
|
6455
|
-
const chunkK = Math.max(1, Math.round(chunkTokens / 1e3));
|
|
6456
|
-
targets.push(` \u2022 compress ${startRef}\u2192${endRef}: ${chunk.length} blocks (~${chunkK}K tokens)`);
|
|
6395
|
+
// lib/messages/sync.ts
|
|
6396
|
+
function sortBlocksByCreation(a, b) {
|
|
6397
|
+
const createdAtDiff = a.createdAt - b.createdAt;
|
|
6398
|
+
if (createdAtDiff !== 0) {
|
|
6399
|
+
return createdAtDiff;
|
|
6400
|
+
}
|
|
6401
|
+
return a.blockId - b.blockId;
|
|
6402
|
+
}
|
|
6403
|
+
var syncCompressionBlocks = (state, logger, messages) => {
|
|
6404
|
+
const messagesState = state.prune.messages;
|
|
6405
|
+
if (!messagesState?.blocksById?.size) {
|
|
6406
|
+
return;
|
|
6407
|
+
}
|
|
6408
|
+
const messageIds = new Set(messages.map((msg) => msg.info.id));
|
|
6409
|
+
const previousActiveBlockIds = new Set(
|
|
6410
|
+
Array.from(messagesState.blocksById.values()).filter((block) => block.active).map((block) => block.blockId)
|
|
6411
|
+
);
|
|
6412
|
+
messagesState.activeBlockIds.clear();
|
|
6413
|
+
messagesState.activeByAnchorMessageId.clear();
|
|
6414
|
+
const now = Date.now();
|
|
6415
|
+
const orderedBlocks = Array.from(messagesState.blocksById.values()).sort(sortBlocksByCreation);
|
|
6416
|
+
for (const block of orderedBlocks) {
|
|
6417
|
+
if (block.deactivatedByUser || block.deactivatedByUserDeep) {
|
|
6418
|
+
block.active = false;
|
|
6419
|
+
if (block.deactivatedAt === void 0) {
|
|
6420
|
+
block.deactivatedAt = now;
|
|
6457
6421
|
}
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6422
|
+
block.deactivatedByBlockId = void 0;
|
|
6423
|
+
continue;
|
|
6424
|
+
}
|
|
6425
|
+
for (const consumedBlockId of block.consumedBlockIds) {
|
|
6426
|
+
if (!messagesState.activeBlockIds.has(consumedBlockId)) {
|
|
6427
|
+
continue;
|
|
6428
|
+
}
|
|
6429
|
+
const consumedBlock = messagesState.blocksById.get(consumedBlockId);
|
|
6430
|
+
if (consumedBlock) {
|
|
6431
|
+
consumedBlock.active = false;
|
|
6432
|
+
consumedBlock.deactivatedAt = now;
|
|
6433
|
+
consumedBlock.deactivatedByBlockId = block.blockId;
|
|
6434
|
+
const mappedBlockId = messagesState.activeByAnchorMessageId.get(
|
|
6435
|
+
consumedBlock.anchorMessageId
|
|
6436
|
+
);
|
|
6437
|
+
if (mappedBlockId === consumedBlock.blockId) {
|
|
6438
|
+
messagesState.activeByAnchorMessageId.delete(consumedBlock.anchorMessageId);
|
|
6439
|
+
}
|
|
6462
6440
|
}
|
|
6441
|
+
messagesState.activeBlockIds.delete(consumedBlockId);
|
|
6442
|
+
}
|
|
6443
|
+
block.active = true;
|
|
6444
|
+
block.deactivatedAt = void 0;
|
|
6445
|
+
block.deactivatedByBlockId = void 0;
|
|
6446
|
+
messagesState.activeBlockIds.add(block.blockId);
|
|
6447
|
+
if (messageIds.has(block.anchorMessageId)) {
|
|
6448
|
+
messagesState.activeByAnchorMessageId.set(block.anchorMessageId, block.blockId);
|
|
6463
6449
|
}
|
|
6464
6450
|
}
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
return nudgeText;
|
|
6451
|
+
for (const entry of messagesState.byMessageId.values()) {
|
|
6452
|
+
const allBlockIds = Array.isArray(entry.allBlockIds) ? [...new Set(entry.allBlockIds.filter((id) => Number.isInteger(id) && id > 0))] : [];
|
|
6453
|
+
entry.allBlockIds = allBlockIds;
|
|
6454
|
+
entry.activeBlockIds = allBlockIds.filter((id) => messagesState.activeBlockIds.has(id));
|
|
6470
6455
|
}
|
|
6471
|
-
const
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6456
|
+
const nextActiveBlockIds = messagesState.activeBlockIds;
|
|
6457
|
+
let deactivatedCount = 0;
|
|
6458
|
+
let reactivatedCount = 0;
|
|
6459
|
+
for (const blockId of previousActiveBlockIds) {
|
|
6460
|
+
if (!nextActiveBlockIds.has(blockId)) {
|
|
6461
|
+
deactivatedCount++;
|
|
6462
|
+
}
|
|
6475
6463
|
}
|
|
6476
|
-
const
|
|
6477
|
-
|
|
6478
|
-
|
|
6464
|
+
for (const blockId of nextActiveBlockIds) {
|
|
6465
|
+
if (!previousActiveBlockIds.has(blockId)) {
|
|
6466
|
+
reactivatedCount++;
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6469
|
+
if (deactivatedCount > 0 || reactivatedCount > 0) {
|
|
6470
|
+
logger.info("Synced compress block state", {
|
|
6471
|
+
deactivatedCount,
|
|
6472
|
+
reactivatedCount
|
|
6473
|
+
});
|
|
6474
|
+
}
|
|
6475
|
+
};
|
|
6479
6476
|
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6477
|
+
// lib/host-permissions.ts
|
|
6478
|
+
var findLastMatchingRule = (rules, predicate) => {
|
|
6479
|
+
for (let index = rules.length - 1; index >= 0; index -= 1) {
|
|
6480
|
+
const rule = rules[index];
|
|
6481
|
+
if (rule && predicate(rule)) {
|
|
6482
|
+
return rule;
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
return void 0;
|
|
6486
|
+
};
|
|
6487
|
+
var wildcardMatch = (value, pattern) => {
|
|
6488
|
+
const normalizedValue = value.replaceAll("\\", "/");
|
|
6489
|
+
let escaped = pattern.replaceAll("\\", "/").replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
6490
|
+
if (escaped.endsWith(" .*")) {
|
|
6491
|
+
escaped = escaped.slice(0, -3) + "( .*)?";
|
|
6492
|
+
}
|
|
6493
|
+
const flags = process.platform === "win32" ? "si" : "s";
|
|
6494
|
+
return new RegExp(`^${escaped}$`, flags).test(normalizedValue);
|
|
6495
|
+
};
|
|
6496
|
+
var getPermissionRules = (permissionConfigs) => {
|
|
6497
|
+
const rules = [];
|
|
6498
|
+
for (const permissionConfig of permissionConfigs) {
|
|
6499
|
+
if (!permissionConfig) {
|
|
6500
|
+
continue;
|
|
6501
|
+
}
|
|
6502
|
+
for (const [permission, value] of Object.entries(permissionConfig)) {
|
|
6503
|
+
if (value === "ask" || value === "allow" || value === "deny") {
|
|
6504
|
+
rules.push({ permission, pattern: "*", action: value });
|
|
6505
|
+
continue;
|
|
6506
|
+
}
|
|
6507
|
+
for (const [pattern, action] of Object.entries(value)) {
|
|
6508
|
+
if (action === "ask" || action === "allow" || action === "deny") {
|
|
6509
|
+
rules.push({ permission, pattern, action });
|
|
6510
|
+
}
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
6513
|
+
}
|
|
6514
|
+
return rules;
|
|
6515
|
+
};
|
|
6516
|
+
var compressDisabledByOpencode = (...permissionConfigs) => {
|
|
6517
|
+
const match = findLastMatchingRule(
|
|
6518
|
+
getPermissionRules(permissionConfigs),
|
|
6519
|
+
(rule) => wildcardMatch("compress", rule.permission)
|
|
6520
|
+
);
|
|
6521
|
+
return match?.pattern === "*" && match.action === "deny";
|
|
6522
|
+
};
|
|
6523
|
+
var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agentName) => {
|
|
6524
|
+
if (basePermission === "deny") {
|
|
6525
|
+
return "deny";
|
|
6526
|
+
}
|
|
6527
|
+
return compressDisabledByOpencode(
|
|
6528
|
+
hostPermissions.global,
|
|
6529
|
+
agentName ? hostPermissions.agents[agentName] : void 0
|
|
6530
|
+
) ? "deny" : basePermission;
|
|
6531
|
+
};
|
|
6532
|
+
var hasExplicitToolPermission = (permissionConfig, tool6) => {
|
|
6533
|
+
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
|
|
6534
|
+
};
|
|
6535
|
+
|
|
6536
|
+
// lib/compress-permission.ts
|
|
6537
|
+
var compressPermission = (state, config) => {
|
|
6538
|
+
return state.compressPermission ?? config.compress.permission;
|
|
6539
|
+
};
|
|
6540
|
+
var syncCompressPermissionState = (state, config, hostPermissions, messages) => {
|
|
6541
|
+
const activeAgent = getLastUserMessage(messages)?.info.agent;
|
|
6542
|
+
state.compressPermission = resolveEffectiveCompressPermission(
|
|
6543
|
+
config.compress.permission,
|
|
6544
|
+
hostPermissions,
|
|
6545
|
+
activeAgent
|
|
6546
|
+
);
|
|
6547
|
+
};
|
|
6483
6548
|
|
|
6484
6549
|
// lib/messages/utils.ts
|
|
6485
6550
|
import { createHash } from "crypto";
|
|
@@ -6655,51 +6720,112 @@ var dropEmptyMessages = (messages) => {
|
|
|
6655
6720
|
return removed;
|
|
6656
6721
|
};
|
|
6657
6722
|
|
|
6658
|
-
//
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
const
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
};
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
}
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6723
|
+
// lib/prompts/extensions/nudge.ts
|
|
6724
|
+
function buildCompressedBlockGuidance(state, context) {
|
|
6725
|
+
const activeBlockIds = Array.from(state.prune.messages.activeBlockIds).filter((id) => Number.isInteger(id) && id > 0).sort((a, b) => a - b);
|
|
6726
|
+
const blockCount = activeBlockIds.length;
|
|
6727
|
+
const blocksForStats = activeBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0 && b.active);
|
|
6728
|
+
const totalSummaryTokens = blocksForStats.reduce((s, b) => s + (b.summaryTokens ?? 0), 0);
|
|
6729
|
+
const totalSummaryDisplay = totalSummaryTokens >= 1e3 ? `${(totalSummaryTokens / 1e3).toFixed(1)}K` : String(totalSummaryTokens);
|
|
6730
|
+
const lastBlock = blocksForStats.length > 0 ? blocksForStats.reduce((latest, b) => b.createdAt > latest.createdAt ? b : latest) : null;
|
|
6731
|
+
const ageStr = lastBlock ? formatAge(lastBlock.createdAt) : "never";
|
|
6732
|
+
const lines = [
|
|
6733
|
+
`- Compressed blocks: ${blockCount} (${totalSummaryDisplay} summary, last ${ageStr}). Use acp_status for details.`
|
|
6734
|
+
];
|
|
6735
|
+
if (blockCount > 50) {
|
|
6736
|
+
const oldBlockIds = activeBlockIds.slice(0, Math.max(0, blockCount - 20));
|
|
6737
|
+
const allOldBlocks = oldBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0);
|
|
6738
|
+
const visibleMessageIds = context?.visibleMessageIds;
|
|
6739
|
+
const visibleOldBlocks = visibleMessageIds === void 0 ? allOldBlocks : allOldBlocks.filter((b) => b.anchorMessageId && visibleMessageIds.has(b.anchorMessageId));
|
|
6740
|
+
if (visibleOldBlocks.length > 5) {
|
|
6741
|
+
const blocksWithRef = visibleOldBlocks.map((block) => {
|
|
6742
|
+
const ref = state.messageIds.byRawId.get(block.anchorMessageId);
|
|
6743
|
+
return ref ? { block, ref } : null;
|
|
6744
|
+
}).filter((x) => x !== null).sort((a, b) => a.ref.localeCompare(b.ref));
|
|
6745
|
+
const totalTokens = blocksWithRef.reduce((s, x) => s + (x.block.summaryTokens ?? 0), 0);
|
|
6746
|
+
const totalK = Math.max(1, Math.round(totalTokens / 1e3));
|
|
6747
|
+
const targets = [];
|
|
6748
|
+
const chunkSize = Math.ceil(blocksWithRef.length / 3);
|
|
6749
|
+
for (let i = 0; i < 3 && i * chunkSize < blocksWithRef.length; i++) {
|
|
6750
|
+
const chunk = blocksWithRef.slice(i * chunkSize, (i + 1) * chunkSize);
|
|
6751
|
+
if (chunk.length < 2) continue;
|
|
6752
|
+
const startRef = chunk[0].ref;
|
|
6753
|
+
const endRef = chunk[chunk.length - 1].ref;
|
|
6754
|
+
const chunkTokens = chunk.reduce((s, x) => s + (x.block.summaryTokens ?? 0), 0);
|
|
6755
|
+
const chunkK = Math.max(1, Math.round(chunkTokens / 1e3));
|
|
6756
|
+
targets.push(` \u2022 compress ${startRef}\u2192${endRef}: ${chunk.length} blocks (~${chunkK}K tokens)`);
|
|
6757
|
+
}
|
|
6758
|
+
if (targets.length > 0) {
|
|
6759
|
+
lines.push(`- \u{1F500} ${blocksWithRef.length} old blocks using ~${totalK}K tokens. Consolidate into ${targets.length}:`);
|
|
6760
|
+
lines.push(...targets);
|
|
6761
|
+
lines.push(` System auto-detects blocks in range \u2014 no need to manually list (bN) placeholders. Just write your summary normally.`);
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
return lines.join("\n");
|
|
6766
|
+
}
|
|
6767
|
+
function appendGuidanceToDcpTag(nudgeText, guidance) {
|
|
6768
|
+
if (!guidance.trim()) {
|
|
6769
|
+
return nudgeText;
|
|
6770
|
+
}
|
|
6771
|
+
const closeTag = "</dcp-system-reminder>";
|
|
6772
|
+
const closeTagIndex = nudgeText.lastIndexOf(closeTag);
|
|
6773
|
+
if (closeTagIndex === -1) {
|
|
6774
|
+
return nudgeText;
|
|
6775
|
+
}
|
|
6776
|
+
const beforeClose = nudgeText.slice(0, closeTagIndex).trimEnd();
|
|
6777
|
+
const afterClose = nudgeText.slice(closeTagIndex);
|
|
6778
|
+
return `${beforeClose}
|
|
6779
|
+
|
|
6780
|
+
${guidance}
|
|
6781
|
+
${afterClose}`;
|
|
6782
|
+
}
|
|
6783
|
+
|
|
6784
|
+
// node_modules/context-compress-algorithms/dist/chunk-BZYW3CH5.js
|
|
6785
|
+
var NUDGE_GROWTH_FLOOR = 6e3;
|
|
6786
|
+
var NUDGE_GROWTH_CAP = 5e4;
|
|
6787
|
+
var NUDGE_GROWTH_RATIO = 0.05;
|
|
6788
|
+
function computeShouldNudge(params) {
|
|
6789
|
+
const { currentTokens, overMinLimit, overMaxLimit } = params;
|
|
6790
|
+
if (currentTokens === void 0) {
|
|
6791
|
+
return { shouldNudge: false, tipsVariant: null };
|
|
6792
|
+
}
|
|
6793
|
+
if (params.lastNudgeTokens === void 0) {
|
|
6794
|
+
return { shouldNudge: false, tipsVariant: null };
|
|
6795
|
+
}
|
|
6796
|
+
const growthSinceLastNudge = currentTokens - params.lastNudgeTokens;
|
|
6797
|
+
const shouldNudge = growthSinceLastNudge >= params.nudgeGrowthTokens || overMaxLimit;
|
|
6798
|
+
if (!shouldNudge) {
|
|
6799
|
+
return { shouldNudge: false, tipsVariant: null };
|
|
6800
|
+
}
|
|
6801
|
+
const tipsVariant = overMaxLimit ? "maxLimit" : overMinLimit ? "minLimit" : "normal";
|
|
6802
|
+
return { shouldNudge: true, tipsVariant };
|
|
6803
|
+
}
|
|
6804
|
+
function resolveAdaptiveNudgeGrowth(modelContextLimit) {
|
|
6805
|
+
if (!modelContextLimit || modelContextLimit <= 0) return NUDGE_GROWTH_FLOOR;
|
|
6806
|
+
return Math.min(
|
|
6807
|
+
NUDGE_GROWTH_CAP,
|
|
6808
|
+
Math.max(NUDGE_GROWTH_FLOOR, Math.round(modelContextLimit * NUDGE_GROWTH_RATIO))
|
|
6809
|
+
);
|
|
6810
|
+
}
|
|
6811
|
+
var defaultTriggerPolicy = {
|
|
6812
|
+
name: "context-compress-algorithms-trigger",
|
|
6813
|
+
version: "1.0.0",
|
|
6814
|
+
description: "Growth-only cadence: nudge when context growth since last nudge exceeds adaptive threshold, or when over max limit.",
|
|
6815
|
+
computeShouldNudge,
|
|
6816
|
+
resolveAdaptiveNudgeGrowth
|
|
6817
|
+
};
|
|
6818
|
+
|
|
6819
|
+
// lib/messages/inject/policy/registry.ts
|
|
6820
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
6821
|
+
var defaultPolicy = null;
|
|
6822
|
+
function registerTriggerPolicy2(policy) {
|
|
6823
|
+
if (!policy.name) {
|
|
6824
|
+
throw new Error("TriggerPolicy must have a name");
|
|
6825
|
+
}
|
|
6826
|
+
registry2.set(policy.name, policy);
|
|
6827
|
+
if (!defaultPolicy) {
|
|
6828
|
+
defaultPolicy = policy;
|
|
6703
6829
|
}
|
|
6704
6830
|
}
|
|
6705
6831
|
function getDefaultTriggerPolicy() {
|
|
@@ -7358,291 +7484,6 @@ function excludeProtectedRanges(ranges, protectedRefs) {
|
|
|
7358
7484
|
);
|
|
7359
7485
|
}
|
|
7360
7486
|
|
|
7361
|
-
// lib/messages/prune-to-fit.ts
|
|
7362
|
-
var WIRE_SAFETY_MARGIN = 8192;
|
|
7363
|
-
var CLEAR_PLACEHOLDER = "[cleared by ACP overflow guard \u2014 re-run tool if needed]";
|
|
7364
|
-
var MIN_CLEAR_TOKENS = 500;
|
|
7365
|
-
function resolveKnownWindow(config, state, providerId, modelId) {
|
|
7366
|
-
if (state.modelContextLimit !== void 0) {
|
|
7367
|
-
return state.modelContextLimit;
|
|
7368
|
-
}
|
|
7369
|
-
const maxLimits = config.compress.modelMaxLimits;
|
|
7370
|
-
if (maxLimits && providerId !== void 0 && modelId !== void 0) {
|
|
7371
|
-
const perModel = maxLimits[`${providerId}/${modelId}`];
|
|
7372
|
-
if (typeof perModel === "number") return perModel;
|
|
7373
|
-
}
|
|
7374
|
-
const global = config.compress.maxContextLimit;
|
|
7375
|
-
if (typeof global === "number") return global;
|
|
7376
|
-
return void 0;
|
|
7377
|
-
}
|
|
7378
|
-
function estimateWireTokens(state, messages) {
|
|
7379
|
-
const base = getCurrentTokenUsage(state, messages);
|
|
7380
|
-
if (base > 0) {
|
|
7381
|
-
let trailing = 0;
|
|
7382
|
-
const lastAsst = [...messages].reverse().find((m) => m.info.role === "assistant");
|
|
7383
|
-
if (lastAsst) {
|
|
7384
|
-
const parts = Array.isArray(lastAsst.parts) ? lastAsst.parts : [];
|
|
7385
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
7386
|
-
const p = parts[i];
|
|
7387
|
-
if (p?.type !== "tool") break;
|
|
7388
|
-
if (p.state.status !== "completed") break;
|
|
7389
|
-
trailing += countTokens2(extractCompletedToolOutput(p) ?? "");
|
|
7390
|
-
}
|
|
7391
|
-
}
|
|
7392
|
-
return base + trailing + WIRE_SAFETY_MARGIN;
|
|
7393
|
-
}
|
|
7394
|
-
let total = 0;
|
|
7395
|
-
for (const msg of messages) {
|
|
7396
|
-
total += countAllMessageTokens(msg);
|
|
7397
|
-
}
|
|
7398
|
-
return total + (state.systemPromptTokens ?? 0) + WIRE_SAFETY_MARGIN;
|
|
7399
|
-
}
|
|
7400
|
-
function pruneToFit(state, config, logger, messages) {
|
|
7401
|
-
if (!config.compress.overflowGuard) return;
|
|
7402
|
-
const knownWindow = resolveKnownWindow(config, state, state.modelProviderID, state.modelID);
|
|
7403
|
-
if (knownWindow === void 0) return;
|
|
7404
|
-
const reserve = config.compress.overflowGuardReserve ?? 32768;
|
|
7405
|
-
const safeBudget = knownWindow - reserve;
|
|
7406
|
-
if (safeBudget <= 0) return;
|
|
7407
|
-
const estimate = estimateWireTokens(state, messages);
|
|
7408
|
-
if (estimate <= safeBudget) return;
|
|
7409
|
-
const protectedRefs = computeProtectedRefs(messages, state, config.compress);
|
|
7410
|
-
const lastNonIgnored = findLastNonIgnoredMessage(messages);
|
|
7411
|
-
const lastMsgId = lastNonIgnored?.message.info.id;
|
|
7412
|
-
const protectedTools = config.compress.protectedTools;
|
|
7413
|
-
const protectedFilePatterns = config.protectedFilePatterns;
|
|
7414
|
-
let freed = 0;
|
|
7415
|
-
let clearedCount = 0;
|
|
7416
|
-
for (const msg of messages) {
|
|
7417
|
-
if (estimate - freed <= safeBudget) break;
|
|
7418
|
-
if (msg.info.id === lastMsgId) continue;
|
|
7419
|
-
if (msg.info.role === "user") continue;
|
|
7420
|
-
const ref = state.messageIds.byRawId.get(msg.info.id);
|
|
7421
|
-
if (ref && protectedRefs.has(ref)) continue;
|
|
7422
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
7423
|
-
for (const part of parts) {
|
|
7424
|
-
if (estimate - freed <= safeBudget) break;
|
|
7425
|
-
if (part?.type !== "tool") continue;
|
|
7426
|
-
const toolState = part.state;
|
|
7427
|
-
if (toolState.status !== "completed") continue;
|
|
7428
|
-
const content = extractCompletedToolOutput(part);
|
|
7429
|
-
if (content === void 0) continue;
|
|
7430
|
-
if (content === CLEAR_PLACEHOLDER) continue;
|
|
7431
|
-
if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
7432
|
-
if (isToolNameProtected(part.tool, protectedTools)) continue;
|
|
7433
|
-
if (protectedFilePatterns.length > 0) {
|
|
7434
|
-
const filePaths = getFilePathsFromParameters(part.tool, toolState.input);
|
|
7435
|
-
if (isFilePathProtected(filePaths, protectedFilePatterns)) continue;
|
|
7436
|
-
}
|
|
7437
|
-
const outputTokens = countTokens2(content);
|
|
7438
|
-
if (outputTokens < MIN_CLEAR_TOKENS) continue;
|
|
7439
|
-
toolState.output = CLEAR_PLACEHOLDER;
|
|
7440
|
-
freed += outputTokens - countTokens2(CLEAR_PLACEHOLDER);
|
|
7441
|
-
clearedCount++;
|
|
7442
|
-
}
|
|
7443
|
-
}
|
|
7444
|
-
const after = estimate - freed;
|
|
7445
|
-
const detail = {
|
|
7446
|
-
session: state.sessionId,
|
|
7447
|
-
estimate,
|
|
7448
|
-
safeBudget,
|
|
7449
|
-
knownWindow,
|
|
7450
|
-
reserve,
|
|
7451
|
-
clearedCount,
|
|
7452
|
-
freedTokens: Math.round(freed),
|
|
7453
|
-
afterTokens: Math.round(after)
|
|
7454
|
-
};
|
|
7455
|
-
if (clearedCount === 0) {
|
|
7456
|
-
logger.error("ACP overflow guard: over window but no clearable tool outputs", detail);
|
|
7457
|
-
return;
|
|
7458
|
-
}
|
|
7459
|
-
if (after <= safeBudget) {
|
|
7460
|
-
logger.warn("ACP overflow guard: cleared tool outputs to fit context window", detail);
|
|
7461
|
-
} else {
|
|
7462
|
-
logger.error(
|
|
7463
|
-
"ACP overflow guard: cleared tool outputs but context STILL exceeds window",
|
|
7464
|
-
detail
|
|
7465
|
-
);
|
|
7466
|
-
}
|
|
7467
|
-
}
|
|
7468
|
-
|
|
7469
|
-
// lib/messages/uncalibrated-window.ts
|
|
7470
|
-
var UNCALIBRATED_WINDOW_WARN_THRESHOLD = 3;
|
|
7471
|
-
function trackUncalibratedWindow(state, logger) {
|
|
7472
|
-
if (state.modelContextLimit === void 0) {
|
|
7473
|
-
state.uncalibratedWindowTransforms++;
|
|
7474
|
-
if (state.uncalibratedWindowTransforms >= UNCALIBRATED_WINDOW_WARN_THRESHOLD && !state.uncalibratedWindowWarned) {
|
|
7475
|
-
state.uncalibratedWindowWarned = true;
|
|
7476
|
-
logger.warn(
|
|
7477
|
-
"Model reports no context window \u2014 ACP percentage thresholds are disabled",
|
|
7478
|
-
{
|
|
7479
|
-
session: state.sessionId,
|
|
7480
|
-
provider: state.modelProviderID,
|
|
7481
|
-
model: state.modelID,
|
|
7482
|
-
transforms: state.uncalibratedWindowTransforms,
|
|
7483
|
-
hint: 'set the model\'s `limit` in opencode.json (e.g. {"context": 262144, "output": 16384}) or use absolute compress.maxContextLimit / compress.minContextLimit in acp.jsonc; the request-side overflow guard also needs a known window to fire'
|
|
7484
|
-
}
|
|
7485
|
-
);
|
|
7486
|
-
}
|
|
7487
|
-
} else {
|
|
7488
|
-
state.uncalibratedWindowTransforms = 0;
|
|
7489
|
-
}
|
|
7490
|
-
}
|
|
7491
|
-
|
|
7492
|
-
// lib/messages/sync.ts
|
|
7493
|
-
function sortBlocksByCreation(a, b) {
|
|
7494
|
-
const createdAtDiff = a.createdAt - b.createdAt;
|
|
7495
|
-
if (createdAtDiff !== 0) {
|
|
7496
|
-
return createdAtDiff;
|
|
7497
|
-
}
|
|
7498
|
-
return a.blockId - b.blockId;
|
|
7499
|
-
}
|
|
7500
|
-
var syncCompressionBlocks = (state, logger, messages) => {
|
|
7501
|
-
const messagesState = state.prune.messages;
|
|
7502
|
-
if (!messagesState?.blocksById?.size) {
|
|
7503
|
-
return;
|
|
7504
|
-
}
|
|
7505
|
-
const messageIds = new Set(messages.map((msg) => msg.info.id));
|
|
7506
|
-
const previousActiveBlockIds = new Set(
|
|
7507
|
-
Array.from(messagesState.blocksById.values()).filter((block) => block.active).map((block) => block.blockId)
|
|
7508
|
-
);
|
|
7509
|
-
messagesState.activeBlockIds.clear();
|
|
7510
|
-
messagesState.activeByAnchorMessageId.clear();
|
|
7511
|
-
const now = Date.now();
|
|
7512
|
-
const orderedBlocks = Array.from(messagesState.blocksById.values()).sort(sortBlocksByCreation);
|
|
7513
|
-
for (const block of orderedBlocks) {
|
|
7514
|
-
if (block.deactivatedByUser || block.deactivatedByUserDeep) {
|
|
7515
|
-
block.active = false;
|
|
7516
|
-
if (block.deactivatedAt === void 0) {
|
|
7517
|
-
block.deactivatedAt = now;
|
|
7518
|
-
}
|
|
7519
|
-
block.deactivatedByBlockId = void 0;
|
|
7520
|
-
continue;
|
|
7521
|
-
}
|
|
7522
|
-
for (const consumedBlockId of block.consumedBlockIds) {
|
|
7523
|
-
if (!messagesState.activeBlockIds.has(consumedBlockId)) {
|
|
7524
|
-
continue;
|
|
7525
|
-
}
|
|
7526
|
-
const consumedBlock = messagesState.blocksById.get(consumedBlockId);
|
|
7527
|
-
if (consumedBlock) {
|
|
7528
|
-
consumedBlock.active = false;
|
|
7529
|
-
consumedBlock.deactivatedAt = now;
|
|
7530
|
-
consumedBlock.deactivatedByBlockId = block.blockId;
|
|
7531
|
-
const mappedBlockId = messagesState.activeByAnchorMessageId.get(
|
|
7532
|
-
consumedBlock.anchorMessageId
|
|
7533
|
-
);
|
|
7534
|
-
if (mappedBlockId === consumedBlock.blockId) {
|
|
7535
|
-
messagesState.activeByAnchorMessageId.delete(consumedBlock.anchorMessageId);
|
|
7536
|
-
}
|
|
7537
|
-
}
|
|
7538
|
-
messagesState.activeBlockIds.delete(consumedBlockId);
|
|
7539
|
-
}
|
|
7540
|
-
block.active = true;
|
|
7541
|
-
block.deactivatedAt = void 0;
|
|
7542
|
-
block.deactivatedByBlockId = void 0;
|
|
7543
|
-
messagesState.activeBlockIds.add(block.blockId);
|
|
7544
|
-
if (messageIds.has(block.anchorMessageId)) {
|
|
7545
|
-
messagesState.activeByAnchorMessageId.set(block.anchorMessageId, block.blockId);
|
|
7546
|
-
}
|
|
7547
|
-
}
|
|
7548
|
-
for (const entry of messagesState.byMessageId.values()) {
|
|
7549
|
-
const allBlockIds = Array.isArray(entry.allBlockIds) ? [...new Set(entry.allBlockIds.filter((id) => Number.isInteger(id) && id > 0))] : [];
|
|
7550
|
-
entry.allBlockIds = allBlockIds;
|
|
7551
|
-
entry.activeBlockIds = allBlockIds.filter((id) => messagesState.activeBlockIds.has(id));
|
|
7552
|
-
}
|
|
7553
|
-
const nextActiveBlockIds = messagesState.activeBlockIds;
|
|
7554
|
-
let deactivatedCount = 0;
|
|
7555
|
-
let reactivatedCount = 0;
|
|
7556
|
-
for (const blockId of previousActiveBlockIds) {
|
|
7557
|
-
if (!nextActiveBlockIds.has(blockId)) {
|
|
7558
|
-
deactivatedCount++;
|
|
7559
|
-
}
|
|
7560
|
-
}
|
|
7561
|
-
for (const blockId of nextActiveBlockIds) {
|
|
7562
|
-
if (!previousActiveBlockIds.has(blockId)) {
|
|
7563
|
-
reactivatedCount++;
|
|
7564
|
-
}
|
|
7565
|
-
}
|
|
7566
|
-
if (deactivatedCount > 0 || reactivatedCount > 0) {
|
|
7567
|
-
logger.info("Synced compress block state", {
|
|
7568
|
-
deactivatedCount,
|
|
7569
|
-
reactivatedCount
|
|
7570
|
-
});
|
|
7571
|
-
}
|
|
7572
|
-
};
|
|
7573
|
-
|
|
7574
|
-
// lib/host-permissions.ts
|
|
7575
|
-
var findLastMatchingRule = (rules, predicate) => {
|
|
7576
|
-
for (let index = rules.length - 1; index >= 0; index -= 1) {
|
|
7577
|
-
const rule = rules[index];
|
|
7578
|
-
if (rule && predicate(rule)) {
|
|
7579
|
-
return rule;
|
|
7580
|
-
}
|
|
7581
|
-
}
|
|
7582
|
-
return void 0;
|
|
7583
|
-
};
|
|
7584
|
-
var wildcardMatch = (value, pattern) => {
|
|
7585
|
-
const normalizedValue = value.replaceAll("\\", "/");
|
|
7586
|
-
let escaped = pattern.replaceAll("\\", "/").replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
7587
|
-
if (escaped.endsWith(" .*")) {
|
|
7588
|
-
escaped = escaped.slice(0, -3) + "( .*)?";
|
|
7589
|
-
}
|
|
7590
|
-
const flags = process.platform === "win32" ? "si" : "s";
|
|
7591
|
-
return new RegExp(`^${escaped}$`, flags).test(normalizedValue);
|
|
7592
|
-
};
|
|
7593
|
-
var getPermissionRules = (permissionConfigs) => {
|
|
7594
|
-
const rules = [];
|
|
7595
|
-
for (const permissionConfig of permissionConfigs) {
|
|
7596
|
-
if (!permissionConfig) {
|
|
7597
|
-
continue;
|
|
7598
|
-
}
|
|
7599
|
-
for (const [permission, value] of Object.entries(permissionConfig)) {
|
|
7600
|
-
if (value === "ask" || value === "allow" || value === "deny") {
|
|
7601
|
-
rules.push({ permission, pattern: "*", action: value });
|
|
7602
|
-
continue;
|
|
7603
|
-
}
|
|
7604
|
-
for (const [pattern, action] of Object.entries(value)) {
|
|
7605
|
-
if (action === "ask" || action === "allow" || action === "deny") {
|
|
7606
|
-
rules.push({ permission, pattern, action });
|
|
7607
|
-
}
|
|
7608
|
-
}
|
|
7609
|
-
}
|
|
7610
|
-
}
|
|
7611
|
-
return rules;
|
|
7612
|
-
};
|
|
7613
|
-
var compressDisabledByOpencode = (...permissionConfigs) => {
|
|
7614
|
-
const match = findLastMatchingRule(
|
|
7615
|
-
getPermissionRules(permissionConfigs),
|
|
7616
|
-
(rule) => wildcardMatch("compress", rule.permission)
|
|
7617
|
-
);
|
|
7618
|
-
return match?.pattern === "*" && match.action === "deny";
|
|
7619
|
-
};
|
|
7620
|
-
var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agentName) => {
|
|
7621
|
-
if (basePermission === "deny") {
|
|
7622
|
-
return "deny";
|
|
7623
|
-
}
|
|
7624
|
-
return compressDisabledByOpencode(
|
|
7625
|
-
hostPermissions.global,
|
|
7626
|
-
agentName ? hostPermissions.agents[agentName] : void 0
|
|
7627
|
-
) ? "deny" : basePermission;
|
|
7628
|
-
};
|
|
7629
|
-
var hasExplicitToolPermission = (permissionConfig, tool6) => {
|
|
7630
|
-
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
|
|
7631
|
-
};
|
|
7632
|
-
|
|
7633
|
-
// lib/compress-permission.ts
|
|
7634
|
-
var compressPermission = (state, config) => {
|
|
7635
|
-
return state.compressPermission ?? config.compress.permission;
|
|
7636
|
-
};
|
|
7637
|
-
var syncCompressPermissionState = (state, config, hostPermissions, messages) => {
|
|
7638
|
-
const activeAgent = getLastUserMessage(messages)?.info.agent;
|
|
7639
|
-
state.compressPermission = resolveEffectiveCompressPermission(
|
|
7640
|
-
config.compress.permission,
|
|
7641
|
-
hostPermissions,
|
|
7642
|
-
activeAgent
|
|
7643
|
-
);
|
|
7644
|
-
};
|
|
7645
|
-
|
|
7646
7487
|
// lib/messages/inject/inject.ts
|
|
7647
7488
|
var ACP_SUFFIX_SEED = "acp-dynamic-guidance";
|
|
7648
7489
|
function createSuffixMessage(messages) {
|
|
@@ -9272,7 +9113,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
9272
9113
|
import { join as join3 } from "path";
|
|
9273
9114
|
import { existsSync as existsSync3 } from "fs";
|
|
9274
9115
|
import { homedir as homedir3 } from "os";
|
|
9275
|
-
var LOG_VERSION = true ? "1.14.25-pr.
|
|
9116
|
+
var LOG_VERSION = true ? "1.14.25-pr.350.73" : "dev";
|
|
9276
9117
|
var LEVEL_RANK = {
|
|
9277
9118
|
debug: 10,
|
|
9278
9119
|
info: 20,
|
|
@@ -10157,6 +9998,148 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
10157
9998
|
}
|
|
10158
9999
|
}
|
|
10159
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
|
+
|
|
10160
10143
|
// lib/commands/context.ts
|
|
10161
10144
|
function analyzeTokens(state, messages) {
|
|
10162
10145
|
const breakdown = {
|
|
@@ -11258,7 +11241,16 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11258
11241
|
});
|
|
11259
11242
|
}
|
|
11260
11243
|
await updatePerTurnState(state, logger, messages);
|
|
11261
|
-
|
|
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
|
+
}
|
|
11262
11254
|
}
|
|
11263
11255
|
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
11264
11256
|
if (state.isSubAgent && !config.allowSubAgents) {
|
|
@@ -11289,7 +11281,7 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11289
11281
|
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
11290
11282
|
prune(state, logger, config, output.messages);
|
|
11291
11283
|
truncateLargeToolOutputs(state, config, logger, output.messages);
|
|
11292
|
-
|
|
11284
|
+
enforceContextBudget(state, config, logger, output.messages);
|
|
11293
11285
|
hideConsumedCompressCalls(state, output.messages);
|
|
11294
11286
|
assignMessageRefs(state, output.messages);
|
|
11295
11287
|
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
@@ -11715,7 +11707,7 @@ var server = (async (ctx) => {
|
|
|
11715
11707
|
}
|
|
11716
11708
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
11717
11709
|
logger.info("ACP plugin initialized", {
|
|
11718
|
-
version: true ? "1.14.25-pr.
|
|
11710
|
+
version: true ? "1.14.25-pr.350.73" : "dev",
|
|
11719
11711
|
workspace: ctx.directory,
|
|
11720
11712
|
logLevel: logger.level,
|
|
11721
11713
|
debug: config.debug,
|