billion-context-pi 0.1.71 → 0.1.72
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/README.md +2 -1
- package/dist/cache-tool.d.ts +10 -0
- package/dist/index.js +651 -105
- package/dist/index.js.map +1 -1
- package/dist/proxy-detect.d.ts +13 -0
- package/dist/surface.d.ts +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { readFileSync as readFileSync6 } from "fs";
|
|
|
14
14
|
import { homedir as homedir10 } from "os";
|
|
15
15
|
import { join as join16 } from "path";
|
|
16
16
|
|
|
17
|
-
// node_modules/acp-kernel/dist/chunk-
|
|
17
|
+
// node_modules/acp-kernel/dist/chunk-QPVKTDYF.js
|
|
18
18
|
import { createRequire } from "module";
|
|
19
19
|
var require2 = createRequire(import.meta.url);
|
|
20
20
|
function defaultCountTokens(text) {
|
|
@@ -216,7 +216,7 @@ function formatRanges(compressible, protectedRanges) {
|
|
|
216
216
|
if (compressible.length === 0 && protectedRanges.length === 0) {
|
|
217
217
|
return "[No specific ranges detected \u2014 compress any consumed content.]";
|
|
218
218
|
}
|
|
219
|
-
const
|
|
219
|
+
const refNum3 = (ref) => {
|
|
220
220
|
const m2 = ref.match(/\d+/);
|
|
221
221
|
return m2 ? parseInt(m2[0], 10) : 0;
|
|
222
222
|
};
|
|
@@ -225,8 +225,8 @@ function formatRanges(compressible, protectedRanges) {
|
|
|
225
225
|
entries.push({
|
|
226
226
|
startRef: r.startRef,
|
|
227
227
|
endRef: r.endRef,
|
|
228
|
-
startNum:
|
|
229
|
-
endNum:
|
|
228
|
+
startNum: refNum3(r.startRef),
|
|
229
|
+
endNum: refNum3(r.endRef),
|
|
230
230
|
count: r.count,
|
|
231
231
|
tokens: r.tokens,
|
|
232
232
|
userMsgs: r.userMsgs ?? 0,
|
|
@@ -244,8 +244,8 @@ function formatRanges(compressible, protectedRanges) {
|
|
|
244
244
|
entries.push({
|
|
245
245
|
startRef: r.startRef,
|
|
246
246
|
endRef: r.endRef,
|
|
247
|
-
startNum:
|
|
248
|
-
endNum:
|
|
247
|
+
startNum: refNum3(r.startRef),
|
|
248
|
+
endNum: refNum3(r.endRef),
|
|
249
249
|
count: r.count,
|
|
250
250
|
tokens: r.tokens,
|
|
251
251
|
userMsgs: 0,
|
|
@@ -379,6 +379,24 @@ var VIABLE_RANGE_MIN_TOKENS = 200;
|
|
|
379
379
|
function viableRanges(ranges) {
|
|
380
380
|
return ranges.filter((r) => r.tokens >= VIABLE_RANGE_MIN_TOKENS);
|
|
381
381
|
}
|
|
382
|
+
function isHighSurrogate(c) {
|
|
383
|
+
return c >= 55296 && c <= 56319;
|
|
384
|
+
}
|
|
385
|
+
function isLowSurrogate(c) {
|
|
386
|
+
return c >= 56320 && c <= 57343;
|
|
387
|
+
}
|
|
388
|
+
function clampPrefix(text, maxUnits) {
|
|
389
|
+
const cut = Math.min(maxUnits, text.length);
|
|
390
|
+
if (cut > 0 && isHighSurrogate(text.charCodeAt(cut - 1))) return text.slice(0, cut - 1);
|
|
391
|
+
return text.slice(0, cut);
|
|
392
|
+
}
|
|
393
|
+
function clampWindow(text, start, end) {
|
|
394
|
+
let s = Math.max(0, Math.min(start, text.length));
|
|
395
|
+
let e = Math.min(text.length, Math.max(s, end));
|
|
396
|
+
if (s > 0 && isLowSurrogate(text.charCodeAt(s))) s += 1;
|
|
397
|
+
if (e > s && isHighSurrogate(text.charCodeAt(e - 1))) e -= 1;
|
|
398
|
+
return text.slice(s, Math.max(s, e));
|
|
399
|
+
}
|
|
382
400
|
|
|
383
401
|
// node_modules/acp-kernel/dist/chunk-UX4LINT7.js
|
|
384
402
|
function createInitialState() {
|
|
@@ -566,9 +584,43 @@ function collectSummaryAnchors(state, indexById, summaryIndexById) {
|
|
|
566
584
|
anchors.sort((left, right) => left.insertAt - right.insertAt);
|
|
567
585
|
return anchors;
|
|
568
586
|
}
|
|
587
|
+
function pairSafeAnchorIndex(messages, index) {
|
|
588
|
+
let safe = index;
|
|
589
|
+
while (safe > 0 && safe < messages.length && messages[safe - 1].role === "assistant" && messages[safe].role === "assistant") {
|
|
590
|
+
safe--;
|
|
591
|
+
}
|
|
592
|
+
const resultIndexByCallId2 = /* @__PURE__ */ new Map();
|
|
593
|
+
messages.forEach((message, at) => {
|
|
594
|
+
if (message.contentType !== "tool-result") return;
|
|
595
|
+
if (typeof message.toolCallId !== "string") return;
|
|
596
|
+
if (!resultIndexByCallId2.has(message.toolCallId)) {
|
|
597
|
+
resultIndexByCallId2.set(message.toolCallId, at);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
for (let guard = 0; guard < messages.length; guard++) {
|
|
601
|
+
let moved = safe;
|
|
602
|
+
for (let at = 0; at < safe && at < messages.length; at++) {
|
|
603
|
+
const message = messages[at];
|
|
604
|
+
if (message.role !== "assistant") continue;
|
|
605
|
+
if (message.contentType !== "tool-call") continue;
|
|
606
|
+
if (typeof message.toolCallId !== "string") continue;
|
|
607
|
+
const resultIndex = resultIndexByCallId2.get(message.toolCallId);
|
|
608
|
+
if (resultIndex !== void 0 && resultIndex >= safe && resultIndex + 1 > moved) {
|
|
609
|
+
moved = resultIndex + 1;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (moved === safe) break;
|
|
613
|
+
safe = moved;
|
|
614
|
+
}
|
|
615
|
+
return safe;
|
|
616
|
+
}
|
|
569
617
|
function rebuildMessages(messages, covered, firstUserIndex, anchors) {
|
|
618
|
+
const safeAnchors = anchors.map((anchor) => ({
|
|
619
|
+
...anchor,
|
|
620
|
+
insertAt: pairSafeAnchorIndex(messages, anchor.insertAt)
|
|
621
|
+
})).sort((left, right) => left.insertAt - right.insertAt);
|
|
570
622
|
const result = [];
|
|
571
|
-
const pending = [...
|
|
623
|
+
const pending = [...safeAnchors];
|
|
572
624
|
const anchoredSummaryIds = new Set(
|
|
573
625
|
anchors.map((anchor) => summaryMessageId(anchor.blockId))
|
|
574
626
|
);
|
|
@@ -661,6 +713,7 @@ function syncBlocks(messages, state) {
|
|
|
661
713
|
nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
|
|
662
714
|
stats: { ...state.stats },
|
|
663
715
|
absorbed: (state.absorbed ?? []).map((record) => ({ ...record })),
|
|
716
|
+
terminalStreak: state.terminalStreak,
|
|
664
717
|
nextBlockId: state.nextBlockId,
|
|
665
718
|
nextRunId: state.nextRunId
|
|
666
719
|
};
|
|
@@ -716,7 +769,7 @@ function defaultConfig(modelContextLimit, overrides = {}) {
|
|
|
716
769
|
tier2GrowthMultiplier: 1.5
|
|
717
770
|
},
|
|
718
771
|
promotionThreshold: 5,
|
|
719
|
-
truncate: { threshold: 0.95 },
|
|
772
|
+
truncate: { threshold: 0.95, terminalEscapeAfter: 3 },
|
|
720
773
|
compress: {
|
|
721
774
|
minCompressRange: 5e3,
|
|
722
775
|
maxSummaryLength: 2e4,
|
|
@@ -768,6 +821,9 @@ function validateConfig(config) {
|
|
|
768
821
|
if (config.truncate.threshold <= 0 || config.truncate.threshold > 1) {
|
|
769
822
|
errors.push("truncate.threshold must be in (0, 1]");
|
|
770
823
|
}
|
|
824
|
+
if (config.truncate.terminalEscapeAfter !== void 0 && (!Number.isInteger(config.truncate.terminalEscapeAfter) || config.truncate.terminalEscapeAfter < 0)) {
|
|
825
|
+
errors.push("truncate.terminalEscapeAfter must be an integer >= 0");
|
|
826
|
+
}
|
|
771
827
|
for (const tier of [config.tiers.tier2Trigger, config.tiers.tier3Trigger]) {
|
|
772
828
|
if (tier < 1) errors.push("tier triggers must be >= 1");
|
|
773
829
|
}
|
|
@@ -1013,47 +1069,69 @@ var DEFAULTS = {
|
|
|
1013
1069
|
};
|
|
1014
1070
|
function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, options = {}) {
|
|
1015
1071
|
const opts = { ...DEFAULTS, ...options };
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const protectedIndex = messages.length - opts.protectRecentMessages;
|
|
1020
|
-
const candidates = [];
|
|
1021
|
-
for (let index = 0; index < messages.length; index++) {
|
|
1022
|
-
if (index >= protectedIndex) break;
|
|
1023
|
-
const message = messages[index];
|
|
1024
|
-
if (message.contentType !== "tool-result") continue;
|
|
1025
|
-
const text = message.text ?? "";
|
|
1026
|
-
if (text.length === 0 || text.includes(TRUNCATION_MARKER)) continue;
|
|
1027
|
-
const tokens = countTokens(text);
|
|
1028
|
-
if (tokens < opts.minOutputTokens) continue;
|
|
1029
|
-
candidates.push({ index, tokens });
|
|
1072
|
+
const limit = config.modelContextLimit;
|
|
1073
|
+
if (limit <= 0 || tokenCount < config.truncate.threshold * limit) {
|
|
1074
|
+
return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound: 0 };
|
|
1030
1075
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1076
|
+
const protectedIndex = messages.length - opts.protectRecentMessages;
|
|
1077
|
+
const findCandidates = (predicate) => {
|
|
1078
|
+
const found = [];
|
|
1079
|
+
for (let i = 0; i < protectedIndex; i++) {
|
|
1080
|
+
const message = messages[i];
|
|
1081
|
+
if (!predicate(message)) continue;
|
|
1082
|
+
const text = message.text ?? "";
|
|
1083
|
+
if (text.length === 0 || text.includes(TRUNCATION_MARKER)) continue;
|
|
1084
|
+
if (countTokens(text) < opts.minOutputTokens) continue;
|
|
1085
|
+
found.push(message);
|
|
1086
|
+
}
|
|
1087
|
+
return found.sort(
|
|
1088
|
+
(a, b2) => countTokens(b2.text ?? "") - countTokens(a.text ?? "")
|
|
1089
|
+
);
|
|
1090
|
+
};
|
|
1091
|
+
const toolResults = findCandidates((m2) => m2.contentType === "tool-result");
|
|
1092
|
+
const textMessages = options.includeTextMessages ? findCandidates(
|
|
1093
|
+
(m2) => m2.contentType === "text" && (m2.role === "user" || m2.role === "assistant") && !isRenderedSummaryMessage(m2) && !m2.text?.startsWith(SUMMARY_HEADER)
|
|
1094
|
+
) : [];
|
|
1095
|
+
const candidatesFound = toolResults.length + textMessages.length;
|
|
1036
1096
|
let truncatedCount = 0;
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1097
|
+
let savedTokens = 0;
|
|
1098
|
+
let remaining = tokenCount;
|
|
1099
|
+
const targetTokens = config.truncate.threshold * limit * 0.9;
|
|
1100
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
1101
|
+
const applyTo = (candidates) => {
|
|
1102
|
+
for (const candidate of candidates) {
|
|
1103
|
+
if (remaining <= targetTokens) break;
|
|
1104
|
+
const original = candidate.text ?? "";
|
|
1105
|
+
const tokens = countTokens(original);
|
|
1106
|
+
if (original.length <= opts.keepPrefixChars + opts.keepSuffixChars) {
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
const prefix = clampPrefix(original, opts.keepPrefixChars);
|
|
1110
|
+
const suffix = clampWindow(original, original.length - opts.keepSuffixChars, original.length);
|
|
1111
|
+
const replacement = `${prefix}
|
|
1112
|
+
|
|
1113
|
+
...${TRUNCATION_MARKER} \u2014 original ~${tokens} tokens]...
|
|
1114
|
+
|
|
1115
|
+
${suffix}`;
|
|
1116
|
+
replacements.set(candidate.id, replacement);
|
|
1117
|
+
truncatedCount++;
|
|
1118
|
+
remaining -= tokens - countTokens(replacement);
|
|
1119
|
+
savedTokens += tokens - countTokens(replacement);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
applyTo(toolResults);
|
|
1123
|
+
applyTo(textMessages);
|
|
1124
|
+
if (replacements.size === 0) {
|
|
1125
|
+
return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound };
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
messages: messages.map(
|
|
1129
|
+
(m2) => replacements.has(m2.id) ? { ...m2, text: replacements.get(m2.id) } : m2
|
|
1130
|
+
),
|
|
1131
|
+
truncatedCount,
|
|
1132
|
+
savedTokens,
|
|
1133
|
+
candidatesFound
|
|
1134
|
+
};
|
|
1057
1135
|
}
|
|
1058
1136
|
var KEEP_LAST_ORPHANED = 2;
|
|
1059
1137
|
function rangeKey(startRef, endRef) {
|
|
@@ -1106,7 +1184,7 @@ function compactEntry(entry) {
|
|
|
1106
1184
|
if (!entry || typeof entry !== "object") return entry;
|
|
1107
1185
|
const e = entry;
|
|
1108
1186
|
if (typeof e.summary !== "string" || e.summary.length <= SUMMARY_STUB_CHARS) return entry;
|
|
1109
|
-
return { ...e, summary: `${e.summary
|
|
1187
|
+
return { ...e, summary: `${clampPrefix(e.summary, SUMMARY_STUB_CHARS - 1)}\u2026` };
|
|
1110
1188
|
}
|
|
1111
1189
|
function serializeCompacted(obj, content, contentWasString) {
|
|
1112
1190
|
let changed = false;
|
|
@@ -1205,6 +1283,7 @@ var COMPRESS_TOOL_NAME = "compress";
|
|
|
1205
1283
|
var DECOMPRESS_TOOL_NAME = "decompress";
|
|
1206
1284
|
var SEARCH_CONTEXT_TOOL_NAME = "search_context";
|
|
1207
1285
|
var ACP_STATUS_TOOL_NAME = "acp_status";
|
|
1286
|
+
var ACP_CACHE_TOOL_NAME = "acp_cache";
|
|
1208
1287
|
var ABSORB_TOOL_NAME = "absorb";
|
|
1209
1288
|
var ACP_TEXT_OPEN = "<acp_compress>";
|
|
1210
1289
|
var ACP_TEXT_CLOSE = "</acp_compress>";
|
|
@@ -1308,10 +1387,15 @@ var COMPRESS_TOOL_OPENAI = {
|
|
|
1308
1387
|
}
|
|
1309
1388
|
};
|
|
1310
1389
|
var TEXT_PROMPT_SECTIONS = [
|
|
1311
|
-
[
|
|
1390
|
+
[
|
|
1391
|
+
"acpTags",
|
|
1392
|
+
`ACP TAGS
|
|
1312
1393
|
|
|
1313
|
-
Each message in the conversation is annotated with a <acp tokens="2.1K" type="tool:bash">m00175</acp> tag showing its reference ID, approximate token size, and content type. These tags are system metadata. NEVER echo these history tags. Use only the ref ID (e.g. m00005), never the XML wrapper.`
|
|
1314
|
-
|
|
1394
|
+
Each message in the conversation is annotated with a <acp tokens="2.1K" type="tool:bash">m00175</acp> tag showing its reference ID, approximate token size, and content type. These tags are system metadata. NEVER echo these history tags. Use only the ref ID (e.g. m00005), never the XML wrapper.`
|
|
1395
|
+
],
|
|
1396
|
+
[
|
|
1397
|
+
"textProtocol",
|
|
1398
|
+
`COMPRESSION PROTOCOL (TEXT)
|
|
1315
1399
|
|
|
1316
1400
|
You manage context by emitting a special trigger in your text output. When you decide a range of conversation is genuinely consumed and should be compressed into a summary, output EXACTLY this marker (the proxy intercepts and executes it; the marker is stripped from what the user sees):
|
|
1317
1401
|
|
|
@@ -1322,8 +1406,11 @@ Rules for the trigger:
|
|
|
1322
1406
|
- JSON shape matches the compress tool: {"content":[{startId,endId,summary,topic?}]}. Batch multiple ranges in one trigger.
|
|
1323
1407
|
- After emitting the marker, STOP your turn. Do not continue with other text \u2014 the proxy will execute the compression and return the result, then you continue fresh.
|
|
1324
1408
|
- Do NOT wrap the marker in code fences, quotes, or commentary.
|
|
1325
|
-
- NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.`
|
|
1326
|
-
|
|
1409
|
+
- NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.`
|
|
1410
|
+
],
|
|
1411
|
+
[
|
|
1412
|
+
"textTools",
|
|
1413
|
+
`ACP TOOLS (TEXT TRIGGERS)
|
|
1327
1414
|
|
|
1328
1415
|
Since host tools cannot coexist with a declared tools field, ALL ACP tools use text triggers. Emit the marker; the proxy intercepts and executes it; the marker is stripped from what the user sees.
|
|
1329
1416
|
|
|
@@ -1343,13 +1430,19 @@ Since host tools cannot coexist with a declared tools field, ALL ACP tools use t
|
|
|
1343
1430
|
Rules for ALL triggers:
|
|
1344
1431
|
- Output on its own, NO surrounding prose. Just the raw marker.
|
|
1345
1432
|
- After emitting, STOP your turn. The proxy executes and returns the result.
|
|
1346
|
-
- Do NOT wrap in code fences, quotes, or commentary.`
|
|
1433
|
+
- Do NOT wrap in code fences, quotes, or commentary.`
|
|
1434
|
+
]
|
|
1347
1435
|
];
|
|
1348
1436
|
var HYBRID_PROMPT_SECTIONS = [
|
|
1349
|
-
[
|
|
1437
|
+
[
|
|
1438
|
+
"acpTags",
|
|
1439
|
+
`ACP TAGS
|
|
1350
1440
|
|
|
1351
|
-
Each message in the conversation is annotated with a <acp> tag showing its reference ID, approximate token size, and content type. These tags are system metadata. NEVER echo these history tags. Use only the ref ID (e.g. m00005), never the XML wrapper.`
|
|
1352
|
-
|
|
1441
|
+
Each message in the conversation is annotated with a <acp> tag showing its reference ID, approximate token size, and content type. These tags are system metadata. NEVER echo these history tags. Use only the ref ID (e.g. m00005), never the XML wrapper.`
|
|
1442
|
+
],
|
|
1443
|
+
[
|
|
1444
|
+
"textProtocol",
|
|
1445
|
+
`COMPRESSION PROTOCOL (TEXT)
|
|
1353
1446
|
|
|
1354
1447
|
You manage context by emitting a special trigger in your text output. When you decide a range of conversation is genuinely consumed and should be compressed into a summary, output EXACTLY this marker (the proxy intercepts and executes it; the marker is stripped from what the user sees):
|
|
1355
1448
|
|
|
@@ -1360,8 +1453,11 @@ Rules for the trigger:
|
|
|
1360
1453
|
- JSON shape: {"content":[{startId,endId,summary,topic?}]}. Batch multiple ranges in one trigger.
|
|
1361
1454
|
- After emitting the marker, STOP your turn. Do not continue with other text \u2014 the proxy will execute the compression and return the result, then you continue fresh.
|
|
1362
1455
|
- Do NOT wrap the marker in code fences, quotes, or commentary.
|
|
1363
|
-
- NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.`
|
|
1364
|
-
|
|
1456
|
+
- NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.`
|
|
1457
|
+
],
|
|
1458
|
+
[
|
|
1459
|
+
"functionTools",
|
|
1460
|
+
`ACP TOOLS (FUNCTION CALLS)
|
|
1365
1461
|
|
|
1366
1462
|
The proxy also provides these as real function tools you can call directly (they appear in your tool list). Call them like any other function; the proxy executes them and returns the result, then you continue.
|
|
1367
1463
|
|
|
@@ -1369,7 +1465,8 @@ The proxy also provides these as real function tools you can call directly (they
|
|
|
1369
1465
|
- search_context \u2014 search compressed block summaries by keyword. Arguments: {"query":"...","limit":5}.
|
|
1370
1466
|
- decompress \u2014 restore compressed content for exact details. Arguments: {"blockId":"b5"} (optional "toFile":"/tmp/x.txt", "full":true).
|
|
1371
1467
|
|
|
1372
|
-
Note: compress is ONLY available via the text marker above (it needs batch ranges + an immediate stop), NOT as a function tool.`
|
|
1468
|
+
Note: compress is ONLY available via the text marker above (it needs batch ranges + an immediate stop), NOT as a function tool.`
|
|
1469
|
+
]
|
|
1373
1470
|
];
|
|
1374
1471
|
var DECOMPRESS_TOOL_OPENAI = {
|
|
1375
1472
|
type: "function",
|
|
@@ -1422,6 +1519,18 @@ var ACP_STATUS_TOOL_OPENAI = {
|
|
|
1422
1519
|
}
|
|
1423
1520
|
}
|
|
1424
1521
|
};
|
|
1522
|
+
var ACP_CACHE_TOOL_DESCRIPTION = "Prompt-cache reconciliation: grand ledger (total input/cached/output, overall hit rate) with every request's miss split into new content / compression re-pay / TTL expiry, plus per-fold economics (breakeven turns vs measured cadence). Read-only. Call when asked about cache hits, cache invalidation, or what compression costs.";
|
|
1523
|
+
var ACP_CACHE_TOOL_OPENAI = {
|
|
1524
|
+
type: "function",
|
|
1525
|
+
function: {
|
|
1526
|
+
name: ACP_CACHE_TOOL_NAME,
|
|
1527
|
+
description: ACP_CACHE_TOOL_DESCRIPTION,
|
|
1528
|
+
parameters: {
|
|
1529
|
+
type: "object",
|
|
1530
|
+
properties: {}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1425
1534
|
var DECOMPRESS_TOOL = {
|
|
1426
1535
|
name: DECOMPRESS_TOOL_NAME,
|
|
1427
1536
|
description: DECOMPRESS_TOOL_OPENAI.function.description,
|
|
@@ -1437,6 +1546,11 @@ var ACP_STATUS_TOOL = {
|
|
|
1437
1546
|
description: ACP_STATUS_TOOL_OPENAI.function.description,
|
|
1438
1547
|
input_schema: ACP_STATUS_TOOL_OPENAI.function.parameters
|
|
1439
1548
|
};
|
|
1549
|
+
var ACP_CACHE_TOOL = {
|
|
1550
|
+
name: ACP_CACHE_TOOL_NAME,
|
|
1551
|
+
description: ACP_CACHE_TOOL_DESCRIPTION,
|
|
1552
|
+
input_schema: ACP_CACHE_TOOL_OPENAI.function.parameters
|
|
1553
|
+
};
|
|
1440
1554
|
var COMPRESS_TOOL_RESPONSES = {
|
|
1441
1555
|
type: "function",
|
|
1442
1556
|
name: COMPRESS_TOOL_NAME,
|
|
@@ -1461,11 +1575,18 @@ var ACP_STATUS_TOOL_RESPONSES = {
|
|
|
1461
1575
|
description: ACP_STATUS_TOOL_OPENAI.function.description,
|
|
1462
1576
|
parameters: ACP_STATUS_TOOL_OPENAI.function.parameters
|
|
1463
1577
|
};
|
|
1578
|
+
var ACP_CACHE_TOOL_RESPONSES = {
|
|
1579
|
+
type: "function",
|
|
1580
|
+
name: ACP_CACHE_TOOL_NAME,
|
|
1581
|
+
description: ACP_CACHE_TOOL_DESCRIPTION,
|
|
1582
|
+
parameters: ACP_CACHE_TOOL_OPENAI.function.parameters
|
|
1583
|
+
};
|
|
1464
1584
|
var ACP_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1465
1585
|
COMPRESS_TOOL_NAME,
|
|
1466
1586
|
DECOMPRESS_TOOL_NAME,
|
|
1467
1587
|
SEARCH_CONTEXT_TOOL_NAME,
|
|
1468
|
-
ACP_STATUS_TOOL_NAME
|
|
1588
|
+
ACP_STATUS_TOOL_NAME,
|
|
1589
|
+
ACP_CACHE_TOOL_NAME
|
|
1469
1590
|
]);
|
|
1470
1591
|
var ALWAYS_PROTECTED_TOOLS = ["compress"];
|
|
1471
1592
|
var NEVER_PRESERVE_RECENT_TOOLS = [
|
|
@@ -2084,8 +2205,9 @@ function mergeRangesToThreshold(ranges, minChars) {
|
|
|
2084
2205
|
batchChars = 0;
|
|
2085
2206
|
}
|
|
2086
2207
|
}
|
|
2087
|
-
if (batch.length > 0) {
|
|
2088
|
-
result.
|
|
2208
|
+
if (batch.length > 0 && result.length > 0) {
|
|
2209
|
+
const prev = result[result.length - 1];
|
|
2210
|
+
result[result.length - 1] = mergeBatch([prev, ...batch]);
|
|
2089
2211
|
}
|
|
2090
2212
|
return result;
|
|
2091
2213
|
}
|
|
@@ -2288,6 +2410,7 @@ function createCore(ports = {}) {
|
|
|
2288
2410
|
state.nudge.lastPerMessageNudgeTokens = 0;
|
|
2289
2411
|
state.nudge.lastNudgeShownTokens = 0;
|
|
2290
2412
|
state.nudge.lastShownByTier = {};
|
|
2413
|
+
state.terminalStreak = 0;
|
|
2291
2414
|
}
|
|
2292
2415
|
return {
|
|
2293
2416
|
state,
|
|
@@ -2317,7 +2440,9 @@ function createCore(ports = {}) {
|
|
|
2317
2440
|
return {
|
|
2318
2441
|
messages: result.messages,
|
|
2319
2442
|
state: result.state,
|
|
2320
|
-
nudge: result.effects.nudge
|
|
2443
|
+
nudge: result.effects.nudge,
|
|
2444
|
+
terminalEscape: result.effects.terminalEscape,
|
|
2445
|
+
truncationSkipped: result.effects.truncationSkipped
|
|
2321
2446
|
};
|
|
2322
2447
|
}
|
|
2323
2448
|
function decompress(blockId, state) {
|
|
@@ -2510,18 +2635,49 @@ var emergencyTruncateNode = {
|
|
|
2510
2635
|
name: "emergency-truncate",
|
|
2511
2636
|
run(io, ctx) {
|
|
2512
2637
|
const usage = ctx.config.modelContextLimit > 0 ? ctx.tokenCount / ctx.config.modelContextLimit : 0;
|
|
2513
|
-
|
|
2638
|
+
const prevStreak = io.state.terminalStreak ?? 0;
|
|
2639
|
+
if (usage < ctx.config.truncate.threshold) {
|
|
2640
|
+
return prevStreak > 0 ? { ...io, state: { ...io.state, terminalStreak: 0 } } : io;
|
|
2641
|
+
}
|
|
2514
2642
|
const trunc = truncateLargeToolOutputs(
|
|
2515
2643
|
io.messages,
|
|
2516
2644
|
ctx.tokenCount,
|
|
2517
2645
|
ctx.config,
|
|
2518
2646
|
ctx.countTokens,
|
|
2519
|
-
{
|
|
2647
|
+
{
|
|
2648
|
+
protectRecentMessages: ctx.config.preserveRecentMessages,
|
|
2649
|
+
includeTextMessages: true
|
|
2650
|
+
}
|
|
2520
2651
|
);
|
|
2652
|
+
const nudge = io.effects.nudge;
|
|
2653
|
+
const minBenefit = nudge?.breakdown.minPressureBenefit ?? 0;
|
|
2654
|
+
const maxPending = nudge?.breakdown.maxPending ?? 0;
|
|
2655
|
+
const noViableCompression = nudge !== void 0 && (minBenefit > 0 ? maxPending < minBenefit : maxPending <= 0);
|
|
2656
|
+
const stuck = noViableCompression && trunc.savedTokens <= 0;
|
|
2657
|
+
const streak = stuck ? prevStreak + 1 : 0;
|
|
2658
|
+
const escapeAfter = ctx.config.truncate.terminalEscapeAfter ?? 3;
|
|
2659
|
+
const triggered = stuck && escapeAfter > 0 && streak >= escapeAfter;
|
|
2660
|
+
const effects = {
|
|
2661
|
+
...io.effects,
|
|
2662
|
+
truncatedCount: trunc.truncatedCount
|
|
2663
|
+
};
|
|
2664
|
+
if (trunc.savedTokens <= 0) {
|
|
2665
|
+
effects.truncationSkipped = trunc.candidatesFound === 0 ? `emergency-truncate ran at ${Math.round(usage * 100)}% usage but found no truncatable content (no oversized tool-result or text message outside the last ${ctx.config.preserveRecentMessages} messages)` : `emergency-truncate found ${trunc.candidatesFound} candidate(s) at ${Math.round(usage * 100)}% usage but none were large enough to save tokens`;
|
|
2666
|
+
}
|
|
2667
|
+
if (triggered) {
|
|
2668
|
+
effects.terminalEscape = {
|
|
2669
|
+
message: `Usage at ${Math.round(usage * 100)}% (${ctx.tokenCount}/${ctx.config.modelContextLimit} tokens) persists with nothing compressible above the benefit floor and no truncatable content: compression cannot reduce this context below the limit. Start a new session or use native compaction.`,
|
|
2670
|
+
usage,
|
|
2671
|
+
tokenCount: ctx.tokenCount,
|
|
2672
|
+
modelContextLimit: ctx.config.modelContextLimit,
|
|
2673
|
+
stuckEvents: streak
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2521
2676
|
return {
|
|
2522
2677
|
...io,
|
|
2523
2678
|
messages: trunc.messages,
|
|
2524
|
-
|
|
2679
|
+
state: { ...io.state, terminalStreak: streak },
|
|
2680
|
+
effects
|
|
2525
2681
|
};
|
|
2526
2682
|
}
|
|
2527
2683
|
};
|
|
@@ -2614,7 +2770,8 @@ function applySingleRange(input) {
|
|
|
2614
2770
|
for (const m2 of input.messages) {
|
|
2615
2771
|
if (!m2.id) continue;
|
|
2616
2772
|
if (m2.contentType === "reasoning") reasoningIds.add(m2.id);
|
|
2617
|
-
if (m2.role === "assistant" && m2.contentType === "tool-call")
|
|
2773
|
+
if (m2.role === "assistant" && m2.contentType === "tool-call")
|
|
2774
|
+
callIds.add(m2.id);
|
|
2618
2775
|
}
|
|
2619
2776
|
const withdrawIds = /* @__PURE__ */ new Set();
|
|
2620
2777
|
let splitTurnCount = 0;
|
|
@@ -2630,17 +2787,42 @@ function applySingleRange(input) {
|
|
|
2630
2787
|
splitTurnCount++;
|
|
2631
2788
|
for (const id of group) withdrawIds.add(id);
|
|
2632
2789
|
}
|
|
2790
|
+
const resultIdByCallId = /* @__PURE__ */ new Map();
|
|
2791
|
+
for (const m2 of input.messages) {
|
|
2792
|
+
if (!m2.id || m2.contentType !== "tool-result") continue;
|
|
2793
|
+
if (typeof m2.toolCallId !== "string") continue;
|
|
2794
|
+
if (!resultIdByCallId.has(m2.toolCallId)) {
|
|
2795
|
+
resultIdByCallId.set(m2.toolCallId, m2.id);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
let splitPairCount = 0;
|
|
2799
|
+
for (const m2 of input.messages) {
|
|
2800
|
+
if (!m2.id || m2.contentType !== "tool-call") continue;
|
|
2801
|
+
if (typeof m2.toolCallId !== "string") continue;
|
|
2802
|
+
const resultId = resultIdByCallId.get(m2.toolCallId);
|
|
2803
|
+
if (resultId === void 0) continue;
|
|
2804
|
+
if (effectiveMessageIds.has(m2.id) === effectiveMessageIds.has(resultId)) {
|
|
2805
|
+
continue;
|
|
2806
|
+
}
|
|
2807
|
+
withdrawIds.add(m2.id);
|
|
2808
|
+
withdrawIds.add(resultId);
|
|
2809
|
+
splitPairCount++;
|
|
2810
|
+
}
|
|
2633
2811
|
if (withdrawIds.size > 0) {
|
|
2634
2812
|
for (const id of withdrawIds) effectiveMessageIds.delete(id);
|
|
2635
2813
|
const beforeWithdraw = filteredIds.length;
|
|
2636
2814
|
filteredIds = filteredIds.filter((id) => !withdrawIds.has(id));
|
|
2815
|
+
const splitDesc = [
|
|
2816
|
+
splitTurnCount > 0 ? `${splitTurnCount} turn(s)` : null,
|
|
2817
|
+
splitPairCount > 0 ? `${splitPairCount} tool call/result pair(s)` : null
|
|
2818
|
+
].filter((part) => part !== null).join(" and ");
|
|
2637
2819
|
if (filteredIds.length === 0 && consumedBlockIds.length === 0) {
|
|
2638
2820
|
throw new Error(
|
|
2639
|
-
`Range would split ${
|
|
2821
|
+
`Range would split ${splitDesc} at the protected-zone boundary: a visible tool-call must keep its reasoning run and its results (strict providers reject a rebuilt request that lost either). Shrink the range to end before the turn starts, or wait until the whole turn ages out of the protected zone.`
|
|
2640
2822
|
);
|
|
2641
2823
|
}
|
|
2642
2824
|
warnings.push(
|
|
2643
|
-
`Withdrawn ${beforeWithdraw - filteredIds.length} message(s) from compression range to keep ${
|
|
2825
|
+
`Withdrawn ${beforeWithdraw - filteredIds.length} message(s) from compression range to keep ${splitDesc} intact (visible tool-call would lose its reasoning run or its results).`
|
|
2644
2826
|
);
|
|
2645
2827
|
}
|
|
2646
2828
|
}
|
|
@@ -2860,6 +3042,7 @@ function decideNudge(input) {
|
|
|
2860
3042
|
const t1Eff = tiers[1]?.pending ?? 0;
|
|
2861
3043
|
const t2Pen = tiers[2]?.pending ?? 0;
|
|
2862
3044
|
const t3Pen = tiers[3]?.pending ?? 0;
|
|
3045
|
+
const maxPending = Math.max(0, t1Eff, t2Pen, t3Pen);
|
|
2863
3046
|
const firstSightMassReady = state.nudge.lastNudgeShownTokens === 0 && baseline === 0 && usage >= config.nudge.minContextLimitPct && Math.max(t1Eff, t2Pen, t3Pen) >= nudgeGrowthTokens;
|
|
2864
3047
|
const growthReady = firstSightMassReady || growthSinceReference >= growthFloor;
|
|
2865
3048
|
const t2Count = tiers[2]?.targetBlocks.length ?? 0;
|
|
@@ -2932,10 +3115,6 @@ function decideNudge(input) {
|
|
|
2932
3115
|
(t) => ((tiers[t]?.pending ?? 0) >= nudgeGrowthTokens || countReady(t)) && (state.nudge.lastShownByTier[t] ?? 0) > 0 && tokenCount - (state.nudge.lastShownByTier[t] ?? 0) < growthFloor
|
|
2933
3116
|
).map((t) => `T${t} (cadence)`);
|
|
2934
3117
|
const blockedHint = blocked.length > 0 ? `, blocked: ${blocked.join(", ")}` : "";
|
|
2935
|
-
const maxPending = Math.max(
|
|
2936
|
-
0,
|
|
2937
|
-
...Object.values(tiers).map((t) => t.pending)
|
|
2938
|
-
);
|
|
2939
3118
|
const pendingShort = maxPending < nudgeGrowthTokens;
|
|
2940
3119
|
const growthShort = growthSinceReference < growthFloor;
|
|
2941
3120
|
const parts = [];
|
|
@@ -2979,7 +3158,8 @@ function decideNudge(input) {
|
|
|
2979
3158
|
minPressureBenefit,
|
|
2980
3159
|
pendingT1: tiers[1].pending,
|
|
2981
3160
|
pendingT2: tiers[2].pending,
|
|
2982
|
-
pendingT3: tiers[3].pending
|
|
3161
|
+
pendingT3: tiers[3].pending,
|
|
3162
|
+
maxPending
|
|
2983
3163
|
},
|
|
2984
3164
|
contextBreakdown: ctxBreakdown
|
|
2985
3165
|
};
|
|
@@ -3019,6 +3199,7 @@ function cloneState(state) {
|
|
|
3019
3199
|
nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
|
|
3020
3200
|
stats: { ...state.stats },
|
|
3021
3201
|
absorbed: (state.absorbed ?? []).map((record) => ({ ...record })),
|
|
3202
|
+
terminalStreak: state.terminalStreak,
|
|
3022
3203
|
nextBlockId: state.nextBlockId,
|
|
3023
3204
|
nextRunId: state.nextRunId
|
|
3024
3205
|
};
|
|
@@ -3488,7 +3669,7 @@ function renderUncompressedRanges(visible) {
|
|
|
3488
3669
|
lines.push(" (no uncompressed messages)");
|
|
3489
3670
|
return lines.join("\n");
|
|
3490
3671
|
}
|
|
3491
|
-
const
|
|
3672
|
+
const refNum22 = (ref) => {
|
|
3492
3673
|
const m2 = ref.match(/\d+/);
|
|
3493
3674
|
return m2 ? parseInt(m2[0], 10) : 0;
|
|
3494
3675
|
};
|
|
@@ -3505,16 +3686,16 @@ function renderUncompressedRanges(visible) {
|
|
|
3505
3686
|
};
|
|
3506
3687
|
const merged = [];
|
|
3507
3688
|
for (const m2 of visible) {
|
|
3508
|
-
const
|
|
3689
|
+
const num3 = refNum22(m2.ref);
|
|
3509
3690
|
const last = merged[merged.length - 1];
|
|
3510
|
-
if (last &&
|
|
3691
|
+
if (last && num3 === last.startNum + last.count) {
|
|
3511
3692
|
last.endRef = m2.ref;
|
|
3512
3693
|
last.count += 1;
|
|
3513
3694
|
last.tokens += m2.tokens;
|
|
3514
3695
|
last.toolTokens.set(m2.tool, (last.toolTokens.get(m2.tool) ?? 0) + m2.tokens);
|
|
3515
3696
|
} else {
|
|
3516
3697
|
const toolTokens = /* @__PURE__ */ new Map([[m2.tool, m2.tokens]]);
|
|
3517
|
-
merged.push({ startRef: m2.ref, endRef: m2.ref, startNum:
|
|
3698
|
+
merged.push({ startRef: m2.ref, endRef: m2.ref, startNum: num3, count: 1, tokens: m2.tokens, toolTokens });
|
|
3518
3699
|
}
|
|
3519
3700
|
}
|
|
3520
3701
|
for (const r of merged.slice(0, 30)) {
|
|
@@ -3587,6 +3768,276 @@ function renderCompressedDrilldown(blocks, state, sort, limit, countTokens, meta
|
|
|
3587
3768
|
}
|
|
3588
3769
|
return lines.join("\n");
|
|
3589
3770
|
}
|
|
3771
|
+
function decomposeSample(prev, cur, pendingFolds) {
|
|
3772
|
+
const missed = Math.max(0, cur.input - cur.cached);
|
|
3773
|
+
const growth = prev ? Math.max(0, cur.input - prev.input) : 0;
|
|
3774
|
+
const newContent = Math.min(missed, growth);
|
|
3775
|
+
const remainder = missed - newContent;
|
|
3776
|
+
let compRepay = 0;
|
|
3777
|
+
let foldIndex = null;
|
|
3778
|
+
if (remainder > 0 && pendingFolds.length > 0) {
|
|
3779
|
+
let bestX = Infinity;
|
|
3780
|
+
let owner = -1;
|
|
3781
|
+
for (let i = 0; i < pendingFolds.length; i++) {
|
|
3782
|
+
const f = pendingFolds[i];
|
|
3783
|
+
const x2 = f.firstFoldStartTokens ?? f.viewAfter ?? 0;
|
|
3784
|
+
if (x2 < bestX) {
|
|
3785
|
+
bestX = x2;
|
|
3786
|
+
owner = i;
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
const structuralExcess = Math.max(0, cur.input - bestX - newContent);
|
|
3790
|
+
compRepay = Math.min(remainder, structuralExcess);
|
|
3791
|
+
if (compRepay > 0) foldIndex = owner;
|
|
3792
|
+
}
|
|
3793
|
+
return {
|
|
3794
|
+
missed,
|
|
3795
|
+
newContent,
|
|
3796
|
+
compRepay,
|
|
3797
|
+
ttlRepay: remainder - compRepay,
|
|
3798
|
+
foldIndex
|
|
3799
|
+
};
|
|
3800
|
+
}
|
|
3801
|
+
function hitPct(input, cached) {
|
|
3802
|
+
return input > 0 ? cached / input * 100 : 0;
|
|
3803
|
+
}
|
|
3804
|
+
function computeFoldEconomics(f, price) {
|
|
3805
|
+
const w2 = price?.w ?? 1;
|
|
3806
|
+
const r = price?.r ?? 0.1;
|
|
3807
|
+
const q2 = price?.q ?? 4;
|
|
3808
|
+
const netTokenDelta = f.T + f.sigma - f.S;
|
|
3809
|
+
const savingPerTurn = f.S - f.sigma;
|
|
3810
|
+
const oneTimeCostUnits = (w2 - r) * f.T + q2 * f.sigma - r * f.S;
|
|
3811
|
+
const perTurnSavingUnits = savingPerTurn * r;
|
|
3812
|
+
const breakevenTurns = perTurnSavingUnits > 0 ? Math.max(0, oneTimeCostUnits) / perTurnSavingUnits : null;
|
|
3813
|
+
const paidBack = f.turnsToNextFold !== null && breakevenTurns !== null ? f.turnsToNextFold >= breakevenTurns : null;
|
|
3814
|
+
return {
|
|
3815
|
+
seq: f.seq,
|
|
3816
|
+
at: f.at,
|
|
3817
|
+
S: f.S,
|
|
3818
|
+
sigma: f.sigma,
|
|
3819
|
+
Vprime: f.Vprime ?? null,
|
|
3820
|
+
hPct: f.hPct,
|
|
3821
|
+
T: f.T,
|
|
3822
|
+
requestsAfter: f.requestsAfter,
|
|
3823
|
+
savedSoFar: Math.max(0, savingPerTurn) * f.requestsAfter,
|
|
3824
|
+
turnsToNextFold: f.turnsToNextFold,
|
|
3825
|
+
netTokenDelta,
|
|
3826
|
+
oneTimeCostUnits: round1(oneTimeCostUnits),
|
|
3827
|
+
perTurnSavingUnits: round1(perTurnSavingUnits),
|
|
3828
|
+
breakevenTurns,
|
|
3829
|
+
paidBack
|
|
3830
|
+
};
|
|
3831
|
+
}
|
|
3832
|
+
function summarizeFoldEconomics(foldEcon) {
|
|
3833
|
+
const econ = {
|
|
3834
|
+
folds: foldEcon.length,
|
|
3835
|
+
grossSaved: foldEcon.reduce((n, e) => n + e.savedSoFar, 0),
|
|
3836
|
+
repayCost: foldEcon.reduce((n, e) => n + e.T, 0),
|
|
3837
|
+
summaryCost: foldEcon.reduce((n, e) => n + e.sigma, 0),
|
|
3838
|
+
netTokens: 0,
|
|
3839
|
+
paidBackCount: 0,
|
|
3840
|
+
notPaidBackCount: 0,
|
|
3841
|
+
unobservedCount: 0
|
|
3842
|
+
};
|
|
3843
|
+
econ.netTokens = econ.grossSaved - econ.repayCost - econ.summaryCost;
|
|
3844
|
+
for (const e of foldEcon) {
|
|
3845
|
+
if (e.paidBack === true) econ.paidBackCount++;
|
|
3846
|
+
else if (e.paidBack === false) econ.notPaidBackCount++;
|
|
3847
|
+
else econ.unobservedCount++;
|
|
3848
|
+
}
|
|
3849
|
+
return econ;
|
|
3850
|
+
}
|
|
3851
|
+
function buildCacheReport(samplesIn, foldsIn, opts = {}) {
|
|
3852
|
+
const samples = [...samplesIn].sort((a, b2) => a.at - b2.at);
|
|
3853
|
+
const folds = [...foldsIn].sort(
|
|
3854
|
+
(a, b2) => a.at - b2.at || a.tokensCompressed - b2.tokensCompressed
|
|
3855
|
+
);
|
|
3856
|
+
const maxLines = opts.maxLines && opts.maxLines > 0 ? opts.maxLines : 512;
|
|
3857
|
+
const price = opts.priceProfile;
|
|
3858
|
+
const w2 = price?.w ?? 1;
|
|
3859
|
+
const r = price?.r ?? 0.1;
|
|
3860
|
+
const q2 = price?.q ?? 4;
|
|
3861
|
+
const decs = [];
|
|
3862
|
+
for (let i = 0; i < samples.length; i++) {
|
|
3863
|
+
const s = samples[i];
|
|
3864
|
+
const prev = i > 0 ? samples[i - 1] : null;
|
|
3865
|
+
const prevAt = prev ? prev.at : Number.NEGATIVE_INFINITY;
|
|
3866
|
+
const pending = [];
|
|
3867
|
+
let pendingBase = 0;
|
|
3868
|
+
for (let j2 = 0; j2 < folds.length; j2++) {
|
|
3869
|
+
const fj = folds[j2];
|
|
3870
|
+
if (fj.at <= prevAt) continue;
|
|
3871
|
+
if (fj.at > s.at) break;
|
|
3872
|
+
if (pending.length === 0) pendingBase = j2;
|
|
3873
|
+
pending.push(fj);
|
|
3874
|
+
}
|
|
3875
|
+
const d2 = decomposeSample(prev, s, pending);
|
|
3876
|
+
decs.push({
|
|
3877
|
+
...d2,
|
|
3878
|
+
foldSeq: d2.foldIndex !== null ? pendingBase + d2.foldIndex + 1 : null
|
|
3879
|
+
});
|
|
3880
|
+
}
|
|
3881
|
+
const totals = {
|
|
3882
|
+
requests: samples.length,
|
|
3883
|
+
input: 0,
|
|
3884
|
+
cached: 0,
|
|
3885
|
+
output: 0,
|
|
3886
|
+
hitPct: 0,
|
|
3887
|
+
newContent: 0,
|
|
3888
|
+
compRepay: 0,
|
|
3889
|
+
ttlRepay: 0,
|
|
3890
|
+
residual: 0,
|
|
3891
|
+
balanced: true
|
|
3892
|
+
};
|
|
3893
|
+
for (let i = 0; i < samples.length; i++) {
|
|
3894
|
+
const s = samples[i];
|
|
3895
|
+
const d2 = decs[i];
|
|
3896
|
+
totals.input += s.input;
|
|
3897
|
+
totals.cached += s.cached;
|
|
3898
|
+
totals.output += s.output ?? 0;
|
|
3899
|
+
totals.newContent += d2.newContent;
|
|
3900
|
+
totals.compRepay += d2.compRepay;
|
|
3901
|
+
totals.ttlRepay += d2.ttlRepay;
|
|
3902
|
+
}
|
|
3903
|
+
totals.hitPct = round1(hitPct(totals.input, totals.cached));
|
|
3904
|
+
totals.residual = totals.input - totals.cached - (totals.newContent + totals.compRepay + totals.ttlRepay);
|
|
3905
|
+
totals.balanced = totals.residual === 0;
|
|
3906
|
+
const lines = samples.map((s, i) => {
|
|
3907
|
+
const { foldIndex, ...rest } = decs[i];
|
|
3908
|
+
return {
|
|
3909
|
+
seq: i + 1,
|
|
3910
|
+
at: s.at,
|
|
3911
|
+
input: s.input,
|
|
3912
|
+
cached: s.cached,
|
|
3913
|
+
output: s.output ?? 0,
|
|
3914
|
+
hitPct: round1(hitPct(s.input, s.cached)),
|
|
3915
|
+
...rest
|
|
3916
|
+
};
|
|
3917
|
+
});
|
|
3918
|
+
const linesOmitted = Math.max(0, lines.length - maxLines);
|
|
3919
|
+
const visibleLines = lines.slice(linesOmitted);
|
|
3920
|
+
const foldEcon = folds.map((f, j2) => {
|
|
3921
|
+
const seq = j2 + 1;
|
|
3922
|
+
const S = f.tokensCompressed;
|
|
3923
|
+
const sigma = f.summaryTokens ?? 0;
|
|
3924
|
+
const T2 = decs.reduce(
|
|
3925
|
+
(n, d2) => d2.foldSeq === seq ? n + d2.compRepay : n,
|
|
3926
|
+
0
|
|
3927
|
+
);
|
|
3928
|
+
let hPct = null;
|
|
3929
|
+
let requestsAfter = 0;
|
|
3930
|
+
for (const s of samples) {
|
|
3931
|
+
if (s.at <= f.at) continue;
|
|
3932
|
+
requestsAfter++;
|
|
3933
|
+
if (hPct === null) hPct = round1(hitPct(s.input, s.cached));
|
|
3934
|
+
}
|
|
3935
|
+
const nextAt = j2 + 1 < folds.length ? folds[j2 + 1]?.at : void 0;
|
|
3936
|
+
const turnsToNextFold = nextAt !== void 0 ? samples.filter((s) => s.at > f.at && s.at <= nextAt).length : null;
|
|
3937
|
+
return computeFoldEconomics(
|
|
3938
|
+
{
|
|
3939
|
+
seq,
|
|
3940
|
+
at: f.at,
|
|
3941
|
+
S,
|
|
3942
|
+
sigma,
|
|
3943
|
+
Vprime: f.viewAfter,
|
|
3944
|
+
hPct,
|
|
3945
|
+
T: T2,
|
|
3946
|
+
requestsAfter,
|
|
3947
|
+
turnsToNextFold
|
|
3948
|
+
},
|
|
3949
|
+
{ w: w2, r, q: q2 }
|
|
3950
|
+
);
|
|
3951
|
+
});
|
|
3952
|
+
const econ = summarizeFoldEconomics(foldEcon);
|
|
3953
|
+
return {
|
|
3954
|
+
generatedAt: Date.now(),
|
|
3955
|
+
profile: { w: w2, r, q: q2 },
|
|
3956
|
+
totals,
|
|
3957
|
+
economics: econ,
|
|
3958
|
+
folds: foldEcon,
|
|
3959
|
+
lines: visibleLines,
|
|
3960
|
+
linesOmitted
|
|
3961
|
+
};
|
|
3962
|
+
}
|
|
3963
|
+
function round1(n) {
|
|
3964
|
+
return Math.round(n * 10) / 10;
|
|
3965
|
+
}
|
|
3966
|
+
function fmtTok(n) {
|
|
3967
|
+
const v2 = Math.round(n);
|
|
3968
|
+
return v2 >= 1e6 ? `${(v2 / 1e6).toFixed(2)}M` : v2 >= 1e4 ? `${(v2 / 1e3).toFixed(1)}K` : String(v2);
|
|
3969
|
+
}
|
|
3970
|
+
function fmtTime(at) {
|
|
3971
|
+
const d2 = new Date(at);
|
|
3972
|
+
const p = (x2) => String(x2).padStart(2, "0");
|
|
3973
|
+
return `${p(d2.getHours())}:${p(d2.getMinutes())}:${p(d2.getSeconds())}`;
|
|
3974
|
+
}
|
|
3975
|
+
function formatCacheReport(report, sessionLabel) {
|
|
3976
|
+
const t = report.totals;
|
|
3977
|
+
const out = [];
|
|
3978
|
+
out.push(
|
|
3979
|
+
`ACP CACHE REPORT${sessionLabel ? ` (${sessionLabel})` : ""} \u2014 ${t.requests} requests`
|
|
3980
|
+
);
|
|
3981
|
+
out.push("");
|
|
3982
|
+
out.push("GRAND LEDGER");
|
|
3983
|
+
out.push(` total input ${fmtTok(t.input)} tok`);
|
|
3984
|
+
out.push(
|
|
3985
|
+
` total cached ${fmtTok(t.cached)} tok (hit ${t.hitPct.toFixed(1)}%)`
|
|
3986
|
+
);
|
|
3987
|
+
out.push(` total output ${fmtTok(t.output)} tok`);
|
|
3988
|
+
out.push(
|
|
3989
|
+
` miss breakdown (input \u2212 cached = ${fmtTok(Math.max(0, t.input - t.cached))} tok):`
|
|
3990
|
+
);
|
|
3991
|
+
out.push(
|
|
3992
|
+
` new content ${fmtTok(t.newContent)} tok (fresh append \u2014 not an invalidation)`
|
|
3993
|
+
);
|
|
3994
|
+
out.push(` compress re-pay ${fmtTok(t.compRepay)} tok (caused by folds)`);
|
|
3995
|
+
out.push(
|
|
3996
|
+
` ttl/other ${fmtTok(t.ttlRepay)} tok (stable-prefix misses: TTL expiry / eviction)`
|
|
3997
|
+
);
|
|
3998
|
+
out.push(
|
|
3999
|
+
` identity check ${t.balanced ? "OK" : "BROKEN"} \u2014 ${fmtTok(t.input)} = ${fmtTok(t.cached)} + ${fmtTok(t.newContent)} + ${fmtTok(t.compRepay)} + ${fmtTok(t.ttlRepay)} (residual ${t.residual})`
|
|
4000
|
+
);
|
|
4001
|
+
const e = report.economics;
|
|
4002
|
+
if (e.folds > 0) {
|
|
4003
|
+
const p = report.profile;
|
|
4004
|
+
out.push("");
|
|
4005
|
+
out.push(`FOLD ECONOMICS (${e.folds} folds @ w=${p.w} r=${p.r} q=${p.q})`);
|
|
4006
|
+
out.push(
|
|
4007
|
+
` gross saved ${fmtTok(e.grossSaved)} tok \xB7 repay cost ${fmtTok(e.repayCost)} tok \xB7 summary cost ${fmtTok(e.summaryCost)} tok \u2192 net ${e.netTokens >= 0 ? "+" : ""}${fmtTok(e.netTokens)} tok`
|
|
4008
|
+
);
|
|
4009
|
+
out.push(
|
|
4010
|
+
` verdict: ${e.paidBackCount} paid back, ${e.notPaidBackCount} not paid back, ${e.unobservedCount} unobserved`
|
|
4011
|
+
);
|
|
4012
|
+
for (const f of report.folds) {
|
|
4013
|
+
const nstar = f.breakevenTurns === null ? "n/a" : f.breakevenTurns.toFixed(1);
|
|
4014
|
+
const k = f.turnsToNextFold === null ? "\u2014" : String(f.turnsToNextFold);
|
|
4015
|
+
const verdict = f.paidBack === null ? "?" : f.paidBack ? "PAID BACK" : "NOT PAID BACK";
|
|
4016
|
+
const h = f.hPct === null ? "n/a" : `${f.hPct.toFixed(1)}%`;
|
|
4017
|
+
out.push(
|
|
4018
|
+
` #${f.seq} ${fmtTime(f.at)} S=${fmtTok(f.S)} \u03C3=${fmtTok(f.sigma)} h=${h} T=${fmtTok(f.T)} \u0394C\u2081=${fmtTok(f.oneTimeCostUnits)} \u0394s=${fmtTok(f.perTurnSavingUnits)}/turn n*=${nstar} k=${k} \u2192 ${verdict}`
|
|
4019
|
+
);
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
if (report.lines.length > 0) {
|
|
4023
|
+
out.push("");
|
|
4024
|
+
if (report.linesOmitted > 0)
|
|
4025
|
+
out.push(
|
|
4026
|
+
`LINE ITEMS (last ${report.lines.length} of ${report.lines.length + report.linesOmitted}):`
|
|
4027
|
+
);
|
|
4028
|
+
else out.push("LINE ITEMS:");
|
|
4029
|
+
out.push(
|
|
4030
|
+
" # time input cached hit% new comp ttl fold"
|
|
4031
|
+
);
|
|
4032
|
+
for (const l3 of report.lines) {
|
|
4033
|
+
const fold = l3.foldSeq !== null ? `#${l3.foldSeq}` : "";
|
|
4034
|
+
out.push(
|
|
4035
|
+
` ${String(l3.seq).padStart(4)} ${fmtTime(l3.at)} ${fmtTok(l3.input).padStart(7)} ${fmtTok(l3.cached).padStart(7)} ${l3.hitPct.toFixed(1).padStart(5)}% ${fmtTok(l3.newContent).padStart(6)} ${fmtTok(l3.compRepay).padStart(6)} ${fmtTok(l3.ttlRepay).padStart(6)} ${fold}`
|
|
4036
|
+
);
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
return out.join("\n");
|
|
4040
|
+
}
|
|
3590
4041
|
function renderMessage2(m2) {
|
|
3591
4042
|
const parts = [];
|
|
3592
4043
|
switch (m2.contentType) {
|
|
@@ -3672,7 +4123,7 @@ function parseCompressArgs(input, opts) {
|
|
|
3672
4123
|
return parseObjectValue(input, callId, diag);
|
|
3673
4124
|
}
|
|
3674
4125
|
function parseStringInput(raw, callId, diag) {
|
|
3675
|
-
diag.rawPrefix = raw
|
|
4126
|
+
diag.rawPrefix = clampPrefix(raw, 800);
|
|
3676
4127
|
diag.length = raw.length;
|
|
3677
4128
|
const cleaned = stripFence(raw.trim());
|
|
3678
4129
|
const first = parseStringCore(cleaned, callId, diag);
|
|
@@ -3856,7 +4307,7 @@ function deriveTopicFromSummary(summary) {
|
|
|
3856
4307
|
const source = heading !== null ? heading[1] : summary.split("\n").find((l3) => l3.trim().length > 0) ?? "";
|
|
3857
4308
|
const text = source.trim().replace(/^[\u2022\-*]\s+/, "");
|
|
3858
4309
|
if (text.length === 0) return void 0;
|
|
3859
|
-
return text.length > 60 ? text
|
|
4310
|
+
return text.length > 60 ? clampPrefix(text, 60).trimEnd() : text;
|
|
3860
4311
|
}
|
|
3861
4312
|
function splitLineEntries(content) {
|
|
3862
4313
|
const parts = content.split(/\n(?=\s*(?:["']\s*,\s*)?["']?\s*[mb]\d{1,7}\s*(?:[-\u2013\u2014\u2026~]|\.\.\.|to)\s*[mb]\d{1,7}\b)/i).map((p) => p.trim().replace(/^(?:["']\s*,?\s*)+/, "").replace(/["']\s*$/, "")).filter((p) => p.length > 0);
|
|
@@ -4369,7 +4820,7 @@ function messageDocs(msgs) {
|
|
|
4369
4820
|
kind: "message",
|
|
4370
4821
|
ref: m2.ref,
|
|
4371
4822
|
text: m2.text,
|
|
4372
|
-
title: `${m2.role}: ${m2.text
|
|
4823
|
+
title: `${m2.role}: ${clampPrefix(m2.text, 60)}`,
|
|
4373
4824
|
role: m2.role,
|
|
4374
4825
|
blockId: m2.blockId,
|
|
4375
4826
|
tier: m2.tier,
|
|
@@ -4431,7 +4882,7 @@ function searchBlocks(docs, query, options = {}) {
|
|
|
4431
4882
|
function makePreview(text, query, len) {
|
|
4432
4883
|
if (!text) return "";
|
|
4433
4884
|
const terms = query.toLowerCase().trim().split(/\s+/).filter((t) => t.length > 1);
|
|
4434
|
-
if (terms.length === 0) return text
|
|
4885
|
+
if (terms.length === 0) return clampPrefix(text, len);
|
|
4435
4886
|
const lower = text.toLowerCase();
|
|
4436
4887
|
let hitIdx = -1;
|
|
4437
4888
|
for (const term of terms) {
|
|
@@ -4441,13 +4892,13 @@ function makePreview(text, query, len) {
|
|
|
4441
4892
|
break;
|
|
4442
4893
|
}
|
|
4443
4894
|
}
|
|
4444
|
-
if (hitIdx < 0) return text
|
|
4895
|
+
if (hitIdx < 0) return clampPrefix(text, len);
|
|
4445
4896
|
const half = Math.max(0, Math.floor(len / 2) - 10);
|
|
4446
4897
|
const start = Math.max(0, hitIdx - half);
|
|
4447
4898
|
const end = Math.min(text.length, start + len);
|
|
4448
4899
|
const prefix = start > 0 ? "\u2026" : "";
|
|
4449
4900
|
const suffix = end < text.length ? "\u2026" : "";
|
|
4450
|
-
return prefix + text
|
|
4901
|
+
return prefix + clampWindow(text, start, end).trim() + suffix;
|
|
4451
4902
|
}
|
|
4452
4903
|
|
|
4453
4904
|
// src/log.ts
|
|
@@ -5695,7 +6146,7 @@ When a background delegate finishes, an automated completion notification is inj
|
|
|
5695
6146
|
`;
|
|
5696
6147
|
|
|
5697
6148
|
// src/surface.ts
|
|
5698
|
-
var TOOL_NAMES = /* @__PURE__ */ new Set(["compress", "decompress", "search_context", "acp_status"]);
|
|
6149
|
+
var TOOL_NAMES = /* @__PURE__ */ new Set(["compress", "decompress", "search_context", "acp_status", "acp_cache"]);
|
|
5699
6150
|
var NUDGE_KEYS = /* @__PURE__ */ new Set(["efficiencyNote", "emergencyHeader", "t2Guidance", "t3Guidance"]);
|
|
5700
6151
|
function normalizeGuidelines(g2) {
|
|
5701
6152
|
return Array.isArray(g2) ? g2 : [g2];
|
|
@@ -11672,7 +12123,7 @@ function resolveActivePack(adapter, cwd, provider, modelId, resolver) {
|
|
|
11672
12123
|
}
|
|
11673
12124
|
var HOST_VERSION = (() => {
|
|
11674
12125
|
try {
|
|
11675
|
-
return "0.1.
|
|
12126
|
+
return "0.1.72";
|
|
11676
12127
|
} catch {
|
|
11677
12128
|
return "dev";
|
|
11678
12129
|
}
|
|
@@ -13720,6 +14171,71 @@ async function handleStatus(args, runtime, ctx) {
|
|
|
13720
14171
|
${extra.join("\n")}` : base;
|
|
13721
14172
|
}
|
|
13722
14173
|
|
|
14174
|
+
// src/cache-tool.ts
|
|
14175
|
+
var CacheParams = typebox_exports.Object({});
|
|
14176
|
+
var num = (v2) => typeof v2 === "number" && Number.isFinite(v2) && v2 > 0 ? v2 : 0;
|
|
14177
|
+
var refNum2 = (ref) => Number(ref.replace(/\D/g, "")) || 0;
|
|
14178
|
+
function cacheSamples(entries) {
|
|
14179
|
+
const out = [];
|
|
14180
|
+
for (const e of entries) {
|
|
14181
|
+
if (e.type !== "message") continue;
|
|
14182
|
+
const m2 = e.message;
|
|
14183
|
+
if (m2?.role !== "assistant" || !m2.usage) continue;
|
|
14184
|
+
const read = num(m2.usage.cacheRead);
|
|
14185
|
+
const write = num(m2.usage.cacheWrite);
|
|
14186
|
+
const fresh = num(m2.usage.input);
|
|
14187
|
+
if (fresh + read + write <= 0) continue;
|
|
14188
|
+
out.push({ at: Date.parse(e.timestamp) || 0, input: fresh + read + write, cached: read, output: num(m2.usage.output) });
|
|
14189
|
+
}
|
|
14190
|
+
return out;
|
|
14191
|
+
}
|
|
14192
|
+
function foldEvents(state) {
|
|
14193
|
+
const byRef = state.messageRefs?.byRef ?? {};
|
|
14194
|
+
const snap = state.tokenSnapshot ?? {};
|
|
14195
|
+
const prefixBefore = (ref) => {
|
|
14196
|
+
if (!ref || refNum2(ref) === 0) return void 0;
|
|
14197
|
+
let n = 0;
|
|
14198
|
+
for (const key of Object.keys(byRef)) {
|
|
14199
|
+
if (refNum2(key) < refNum2(ref)) n += snap[key] ?? 0;
|
|
14200
|
+
}
|
|
14201
|
+
return n;
|
|
14202
|
+
};
|
|
14203
|
+
return [...state.blocks].sort((a, b2) => a.createdAt - b2.createdAt).map((b2) => ({
|
|
14204
|
+
at: b2.createdAt,
|
|
14205
|
+
tokensCompressed: b2.compressedTokens,
|
|
14206
|
+
summaryTokens: defaultCountTokens(b2.summary || ""),
|
|
14207
|
+
firstFoldStartTokens: prefixBefore(b2.startRef)
|
|
14208
|
+
}));
|
|
14209
|
+
}
|
|
14210
|
+
async function cacheReportText(runtime, ctx) {
|
|
14211
|
+
const { state, entries } = await runtime.stateFor(ctx);
|
|
14212
|
+
const report = buildCacheReport(cacheSamples(entries), foldEvents(state));
|
|
14213
|
+
return formatCacheReport(report, ctx.sessionManager.getSessionId());
|
|
14214
|
+
}
|
|
14215
|
+
function makeCacheTool(runtime, overrides) {
|
|
14216
|
+
return applyToolPromptOverrides({
|
|
14217
|
+
name: "acp_cache",
|
|
14218
|
+
label: "ACP Cache Report",
|
|
14219
|
+
description: "Prompt-cache reconciliation: grand ledger (total input/cached/output, session hit rate) with every request's miss split into new content / compression re-pay / TTL expiry, plus per-fold economics (one-time cost, breakeven turns vs measured cadence). Read-only.",
|
|
14220
|
+
promptSnippet: "acp_cache({})",
|
|
14221
|
+
promptGuidelines: [
|
|
14222
|
+
"Call when asked about cache hits, cache invalidation, or what compression costs.",
|
|
14223
|
+
"Read-only: reports numbers, never mutates context."
|
|
14224
|
+
],
|
|
14225
|
+
parameters: CacheParams,
|
|
14226
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
14227
|
+
if (runtime.refused) return { details: void 0, content: [{ type: "text", text: runtime.refusalMessage ?? UNSUPPORTED_HOST_MESSAGE }] };
|
|
14228
|
+
try {
|
|
14229
|
+
const text = await cacheReportText(runtime, ctx);
|
|
14230
|
+
return { details: void 0, content: [{ type: "text", text }] };
|
|
14231
|
+
} catch (e) {
|
|
14232
|
+
logThrow("cache", e, { sid: ctx.sessionManager.getSessionId() });
|
|
14233
|
+
throw e;
|
|
14234
|
+
}
|
|
14235
|
+
}
|
|
14236
|
+
}, overrides);
|
|
14237
|
+
}
|
|
14238
|
+
|
|
13723
14239
|
// src/commands.ts
|
|
13724
14240
|
import * as path12 from "path";
|
|
13725
14241
|
|
|
@@ -13847,7 +14363,7 @@ function parseExportArgs(args) {
|
|
|
13847
14363
|
function topicFallback(summary) {
|
|
13848
14364
|
const first = summary.split(/[.\n]/)[0] ?? "";
|
|
13849
14365
|
const t = first.trim().replace(/^["'`]+/, "").trim();
|
|
13850
|
-
return t.length <= 30 ? t : `${t
|
|
14366
|
+
return t.length <= 30 ? t : `${clampPrefix(t, 30).trimEnd()}\u2026`;
|
|
13851
14367
|
}
|
|
13852
14368
|
function formatCompactTokens2(count) {
|
|
13853
14369
|
if (count < 1e3) return count.toString();
|
|
@@ -13856,7 +14372,7 @@ function formatCompactTokens2(count) {
|
|
|
13856
14372
|
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
13857
14373
|
return `${Math.round(count / 1e6)}M`;
|
|
13858
14374
|
}
|
|
13859
|
-
function
|
|
14375
|
+
function num2(v2) {
|
|
13860
14376
|
return typeof v2 === "number" && Number.isFinite(v2) && v2 > 0 ? v2 : 0;
|
|
13861
14377
|
}
|
|
13862
14378
|
function cacheHitStats(usages) {
|
|
@@ -13865,9 +14381,9 @@ function cacheHitStats(usages) {
|
|
|
13865
14381
|
let requests = 0;
|
|
13866
14382
|
let last;
|
|
13867
14383
|
for (const u of usages) {
|
|
13868
|
-
const read =
|
|
13869
|
-
const write =
|
|
13870
|
-
const input =
|
|
14384
|
+
const read = num2(u?.cacheRead);
|
|
14385
|
+
const write = num2(u?.cacheWrite);
|
|
14386
|
+
const input = num2(u?.input);
|
|
13871
14387
|
if (read + write <= 0) continue;
|
|
13872
14388
|
const total = read + write + input;
|
|
13873
14389
|
if (total <= 0) continue;
|
|
@@ -18195,6 +18711,26 @@ function makeCommands(runtime, pi) {
|
|
|
18195
18711
|
handler: statusHandler
|
|
18196
18712
|
}
|
|
18197
18713
|
},
|
|
18714
|
+
{
|
|
18715
|
+
name: "acp-cache",
|
|
18716
|
+
options: {
|
|
18717
|
+
description: "Prompt-cache reconciliation: grand ledger (input/cached/hit rate) with every request's miss split into new content / compression re-pay / TTL expiry, plus per-fold economics.",
|
|
18718
|
+
handler: async (_args, ctx) => {
|
|
18719
|
+
let text;
|
|
18720
|
+
try {
|
|
18721
|
+
text = await cacheReportText(runtime, ctx);
|
|
18722
|
+
} catch (e) {
|
|
18723
|
+
ctx.ui.notify(e instanceof Error ? e.message : String(e), "error");
|
|
18724
|
+
return;
|
|
18725
|
+
}
|
|
18726
|
+
if (typeof pi?.sendMessage === "function") {
|
|
18727
|
+
pi.sendMessage({ customType: ACP_STATUS_CUSTOM_TYPE, content: text, display: true });
|
|
18728
|
+
return;
|
|
18729
|
+
}
|
|
18730
|
+
ctx.ui.notify(text);
|
|
18731
|
+
}
|
|
18732
|
+
}
|
|
18733
|
+
},
|
|
18198
18734
|
{
|
|
18199
18735
|
name: "acp-export",
|
|
18200
18736
|
options: {
|
|
@@ -18332,7 +18868,7 @@ async function statusReport(runtime, ctx) {
|
|
|
18332
18868
|
const sentTokens = estimateTokens(coreMessages, coveredIds, imageTokens) + systemPromptTokens;
|
|
18333
18869
|
const viewSentTokens = adjustedTokenCount(runtime.core, coreMessages, state, config, sentTokens, imageTokens, systemPromptTokens);
|
|
18334
18870
|
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: anchorStale ? viewSentTokens : Math.max(viewSentTokens, realUsage?.tokens ?? 0) });
|
|
18335
|
-
const versionStr = "0.1.
|
|
18871
|
+
const versionStr = "0.1.72" ? `billion-context-pi@${"0.1.72"} \xB7 pack: ${resolveSurfaceMeta(runtime.adapter, ctx?.cwd ?? process.cwd(), ctx?.model?.provider, ctx?.model?.id).pack}` : void 0;
|
|
18336
18872
|
let text = buildStatusPanel({
|
|
18337
18873
|
version: versionStr,
|
|
18338
18874
|
tokenCount: sessionTokens,
|
|
@@ -18944,7 +19480,7 @@ async function autoInstallLatest(latest, extDirOverride) {
|
|
|
18944
19480
|
"--no-fund",
|
|
18945
19481
|
"--no-save"
|
|
18946
19482
|
];
|
|
18947
|
-
const prevVersion = (await readPackageJson(join15(extDir, "package.json")))?.version ?? "0.1.
|
|
19483
|
+
const prevVersion = (await readPackageJson(join15(extDir, "package.json")))?.version ?? "0.1.72";
|
|
18948
19484
|
const { code, stderr } = await runNpmImpl(installArgs(latest), { cwd: npmDir, timeout: 6e4 });
|
|
18949
19485
|
if (code !== 0) {
|
|
18950
19486
|
logWarn("update", {
|
|
@@ -18962,7 +19498,7 @@ async function autoInstallLatest(latest, extDirOverride) {
|
|
|
18962
19498
|
}
|
|
18963
19499
|
const verify = await verifyInstall(npmDir, latest);
|
|
18964
19500
|
if (!verify.ok) {
|
|
18965
|
-
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.
|
|
19501
|
+
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.72";
|
|
18966
19502
|
logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
|
|
18967
19503
|
const rb = await runNpmImpl(installArgs(rollbackTo), { cwd: npmDir, timeout: 6e4 });
|
|
18968
19504
|
logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
|
|
@@ -19037,7 +19573,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
19037
19573
|
}
|
|
19038
19574
|
const latest = await fetchLatestVersion(tag);
|
|
19039
19575
|
if (!latest) return;
|
|
19040
|
-
const current = runtimeVersion ?? "0.1.
|
|
19576
|
+
const current = runtimeVersion ?? "0.1.72";
|
|
19041
19577
|
const hasUpdate = isVersionNewer(latest, current);
|
|
19042
19578
|
debug.event("update-check", {
|
|
19043
19579
|
current,
|
|
@@ -19115,6 +19651,13 @@ var PROXY_STAND_DOWN_MESSAGE = [
|
|
|
19115
19651
|
"If your traffic reaches the proxy via HTTPS_PROXY instead (no /bili/ prefix in the URL), export BILLION_CONTEXT_PROXY=1 before starting pi.",
|
|
19116
19652
|
"Docs: https://github.com/ranxianglei/billion-context"
|
|
19117
19653
|
].join("\n");
|
|
19654
|
+
function nativeStandDownMessage(host) {
|
|
19655
|
+
return [
|
|
19656
|
+
`[billion-context-pi] billion-context native host detected (BILLION_CONTEXT_NATIVE=${host}) \u2014 the in-process native plugin proxies model API traffic and owns compression.`,
|
|
19657
|
+
"ACP client-side compression has been disabled for this session to avoid double compression; nothing else to configure.",
|
|
19658
|
+
"Docs: https://github.com/ranxianglei/billion-context"
|
|
19659
|
+
].join("\n");
|
|
19660
|
+
}
|
|
19118
19661
|
|
|
19119
19662
|
// src/index.ts
|
|
19120
19663
|
function createAcpExtension(adapter = {}) {
|
|
@@ -19128,16 +19671,18 @@ function createAcpExtension(adapter = {}) {
|
|
|
19128
19671
|
return;
|
|
19129
19672
|
}
|
|
19130
19673
|
const runtime = createRuntime(adapter);
|
|
19131
|
-
let
|
|
19674
|
+
let standDownWarned = false;
|
|
19132
19675
|
const standDownIfProxied = (ctx) => {
|
|
19133
|
-
|
|
19676
|
+
const nativeHost = process.env.BILLION_CONTEXT_NATIVE || void 0;
|
|
19677
|
+
if (nativeHost === void 0 && !process.env.BILLION_CONTEXT_PROXY && !isBiliProxyBaseUrl(ctx.model?.baseUrl)) return false;
|
|
19678
|
+
const message = nativeHost !== void 0 ? nativeStandDownMessage(nativeHost) : PROXY_STAND_DOWN_MESSAGE;
|
|
19134
19679
|
runtime.refused = true;
|
|
19135
|
-
runtime.refusalMessage =
|
|
19136
|
-
if (!
|
|
19137
|
-
|
|
19138
|
-
logWarn("host", { event: "
|
|
19139
|
-
if (ctx.hasUI) ctx.ui.notify(
|
|
19140
|
-
else console.error(
|
|
19680
|
+
runtime.refusalMessage = message;
|
|
19681
|
+
if (!standDownWarned) {
|
|
19682
|
+
standDownWarned = true;
|
|
19683
|
+
logWarn("host", { event: nativeHost !== void 0 ? "native-host-detected" : "proxy-detected", sid: ctx.sessionManager.getSessionId(), action: "refused", native: nativeHost ?? null });
|
|
19684
|
+
if (ctx.hasUI) ctx.ui.notify(message, "warning");
|
|
19685
|
+
else console.error(message);
|
|
19141
19686
|
}
|
|
19142
19687
|
return true;
|
|
19143
19688
|
};
|
|
@@ -19154,6 +19699,7 @@ function createAcpExtension(adapter = {}) {
|
|
|
19154
19699
|
pi.registerTool(makeDecompressTool(runtime, toolSurface.decompress));
|
|
19155
19700
|
pi.registerTool(makeSearchTool(runtime, toolSurface.search_context));
|
|
19156
19701
|
pi.registerTool(makeStatusTool(runtime, toolSurface.acp_status));
|
|
19702
|
+
pi.registerTool(makeCacheTool(runtime, toolSurface.acp_cache));
|
|
19157
19703
|
for (const { name, options } of makeCommands(runtime, pi)) {
|
|
19158
19704
|
pi.registerCommand(name, options);
|
|
19159
19705
|
}
|
|
@@ -19229,7 +19775,7 @@ function wireSessionLifecycle(pi, runtime, standDownIfProxied) {
|
|
|
19229
19775
|
setDelegatePolicy(DEFAULT_DELEGATE_POLICY);
|
|
19230
19776
|
const sid = ctx.sessionManager.getSessionId();
|
|
19231
19777
|
const modelInfo = ctx.model;
|
|
19232
|
-
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.
|
|
19778
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.72" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
|
|
19233
19779
|
let delegateStoodDown = false;
|
|
19234
19780
|
try {
|
|
19235
19781
|
await runtime.reloadConfig(ctx.cwd);
|