billion-context-pi 0.1.46 → 0.1.47-pr.216.12
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 +740 -98
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -324,7 +324,7 @@ function viableRanges(ranges) {
|
|
|
324
324
|
return ranges.filter((r) => r.tokens >= VIABLE_RANGE_MIN_TOKENS);
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
// node_modules/acp-kernel/dist/chunk-
|
|
327
|
+
// node_modules/acp-kernel/dist/chunk-UX4LINT7.js
|
|
328
328
|
function createInitialState() {
|
|
329
329
|
return {
|
|
330
330
|
blocks: [],
|
|
@@ -337,7 +337,8 @@ function createInitialState() {
|
|
|
337
337
|
anchors: {},
|
|
338
338
|
lastShownByTier: {}
|
|
339
339
|
},
|
|
340
|
-
stats: { tokensCompressed: 0, compressionCount: 0 },
|
|
340
|
+
stats: { tokensCompressed: 0, compressionCount: 0, absorbedTokens: 0 },
|
|
341
|
+
absorbed: [],
|
|
341
342
|
nextBlockId: 1,
|
|
342
343
|
nextRunId: 1
|
|
343
344
|
};
|
|
@@ -601,6 +602,7 @@ function syncBlocks(messages, state) {
|
|
|
601
602
|
tokenSnapshot: { ...state.tokenSnapshot ?? {} },
|
|
602
603
|
nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
|
|
603
604
|
stats: { ...state.stats },
|
|
605
|
+
absorbed: (state.absorbed ?? []).map((record) => ({ ...record })),
|
|
604
606
|
nextBlockId: state.nextBlockId,
|
|
605
607
|
nextRunId: state.nextRunId
|
|
606
608
|
};
|
|
@@ -661,7 +663,14 @@ function defaultConfig(modelContextLimit, overrides = {}) {
|
|
|
661
663
|
protectedTools: [],
|
|
662
664
|
preserveRecentMessages: 5,
|
|
663
665
|
preserveRecentTokens: 5e3,
|
|
664
|
-
modelContextLimit
|
|
666
|
+
modelContextLimit,
|
|
667
|
+
absorb: {
|
|
668
|
+
enabled: false,
|
|
669
|
+
toolName: "absorb",
|
|
670
|
+
minToolTokens: 1e3,
|
|
671
|
+
contextThresholdPct: 0,
|
|
672
|
+
excludeTools: []
|
|
673
|
+
}
|
|
665
674
|
};
|
|
666
675
|
return {
|
|
667
676
|
...base,
|
|
@@ -669,7 +678,8 @@ function defaultConfig(modelContextLimit, overrides = {}) {
|
|
|
669
678
|
tiers: { ...base.tiers, ...overrides.tiers },
|
|
670
679
|
nudge: { ...base.nudge, ...overrides.nudge },
|
|
671
680
|
truncate: { ...base.truncate, ...overrides.truncate },
|
|
672
|
-
compress: { ...base.compress, ...overrides.compress }
|
|
681
|
+
compress: { ...base.compress, ...overrides.compress },
|
|
682
|
+
absorb: overrides.absorb ? { ...base.absorb, ...overrides.absorb } : base.absorb
|
|
673
683
|
};
|
|
674
684
|
}
|
|
675
685
|
function validateConfig(config) {
|
|
@@ -699,6 +709,17 @@ function validateConfig(config) {
|
|
|
699
709
|
if (config.tiers.tier3Trigger <= config.tiers.tier2Trigger) {
|
|
700
710
|
errors.push("tiers.tier3Trigger must be greater than tiers.tier2Trigger");
|
|
701
711
|
}
|
|
712
|
+
if (config.absorb) {
|
|
713
|
+
if (config.absorb.enabled && !config.absorb.toolName) {
|
|
714
|
+
errors.push("absorb.toolName must be a non-empty string when enabled");
|
|
715
|
+
}
|
|
716
|
+
if (!Number.isFinite(config.absorb.minToolTokens) || config.absorb.minToolTokens < 0) {
|
|
717
|
+
errors.push("absorb.minToolTokens must be >= 0");
|
|
718
|
+
}
|
|
719
|
+
if (config.absorb.contextThresholdPct < 0 || config.absorb.contextThresholdPct > 1) {
|
|
720
|
+
errors.push("absorb.contextThresholdPct must be in [0, 1]");
|
|
721
|
+
}
|
|
722
|
+
}
|
|
702
723
|
return errors;
|
|
703
724
|
}
|
|
704
725
|
var MESSAGE_REF_PATTERN = /^m0*(\d{1,5})$/;
|
|
@@ -1055,6 +1076,306 @@ function hideConsumedCompressCalls(state, messages) {
|
|
|
1055
1076
|
}
|
|
1056
1077
|
return { messages: result, hidden };
|
|
1057
1078
|
}
|
|
1079
|
+
var COMPRESS_TOOL_NAME = "compress";
|
|
1080
|
+
var DECOMPRESS_TOOL_NAME = "decompress";
|
|
1081
|
+
var SEARCH_CONTEXT_TOOL_NAME = "search_context";
|
|
1082
|
+
var ACP_STATUS_TOOL_NAME = "acp_status";
|
|
1083
|
+
var ABSORB_TOOL_NAME = "absorb";
|
|
1084
|
+
var COMPRESS_TOOL = {
|
|
1085
|
+
name: COMPRESS_TOOL_NAME,
|
|
1086
|
+
description: "Replace a contiguous range of older conversation with a detailed summary you write. Use when content is genuinely consumed. Batch form: content=[{startId,endId,summary,topic?}]. REQUIRED \u2014 compress without content is invalid.",
|
|
1087
|
+
input_schema: {
|
|
1088
|
+
type: "object",
|
|
1089
|
+
properties: {
|
|
1090
|
+
topic: {
|
|
1091
|
+
type: "string",
|
|
1092
|
+
description: "Optional short title for the compressed range"
|
|
1093
|
+
},
|
|
1094
|
+
content: {
|
|
1095
|
+
type: "array",
|
|
1096
|
+
description: "One or more ranges to compress into separate summary blocks",
|
|
1097
|
+
items: {
|
|
1098
|
+
type: "object",
|
|
1099
|
+
properties: {
|
|
1100
|
+
topic: { type: "string" },
|
|
1101
|
+
startId: {
|
|
1102
|
+
type: "string",
|
|
1103
|
+
description: "mNNNNN ref at the start of the range"
|
|
1104
|
+
},
|
|
1105
|
+
endId: {
|
|
1106
|
+
type: "string",
|
|
1107
|
+
description: "mNNNNN ref at the end of the range"
|
|
1108
|
+
},
|
|
1109
|
+
summary: {
|
|
1110
|
+
type: "string",
|
|
1111
|
+
description: "Self-contained summary replacing the range"
|
|
1112
|
+
}
|
|
1113
|
+
},
|
|
1114
|
+
required: ["startId", "endId", "summary"]
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
},
|
|
1118
|
+
required: ["content"]
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
var COMPRESS_TOOL_OPENAI = {
|
|
1122
|
+
type: "function",
|
|
1123
|
+
function: {
|
|
1124
|
+
name: COMPRESS_TOOL_NAME,
|
|
1125
|
+
description: COMPRESS_TOOL.description,
|
|
1126
|
+
parameters: {
|
|
1127
|
+
type: "object",
|
|
1128
|
+
properties: {
|
|
1129
|
+
topic: {
|
|
1130
|
+
type: "string",
|
|
1131
|
+
description: "Optional short title for the compressed range"
|
|
1132
|
+
},
|
|
1133
|
+
content: {
|
|
1134
|
+
type: "array",
|
|
1135
|
+
description: "One or more ranges to compress into separate summary blocks. REQUIRED \u2014 compress without content is invalid.",
|
|
1136
|
+
items: {
|
|
1137
|
+
type: "object",
|
|
1138
|
+
properties: {
|
|
1139
|
+
topic: { type: "string" },
|
|
1140
|
+
startId: {
|
|
1141
|
+
type: "string",
|
|
1142
|
+
description: "mNNNNN ref at the start of the range"
|
|
1143
|
+
},
|
|
1144
|
+
endId: {
|
|
1145
|
+
type: "string",
|
|
1146
|
+
description: "mNNNNN ref at the end of the range"
|
|
1147
|
+
},
|
|
1148
|
+
summary: {
|
|
1149
|
+
type: "string",
|
|
1150
|
+
description: "Self-contained summary replacing the range"
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
required: ["startId", "endId", "summary"]
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
},
|
|
1157
|
+
required: ["content"]
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
var DECOMPRESS_TOOL_OPENAI = {
|
|
1162
|
+
type: "function",
|
|
1163
|
+
function: {
|
|
1164
|
+
name: DECOMPRESS_TOOL_NAME,
|
|
1165
|
+
description: "Restores previously compressed content. Use when you need exact details lost in compression. By default restores one tier up. Use full:true for all the way to original messages. Use toFile to write to file instead of inflating context.",
|
|
1166
|
+
parameters: {
|
|
1167
|
+
type: "object",
|
|
1168
|
+
properties: {
|
|
1169
|
+
blockId: {
|
|
1170
|
+
type: "string",
|
|
1171
|
+
description: "Block ID to decompress (e.g. b5)"
|
|
1172
|
+
},
|
|
1173
|
+
toFile: {
|
|
1174
|
+
type: "string",
|
|
1175
|
+
description: "Optional: write content to file instead of context"
|
|
1176
|
+
},
|
|
1177
|
+
full: {
|
|
1178
|
+
type: "boolean",
|
|
1179
|
+
description: "Restore all the way to original messages"
|
|
1180
|
+
}
|
|
1181
|
+
},
|
|
1182
|
+
required: ["blockId"]
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
var SEARCH_CONTEXT_TOOL_OPENAI = {
|
|
1187
|
+
type: "function",
|
|
1188
|
+
function: {
|
|
1189
|
+
name: SEARCH_CONTEXT_TOOL_NAME,
|
|
1190
|
+
description: "Search through compressed block summaries by keyword. Use BEFORE decompressing to find the right block.",
|
|
1191
|
+
parameters: {
|
|
1192
|
+
type: "object",
|
|
1193
|
+
properties: {
|
|
1194
|
+
query: { type: "string", description: "Search query" },
|
|
1195
|
+
limit: { type: "number", description: "Max results (default 5)" }
|
|
1196
|
+
},
|
|
1197
|
+
required: ["query"]
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
var ACP_STATUS_TOOL_OPENAI = {
|
|
1202
|
+
type: "function",
|
|
1203
|
+
function: {
|
|
1204
|
+
name: ACP_STATUS_TOOL_NAME,
|
|
1205
|
+
description: "Show context usage and compressible ranges. No args = overview. Use to find what to compress next.",
|
|
1206
|
+
parameters: {
|
|
1207
|
+
type: "object",
|
|
1208
|
+
properties: {}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
var DECOMPRESS_TOOL = {
|
|
1213
|
+
name: DECOMPRESS_TOOL_NAME,
|
|
1214
|
+
description: DECOMPRESS_TOOL_OPENAI.function.description,
|
|
1215
|
+
input_schema: DECOMPRESS_TOOL_OPENAI.function.parameters
|
|
1216
|
+
};
|
|
1217
|
+
var SEARCH_CONTEXT_TOOL = {
|
|
1218
|
+
name: SEARCH_CONTEXT_TOOL_NAME,
|
|
1219
|
+
description: SEARCH_CONTEXT_TOOL_OPENAI.function.description,
|
|
1220
|
+
input_schema: SEARCH_CONTEXT_TOOL_OPENAI.function.parameters
|
|
1221
|
+
};
|
|
1222
|
+
var ACP_STATUS_TOOL = {
|
|
1223
|
+
name: ACP_STATUS_TOOL_NAME,
|
|
1224
|
+
description: ACP_STATUS_TOOL_OPENAI.function.description,
|
|
1225
|
+
input_schema: ACP_STATUS_TOOL_OPENAI.function.parameters
|
|
1226
|
+
};
|
|
1227
|
+
var COMPRESS_TOOL_RESPONSES = {
|
|
1228
|
+
type: "function",
|
|
1229
|
+
name: COMPRESS_TOOL_NAME,
|
|
1230
|
+
description: COMPRESS_TOOL.description,
|
|
1231
|
+
parameters: COMPRESS_TOOL_OPENAI.function.parameters
|
|
1232
|
+
};
|
|
1233
|
+
var DECOMPRESS_TOOL_RESPONSES = {
|
|
1234
|
+
type: "function",
|
|
1235
|
+
name: DECOMPRESS_TOOL_OPENAI.function.name,
|
|
1236
|
+
description: DECOMPRESS_TOOL_OPENAI.function.description,
|
|
1237
|
+
parameters: DECOMPRESS_TOOL_OPENAI.function.parameters
|
|
1238
|
+
};
|
|
1239
|
+
var SEARCH_CONTEXT_TOOL_RESPONSES = {
|
|
1240
|
+
type: "function",
|
|
1241
|
+
name: SEARCH_CONTEXT_TOOL_OPENAI.function.name,
|
|
1242
|
+
description: SEARCH_CONTEXT_TOOL_OPENAI.function.description,
|
|
1243
|
+
parameters: SEARCH_CONTEXT_TOOL_OPENAI.function.parameters
|
|
1244
|
+
};
|
|
1245
|
+
var ACP_STATUS_TOOL_RESPONSES = {
|
|
1246
|
+
type: "function",
|
|
1247
|
+
name: ACP_STATUS_TOOL_OPENAI.function.name,
|
|
1248
|
+
description: ACP_STATUS_TOOL_OPENAI.function.description,
|
|
1249
|
+
parameters: ACP_STATUS_TOOL_OPENAI.function.parameters
|
|
1250
|
+
};
|
|
1251
|
+
var ACP_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1252
|
+
COMPRESS_TOOL_NAME,
|
|
1253
|
+
DECOMPRESS_TOOL_NAME,
|
|
1254
|
+
SEARCH_CONTEXT_TOOL_NAME,
|
|
1255
|
+
ACP_STATUS_TOOL_NAME
|
|
1256
|
+
]);
|
|
1257
|
+
var ALWAYS_PROTECTED_TOOLS = ["compress"];
|
|
1258
|
+
var NEVER_PRESERVE_RECENT_TOOLS = [
|
|
1259
|
+
"decompress",
|
|
1260
|
+
"search_context",
|
|
1261
|
+
"read",
|
|
1262
|
+
"bash"
|
|
1263
|
+
];
|
|
1264
|
+
function isNeverPreserveRecent(msg) {
|
|
1265
|
+
if (msg.contentType !== "tool-call" && msg.contentType !== "tool-result") {
|
|
1266
|
+
return false;
|
|
1267
|
+
}
|
|
1268
|
+
if (!msg.toolName) return false;
|
|
1269
|
+
return NEVER_PRESERVE_RECENT_TOOLS.includes(msg.toolName);
|
|
1270
|
+
}
|
|
1271
|
+
function matchToolPattern(toolName, pattern) {
|
|
1272
|
+
if (pattern.endsWith("*")) {
|
|
1273
|
+
return toolName.startsWith(pattern.slice(0, -1));
|
|
1274
|
+
}
|
|
1275
|
+
return toolName === pattern;
|
|
1276
|
+
}
|
|
1277
|
+
function isMessageProtected(msg, config) {
|
|
1278
|
+
if (msg.contentType !== "tool-call" && msg.contentType !== "tool-result" || !msg.toolName) {
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
if (ALWAYS_PROTECTED_TOOLS.includes(msg.toolName)) {
|
|
1282
|
+
return true;
|
|
1283
|
+
}
|
|
1284
|
+
for (const pattern of config.protectedTools) {
|
|
1285
|
+
if (matchToolPattern(msg.toolName, pattern)) return true;
|
|
1286
|
+
}
|
|
1287
|
+
if (config.isToolProtected?.(msg.toolName, msg.text)) return true;
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
function collectProtectedToolCallIds(messages, config) {
|
|
1291
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1292
|
+
for (const m of messages) {
|
|
1293
|
+
if (m.contentType === "tool-call" && m.toolCallId && isMessageProtected(m, config)) {
|
|
1294
|
+
ids.add(m.toolCallId);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return ids;
|
|
1298
|
+
}
|
|
1299
|
+
function isMessageProtectedWithPairing(msg, config, protectedCallIds) {
|
|
1300
|
+
if (isMessageProtected(msg, config)) return true;
|
|
1301
|
+
if (msg.contentType === "tool-result" && msg.toolCallId && protectedCallIds.has(msg.toolCallId)) {
|
|
1302
|
+
return true;
|
|
1303
|
+
}
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
var ABSORB_PROMPT_MARKER = "[ACP absorb]";
|
|
1307
|
+
var DEFAULT_ABSORB_CONFIG = {
|
|
1308
|
+
enabled: false,
|
|
1309
|
+
toolName: ABSORB_TOOL_NAME,
|
|
1310
|
+
minToolTokens: 1e3,
|
|
1311
|
+
contextThresholdPct: 0,
|
|
1312
|
+
excludeTools: []
|
|
1313
|
+
};
|
|
1314
|
+
function resolveAbsorbConfig(config) {
|
|
1315
|
+
return { ...DEFAULT_ABSORB_CONFIG, ...config.absorb ?? {} };
|
|
1316
|
+
}
|
|
1317
|
+
function formatTokenCount(tokens) {
|
|
1318
|
+
if (tokens < 1e3) return String(tokens);
|
|
1319
|
+
if (tokens < 1e4) return (tokens / 1e3).toFixed(1) + "K";
|
|
1320
|
+
return Math.round(tokens / 1e3) + "K";
|
|
1321
|
+
}
|
|
1322
|
+
function buildAbsorbPrompt(ref, tokens, toolName = ABSORB_TOOL_NAME) {
|
|
1323
|
+
return `${ABSORB_PROMPT_MARKER} This tool result (~${formatTokenCount(tokens)} tokens) will be REMOVED from context. Your IMMEDIATE next action: call ${toolName}({ ref: "${ref}", summary: "..." }) \u2014 summary = distilled essentials only (outcome, key values, exact paths:lines, error text verbatim, decisions). Afterwards work from your summary; do NOT re-run this tool. If the result contains nothing you need, call ${toolName} with summary "(nothing needed)".`;
|
|
1324
|
+
}
|
|
1325
|
+
function isAcpOrConfiguredTool(toolName, cfg) {
|
|
1326
|
+
if (!toolName) return false;
|
|
1327
|
+
if (toolName === cfg.toolName) return true;
|
|
1328
|
+
return ACP_TOOL_NAMES.has(toolName);
|
|
1329
|
+
}
|
|
1330
|
+
function isAbsorbCandidate(msg, config) {
|
|
1331
|
+
if (msg.contentType !== "tool-result" || !msg.toolCallId) return false;
|
|
1332
|
+
const cfg = resolveAbsorbConfig(config);
|
|
1333
|
+
if (isAcpOrConfiguredTool(msg.toolName, cfg)) return false;
|
|
1334
|
+
if (isMessageProtected(msg, config)) return false;
|
|
1335
|
+
for (const pattern of cfg.excludeTools) {
|
|
1336
|
+
if (msg.toolName && matchToolPattern(msg.toolName, pattern)) return false;
|
|
1337
|
+
}
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1340
|
+
function hideAbsorbedMessages(messages, state) {
|
|
1341
|
+
const records = state.absorbed ?? [];
|
|
1342
|
+
if (records.length === 0) return messages;
|
|
1343
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
1344
|
+
for (const record of records) {
|
|
1345
|
+
if (record.callMessageId) hidden.add(record.callMessageId);
|
|
1346
|
+
if (record.resultMessageId) hidden.add(record.resultMessageId);
|
|
1347
|
+
}
|
|
1348
|
+
return messages.filter((msg) => !hidden.has(msg.id));
|
|
1349
|
+
}
|
|
1350
|
+
function appendAbsorbPrompts(messages, state, config, tokenCount, countTokens) {
|
|
1351
|
+
const cfg = resolveAbsorbConfig(config);
|
|
1352
|
+
if (!cfg.enabled) return { messages, promptedCount: 0 };
|
|
1353
|
+
const limit = config.modelContextLimit;
|
|
1354
|
+
if (cfg.contextThresholdPct > 0 && limit > 0 && tokenCount < cfg.contextThresholdPct * limit) {
|
|
1355
|
+
return { messages, promptedCount: 0 };
|
|
1356
|
+
}
|
|
1357
|
+
const absorbedIds = /* @__PURE__ */ new Set();
|
|
1358
|
+
for (const record of state.absorbed ?? []) {
|
|
1359
|
+
if (record.resultMessageId) absorbedIds.add(record.resultMessageId);
|
|
1360
|
+
}
|
|
1361
|
+
let promptedCount = 0;
|
|
1362
|
+
const out = messages.map((msg) => {
|
|
1363
|
+
if (!isAbsorbCandidate(msg, config)) return msg;
|
|
1364
|
+
if (absorbedIds.has(msg.id)) return msg;
|
|
1365
|
+
const text = msg.text ?? "";
|
|
1366
|
+
if (text.includes(ABSORB_PROMPT_MARKER)) return msg;
|
|
1367
|
+
const tokens = countTokens(text);
|
|
1368
|
+
if (tokens < cfg.minToolTokens) return msg;
|
|
1369
|
+
const ref = refForRaw(state.messageRefs, msg.id);
|
|
1370
|
+
if (!ref || ref === BLOCKED_REF) return msg;
|
|
1371
|
+
promptedCount++;
|
|
1372
|
+
return {
|
|
1373
|
+
...msg,
|
|
1374
|
+
text: text + "\n\n" + buildAbsorbPrompt(ref, tokens, cfg.toolName)
|
|
1375
|
+
};
|
|
1376
|
+
});
|
|
1377
|
+
return { messages: out, promptedCount };
|
|
1378
|
+
}
|
|
1058
1379
|
var registry = /* @__PURE__ */ new Map();
|
|
1059
1380
|
function listMessageFilters() {
|
|
1060
1381
|
return [...registry.values()];
|
|
@@ -1205,55 +1526,6 @@ function createRenderRefsNode(strategy) {
|
|
|
1205
1526
|
};
|
|
1206
1527
|
}
|
|
1207
1528
|
var renderRefsNode = createRenderRefsNode("all");
|
|
1208
|
-
var ALWAYS_PROTECTED_TOOLS = ["compress"];
|
|
1209
|
-
var NEVER_PRESERVE_RECENT_TOOLS = [
|
|
1210
|
-
"decompress",
|
|
1211
|
-
"search_context",
|
|
1212
|
-
"read",
|
|
1213
|
-
"bash"
|
|
1214
|
-
];
|
|
1215
|
-
function isNeverPreserveRecent(msg) {
|
|
1216
|
-
if (msg.contentType !== "tool-call" && msg.contentType !== "tool-result") {
|
|
1217
|
-
return false;
|
|
1218
|
-
}
|
|
1219
|
-
if (!msg.toolName) return false;
|
|
1220
|
-
return NEVER_PRESERVE_RECENT_TOOLS.includes(msg.toolName);
|
|
1221
|
-
}
|
|
1222
|
-
function matchToolPattern(toolName, pattern) {
|
|
1223
|
-
if (pattern.endsWith("*")) {
|
|
1224
|
-
return toolName.startsWith(pattern.slice(0, -1));
|
|
1225
|
-
}
|
|
1226
|
-
return toolName === pattern;
|
|
1227
|
-
}
|
|
1228
|
-
function isMessageProtected(msg, config) {
|
|
1229
|
-
if (msg.contentType !== "tool-call" && msg.contentType !== "tool-result" || !msg.toolName) {
|
|
1230
|
-
return false;
|
|
1231
|
-
}
|
|
1232
|
-
if (ALWAYS_PROTECTED_TOOLS.includes(msg.toolName)) {
|
|
1233
|
-
return true;
|
|
1234
|
-
}
|
|
1235
|
-
for (const pattern of config.protectedTools) {
|
|
1236
|
-
if (matchToolPattern(msg.toolName, pattern)) return true;
|
|
1237
|
-
}
|
|
1238
|
-
if (config.isToolProtected?.(msg.toolName, msg.text)) return true;
|
|
1239
|
-
return false;
|
|
1240
|
-
}
|
|
1241
|
-
function collectProtectedToolCallIds(messages, config) {
|
|
1242
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1243
|
-
for (const m of messages) {
|
|
1244
|
-
if (m.contentType === "tool-call" && m.toolCallId && isMessageProtected(m, config)) {
|
|
1245
|
-
ids.add(m.toolCallId);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
return ids;
|
|
1249
|
-
}
|
|
1250
|
-
function isMessageProtectedWithPairing(msg, config, protectedCallIds) {
|
|
1251
|
-
if (isMessageProtected(msg, config)) return true;
|
|
1252
|
-
if (msg.contentType === "tool-result" && msg.toolCallId && protectedCallIds.has(msg.toolCallId)) {
|
|
1253
|
-
return true;
|
|
1254
|
-
}
|
|
1255
|
-
return false;
|
|
1256
|
-
}
|
|
1257
1529
|
function adjustBoundariesForToolPairs(startIndex, endIndex, messages, maxScan = 20) {
|
|
1258
1530
|
const callIdsInRange = /* @__PURE__ */ new Set();
|
|
1259
1531
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
@@ -1628,7 +1900,7 @@ function createCore(ports = {}) {
|
|
|
1628
1900
|
if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
|
|
1629
1901
|
const live = activeBlocks(state).map((b) => b.blockId).sort((x, y) => numericBlockId(x) - numericBlockId(y));
|
|
1630
1902
|
const liveHint = live.length > 0 ? ` Current active blocks span ${live[0]}..${live[live.length - 1]} \u2014 retry with startId/endId set to active block IDs in that span.` : "";
|
|
1631
|
-
const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref failed with "does not exist in this session". Refs recorded before an earlier compress are stale: each successful compress renumbers the remaining refs. Run acp_status, then
|
|
1903
|
+
const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref failed with "does not exist in this session". Refs recorded before an earlier compress are stale: each successful compress renumbers the remaining refs. Run acp_status, then call the compress tool again using only the refs it reports.` : consumedRanges.length > 0 ? `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}) \u2014 your refs are stale: a prior compress renumbered the remaining refs, so this range now falls inside an active block. Run acp_status, then call the compress tool again using only the CURRENT compressible ranges it reports.${liveHint}` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
|
|
1632
1904
|
return {
|
|
1633
1905
|
state: input.state,
|
|
1634
1906
|
result: {
|
|
@@ -1746,6 +2018,8 @@ function createCore(ports = {}) {
|
|
|
1746
2018
|
assignRefsNode,
|
|
1747
2019
|
syncBlocksNode,
|
|
1748
2020
|
pruneNode,
|
|
2021
|
+
absorbHideNode,
|
|
2022
|
+
absorbPromptNode,
|
|
1749
2023
|
filterNode,
|
|
1750
2024
|
hideCompressCallsNode,
|
|
1751
2025
|
recommendNode,
|
|
@@ -1791,6 +2065,31 @@ var pruneNode = {
|
|
|
1791
2065
|
return { ...io, messages: prune(io.messages, io.state) };
|
|
1792
2066
|
}
|
|
1793
2067
|
};
|
|
2068
|
+
var absorbHideNode = {
|
|
2069
|
+
name: "absorb-hide",
|
|
2070
|
+
enabled: (io) => (io.state.absorbed?.length ?? 0) > 0,
|
|
2071
|
+
run(io) {
|
|
2072
|
+
return { ...io, messages: hideAbsorbedMessages(io.messages, io.state) };
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
var absorbPromptNode = {
|
|
2076
|
+
name: "absorb-prompt",
|
|
2077
|
+
enabled: (_io, ctx) => ctx.config.absorb?.enabled === true,
|
|
2078
|
+
run(io, ctx) {
|
|
2079
|
+
const applied = appendAbsorbPrompts(
|
|
2080
|
+
io.messages,
|
|
2081
|
+
io.state,
|
|
2082
|
+
ctx.config,
|
|
2083
|
+
ctx.tokenCount,
|
|
2084
|
+
ctx.countTokens
|
|
2085
|
+
);
|
|
2086
|
+
return {
|
|
2087
|
+
...io,
|
|
2088
|
+
messages: applied.messages,
|
|
2089
|
+
effects: { ...io.effects, absorbPromptedCount: applied.promptedCount }
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
1794
2093
|
var filterNode = {
|
|
1795
2094
|
name: "filter",
|
|
1796
2095
|
enabled: (_io, ctx) => !!ctx.config.messageFilters?.enabled && listMessageFilters().length > 0,
|
|
@@ -2328,6 +2627,7 @@ function cloneState(state) {
|
|
|
2328
2627
|
tokenSnapshot: { ...state.tokenSnapshot ?? {} },
|
|
2329
2628
|
nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
|
|
2330
2629
|
stats: { ...state.stats },
|
|
2630
|
+
absorbed: (state.absorbed ?? []).map((record) => ({ ...record })),
|
|
2331
2631
|
nextBlockId: state.nextBlockId,
|
|
2332
2632
|
nextRunId: state.nextRunId
|
|
2333
2633
|
};
|
|
@@ -2544,14 +2844,14 @@ function renderUncompressedRanges(visible) {
|
|
|
2544
2844
|
};
|
|
2545
2845
|
const merged = [];
|
|
2546
2846
|
for (const m of visible) {
|
|
2547
|
-
const
|
|
2847
|
+
const num2 = refNum2(m.ref);
|
|
2548
2848
|
const last = merged[merged.length - 1];
|
|
2549
|
-
if (last &&
|
|
2849
|
+
if (last && num2 === last.startNum + last.count) {
|
|
2550
2850
|
last.endRef = m.ref;
|
|
2551
2851
|
last.count += 1;
|
|
2552
2852
|
last.tokens += m.tokens;
|
|
2553
2853
|
} else {
|
|
2554
|
-
merged.push({ startRef: m.ref, endRef: m.ref, startNum:
|
|
2854
|
+
merged.push({ startRef: m.ref, endRef: m.ref, startNum: num2, count: 1, tokens: m.tokens, tool: m.tool });
|
|
2555
2855
|
}
|
|
2556
2856
|
}
|
|
2557
2857
|
for (const r of merged.slice(0, 30)) {
|
|
@@ -2618,6 +2918,296 @@ function renderCompressedDrilldown(blocks, state, sort, limit, countTokens) {
|
|
|
2618
2918
|
}
|
|
2619
2919
|
return lines.join("\n");
|
|
2620
2920
|
}
|
|
2921
|
+
function parseCompressArgs(input, opts) {
|
|
2922
|
+
const callId = opts?.callId;
|
|
2923
|
+
const diag = { ok: false, kind: "ok", invalidItems: 0 };
|
|
2924
|
+
if (input === null || input === void 0) {
|
|
2925
|
+
diag.kind = "empty-input";
|
|
2926
|
+
return finish([], diag);
|
|
2927
|
+
}
|
|
2928
|
+
if (typeof input === "string") {
|
|
2929
|
+
return parseStringInput(input, callId, diag);
|
|
2930
|
+
}
|
|
2931
|
+
if (typeof input !== "object" || Array.isArray(input)) {
|
|
2932
|
+
diag.kind = "not-object";
|
|
2933
|
+
return finish([], diag);
|
|
2934
|
+
}
|
|
2935
|
+
return parseObjectValue(input, callId, diag);
|
|
2936
|
+
}
|
|
2937
|
+
function parseStringInput(raw, callId, diag) {
|
|
2938
|
+
diag.rawPrefix = raw.slice(0, 800);
|
|
2939
|
+
diag.length = raw.length;
|
|
2940
|
+
const cleaned = stripFence(raw.trim());
|
|
2941
|
+
if (cleaned === "") {
|
|
2942
|
+
diag.kind = "empty-input";
|
|
2943
|
+
return finish([], diag);
|
|
2944
|
+
}
|
|
2945
|
+
let value = tryParseLenient(cleaned);
|
|
2946
|
+
if (typeof value === "string") {
|
|
2947
|
+
const inner = tryParseLenient(stripFence(value));
|
|
2948
|
+
if (inner !== void 0) value = inner;
|
|
2949
|
+
}
|
|
2950
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
2951
|
+
return parseObjectValue(value, callId, diag);
|
|
2952
|
+
}
|
|
2953
|
+
if (value !== void 0) {
|
|
2954
|
+
diag.kind = "not-object";
|
|
2955
|
+
return finish([], diag);
|
|
2956
|
+
}
|
|
2957
|
+
const entries = salvageContentEntries(cleaned);
|
|
2958
|
+
return finishSalvage(entries, callId, diag, looksTruncated(cleaned));
|
|
2959
|
+
}
|
|
2960
|
+
function parseObjectValue(value, callId, diag) {
|
|
2961
|
+
diag.keys = Object.keys(value);
|
|
2962
|
+
const content = value["content"];
|
|
2963
|
+
if (content === void 0) {
|
|
2964
|
+
const single = validateEntry(value, callId);
|
|
2965
|
+
if (single) {
|
|
2966
|
+
diag.kind = "ok";
|
|
2967
|
+
return finish([single], diag);
|
|
2968
|
+
}
|
|
2969
|
+
diag.kind = "missing-content";
|
|
2970
|
+
return finish([], diag);
|
|
2971
|
+
}
|
|
2972
|
+
let entries;
|
|
2973
|
+
let salvaged = false;
|
|
2974
|
+
if (Array.isArray(content)) {
|
|
2975
|
+
entries = content;
|
|
2976
|
+
} else if (typeof content === "string") {
|
|
2977
|
+
const parsed = parseContentArray(content);
|
|
2978
|
+
if (parsed === null) {
|
|
2979
|
+
diag.kind = "content-not-array";
|
|
2980
|
+
return finish([], diag);
|
|
2981
|
+
}
|
|
2982
|
+
entries = parsed.entries;
|
|
2983
|
+
salvaged = parsed.salvaged;
|
|
2984
|
+
} else {
|
|
2985
|
+
diag.kind = "content-not-array";
|
|
2986
|
+
return finish([], diag);
|
|
2987
|
+
}
|
|
2988
|
+
const { ranges, invalid } = validateEntries(entries, callId);
|
|
2989
|
+
const topTopic = stringOr(value["topic"]);
|
|
2990
|
+
const topMaxChars = value["summaryMaxChars"];
|
|
2991
|
+
const hasTopMaxChars = typeof topMaxChars === "number" && Number.isFinite(topMaxChars);
|
|
2992
|
+
if (topTopic !== void 0 || hasTopMaxChars) {
|
|
2993
|
+
for (const r of ranges) {
|
|
2994
|
+
if (r.topic === void 0 && topTopic !== void 0) r.topic = topTopic;
|
|
2995
|
+
if (r.summaryMaxChars === void 0 && hasTopMaxChars) r.summaryMaxChars = topMaxChars;
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
diag.invalidItems = invalid;
|
|
2999
|
+
diag.kind = salvaged ? "truncated" : ranges.length > 0 ? "ok" : "no-valid-ranges";
|
|
3000
|
+
return finish(ranges, diag);
|
|
3001
|
+
}
|
|
3002
|
+
function parseContentArray(s) {
|
|
3003
|
+
const cleaned = stripFence(s.trim());
|
|
3004
|
+
let value = cleaned === "" ? void 0 : tryParseLenient(cleaned);
|
|
3005
|
+
if (typeof value === "string") {
|
|
3006
|
+
value = tryParseLenient(stripFence(value));
|
|
3007
|
+
}
|
|
3008
|
+
if (Array.isArray(value)) {
|
|
3009
|
+
return { entries: value, salvaged: false };
|
|
3010
|
+
}
|
|
3011
|
+
if (value === void 0) {
|
|
3012
|
+
const entries = salvageContentEntries('{"content": ' + cleaned);
|
|
3013
|
+
return { entries, salvaged: entries.length > 0 };
|
|
3014
|
+
}
|
|
3015
|
+
return null;
|
|
3016
|
+
}
|
|
3017
|
+
function finish(ranges, diag) {
|
|
3018
|
+
diag.ok = ranges.length > 0;
|
|
3019
|
+
return { ranges, diagnostics: diag };
|
|
3020
|
+
}
|
|
3021
|
+
function finishSalvage(entries, callId, diag, truncatedShape) {
|
|
3022
|
+
const { ranges, invalid } = validateEntries(entries, callId);
|
|
3023
|
+
diag.invalidItems = invalid;
|
|
3024
|
+
diag.kind = entries.length > 0 || truncatedShape ? "truncated" : "malformed-json";
|
|
3025
|
+
return finish(ranges, diag);
|
|
3026
|
+
}
|
|
3027
|
+
function validateEntries(entries, callId) {
|
|
3028
|
+
const ranges = [];
|
|
3029
|
+
let invalid = 0;
|
|
3030
|
+
for (const entry of entries) {
|
|
3031
|
+
const range = validateEntry(entry, callId);
|
|
3032
|
+
if (range !== null) ranges.push(range);
|
|
3033
|
+
else invalid++;
|
|
3034
|
+
}
|
|
3035
|
+
return { ranges, invalid };
|
|
3036
|
+
}
|
|
3037
|
+
function validateEntry(entry, callId) {
|
|
3038
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
3039
|
+
const e = entry;
|
|
3040
|
+
const start = stringOr(e["startRef"]) ?? stringOr(e["startId"]) ?? stringOr(e["messageId"]);
|
|
3041
|
+
const end = stringOr(e["endRef"]) ?? stringOr(e["endId"]) ?? stringOr(e["messageId"]);
|
|
3042
|
+
if (start === void 0 || end === void 0) return null;
|
|
3043
|
+
const summary = stringOr(e["summary"]);
|
|
3044
|
+
if (summary === void 0) return null;
|
|
3045
|
+
const range = { startRef: start, endRef: end, summary };
|
|
3046
|
+
const topic = stringOr(e["topic"]);
|
|
3047
|
+
if (topic !== void 0) range.topic = topic;
|
|
3048
|
+
const maxChars = e["summaryMaxChars"];
|
|
3049
|
+
if (typeof maxChars === "number" && Number.isFinite(maxChars)) range.summaryMaxChars = maxChars;
|
|
3050
|
+
if (callId !== void 0) range.compressCallId = callId;
|
|
3051
|
+
return range;
|
|
3052
|
+
}
|
|
3053
|
+
function stringOr(value) {
|
|
3054
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3055
|
+
}
|
|
3056
|
+
function tryParseLenient(s) {
|
|
3057
|
+
if (s === "") return void 0;
|
|
3058
|
+
try {
|
|
3059
|
+
return JSON.parse(s);
|
|
3060
|
+
} catch {
|
|
3061
|
+
}
|
|
3062
|
+
const noTrailingCommas = stripTrailingCommas(s);
|
|
3063
|
+
if (noTrailingCommas !== s) {
|
|
3064
|
+
try {
|
|
3065
|
+
return JSON.parse(noTrailingCommas);
|
|
3066
|
+
} catch {
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
const fixed = escapeRawNewlinesInStrings(noTrailingCommas);
|
|
3070
|
+
if (fixed !== noTrailingCommas) {
|
|
3071
|
+
try {
|
|
3072
|
+
return JSON.parse(fixed);
|
|
3073
|
+
} catch {
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
return void 0;
|
|
3077
|
+
}
|
|
3078
|
+
function stripFence(s) {
|
|
3079
|
+
if (!s.startsWith("```")) return s;
|
|
3080
|
+
const firstNewline = s.indexOf("\n");
|
|
3081
|
+
if (firstNewline === -1) return s;
|
|
3082
|
+
const bodyStart = firstNewline + 1;
|
|
3083
|
+
const end = s.lastIndexOf("```");
|
|
3084
|
+
return end > bodyStart ? s.slice(bodyStart, end).trim() : s.slice(bodyStart).trim();
|
|
3085
|
+
}
|
|
3086
|
+
function stripTrailingCommas(s) {
|
|
3087
|
+
let out = "";
|
|
3088
|
+
let inString = false;
|
|
3089
|
+
let escaped = false;
|
|
3090
|
+
for (let i = 0; i < s.length; i++) {
|
|
3091
|
+
const ch = s.charAt(i);
|
|
3092
|
+
if (inString) {
|
|
3093
|
+
out += ch;
|
|
3094
|
+
if (escaped) escaped = false;
|
|
3095
|
+
else if (ch === "\\") escaped = true;
|
|
3096
|
+
else if (ch === '"') inString = false;
|
|
3097
|
+
continue;
|
|
3098
|
+
}
|
|
3099
|
+
if (ch === '"') {
|
|
3100
|
+
inString = true;
|
|
3101
|
+
out += ch;
|
|
3102
|
+
continue;
|
|
3103
|
+
}
|
|
3104
|
+
if (ch === ",") {
|
|
3105
|
+
let j = i + 1;
|
|
3106
|
+
while (j < s.length && (s.charAt(j) === " " || s.charAt(j) === " " || s.charAt(j) === "\n" || s.charAt(j) === "\r")) j++;
|
|
3107
|
+
if (j < s.length && (s.charAt(j) === "}" || s.charAt(j) === "]")) continue;
|
|
3108
|
+
}
|
|
3109
|
+
out += ch;
|
|
3110
|
+
}
|
|
3111
|
+
return out;
|
|
3112
|
+
}
|
|
3113
|
+
function escapeRawNewlinesInStrings(s) {
|
|
3114
|
+
let out = "";
|
|
3115
|
+
let inString = false;
|
|
3116
|
+
let escaped = false;
|
|
3117
|
+
for (let i = 0; i < s.length; i++) {
|
|
3118
|
+
const ch = s.charAt(i);
|
|
3119
|
+
if (!inString) {
|
|
3120
|
+
if (ch === '"') inString = true;
|
|
3121
|
+
out += ch;
|
|
3122
|
+
continue;
|
|
3123
|
+
}
|
|
3124
|
+
if (escaped) {
|
|
3125
|
+
out += ch;
|
|
3126
|
+
escaped = false;
|
|
3127
|
+
continue;
|
|
3128
|
+
}
|
|
3129
|
+
if (ch === "\\") {
|
|
3130
|
+
out += ch;
|
|
3131
|
+
escaped = true;
|
|
3132
|
+
continue;
|
|
3133
|
+
}
|
|
3134
|
+
if (ch === "\n") {
|
|
3135
|
+
out += "\\n";
|
|
3136
|
+
continue;
|
|
3137
|
+
}
|
|
3138
|
+
if (ch === "\r") {
|
|
3139
|
+
out += "\\r";
|
|
3140
|
+
continue;
|
|
3141
|
+
}
|
|
3142
|
+
if (ch === " ") {
|
|
3143
|
+
out += "\\t";
|
|
3144
|
+
continue;
|
|
3145
|
+
}
|
|
3146
|
+
if (ch === '"') inString = false;
|
|
3147
|
+
out += ch;
|
|
3148
|
+
}
|
|
3149
|
+
return out;
|
|
3150
|
+
}
|
|
3151
|
+
function looksTruncated(s) {
|
|
3152
|
+
let depth = 0;
|
|
3153
|
+
let inString = false;
|
|
3154
|
+
let escaped = false;
|
|
3155
|
+
for (let i = 0; i < s.length; i++) {
|
|
3156
|
+
const ch = s.charAt(i);
|
|
3157
|
+
if (inString) {
|
|
3158
|
+
if (escaped) escaped = false;
|
|
3159
|
+
else if (ch === "\\") escaped = true;
|
|
3160
|
+
else if (ch === '"') inString = false;
|
|
3161
|
+
continue;
|
|
3162
|
+
}
|
|
3163
|
+
if (ch === '"') {
|
|
3164
|
+
inString = true;
|
|
3165
|
+
continue;
|
|
3166
|
+
}
|
|
3167
|
+
if (ch === "{" || ch === "[") depth++;
|
|
3168
|
+
else if (ch === "}" || ch === "]") depth--;
|
|
3169
|
+
}
|
|
3170
|
+
return depth > 0 || inString;
|
|
3171
|
+
}
|
|
3172
|
+
function salvageContentEntries(raw) {
|
|
3173
|
+
const match = /"content"\s*:\s*\[/.exec(raw);
|
|
3174
|
+
if (match === null) return [];
|
|
3175
|
+
const arrayStart = match.index + match[0].length - 1;
|
|
3176
|
+
const entries = [];
|
|
3177
|
+
let depth = 0;
|
|
3178
|
+
let inString = false;
|
|
3179
|
+
let escaped = false;
|
|
3180
|
+
let entryStart = -1;
|
|
3181
|
+
for (let i = arrayStart + 1; i < raw.length; i++) {
|
|
3182
|
+
const ch = raw.charAt(i);
|
|
3183
|
+
if (inString) {
|
|
3184
|
+
if (escaped) escaped = false;
|
|
3185
|
+
else if (ch === "\\") escaped = true;
|
|
3186
|
+
else if (ch === '"') inString = false;
|
|
3187
|
+
continue;
|
|
3188
|
+
}
|
|
3189
|
+
if (ch === '"') {
|
|
3190
|
+
inString = true;
|
|
3191
|
+
continue;
|
|
3192
|
+
}
|
|
3193
|
+
if (ch === "{" || ch === "[") {
|
|
3194
|
+
if (depth === 0 && ch === "{" && entryStart === -1) entryStart = i;
|
|
3195
|
+
depth++;
|
|
3196
|
+
continue;
|
|
3197
|
+
}
|
|
3198
|
+
if (ch === "}" || ch === "]") {
|
|
3199
|
+
depth--;
|
|
3200
|
+
if (depth < 0) break;
|
|
3201
|
+
if (depth === 0 && entryStart !== -1) {
|
|
3202
|
+
const entrySlice = raw.slice(entryStart, i + 1);
|
|
3203
|
+
entryStart = -1;
|
|
3204
|
+
const parsed = tryParseLenient(entrySlice);
|
|
3205
|
+
if (parsed !== void 0) entries.push(parsed);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
return entries;
|
|
3210
|
+
}
|
|
2621
3211
|
function stem(word) {
|
|
2622
3212
|
let w = word;
|
|
2623
3213
|
if (w.length <= 3) return w;
|
|
@@ -8668,25 +9258,24 @@ function makeCompressTool(runtime) {
|
|
|
8668
9258
|
}
|
|
8669
9259
|
};
|
|
8670
9260
|
}
|
|
8671
|
-
function normalizeRanges(
|
|
8672
|
-
|
|
8673
|
-
if (
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
} catch (e) {
|
|
8677
|
-
return `Invalid content: not valid JSON (${e instanceof Error ? e.message : String(e)}). content must be an ARRAY of {startId, endId, summary} objects \u2014 pass the array directly, not a string.`;
|
|
8678
|
-
}
|
|
9261
|
+
function normalizeRanges(args) {
|
|
9262
|
+
const { ranges, diagnostics } = parseCompressArgs(args);
|
|
9263
|
+
if (ranges.length === 0) {
|
|
9264
|
+
if (Array.isArray(args.content) && args.content.length === 0) return [];
|
|
9265
|
+
return describeDiagnostics(diagnostics, args.content);
|
|
8679
9266
|
}
|
|
8680
|
-
|
|
8681
|
-
|
|
9267
|
+
return ranges.map((r) => ({ startId: r.startRef, endId: r.endRef, summary: r.summary, topic: r.topic }));
|
|
9268
|
+
}
|
|
9269
|
+
function describeDiagnostics(diagnostics, content) {
|
|
9270
|
+
const shape = typeof content === "string" ? "a JSON-encoded string (non-strict-tool providers stringify array arguments)" : content === null ? "null" : `a ${typeof content}`;
|
|
9271
|
+
const base = `Invalid compress content (${diagnostics.kind}): got ${shape}`;
|
|
9272
|
+
if (diagnostics.kind === "truncated") {
|
|
9273
|
+
return `${base}; the input was truncated and no complete ranges could be recovered. Shorten the summary or split into smaller ranges.`;
|
|
8682
9274
|
}
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
if (!o || typeof o !== "object" || typeof o.startId !== "string" || typeof o.endId !== "string" || typeof o.summary !== "string") {
|
|
8686
|
-
return `Invalid content[${i}]: each range must be an object with string fields startId, endId, summary.`;
|
|
8687
|
-
}
|
|
9275
|
+
if (diagnostics.invalidItems > 0) {
|
|
9276
|
+
return `${base}; ${diagnostics.invalidItems} entr${diagnostics.invalidItems === 1 ? "y was" : "ies were"} dropped as invalid. Each range must be an object with string fields startId, endId, summary.`;
|
|
8688
9277
|
}
|
|
8689
|
-
return
|
|
9278
|
+
return `${base}. content must be an ARRAY of {startId, endId, summary} objects.`;
|
|
8690
9279
|
}
|
|
8691
9280
|
function compressPanelBlocks(text) {
|
|
8692
9281
|
if (!text.trimStart().startsWith("\u25A3 ACP |")) return -1;
|
|
@@ -8713,7 +9302,7 @@ function tier3OnlyRewrite(newBlocks, allBlocks) {
|
|
|
8713
9302
|
return spans;
|
|
8714
9303
|
}
|
|
8715
9304
|
async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
8716
|
-
const maybeRanges = normalizeRanges(args
|
|
9305
|
+
const maybeRanges = normalizeRanges(args);
|
|
8717
9306
|
if (typeof maybeRanges === "string") throw new Error(maybeRanges);
|
|
8718
9307
|
const ranges = maybeRanges;
|
|
8719
9308
|
if (ranges.length === 0) return "No ranges provided.";
|
|
@@ -9923,7 +10512,7 @@ function makeDelegateWaitTool(_pi) {
|
|
|
9923
10512
|
return new Promise((resolve3) => {
|
|
9924
10513
|
let settled = false;
|
|
9925
10514
|
let timer2;
|
|
9926
|
-
const
|
|
10515
|
+
const finish2 = (result) => {
|
|
9927
10516
|
if (settled) return;
|
|
9928
10517
|
settled = true;
|
|
9929
10518
|
run.waiter = void 0;
|
|
@@ -9932,19 +10521,19 @@ function makeDelegateWaitTool(_pi) {
|
|
|
9932
10521
|
resolve3(result);
|
|
9933
10522
|
};
|
|
9934
10523
|
const onAbort = () => {
|
|
9935
|
-
|
|
10524
|
+
finish2({ details: void 0, content: [{ type: "text", text: `Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.` }] });
|
|
9936
10525
|
};
|
|
9937
10526
|
run.waiter = () => {
|
|
9938
10527
|
run.consumed = true;
|
|
9939
10528
|
if (run.status === "cancelled") {
|
|
9940
|
-
|
|
10529
|
+
finish2(buildWaitResult(run, `Delegate \`${run.runId}\` was cancelled (no result).${remainingLineForWait(run.runId)}`, displayMode));
|
|
9941
10530
|
return;
|
|
9942
10531
|
}
|
|
9943
|
-
|
|
10532
|
+
finish2(buildWaitResult(run, formatRunResult(run), displayMode));
|
|
9944
10533
|
};
|
|
9945
10534
|
signal?.addEventListener("abort", onAbort);
|
|
9946
10535
|
timer2 = setTimeout(
|
|
9947
|
-
() =>
|
|
10536
|
+
() => finish2({ details: void 0, content: [{ type: "text", text: `Failed: delegate \`${args.runId}\` result not ready after ${Math.round(timeoutMs / 1e3)}s. Do NOT keep waiting or retry \u2014 go do other work now. The run continues in the background and a completion notification (with the result file path) will be injected into the chat when it finishes.` }] }),
|
|
9948
10537
|
timeoutMs
|
|
9949
10538
|
);
|
|
9950
10539
|
});
|
|
@@ -10222,20 +10811,20 @@ function waitForChild(child, signal) {
|
|
|
10222
10811
|
});
|
|
10223
10812
|
const timer2 = setTimeout(() => {
|
|
10224
10813
|
child.kill("SIGTERM");
|
|
10225
|
-
|
|
10814
|
+
finish2({ code: null, stdout: "", stderr: stderrText, timedOut: true });
|
|
10226
10815
|
}, SYNC_TIMEOUT_MS);
|
|
10227
10816
|
const onAbort = () => {
|
|
10228
10817
|
clearTimeout(timer2);
|
|
10229
10818
|
child.kill("SIGTERM");
|
|
10230
10819
|
};
|
|
10231
10820
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
10232
|
-
function
|
|
10821
|
+
function finish2(r) {
|
|
10233
10822
|
clearTimeout(timer2);
|
|
10234
10823
|
signal?.removeEventListener("abort", onAbort);
|
|
10235
10824
|
resolve3(r);
|
|
10236
10825
|
}
|
|
10237
10826
|
child.on("close", (code) => {
|
|
10238
|
-
|
|
10827
|
+
finish2({
|
|
10239
10828
|
code,
|
|
10240
10829
|
stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
|
|
10241
10830
|
stderr: stderrText,
|
|
@@ -10243,7 +10832,7 @@ function waitForChild(child, signal) {
|
|
|
10243
10832
|
});
|
|
10244
10833
|
});
|
|
10245
10834
|
child.on("error", (err) => {
|
|
10246
|
-
|
|
10835
|
+
finish2({ code: null, stdout: "", stderr: err.message, timedOut: false });
|
|
10247
10836
|
});
|
|
10248
10837
|
});
|
|
10249
10838
|
}
|
|
@@ -10448,6 +11037,37 @@ function formatCompactTokens2(count) {
|
|
|
10448
11037
|
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
10449
11038
|
return `${Math.round(count / 1e6)}M`;
|
|
10450
11039
|
}
|
|
11040
|
+
function num(v) {
|
|
11041
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
11042
|
+
}
|
|
11043
|
+
function cacheHitStats(usages) {
|
|
11044
|
+
let cacheRead = 0;
|
|
11045
|
+
let billedPrompt = 0;
|
|
11046
|
+
let requests = 0;
|
|
11047
|
+
let last;
|
|
11048
|
+
for (const u of usages) {
|
|
11049
|
+
const read = num(u?.cacheRead);
|
|
11050
|
+
const write = num(u?.cacheWrite);
|
|
11051
|
+
const input = num(u?.input);
|
|
11052
|
+
if (read + write <= 0) continue;
|
|
11053
|
+
const total = read + write + input;
|
|
11054
|
+
if (total <= 0) continue;
|
|
11055
|
+
cacheRead += read;
|
|
11056
|
+
billedPrompt += total;
|
|
11057
|
+
requests += 1;
|
|
11058
|
+
last = read / total;
|
|
11059
|
+
}
|
|
11060
|
+
return {
|
|
11061
|
+
session: requests > 0 ? cacheRead / billedPrompt : void 0,
|
|
11062
|
+
last,
|
|
11063
|
+
requests,
|
|
11064
|
+
cacheRead,
|
|
11065
|
+
billedPrompt
|
|
11066
|
+
};
|
|
11067
|
+
}
|
|
11068
|
+
function formatHitRate(rate) {
|
|
11069
|
+
return `${(Math.max(0, Math.min(1, rate)) * 100).toFixed(1)}%`;
|
|
11070
|
+
}
|
|
10451
11071
|
function bar(value, total, width = 20) {
|
|
10452
11072
|
if (total === 0) return "";
|
|
10453
11073
|
const filled = Math.max(0, Math.min(width, Math.round(value / total * width)));
|
|
@@ -10500,6 +11120,15 @@ function buildStatusPanel(input) {
|
|
|
10500
11120
|
lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct2).padStart(3)}% ${fmt2(cat.value)}`);
|
|
10501
11121
|
}
|
|
10502
11122
|
}
|
|
11123
|
+
if (input.cacheUsages) {
|
|
11124
|
+
const cache2 = cacheHitStats(input.cacheUsages);
|
|
11125
|
+
if (cache2.requests > 0 && cache2.session !== void 0 && cache2.last !== void 0) {
|
|
11126
|
+
lines.push("");
|
|
11127
|
+
lines.push(
|
|
11128
|
+
`Prompt cache (provider-reported): ${formatHitRate(cache2.last)} last \xB7 ${formatHitRate(cache2.session)} session avg \u2014 ${fmt2(cache2.cacheRead)} of ${fmt2(cache2.billedPrompt)} billed prompt tokens served from cache (${cache2.requests} req)`
|
|
11129
|
+
);
|
|
11130
|
+
}
|
|
11131
|
+
}
|
|
10503
11132
|
lines.push("");
|
|
10504
11133
|
if (nudge) {
|
|
10505
11134
|
if (nudge.shouldInject) {
|
|
@@ -10730,6 +11359,16 @@ function ensureSubagentAcpTools(settingsPath, options) {
|
|
|
10730
11359
|
}
|
|
10731
11360
|
|
|
10732
11361
|
// src/commands.ts
|
|
11362
|
+
function cacheUsageSamples(entries) {
|
|
11363
|
+
const out = [];
|
|
11364
|
+
for (const e of entries) {
|
|
11365
|
+
if (e.type !== "message") continue;
|
|
11366
|
+
const m = e.message;
|
|
11367
|
+
if (m?.role !== "assistant" || !m.usage) continue;
|
|
11368
|
+
out.push({ input: m.usage.input ?? 0, cacheRead: m.usage.cacheRead ?? 0, cacheWrite: m.usage.cacheWrite ?? 0 });
|
|
11369
|
+
}
|
|
11370
|
+
return out;
|
|
11371
|
+
}
|
|
10733
11372
|
function makeCommands(runtime) {
|
|
10734
11373
|
return [
|
|
10735
11374
|
{
|
|
@@ -10816,7 +11455,7 @@ ${text}`);
|
|
|
10816
11455
|
];
|
|
10817
11456
|
}
|
|
10818
11457
|
async function statusReport(runtime, ctx) {
|
|
10819
|
-
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
11458
|
+
const { state, coreMessages, entries } = await runtime.stateFor(ctx);
|
|
10820
11459
|
const config = runtime.configFor(ctx);
|
|
10821
11460
|
const realUsage = ctx.getContextUsage?.();
|
|
10822
11461
|
const systemPromptText = getSystemPromptText(ctx);
|
|
@@ -10826,7 +11465,7 @@ async function statusReport(runtime, ctx) {
|
|
|
10826
11465
|
const modelId = ctx.model?.id ?? "default";
|
|
10827
11466
|
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
10828
11467
|
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId)) });
|
|
10829
|
-
const versionStr = "0.1.
|
|
11468
|
+
const versionStr = "0.1.47" ? `billion-context-pi@${"0.1.47"}` : void 0;
|
|
10830
11469
|
let text = buildStatusPanel({
|
|
10831
11470
|
version: versionStr,
|
|
10832
11471
|
tokenCount: sessionTokens,
|
|
@@ -10834,7 +11473,8 @@ async function statusReport(runtime, ctx) {
|
|
|
10834
11473
|
state: turn.state,
|
|
10835
11474
|
nudge: turn.nudge,
|
|
10836
11475
|
modelContextLimit: config.modelContextLimit,
|
|
10837
|
-
unprunedTokens: coreMessages.reduce((sum, m) => sum + defaultCountTokens(m.text ?? ""), 0)
|
|
11476
|
+
unprunedTokens: coreMessages.reduce((sum, m) => sum + defaultCountTokens(m.text ?? ""), 0),
|
|
11477
|
+
cacheUsages: cacheUsageSamples(entries ?? [])
|
|
10838
11478
|
});
|
|
10839
11479
|
const delegateUsage = getDelegateUsage();
|
|
10840
11480
|
if (delegateUsage && delegateUsage.totalTokens > 0) {
|
|
@@ -11025,14 +11665,12 @@ function wireToolGuardrails(pi, runtime) {
|
|
|
11025
11665
|
const fullPath = isBash ? event.details?.fullOutputPath : void 0;
|
|
11026
11666
|
const timeoutSecs = isBash && event.isError ? detectBashTimeout(event.content) : void 0;
|
|
11027
11667
|
let modified;
|
|
11028
|
-
const max = runtime.adapter.toolOutputMaxBytes;
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
|
|
11034
|
-
logWarn("guardrail", { event: "output-cap", max, hadPath: !!fullPath });
|
|
11035
|
-
}
|
|
11668
|
+
const max = runtime.adapter.toolOutputMaxBytes ?? DEFAULT_TOOL_OUTPUT_MAX_BYTES;
|
|
11669
|
+
const next = capToolOutput(event.content, max, fullPath);
|
|
11670
|
+
if (next) {
|
|
11671
|
+
modified = next;
|
|
11672
|
+
debug.event("guardrail-output-cap", { max, hadPath: !!fullPath });
|
|
11673
|
+
logWarn("guardrail", { event: "output-cap", max, hadPath: !!fullPath });
|
|
11036
11674
|
}
|
|
11037
11675
|
if (timeoutSecs !== void 0) {
|
|
11038
11676
|
modified = appendTimeoutNotice(modified ?? event.content, timeoutSecs);
|
|
@@ -11188,7 +11826,7 @@ async function autoInstallLatest(latest, extDirOverride) {
|
|
|
11188
11826
|
"--no-fund",
|
|
11189
11827
|
"--no-save"
|
|
11190
11828
|
];
|
|
11191
|
-
const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.
|
|
11829
|
+
const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.47";
|
|
11192
11830
|
const { code, stderr } = await runNpmImpl(installArgs(latest), { cwd: npmDir, timeout: 6e4 });
|
|
11193
11831
|
if (code !== 0) {
|
|
11194
11832
|
logWarn("update", {
|
|
@@ -11201,7 +11839,7 @@ async function autoInstallLatest(latest, extDirOverride) {
|
|
|
11201
11839
|
}
|
|
11202
11840
|
const verify = await verifyInstall(npmDir, latest);
|
|
11203
11841
|
if (!verify.ok) {
|
|
11204
|
-
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.
|
|
11842
|
+
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.47";
|
|
11205
11843
|
logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
|
|
11206
11844
|
const rb = await runNpmImpl(installArgs(rollbackTo), { cwd: npmDir, timeout: 6e4 });
|
|
11207
11845
|
logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
|
|
@@ -11262,7 +11900,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
11262
11900
|
const runtimeVersion = await getRuntimeVersion();
|
|
11263
11901
|
const latest = await fetchLatestVersion();
|
|
11264
11902
|
if (!latest) return;
|
|
11265
|
-
const current = runtimeVersion ?? "0.1.
|
|
11903
|
+
const current = runtimeVersion ?? "0.1.47";
|
|
11266
11904
|
const hasUpdate = isNewer(latest, current);
|
|
11267
11905
|
debug.event("update-check", {
|
|
11268
11906
|
current,
|
|
@@ -11303,6 +11941,10 @@ async function getRuntimeVersion() {
|
|
|
11303
11941
|
// src/index.ts
|
|
11304
11942
|
function createAcpExtension(adapter = {}) {
|
|
11305
11943
|
return (pi) => {
|
|
11944
|
+
if (process.env.BILLION_CONTEXT_PROXY) {
|
|
11945
|
+
console.log("[bcp] disabled: BILLION_CONTEXT_PROXY detected \u2014 proxy handles compression");
|
|
11946
|
+
return;
|
|
11947
|
+
}
|
|
11306
11948
|
const runtime = createRuntime(adapter);
|
|
11307
11949
|
wireCompactionDisable(pi);
|
|
11308
11950
|
wireSessionLifecycle(pi, runtime);
|
|
@@ -11337,7 +11979,7 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
11337
11979
|
const sid = ctx.sessionManager.getSessionId();
|
|
11338
11980
|
runtime.clearSessionTracking(sid);
|
|
11339
11981
|
const modelInfo = ctx.model;
|
|
11340
|
-
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.
|
|
11982
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.47" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
|
|
11341
11983
|
try {
|
|
11342
11984
|
await runtime.reloadConfig(ctx.cwd);
|
|
11343
11985
|
setDelegateDisplayUsage(resolveDelegate(runtime.adapter).displayUsage);
|