opencode-acp 1.18.5-pr.490.223 → 1.18.5
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 +0 -7
- package/README.zh-CN.md +0 -5
- package/dist/index.js +394 -708
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/image-archive.d.ts +0 -2
- package/dist/lib/compress/image-archive.d.ts.map +1 -1
- package/dist/lib/compress/status.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +0 -12
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +0 -2
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/prompts/extensions/system.d.ts +0 -6
- package/dist/lib/prompts/extensions/system.d.ts.map +1 -1
- package/dist/lib/prompts/index.d.ts +1 -1
- package/dist/lib/prompts/index.d.ts.map +1 -1
- package/dist/lib/state/persistence.d.ts +0 -2
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +0 -7
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/token-utils.d.ts +0 -14
- package/dist/lib/token-utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/lib/compress/image-tokens.d.ts +0 -68
- package/dist/lib/compress/image-tokens.d.ts.map +0 -1
- package/dist/lib/messages/large-images.d.ts +0 -35
- package/dist/lib/messages/large-images.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -920,8 +920,6 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
920
920
|
"compress.images.enabled",
|
|
921
921
|
"compress.images.minBytes",
|
|
922
922
|
"compress.images.ttlDays",
|
|
923
|
-
"compress.images.largeBytes",
|
|
924
|
-
"compress.images.nudge",
|
|
925
923
|
"compress.completionReserveTokens",
|
|
926
924
|
"compress.emergencyTruncation",
|
|
927
925
|
"gc",
|
|
@@ -1319,7 +1317,7 @@ function validateConfigTypes(config) {
|
|
|
1319
1317
|
if (typeof compress.images !== "object" || compress.images === null || Array.isArray(compress.images)) {
|
|
1320
1318
|
errors.push({
|
|
1321
1319
|
key: "compress.images",
|
|
1322
|
-
expected: "object { enabled?: boolean; minBytes?: integer (>= 0); ttlDays?: number (> 0)
|
|
1320
|
+
expected: "object { enabled?: boolean; minBytes?: integer (>= 0); ttlDays?: number (> 0) }",
|
|
1323
1321
|
actual: JSON.stringify(compress.images)
|
|
1324
1322
|
});
|
|
1325
1323
|
} else {
|
|
@@ -1344,20 +1342,6 @@ function validateConfigTypes(config) {
|
|
|
1344
1342
|
actual: JSON.stringify(compress.images.ttlDays)
|
|
1345
1343
|
});
|
|
1346
1344
|
}
|
|
1347
|
-
if (compress.images.largeBytes !== void 0 && (typeof compress.images.largeBytes !== "number" || !Number.isInteger(compress.images.largeBytes) || compress.images.largeBytes < 0)) {
|
|
1348
|
-
errors.push({
|
|
1349
|
-
key: "compress.images.largeBytes",
|
|
1350
|
-
expected: "integer (>= 0)",
|
|
1351
|
-
actual: JSON.stringify(compress.images.largeBytes)
|
|
1352
|
-
});
|
|
1353
|
-
}
|
|
1354
|
-
if (compress.images.nudge !== void 0 && typeof compress.images.nudge !== "boolean") {
|
|
1355
|
-
errors.push({
|
|
1356
|
-
key: "compress.images.nudge",
|
|
1357
|
-
expected: "boolean",
|
|
1358
|
-
actual: typeof compress.images.nudge
|
|
1359
|
-
});
|
|
1360
|
-
}
|
|
1361
1345
|
}
|
|
1362
1346
|
}
|
|
1363
1347
|
if (compress.completionReserveTokens !== void 0 && typeof compress.completionReserveTokens !== "number") {
|
|
@@ -1758,9 +1742,7 @@ var DEFAULT_COMPRESS_REASONING = {
|
|
|
1758
1742
|
var DEFAULT_COMPRESS_IMAGES = {
|
|
1759
1743
|
enabled: true,
|
|
1760
1744
|
minBytes: 16384,
|
|
1761
|
-
ttlDays: 7
|
|
1762
|
-
largeBytes: 524288,
|
|
1763
|
-
nudge: true
|
|
1745
|
+
ttlDays: 7
|
|
1764
1746
|
};
|
|
1765
1747
|
var DEFAULT_PROTECTED_TOOLS = [
|
|
1766
1748
|
"task",
|
|
@@ -2043,9 +2025,7 @@ function mergeCompress(base, override) {
|
|
|
2043
2025
|
images: {
|
|
2044
2026
|
enabled: override.images?.enabled ?? base.images?.enabled ?? DEFAULT_COMPRESS_IMAGES.enabled,
|
|
2045
2027
|
minBytes: override.images?.minBytes ?? base.images?.minBytes ?? DEFAULT_COMPRESS_IMAGES.minBytes,
|
|
2046
|
-
ttlDays: override.images?.ttlDays ?? base.images?.ttlDays ?? DEFAULT_COMPRESS_IMAGES.ttlDays
|
|
2047
|
-
largeBytes: override.images?.largeBytes ?? base.images?.largeBytes ?? DEFAULT_COMPRESS_IMAGES.largeBytes,
|
|
2048
|
-
nudge: override.images?.nudge ?? base.images?.nudge ?? DEFAULT_COMPRESS_IMAGES.nudge
|
|
2028
|
+
ttlDays: override.images?.ttlDays ?? base.images?.ttlDays ?? DEFAULT_COMPRESS_IMAGES.ttlDays
|
|
2049
2029
|
},
|
|
2050
2030
|
completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens,
|
|
2051
2031
|
emergencyTruncation: override.emergencyTruncation ?? base.emergencyTruncation
|
|
@@ -2441,18 +2421,174 @@ function isProtectedUserMessage(_config, message) {
|
|
|
2441
2421
|
return false;
|
|
2442
2422
|
}
|
|
2443
2423
|
|
|
2444
|
-
// lib/
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2424
|
+
// lib/token-utils.ts
|
|
2425
|
+
var anthropicCountTokens = _anthropicTokenizer.countTokens ?? _anthropicTokenizer.default?.countTokens;
|
|
2426
|
+
function getCurrentTokenUsage(state, messages) {
|
|
2427
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2428
|
+
const msg = messages[i];
|
|
2429
|
+
if (msg.info.role !== "assistant") {
|
|
2430
|
+
continue;
|
|
2431
|
+
}
|
|
2432
|
+
const assistantInfo = msg.info;
|
|
2433
|
+
const input = assistantInfo.tokens?.input || 0;
|
|
2434
|
+
const output = assistantInfo.tokens?.output || 0;
|
|
2435
|
+
const reasoning = assistantInfo.tokens?.reasoning || 0;
|
|
2436
|
+
const cacheRead = assistantInfo.tokens?.cache?.read || 0;
|
|
2437
|
+
const cacheWrite = assistantInfo.tokens?.cache?.write || 0;
|
|
2438
|
+
if (input <= 0 && output <= 0) {
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
if (state.lastCompaction > 0 && (msg.info.time.created < state.lastCompaction || msg.info.summary === true && msg.info.time.created === state.lastCompaction)) {
|
|
2442
|
+
return 0;
|
|
2443
|
+
}
|
|
2444
|
+
return input + cacheRead + cacheWrite + output + reasoning;
|
|
2445
|
+
}
|
|
2446
|
+
let estimated = 0;
|
|
2447
|
+
for (const m of messages) {
|
|
2448
|
+
estimated += countAllMessageTokens(m);
|
|
2449
|
+
}
|
|
2450
|
+
return estimated;
|
|
2451
|
+
}
|
|
2452
|
+
function estimateSystemPromptTokens(messages) {
|
|
2453
|
+
let firstInput;
|
|
2454
|
+
for (const msg of messages) {
|
|
2455
|
+
if (msg.info.role !== "assistant") continue;
|
|
2456
|
+
const assistantInfo = msg.info;
|
|
2457
|
+
const t = assistantInfo.tokens;
|
|
2458
|
+
if (!t) continue;
|
|
2459
|
+
const input = (t.input || 0) + (t.cache?.read || 0) + (t.cache?.write || 0);
|
|
2460
|
+
if (input > 0) {
|
|
2461
|
+
firstInput = input;
|
|
2462
|
+
break;
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
if (firstInput === void 0) return 0;
|
|
2466
|
+
let firstUserText = "";
|
|
2467
|
+
for (const msg of messages) {
|
|
2468
|
+
if (msg.info.role !== "user") continue;
|
|
2469
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
2470
|
+
for (const part of parts) {
|
|
2471
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
2472
|
+
firstUserText += part.text;
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
if (firstUserText) break;
|
|
2476
|
+
}
|
|
2477
|
+
return Math.max(0, firstInput - countTokens2(firstUserText));
|
|
2478
|
+
}
|
|
2479
|
+
function getCurrentParams(state, messages, logger) {
|
|
2480
|
+
const userMsg = getLastUserMessage(messages);
|
|
2481
|
+
if (!userMsg) {
|
|
2482
|
+
logger.debug("No user message found when determining current params");
|
|
2483
|
+
return {
|
|
2484
|
+
providerId: void 0,
|
|
2485
|
+
modelId: void 0,
|
|
2486
|
+
agent: void 0,
|
|
2487
|
+
variant: void 0
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
const userInfo = userMsg.info;
|
|
2491
|
+
const agent = userInfo.agent;
|
|
2492
|
+
const providerId = userInfo.model.providerID;
|
|
2493
|
+
const modelId = userInfo.model.modelID;
|
|
2494
|
+
const variant = userInfo.model.variant;
|
|
2495
|
+
return { providerId, modelId, agent, variant };
|
|
2496
|
+
}
|
|
2497
|
+
function countTokens2(text) {
|
|
2498
|
+
if (!text) return 0;
|
|
2499
|
+
try {
|
|
2500
|
+
return anthropicCountTokens(text);
|
|
2501
|
+
} catch {
|
|
2502
|
+
return Math.round(text.length / 4);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
function estimateTokensBatch(texts) {
|
|
2506
|
+
if (texts.length === 0) return 0;
|
|
2507
|
+
return countTokens2(texts.join(" "));
|
|
2508
|
+
}
|
|
2509
|
+
var COMPACTED_TOOL_OUTPUT_PLACEHOLDER = "[Old tool result content cleared]";
|
|
2510
|
+
function stringifyToolContent(value) {
|
|
2511
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
2512
|
+
}
|
|
2513
|
+
function extractCompletedToolOutput(part) {
|
|
2514
|
+
if (part?.type !== "tool" || part.state?.status !== "completed" || part.state?.output === void 0) {
|
|
2515
|
+
return void 0;
|
|
2516
|
+
}
|
|
2517
|
+
if (part.state?.time?.compacted) {
|
|
2518
|
+
return COMPACTED_TOOL_OUTPUT_PLACEHOLDER;
|
|
2519
|
+
}
|
|
2520
|
+
return stringifyToolContent(part.state.output);
|
|
2521
|
+
}
|
|
2522
|
+
function extractToolContent(part) {
|
|
2523
|
+
const contents = [];
|
|
2524
|
+
if (part?.type !== "tool") {
|
|
2525
|
+
return contents;
|
|
2526
|
+
}
|
|
2527
|
+
if (part.state?.input !== void 0) {
|
|
2528
|
+
contents.push(stringifyToolContent(part.state.input));
|
|
2529
|
+
}
|
|
2530
|
+
const completedOutput = extractCompletedToolOutput(part);
|
|
2531
|
+
if (completedOutput !== void 0) {
|
|
2532
|
+
contents.push(completedOutput);
|
|
2533
|
+
} else if (part.state?.status === "error" && part.state?.error) {
|
|
2534
|
+
contents.push(stringifyToolContent(part.state.error));
|
|
2535
|
+
}
|
|
2536
|
+
return contents;
|
|
2537
|
+
}
|
|
2538
|
+
function countAllMessageTokens(msg) {
|
|
2539
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
2540
|
+
const texts = [];
|
|
2541
|
+
for (const part of parts) {
|
|
2542
|
+
if (part.type === "text") {
|
|
2543
|
+
texts.push(part.text);
|
|
2544
|
+
} else {
|
|
2545
|
+
texts.push(...extractToolContent(part));
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
if (texts.length === 0) return 0;
|
|
2549
|
+
return estimateTokensBatch(texts);
|
|
2550
|
+
}
|
|
2551
|
+
function countMessageCharacters(msg) {
|
|
2552
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
2553
|
+
let total = 0;
|
|
2554
|
+
for (const part of parts) {
|
|
2555
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
2556
|
+
total += part.text.length;
|
|
2557
|
+
} else {
|
|
2558
|
+
for (const content of extractToolContent(part)) {
|
|
2559
|
+
total += content.length;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
return total;
|
|
2564
|
+
}
|
|
2565
|
+
function estimateAllMessageTokensFast(msg) {
|
|
2566
|
+
return Math.round(countMessageCharacters(msg) / 4);
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// lib/prompts/extensions/tool.ts
|
|
2570
|
+
var RANGE_FORMAT_EXTENSION = `
|
|
2571
|
+
|
|
2572
|
+
THE FORMAT OF COMPRESS
|
|
2573
|
+
|
|
2574
|
+
\`\`\`
|
|
2575
|
+
{
|
|
2576
|
+
topic?: string, // OPTIONAL fallback topic for entries without their own.
|
|
2577
|
+
// Omit when every content entry specifies its own topic.
|
|
2578
|
+
content: [ // One or more ranges to compress
|
|
2579
|
+
{
|
|
2580
|
+
topic?: string, // OPTIONAL per-entry topic for this range.
|
|
2581
|
+
// Falls back to top-level topic.
|
|
2582
|
+
// Give each entry its own topic when compressing
|
|
2583
|
+
// unrelated ranges in one call.
|
|
2584
|
+
startId: string, // Boundary ID at range start: mNNNNN or bN
|
|
2585
|
+
endId: string, // Boundary ID at range end: mNNNNN or bN
|
|
2586
|
+
summary: string // Complete technical summary replacing all content in range
|
|
2587
|
+
}
|
|
2588
|
+
]
|
|
2589
|
+
}
|
|
2590
|
+
\`\`\`
|
|
2591
|
+
Each entry needs a topic \u2014 either its own or the top-level fallback.`;
|
|
2456
2592
|
|
|
2457
2593
|
// lib/state/persistence.ts
|
|
2458
2594
|
import * as fs from "fs/promises";
|
|
@@ -2606,7 +2742,7 @@ function loadPruneMessagesState(persisted) {
|
|
|
2606
2742
|
deactivatedByUser: block.deactivatedByUser === true,
|
|
2607
2743
|
deactivatedByUserDeep: block.deactivatedByUserDeep === true ? true : void 0,
|
|
2608
2744
|
compressedTokens: typeof block.compressedTokens === "number" && Number.isFinite(block.compressedTokens) ? Math.max(0, block.compressedTokens) : 0,
|
|
2609
|
-
summaryTokens: typeof block.summaryTokens === "number" && Number.isFinite(block.summaryTokens) ? Math.max(0, block.summaryTokens) : typeof block.summary === "string" ?
|
|
2745
|
+
summaryTokens: typeof block.summaryTokens === "number" && Number.isFinite(block.summaryTokens) ? Math.max(0, block.summaryTokens) : typeof block.summary === "string" ? countTokens2(block.summary) : 0,
|
|
2610
2746
|
durationMs: typeof block.durationMs === "number" && Number.isFinite(block.durationMs) ? Math.max(0, block.durationMs) : 0,
|
|
2611
2747
|
mode: block.mode === "range" || block.mode === "message" ? block.mode : void 0,
|
|
2612
2748
|
topic: typeof block.topic === "string" ? block.topic : "",
|
|
@@ -2831,7 +2967,6 @@ function saveSessionState(sessionState, logger, sessionName) {
|
|
|
2831
2967
|
modelProviderID: sessionState.modelProviderID,
|
|
2832
2968
|
modelID: sessionState.modelID,
|
|
2833
2969
|
imageArchive: sessionState.imageArchive,
|
|
2834
|
-
imageSurgeNudged: sessionState.imageSurgeNudged,
|
|
2835
2970
|
stickyTruncations: sessionState.stickyTruncations
|
|
2836
2971
|
};
|
|
2837
2972
|
const key = saveQueueKey(sessionState.sessionId, sessionState.storageDir);
|
|
@@ -2929,527 +3064,40 @@ async function loadSessionState(sessionId, logger, storageDir) {
|
|
|
2929
3064
|
(entry) => typeof entry === "string"
|
|
2930
3065
|
);
|
|
2931
3066
|
const dedupedIterationAnchors = [...new Set(validIterationAnchors)];
|
|
2932
|
-
if (validIterationAnchors.length !== rawIterationNudgeAnchors.length) {
|
|
2933
|
-
logger.warn("Filtered out malformed iterationNudgeAnchors entries", {
|
|
2934
|
-
sessionId,
|
|
2935
|
-
original: rawIterationNudgeAnchors.length,
|
|
2936
|
-
valid: validIterationAnchors.length
|
|
2937
|
-
});
|
|
2938
|
-
}
|
|
2939
|
-
state.nudges.iterationNudgeAnchors = dedupedIterationAnchors;
|
|
2940
|
-
const persistedMessageIds = state.messageIds;
|
|
2941
|
-
if (persistedMessageIds) {
|
|
2942
|
-
;
|
|
2943
|
-
state._persistedMessageIds = persistedMessageIds;
|
|
2944
|
-
}
|
|
2945
|
-
const persistedLastCompaction = state.lastCompaction;
|
|
2946
|
-
if (persistedLastCompaction !== void 0) {
|
|
2947
|
-
;
|
|
2948
|
-
state._persistedLastCompaction = persistedLastCompaction;
|
|
2949
|
-
}
|
|
2950
|
-
logger.info("Loaded session state from disk", {
|
|
2951
|
-
sessionId
|
|
2952
|
-
});
|
|
2953
|
-
return state;
|
|
2954
|
-
} catch (error) {
|
|
2955
|
-
logger.warn("Failed to load session state", {
|
|
2956
|
-
sessionId,
|
|
2957
|
-
error: error?.message
|
|
2958
|
-
});
|
|
2959
|
-
return null;
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
|
|
2963
|
-
// lib/compress/image-archive.ts
|
|
2964
|
-
var DATA_URL_REGEX = /^data:(image\/[a-z0-9.+-]+);base64,(.+)$/is;
|
|
2965
|
-
var IMAGE_EXTENSIONS = {
|
|
2966
|
-
"image/png": "png",
|
|
2967
|
-
"image/jpeg": "jpg",
|
|
2968
|
-
"image/jpg": "jpg",
|
|
2969
|
-
"image/gif": "gif",
|
|
2970
|
-
"image/webp": "webp",
|
|
2971
|
-
"image/avif": "avif",
|
|
2972
|
-
"image/bmp": "bmp"
|
|
2973
|
-
};
|
|
2974
|
-
function sha8(input) {
|
|
2975
|
-
return createHash("sha256").update(input).digest("hex").slice(0, 8);
|
|
2976
|
-
}
|
|
2977
|
-
function parseImageDataUrl(url) {
|
|
2978
|
-
if (typeof url !== "string") return void 0;
|
|
2979
|
-
const match = DATA_URL_REGEX.exec(url);
|
|
2980
|
-
if (!match) return void 0;
|
|
2981
|
-
return { mediaType: match[1].toLowerCase(), base64: match[2] };
|
|
2982
|
-
}
|
|
2983
|
-
function extForMediaType(mediaType) {
|
|
2984
|
-
return IMAGE_EXTENSIONS[mediaType.toLowerCase()];
|
|
2985
|
-
}
|
|
2986
|
-
function archiveDirName(sessionId) {
|
|
2987
|
-
let name = sessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 100);
|
|
2988
|
-
if (name !== sessionId) {
|
|
2989
|
-
name = `${name}-${sha8(sessionId)}`;
|
|
2990
|
-
}
|
|
2991
|
-
return name || "-";
|
|
2992
|
-
}
|
|
2993
|
-
function archiveRoot(storageDir, sessionId) {
|
|
2994
|
-
return join3(storageDir ?? getDefaultStorageDir(), "images", archiveDirName(sessionId));
|
|
2995
|
-
}
|
|
2996
|
-
function archiveSessionImages(state, messages, images, logger) {
|
|
2997
|
-
if (!images.enabled || !state.sessionId) return 0;
|
|
2998
|
-
const root = archiveRoot(state.storageDir, state.sessionId);
|
|
2999
|
-
let archived = 0;
|
|
3000
|
-
for (const message of messages) {
|
|
3001
|
-
const rawId = message.info.id;
|
|
3002
|
-
if (!rawId) continue;
|
|
3003
|
-
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
3004
|
-
for (let partIndex = 0; partIndex < parts.length; partIndex++) {
|
|
3005
|
-
const part = parts[partIndex];
|
|
3006
|
-
if (part.type !== "file") continue;
|
|
3007
|
-
const parsed = parseImageDataUrl(part.url);
|
|
3008
|
-
if (!parsed) continue;
|
|
3009
|
-
const ext = extForMediaType(parsed.mediaType);
|
|
3010
|
-
if (!ext) continue;
|
|
3011
|
-
const fileName = `${sha8(rawId)}-p${partIndex}-${sha8(parsed.base64)}.${ext}`;
|
|
3012
|
-
const filePath = join3(root, fileName);
|
|
3013
|
-
const existing = state.imageArchive[rawId]?.find((e) => e.path === filePath);
|
|
3014
|
-
if (existing) continue;
|
|
3015
|
-
let bytes;
|
|
3016
|
-
try {
|
|
3017
|
-
if (existsSync3(filePath)) {
|
|
3018
|
-
bytes = statSync2(filePath).size;
|
|
3019
|
-
} else {
|
|
3020
|
-
const buffer = Buffer.from(parsed.base64, "base64");
|
|
3021
|
-
bytes = buffer.byteLength;
|
|
3022
|
-
if (bytes < images.minBytes) continue;
|
|
3023
|
-
mkdirSync2(root, { recursive: true });
|
|
3024
|
-
writeFileSync2(filePath, buffer, { mode: 384 });
|
|
3025
|
-
}
|
|
3026
|
-
} catch (error) {
|
|
3027
|
-
logger.warn(
|
|
3028
|
-
`[#478] failed to archive image ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
3029
|
-
);
|
|
3030
|
-
continue;
|
|
3031
|
-
}
|
|
3032
|
-
const entry = {
|
|
3033
|
-
path: filePath,
|
|
3034
|
-
mediaType: parsed.mediaType,
|
|
3035
|
-
bytes
|
|
3036
|
-
};
|
|
3037
|
-
if (!state.imageArchive[rawId]) {
|
|
3038
|
-
state.imageArchive[rawId] = [];
|
|
3039
|
-
}
|
|
3040
|
-
state.imageArchive[rawId].push(entry);
|
|
3041
|
-
state.stats.imagesArchived = (state.stats.imagesArchived ?? 0) + 1;
|
|
3042
|
-
state.stats.imageBytesArchived = (state.stats.imageBytesArchived ?? 0) + bytes;
|
|
3043
|
-
archived++;
|
|
3044
|
-
}
|
|
3045
|
-
}
|
|
3046
|
-
return archived;
|
|
3047
|
-
}
|
|
3048
|
-
function pruneImageArchive(state, images, logger) {
|
|
3049
|
-
if (state.imagePruneDone || !state.sessionId) return;
|
|
3050
|
-
state.imagePruneDone = true;
|
|
3051
|
-
try {
|
|
3052
|
-
const root = archiveRoot(state.storageDir, state.sessionId);
|
|
3053
|
-
if (!existsSync3(root)) return;
|
|
3054
|
-
const cutoff = Date.now() - images.ttlDays * 864e5;
|
|
3055
|
-
const prunedPaths = /* @__PURE__ */ new Set();
|
|
3056
|
-
for (const name of readdirSync(root)) {
|
|
3057
|
-
const filePath = join3(root, name);
|
|
3058
|
-
try {
|
|
3059
|
-
if (statSync2(filePath).mtimeMs < cutoff) {
|
|
3060
|
-
unlinkSync(filePath);
|
|
3061
|
-
prunedPaths.add(filePath);
|
|
3062
|
-
}
|
|
3063
|
-
} catch {
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
if (prunedPaths.size > 0) {
|
|
3067
|
-
for (const [rawId, entries] of Object.entries(state.imageArchive)) {
|
|
3068
|
-
const kept = entries.filter((e) => !prunedPaths.has(e.path));
|
|
3069
|
-
if (kept.length === 0) {
|
|
3070
|
-
delete state.imageArchive[rawId];
|
|
3071
|
-
} else if (kept.length !== entries.length) {
|
|
3072
|
-
state.imageArchive[rawId] = kept;
|
|
3073
|
-
}
|
|
3074
|
-
}
|
|
3075
|
-
logger.info(`[#478] TTL sweep removed ${prunedPaths.size} expired archived image(s)`);
|
|
3076
|
-
}
|
|
3077
|
-
if (readdirSync(root).length === 0) {
|
|
3078
|
-
rmdirSync(root);
|
|
3079
|
-
}
|
|
3080
|
-
} catch (error) {
|
|
3081
|
-
logger.warn(
|
|
3082
|
-
`[#478] image TTL sweep failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3083
|
-
);
|
|
3084
|
-
}
|
|
3085
|
-
}
|
|
3086
|
-
function appendArchivedImagePointers(summary, selection, state, enabled) {
|
|
3087
|
-
if (!enabled) return summary;
|
|
3088
|
-
const lines = [];
|
|
3089
|
-
for (const messageId of selection.messageIds) {
|
|
3090
|
-
const entries = state.imageArchive[messageId];
|
|
3091
|
-
if (!entries || entries.length === 0) continue;
|
|
3092
|
-
const ref = state.messageIds.byRawId.get(messageId) ?? messageId;
|
|
3093
|
-
for (const entry of entries) {
|
|
3094
|
-
lines.push(`- ${ref}: ${entry.path} (${entry.mediaType}, ${entry.bytes} bytes)`);
|
|
3095
|
-
}
|
|
3096
|
-
}
|
|
3097
|
-
if (lines.length === 0) return summary;
|
|
3098
|
-
const heading = "\n\nImages captured in this range were archived to disk (their pixels are NOT summarized). Re-read them with your file tools if you need the pixels again:";
|
|
3099
|
-
return summary + heading + "\n" + lines.join("\n");
|
|
3100
|
-
}
|
|
3101
|
-
|
|
3102
|
-
// lib/compress/image-tokens.ts
|
|
3103
|
-
var VISION_MAX_SIDE = 1568;
|
|
3104
|
-
var VISION_TILE_SIZE = 512;
|
|
3105
|
-
var TOKENS_PER_TILE = 167;
|
|
3106
|
-
var VISION_MAX_TILES = Math.ceil(VISION_MAX_SIDE / VISION_TILE_SIZE) ** 2;
|
|
3107
|
-
var UNKNOWN_DIM_LARGE_BYTES = 256 * 1024;
|
|
3108
|
-
function estimateVisionTokensForDimensions(width, height) {
|
|
3109
|
-
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
3110
|
-
return 0;
|
|
3111
|
-
}
|
|
3112
|
-
const scale = Math.min(1, VISION_MAX_SIDE / Math.max(width, height));
|
|
3113
|
-
const scaledWidth = Math.max(1, Math.floor(width * scale));
|
|
3114
|
-
const scaledHeight = Math.max(1, Math.floor(height * scale));
|
|
3115
|
-
const tiles = Math.ceil(scaledWidth / VISION_TILE_SIZE) * Math.ceil(scaledHeight / VISION_TILE_SIZE);
|
|
3116
|
-
return Math.min(tiles, VISION_MAX_TILES) * TOKENS_PER_TILE;
|
|
3117
|
-
}
|
|
3118
|
-
function validDimensions(width, height) {
|
|
3119
|
-
if (typeof width !== "number" || typeof height !== "number" || !Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0 || width > 1e6 || height > 1e6) {
|
|
3120
|
-
return void 0;
|
|
3121
|
-
}
|
|
3122
|
-
return { width, height };
|
|
3123
|
-
}
|
|
3124
|
-
function fourcc(bytes, offset) {
|
|
3125
|
-
return String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
|
|
3126
|
-
}
|
|
3127
|
-
function parseImageDimensions(bytes, mediaType) {
|
|
3128
|
-
const len = bytes.length;
|
|
3129
|
-
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
3130
|
-
switch (mediaType.toLowerCase()) {
|
|
3131
|
-
case "image/png": {
|
|
3132
|
-
if (len < 24) return void 0;
|
|
3133
|
-
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
3134
|
-
for (let i = 0; i < sig.length; i++) {
|
|
3135
|
-
if (bytes[i] !== sig[i]) return void 0;
|
|
3136
|
-
}
|
|
3137
|
-
return validDimensions(dv.getUint32(16, false), dv.getUint32(20, false));
|
|
3138
|
-
}
|
|
3139
|
-
case "image/jpeg":
|
|
3140
|
-
case "image/jpg": {
|
|
3141
|
-
if (len < 4 || bytes[0] !== 255 || bytes[1] !== 216) return void 0;
|
|
3142
|
-
let i = 2;
|
|
3143
|
-
while (i + 9 < len) {
|
|
3144
|
-
if (bytes[i] !== 255) {
|
|
3145
|
-
i++;
|
|
3146
|
-
continue;
|
|
3147
|
-
}
|
|
3148
|
-
const marker = bytes[i + 1];
|
|
3149
|
-
if (marker === 216 || marker === 217 || marker >= 218 && marker <= 223) {
|
|
3150
|
-
return void 0;
|
|
3151
|
-
}
|
|
3152
|
-
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
3153
|
-
if (i + 9 >= len) return void 0;
|
|
3154
|
-
return validDimensions(dv.getUint16(i + 8, false), dv.getUint16(i + 6, false));
|
|
3155
|
-
}
|
|
3156
|
-
const segLen = dv.getUint16(i + 2, false);
|
|
3157
|
-
if (segLen < 2) return void 0;
|
|
3158
|
-
i += 2 + segLen;
|
|
3159
|
-
}
|
|
3160
|
-
return void 0;
|
|
3161
|
-
}
|
|
3162
|
-
case "image/gif": {
|
|
3163
|
-
if (len < 10) return void 0;
|
|
3164
|
-
if (fourcc(bytes, 0) !== "GIF8") return void 0;
|
|
3165
|
-
return validDimensions(dv.getUint16(6, true), dv.getUint16(8, true));
|
|
3166
|
-
}
|
|
3167
|
-
case "image/webp": {
|
|
3168
|
-
if (len < 30) return void 0;
|
|
3169
|
-
if (fourcc(bytes, 0) !== "RIFF" || fourcc(bytes, 8) !== "WEBP") return void 0;
|
|
3170
|
-
let i = 12;
|
|
3171
|
-
while (i + 8 < len) {
|
|
3172
|
-
const id = fourcc(bytes, i);
|
|
3173
|
-
const size = dv.getUint32(i + 4, true);
|
|
3174
|
-
const payload = i + 8;
|
|
3175
|
-
if (id === "VP8 ") {
|
|
3176
|
-
if (payload + 10 > len) return void 0;
|
|
3177
|
-
return validDimensions(
|
|
3178
|
-
dv.getUint16(payload + 6, true) & 16383,
|
|
3179
|
-
dv.getUint16(payload + 8, true) & 16383
|
|
3180
|
-
);
|
|
3181
|
-
}
|
|
3182
|
-
if (id === "VP8L") {
|
|
3183
|
-
if (payload + 5 > len) return void 0;
|
|
3184
|
-
if (bytes[payload] !== 47) return void 0;
|
|
3185
|
-
const packed = bytes[payload + 1] | bytes[payload + 2] << 8 | bytes[payload + 3] << 16;
|
|
3186
|
-
return validDimensions(
|
|
3187
|
-
1 + (packed & 16383),
|
|
3188
|
-
1 + (packed >>> 14 & 16383)
|
|
3189
|
-
);
|
|
3190
|
-
}
|
|
3191
|
-
if (id === "VP8X") {
|
|
3192
|
-
if (payload + 10 > len) return void 0;
|
|
3193
|
-
const w = bytes[payload + 4] | bytes[payload + 5] << 8 | bytes[payload + 6] << 16;
|
|
3194
|
-
const h = bytes[payload + 7] | bytes[payload + 8] << 8 | bytes[payload + 9] << 16;
|
|
3195
|
-
return validDimensions(w + 1, h + 1);
|
|
3196
|
-
}
|
|
3197
|
-
if (size === 4294967295) return void 0;
|
|
3198
|
-
i = payload + size + (size & 1);
|
|
3199
|
-
}
|
|
3200
|
-
return void 0;
|
|
3201
|
-
}
|
|
3202
|
-
case "image/bmp": {
|
|
3203
|
-
if (len < 26) return void 0;
|
|
3204
|
-
if (bytes[0] !== 66 || bytes[1] !== 77) return void 0;
|
|
3205
|
-
return validDimensions(
|
|
3206
|
-
Math.abs(dv.getInt32(18, true)),
|
|
3207
|
-
Math.abs(dv.getInt32(22, true))
|
|
3208
|
-
);
|
|
3209
|
-
}
|
|
3210
|
-
default:
|
|
3211
|
-
return void 0;
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
function decodedBytesFromBase64(base64) {
|
|
3215
|
-
if (typeof base64 !== "string" || base64.length === 0) return 0;
|
|
3216
|
-
return Math.max(0, Math.floor(base64.length * 3 / 4));
|
|
3217
|
-
}
|
|
3218
|
-
function decodeBase64Prefix(base64, maxBytes) {
|
|
3219
|
-
const chars = Math.min(base64.length, Math.ceil(maxBytes * 4 / 3));
|
|
3220
|
-
const aligned = chars - chars % 4;
|
|
3221
|
-
if (aligned < 4) return void 0;
|
|
3222
|
-
try {
|
|
3223
|
-
return new Uint8Array(Buffer.from(base64.slice(0, aligned), "base64"));
|
|
3224
|
-
} catch {
|
|
3225
|
-
return void 0;
|
|
3226
|
-
}
|
|
3227
|
-
}
|
|
3228
|
-
var HEADER_SCAN_BYTES = 256 * 1024;
|
|
3229
|
-
var estimateCache = /* @__PURE__ */ new Map();
|
|
3230
|
-
var ESTIMATE_CACHE_CAP = 512;
|
|
3231
|
-
function remember(key, value) {
|
|
3232
|
-
if (estimateCache.size >= ESTIMATE_CACHE_CAP && !estimateCache.has(key)) {
|
|
3233
|
-
const oldest = estimateCache.keys().next().value;
|
|
3234
|
-
if (oldest !== void 0) {
|
|
3235
|
-
estimateCache.delete(oldest);
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
estimateCache.set(key, value);
|
|
3239
|
-
}
|
|
3240
|
-
function estimateImagePartTokens(part) {
|
|
3241
|
-
if (!part || typeof part !== "object") return 0;
|
|
3242
|
-
const candidate = part;
|
|
3243
|
-
if (candidate.type !== "file") return 0;
|
|
3244
|
-
const url = typeof candidate.url === "string" ? candidate.url : void 0;
|
|
3245
|
-
if (!url) return 0;
|
|
3246
|
-
const parsed = parseImageDataUrl(url);
|
|
3247
|
-
if (!parsed) return 0;
|
|
3248
|
-
const key = `${parsed.mediaType}:${url.length}:${url.slice(-32)}`;
|
|
3249
|
-
const cached = estimateCache.get(key);
|
|
3250
|
-
if (cached !== void 0) return cached;
|
|
3251
|
-
let estimate;
|
|
3252
|
-
const prefix = decodeBase64Prefix(parsed.base64, HEADER_SCAN_BYTES);
|
|
3253
|
-
const dims = prefix ? parseImageDimensions(prefix, parsed.mediaType) : void 0;
|
|
3254
|
-
if (dims) {
|
|
3255
|
-
estimate = estimateVisionTokensForDimensions(dims.width, dims.height);
|
|
3256
|
-
} else {
|
|
3257
|
-
const bytes = decodedBytesFromBase64(parsed.base64);
|
|
3258
|
-
estimate = bytes >= UNKNOWN_DIM_LARGE_BYTES ? VISION_MAX_TILES * TOKENS_PER_TILE : Math.round(parsed.base64.length / 4);
|
|
3259
|
-
}
|
|
3260
|
-
remember(key, estimate);
|
|
3261
|
-
return estimate;
|
|
3262
|
-
}
|
|
3263
|
-
|
|
3264
|
-
// lib/token-utils.ts
|
|
3265
|
-
var anthropicCountTokens = _anthropicTokenizer.countTokens ?? _anthropicTokenizer.default?.countTokens;
|
|
3266
|
-
function countPendingImageTokens(messages, startIndex) {
|
|
3267
|
-
let total = 0;
|
|
3268
|
-
for (let i = Math.max(0, startIndex); i < messages.length; i++) {
|
|
3269
|
-
const parts = Array.isArray(messages[i].parts) ? messages[i].parts : [];
|
|
3270
|
-
for (const part of parts) {
|
|
3271
|
-
if (part.type === "file") {
|
|
3272
|
-
total += estimateImagePartTokens(part);
|
|
3273
|
-
}
|
|
3274
|
-
}
|
|
3275
|
-
}
|
|
3276
|
-
return total;
|
|
3277
|
-
}
|
|
3278
|
-
function getCurrentTokenUsage(state, messages) {
|
|
3279
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3280
|
-
const msg = messages[i];
|
|
3281
|
-
if (msg.info.role !== "assistant") {
|
|
3282
|
-
continue;
|
|
3283
|
-
}
|
|
3284
|
-
const assistantInfo = msg.info;
|
|
3285
|
-
const input = assistantInfo.tokens?.input || 0;
|
|
3286
|
-
const output = assistantInfo.tokens?.output || 0;
|
|
3287
|
-
const reasoning = assistantInfo.tokens?.reasoning || 0;
|
|
3288
|
-
const cacheRead = assistantInfo.tokens?.cache?.read || 0;
|
|
3289
|
-
const cacheWrite = assistantInfo.tokens?.cache?.write || 0;
|
|
3290
|
-
if (input <= 0 && output <= 0) {
|
|
3291
|
-
continue;
|
|
3292
|
-
}
|
|
3293
|
-
if (state.lastCompaction > 0 && (msg.info.time.created < state.lastCompaction || msg.info.summary === true && msg.info.time.created === state.lastCompaction)) {
|
|
3294
|
-
return 0;
|
|
3295
|
-
}
|
|
3296
|
-
return input + cacheRead + cacheWrite + output + reasoning + countPendingImageTokens(messages, i + 1);
|
|
3297
|
-
}
|
|
3298
|
-
let estimated = 0;
|
|
3299
|
-
for (const m of messages) {
|
|
3300
|
-
estimated += countAllMessageTokens(m);
|
|
3301
|
-
}
|
|
3302
|
-
return estimated;
|
|
3303
|
-
}
|
|
3304
|
-
function estimateSystemPromptTokens(messages) {
|
|
3305
|
-
let firstInput;
|
|
3306
|
-
for (const msg of messages) {
|
|
3307
|
-
if (msg.info.role !== "assistant") continue;
|
|
3308
|
-
const assistantInfo = msg.info;
|
|
3309
|
-
const t = assistantInfo.tokens;
|
|
3310
|
-
if (!t) continue;
|
|
3311
|
-
const input = (t.input || 0) + (t.cache?.read || 0) + (t.cache?.write || 0);
|
|
3312
|
-
if (input > 0) {
|
|
3313
|
-
firstInput = input;
|
|
3314
|
-
break;
|
|
3315
|
-
}
|
|
3316
|
-
}
|
|
3317
|
-
if (firstInput === void 0) return 0;
|
|
3318
|
-
let firstUserText = "";
|
|
3319
|
-
for (const msg of messages) {
|
|
3320
|
-
if (msg.info.role !== "user") continue;
|
|
3321
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
3322
|
-
for (const part of parts) {
|
|
3323
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
3324
|
-
firstUserText += part.text;
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
if (firstUserText) break;
|
|
3328
|
-
}
|
|
3329
|
-
return Math.max(0, firstInput - countTokens(firstUserText));
|
|
3330
|
-
}
|
|
3331
|
-
function getCurrentParams(state, messages, logger) {
|
|
3332
|
-
const userMsg = getLastUserMessage(messages);
|
|
3333
|
-
if (!userMsg) {
|
|
3334
|
-
logger.debug("No user message found when determining current params");
|
|
3335
|
-
return {
|
|
3336
|
-
providerId: void 0,
|
|
3337
|
-
modelId: void 0,
|
|
3338
|
-
agent: void 0,
|
|
3339
|
-
variant: void 0
|
|
3340
|
-
};
|
|
3341
|
-
}
|
|
3342
|
-
const userInfo = userMsg.info;
|
|
3343
|
-
const agent = userInfo.agent;
|
|
3344
|
-
const providerId = userInfo.model.providerID;
|
|
3345
|
-
const modelId = userInfo.model.modelID;
|
|
3346
|
-
const variant = userInfo.model.variant;
|
|
3347
|
-
return { providerId, modelId, agent, variant };
|
|
3348
|
-
}
|
|
3349
|
-
function countTokens(text) {
|
|
3350
|
-
if (!text) return 0;
|
|
3351
|
-
try {
|
|
3352
|
-
return anthropicCountTokens(text);
|
|
3353
|
-
} catch {
|
|
3354
|
-
return Math.round(text.length / 4);
|
|
3355
|
-
}
|
|
3356
|
-
}
|
|
3357
|
-
function estimateTokensBatch(texts) {
|
|
3358
|
-
if (texts.length === 0) return 0;
|
|
3359
|
-
return countTokens(texts.join(" "));
|
|
3360
|
-
}
|
|
3361
|
-
var COMPACTED_TOOL_OUTPUT_PLACEHOLDER = "[Old tool result content cleared]";
|
|
3362
|
-
function stringifyToolContent(value) {
|
|
3363
|
-
return typeof value === "string" ? value : JSON.stringify(value);
|
|
3364
|
-
}
|
|
3365
|
-
function extractCompletedToolOutput(part) {
|
|
3366
|
-
if (part?.type !== "tool" || part.state?.status !== "completed" || part.state?.output === void 0) {
|
|
3367
|
-
return void 0;
|
|
3368
|
-
}
|
|
3369
|
-
if (part.state?.time?.compacted) {
|
|
3370
|
-
return COMPACTED_TOOL_OUTPUT_PLACEHOLDER;
|
|
3371
|
-
}
|
|
3372
|
-
return stringifyToolContent(part.state.output);
|
|
3373
|
-
}
|
|
3374
|
-
function extractToolContent(part) {
|
|
3375
|
-
const contents = [];
|
|
3376
|
-
if (part?.type !== "tool") {
|
|
3377
|
-
return contents;
|
|
3378
|
-
}
|
|
3379
|
-
if (part.state?.input !== void 0) {
|
|
3380
|
-
contents.push(stringifyToolContent(part.state.input));
|
|
3381
|
-
}
|
|
3382
|
-
const completedOutput = extractCompletedToolOutput(part);
|
|
3383
|
-
if (completedOutput !== void 0) {
|
|
3384
|
-
contents.push(completedOutput);
|
|
3385
|
-
} else if (part.state?.status === "error" && part.state?.error) {
|
|
3386
|
-
contents.push(stringifyToolContent(part.state.error));
|
|
3387
|
-
}
|
|
3388
|
-
return contents;
|
|
3389
|
-
}
|
|
3390
|
-
function countAllMessageTokens(msg) {
|
|
3391
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
3392
|
-
const texts = [];
|
|
3393
|
-
let imageTokens = 0;
|
|
3394
|
-
for (const part of parts) {
|
|
3395
|
-
if (part.type === "text") {
|
|
3396
|
-
texts.push(part.text);
|
|
3397
|
-
} else if (part.type === "file") {
|
|
3398
|
-
imageTokens += estimateImagePartTokens(part);
|
|
3399
|
-
} else {
|
|
3400
|
-
texts.push(...extractToolContent(part));
|
|
3067
|
+
if (validIterationAnchors.length !== rawIterationNudgeAnchors.length) {
|
|
3068
|
+
logger.warn("Filtered out malformed iterationNudgeAnchors entries", {
|
|
3069
|
+
sessionId,
|
|
3070
|
+
original: rawIterationNudgeAnchors.length,
|
|
3071
|
+
valid: validIterationAnchors.length
|
|
3072
|
+
});
|
|
3401
3073
|
}
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
3408
|
-
let total = 0;
|
|
3409
|
-
for (const part of parts) {
|
|
3410
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
3411
|
-
total += part.text.length;
|
|
3412
|
-
} else if (part.type === "file") {
|
|
3413
|
-
total += estimateImagePartTokens(part) * 4;
|
|
3414
|
-
} else {
|
|
3415
|
-
for (const content of extractToolContent(part)) {
|
|
3416
|
-
total += content.length;
|
|
3417
|
-
}
|
|
3074
|
+
state.nudges.iterationNudgeAnchors = dedupedIterationAnchors;
|
|
3075
|
+
const persistedMessageIds = state.messageIds;
|
|
3076
|
+
if (persistedMessageIds) {
|
|
3077
|
+
;
|
|
3078
|
+
state._persistedMessageIds = persistedMessageIds;
|
|
3418
3079
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
return Math.round(countMessageCharacters(msg) / 4);
|
|
3424
|
-
}
|
|
3425
|
-
|
|
3426
|
-
// lib/prompts/extensions/tool.ts
|
|
3427
|
-
var RANGE_FORMAT_EXTENSION = `
|
|
3428
|
-
|
|
3429
|
-
THE FORMAT OF COMPRESS
|
|
3430
|
-
|
|
3431
|
-
\`\`\`
|
|
3432
|
-
{
|
|
3433
|
-
topic?: string, // OPTIONAL fallback topic for entries without their own.
|
|
3434
|
-
// Omit when every content entry specifies its own topic.
|
|
3435
|
-
content: [ // One or more ranges to compress
|
|
3436
|
-
{
|
|
3437
|
-
topic?: string, // OPTIONAL per-entry topic for this range.
|
|
3438
|
-
// Falls back to top-level topic.
|
|
3439
|
-
// Give each entry its own topic when compressing
|
|
3440
|
-
// unrelated ranges in one call.
|
|
3441
|
-
startId: string, // Boundary ID at range start: mNNNNN or bN
|
|
3442
|
-
endId: string, // Boundary ID at range end: mNNNNN or bN
|
|
3443
|
-
summary: string // Complete technical summary replacing all content in range
|
|
3080
|
+
const persistedLastCompaction = state.lastCompaction;
|
|
3081
|
+
if (persistedLastCompaction !== void 0) {
|
|
3082
|
+
;
|
|
3083
|
+
state._persistedLastCompaction = persistedLastCompaction;
|
|
3444
3084
|
}
|
|
3445
|
-
|
|
3085
|
+
logger.info("Loaded session state from disk", {
|
|
3086
|
+
sessionId
|
|
3087
|
+
});
|
|
3088
|
+
return state;
|
|
3089
|
+
} catch (error) {
|
|
3090
|
+
logger.warn("Failed to load session state", {
|
|
3091
|
+
sessionId,
|
|
3092
|
+
error: error?.message
|
|
3093
|
+
});
|
|
3094
|
+
return null;
|
|
3095
|
+
}
|
|
3446
3096
|
}
|
|
3447
|
-
\`\`\`
|
|
3448
|
-
Each entry needs a topic \u2014 either its own or the top-level fallback.`;
|
|
3449
3097
|
|
|
3450
3098
|
// lib/state/state.ts
|
|
3451
|
-
import { existsSync as
|
|
3452
|
-
import { join as
|
|
3099
|
+
import { existsSync as existsSync3 } from "fs";
|
|
3100
|
+
import { join as join3 } from "path";
|
|
3453
3101
|
import { cwd } from "process";
|
|
3454
3102
|
|
|
3455
3103
|
// lib/message-ids.ts
|
|
@@ -4994,7 +4642,7 @@ function appendMissingBlockSummaries(summary, _missingBlockIds, _summaryByBlockI
|
|
|
4994
4642
|
}
|
|
4995
4643
|
|
|
4996
4644
|
// lib/state/rebuild.ts
|
|
4997
|
-
import { createHash
|
|
4645
|
+
import { createHash } from "crypto";
|
|
4998
4646
|
function collectCompressInvocations(messages) {
|
|
4999
4647
|
const invocations = [];
|
|
5000
4648
|
for (const message of messages) {
|
|
@@ -5065,7 +4713,7 @@ function fingerprintMessage(message) {
|
|
|
5065
4713
|
}
|
|
5066
4714
|
return { type: part.type };
|
|
5067
4715
|
});
|
|
5068
|
-
return
|
|
4716
|
+
return createHash("sha256").update(JSON.stringify({ role: message.info.role, parts })).digest("hex");
|
|
5069
4717
|
}
|
|
5070
4718
|
function mapForkIds(state, parent, parentMessages, forkMessages) {
|
|
5071
4719
|
const parentRefs = parent.messageIds?.byRef;
|
|
@@ -5192,7 +4840,7 @@ function rebuildRangeInvocation(state, input, searchContext, invocation, protect
|
|
|
5192
4840
|
]);
|
|
5193
4841
|
const blockId = allocateBlockId(state);
|
|
5194
4842
|
const storedSummary = wrapCompressedSummary(blockId, plan.entry.summary);
|
|
5195
|
-
const summaryTokens =
|
|
4843
|
+
const summaryTokens = countTokens2(storedSummary);
|
|
5196
4844
|
applyCompressionState(
|
|
5197
4845
|
state,
|
|
5198
4846
|
{
|
|
@@ -5254,7 +4902,7 @@ function rebuildMessageInvocation(state, input, searchContext, invocation, gcCon
|
|
|
5254
4902
|
}
|
|
5255
4903
|
const blockId = allocateBlockId(state);
|
|
5256
4904
|
const storedSummary = wrapCompressedSummary(blockId, entry.summary);
|
|
5257
|
-
const summaryTokens =
|
|
4905
|
+
const summaryTokens = countTokens2(storedSummary);
|
|
5258
4906
|
applyCompressionState(
|
|
5259
4907
|
state,
|
|
5260
4908
|
{
|
|
@@ -5489,7 +5137,6 @@ function createSessionState() {
|
|
|
5489
5137
|
noContextLimitWarned: false,
|
|
5490
5138
|
imageArchive: {},
|
|
5491
5139
|
imagePruneDone: false,
|
|
5492
|
-
imageSurgeNudged: {},
|
|
5493
5140
|
stickyTruncations: {}
|
|
5494
5141
|
};
|
|
5495
5142
|
}
|
|
@@ -5536,7 +5183,6 @@ function resetSessionState(state) {
|
|
|
5536
5183
|
state.noContextLimitWarned = false;
|
|
5537
5184
|
state.imageArchive = {};
|
|
5538
5185
|
state.imagePruneDone = false;
|
|
5539
|
-
state.imageSurgeNudged = {};
|
|
5540
5186
|
state.stickyTruncations = {};
|
|
5541
5187
|
}
|
|
5542
5188
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
|
|
@@ -5554,8 +5200,8 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
5554
5200
|
state.nudges.turnNudgeAnchors = collectTurnNudgeAnchors(messages);
|
|
5555
5201
|
const persisted = await loadSessionState(sessionId, logger, state.storageDir);
|
|
5556
5202
|
if (persisted === null) {
|
|
5557
|
-
const defaultPath =
|
|
5558
|
-
if (state.storageDir &&
|
|
5203
|
+
const defaultPath = join3(getDefaultStorageDir(), `${sessionId}.json`);
|
|
5204
|
+
if (state.storageDir && existsSync3(defaultPath)) {
|
|
5559
5205
|
logger.warn(
|
|
5560
5206
|
"storagePath is set but no valid state was found there; a state file exists at the default location \u2014 move it manually to keep history",
|
|
5561
5207
|
{ sessionId, storageDir: state.storageDir, defaultPath }
|
|
@@ -5638,13 +5284,6 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
5638
5284
|
}
|
|
5639
5285
|
}
|
|
5640
5286
|
}
|
|
5641
|
-
if (persisted.imageSurgeNudged && typeof persisted.imageSurgeNudged === "object") {
|
|
5642
|
-
for (const [key, ts] of Object.entries(persisted.imageSurgeNudged)) {
|
|
5643
|
-
if (typeof key === "string" && typeof ts === "number" && Number.isFinite(ts)) {
|
|
5644
|
-
state.imageSurgeNudged[key] = ts;
|
|
5645
|
-
}
|
|
5646
|
-
}
|
|
5647
|
-
}
|
|
5648
5287
|
const persistedSticky = persisted.stickyTruncations;
|
|
5649
5288
|
if (persistedSticky && typeof persistedSticky === "object") {
|
|
5650
5289
|
for (const [callID, output] of Object.entries(persistedSticky)) {
|
|
@@ -5828,7 +5467,7 @@ function cacheSystemPromptTokens(state, messages) {
|
|
|
5828
5467
|
}
|
|
5829
5468
|
break;
|
|
5830
5469
|
}
|
|
5831
|
-
const estimatedSystemTokens = Math.max(0, firstInputTokens -
|
|
5470
|
+
const estimatedSystemTokens = Math.max(0, firstInputTokens - countTokens2(firstUserText));
|
|
5832
5471
|
state.systemPromptTokens = estimatedSystemTokens > 0 ? estimatedSystemTokens : void 0;
|
|
5833
5472
|
}
|
|
5834
5473
|
|
|
@@ -7065,6 +6704,156 @@ function buildPhantomSkipNotice(identification) {
|
|
|
7065
6704
|
return `Skipped ${identification.phantomIndices.length} already-compressed ${noun} (${skippedNums}) \u2014 they are no-ops; the remaining entries were compressed.`;
|
|
7066
6705
|
}
|
|
7067
6706
|
|
|
6707
|
+
// lib/compress/image-archive.ts
|
|
6708
|
+
import { createHash as createHash2 } from "crypto";
|
|
6709
|
+
import {
|
|
6710
|
+
existsSync as existsSync4,
|
|
6711
|
+
mkdirSync as mkdirSync2,
|
|
6712
|
+
readdirSync,
|
|
6713
|
+
rmdirSync,
|
|
6714
|
+
statSync as statSync2,
|
|
6715
|
+
unlinkSync,
|
|
6716
|
+
writeFileSync as writeFileSync2
|
|
6717
|
+
} from "fs";
|
|
6718
|
+
import { join as join4 } from "path";
|
|
6719
|
+
var DATA_URL_REGEX = /^data:(image\/[a-z0-9.+-]+);base64,(.+)$/is;
|
|
6720
|
+
var IMAGE_EXTENSIONS = {
|
|
6721
|
+
"image/png": "png",
|
|
6722
|
+
"image/jpeg": "jpg",
|
|
6723
|
+
"image/jpg": "jpg",
|
|
6724
|
+
"image/gif": "gif",
|
|
6725
|
+
"image/webp": "webp",
|
|
6726
|
+
"image/avif": "avif",
|
|
6727
|
+
"image/bmp": "bmp"
|
|
6728
|
+
};
|
|
6729
|
+
function sha8(input) {
|
|
6730
|
+
return createHash2("sha256").update(input).digest("hex").slice(0, 8);
|
|
6731
|
+
}
|
|
6732
|
+
function parseImageDataUrl(url) {
|
|
6733
|
+
if (typeof url !== "string") return void 0;
|
|
6734
|
+
const match = DATA_URL_REGEX.exec(url);
|
|
6735
|
+
if (!match) return void 0;
|
|
6736
|
+
return { mediaType: match[1].toLowerCase(), base64: match[2] };
|
|
6737
|
+
}
|
|
6738
|
+
function extForMediaType(mediaType) {
|
|
6739
|
+
return IMAGE_EXTENSIONS[mediaType.toLowerCase()];
|
|
6740
|
+
}
|
|
6741
|
+
function archiveDirName(sessionId) {
|
|
6742
|
+
let name = sessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 100);
|
|
6743
|
+
if (name !== sessionId) {
|
|
6744
|
+
name = `${name}-${sha8(sessionId)}`;
|
|
6745
|
+
}
|
|
6746
|
+
return name || "-";
|
|
6747
|
+
}
|
|
6748
|
+
function archiveRoot(storageDir, sessionId) {
|
|
6749
|
+
return join4(storageDir ?? getDefaultStorageDir(), "images", archiveDirName(sessionId));
|
|
6750
|
+
}
|
|
6751
|
+
function archiveSessionImages(state, messages, images, logger) {
|
|
6752
|
+
if (!images.enabled || !state.sessionId) return 0;
|
|
6753
|
+
const root = archiveRoot(state.storageDir, state.sessionId);
|
|
6754
|
+
let archived = 0;
|
|
6755
|
+
for (const message of messages) {
|
|
6756
|
+
const rawId = message.info.id;
|
|
6757
|
+
if (!rawId) continue;
|
|
6758
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
6759
|
+
for (let partIndex = 0; partIndex < parts.length; partIndex++) {
|
|
6760
|
+
const part = parts[partIndex];
|
|
6761
|
+
if (part.type !== "file") continue;
|
|
6762
|
+
const parsed = parseImageDataUrl(part.url);
|
|
6763
|
+
if (!parsed) continue;
|
|
6764
|
+
const ext = extForMediaType(parsed.mediaType);
|
|
6765
|
+
if (!ext) continue;
|
|
6766
|
+
const fileName = `${sha8(rawId)}-p${partIndex}-${sha8(parsed.base64)}.${ext}`;
|
|
6767
|
+
const filePath = join4(root, fileName);
|
|
6768
|
+
const existing = state.imageArchive[rawId]?.find((e) => e.path === filePath);
|
|
6769
|
+
if (existing) continue;
|
|
6770
|
+
let bytes;
|
|
6771
|
+
try {
|
|
6772
|
+
if (existsSync4(filePath)) {
|
|
6773
|
+
bytes = statSync2(filePath).size;
|
|
6774
|
+
} else {
|
|
6775
|
+
const buffer = Buffer.from(parsed.base64, "base64");
|
|
6776
|
+
bytes = buffer.byteLength;
|
|
6777
|
+
if (bytes < images.minBytes) continue;
|
|
6778
|
+
mkdirSync2(root, { recursive: true });
|
|
6779
|
+
writeFileSync2(filePath, buffer, { mode: 384 });
|
|
6780
|
+
}
|
|
6781
|
+
} catch (error) {
|
|
6782
|
+
logger.warn(
|
|
6783
|
+
`[#478] failed to archive image ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
6784
|
+
);
|
|
6785
|
+
continue;
|
|
6786
|
+
}
|
|
6787
|
+
const entry = {
|
|
6788
|
+
path: filePath,
|
|
6789
|
+
mediaType: parsed.mediaType,
|
|
6790
|
+
bytes
|
|
6791
|
+
};
|
|
6792
|
+
if (!state.imageArchive[rawId]) {
|
|
6793
|
+
state.imageArchive[rawId] = [];
|
|
6794
|
+
}
|
|
6795
|
+
state.imageArchive[rawId].push(entry);
|
|
6796
|
+
state.stats.imagesArchived = (state.stats.imagesArchived ?? 0) + 1;
|
|
6797
|
+
state.stats.imageBytesArchived = (state.stats.imageBytesArchived ?? 0) + bytes;
|
|
6798
|
+
archived++;
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
return archived;
|
|
6802
|
+
}
|
|
6803
|
+
function pruneImageArchive(state, images, logger) {
|
|
6804
|
+
if (state.imagePruneDone || !state.sessionId) return;
|
|
6805
|
+
state.imagePruneDone = true;
|
|
6806
|
+
try {
|
|
6807
|
+
const root = archiveRoot(state.storageDir, state.sessionId);
|
|
6808
|
+
if (!existsSync4(root)) return;
|
|
6809
|
+
const cutoff = Date.now() - images.ttlDays * 864e5;
|
|
6810
|
+
const prunedPaths = /* @__PURE__ */ new Set();
|
|
6811
|
+
for (const name of readdirSync(root)) {
|
|
6812
|
+
const filePath = join4(root, name);
|
|
6813
|
+
try {
|
|
6814
|
+
if (statSync2(filePath).mtimeMs < cutoff) {
|
|
6815
|
+
unlinkSync(filePath);
|
|
6816
|
+
prunedPaths.add(filePath);
|
|
6817
|
+
}
|
|
6818
|
+
} catch {
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
if (prunedPaths.size > 0) {
|
|
6822
|
+
for (const [rawId, entries] of Object.entries(state.imageArchive)) {
|
|
6823
|
+
const kept = entries.filter((e) => !prunedPaths.has(e.path));
|
|
6824
|
+
if (kept.length === 0) {
|
|
6825
|
+
delete state.imageArchive[rawId];
|
|
6826
|
+
} else if (kept.length !== entries.length) {
|
|
6827
|
+
state.imageArchive[rawId] = kept;
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
logger.info(`[#478] TTL sweep removed ${prunedPaths.size} expired archived image(s)`);
|
|
6831
|
+
}
|
|
6832
|
+
if (readdirSync(root).length === 0) {
|
|
6833
|
+
rmdirSync(root);
|
|
6834
|
+
}
|
|
6835
|
+
} catch (error) {
|
|
6836
|
+
logger.warn(
|
|
6837
|
+
`[#478] image TTL sweep failed: ${error instanceof Error ? error.message : String(error)}`
|
|
6838
|
+
);
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
function appendArchivedImagePointers(summary, selection, state, enabled) {
|
|
6842
|
+
if (!enabled) return summary;
|
|
6843
|
+
const lines = [];
|
|
6844
|
+
for (const messageId of selection.messageIds) {
|
|
6845
|
+
const entries = state.imageArchive[messageId];
|
|
6846
|
+
if (!entries || entries.length === 0) continue;
|
|
6847
|
+
const ref = state.messageIds.byRawId.get(messageId) ?? messageId;
|
|
6848
|
+
for (const entry of entries) {
|
|
6849
|
+
lines.push(`- ${ref}: ${entry.path} (${entry.mediaType}, ${entry.bytes} bytes)`);
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6852
|
+
if (lines.length === 0) return summary;
|
|
6853
|
+
const heading = "\n\nImages captured in this range were archived to disk (their pixels are NOT summarized). Re-read them with your file tools if you need the pixels again:";
|
|
6854
|
+
return summary + heading + "\n" + lines.join("\n");
|
|
6855
|
+
}
|
|
6856
|
+
|
|
7068
6857
|
// lib/compress/keep-markers.ts
|
|
7069
6858
|
var KEEP_REGEX = /\[\[KEEP:(m\d+)\]\]/g;
|
|
7070
6859
|
var REF_REGEX = /\[\[REF:(m\d+)\|([^\]]+)\]\]/g;
|
|
@@ -7816,7 +7605,6 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
7816
7605
|
let messageTokens = 0;
|
|
7817
7606
|
let protectedTokens = 0;
|
|
7818
7607
|
let reasoningTokens = 0;
|
|
7819
|
-
let imageTokens = 0;
|
|
7820
7608
|
const perMessage = [];
|
|
7821
7609
|
const perTool = [];
|
|
7822
7610
|
const perCode = [];
|
|
@@ -7874,12 +7662,6 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
7874
7662
|
const tokens = Math.round(part.text.length / 4);
|
|
7875
7663
|
msgTotal += tokens;
|
|
7876
7664
|
reasoningTokens += tokens;
|
|
7877
|
-
} else if (part.type === "file") {
|
|
7878
|
-
const imgTokens = estimateImagePartTokens(part);
|
|
7879
|
-
if (imgTokens > 0) {
|
|
7880
|
-
msgTotal += imgTokens;
|
|
7881
|
-
imageTokens += imgTokens;
|
|
7882
|
-
}
|
|
7883
7665
|
}
|
|
7884
7666
|
}
|
|
7885
7667
|
if (isProtected && !isSummary) {
|
|
@@ -7908,8 +7690,7 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
7908
7690
|
systemTokens,
|
|
7909
7691
|
protectedTokens,
|
|
7910
7692
|
reasoningTokens,
|
|
7911
|
-
|
|
7912
|
-
total: systemTokens + toolTokens + summaryTokens + messageTokens + reasoningTokens + imageTokens,
|
|
7693
|
+
total: systemTokens + toolTokens + summaryTokens + messageTokens + reasoningTokens,
|
|
7913
7694
|
largestRanges: perMessage.slice(0, 15),
|
|
7914
7695
|
largestToolRanges: perTool.slice(0, 15),
|
|
7915
7696
|
largestCodeRanges: perCode.slice(0, 5),
|
|
@@ -8190,8 +7971,6 @@ function computeProtectedRefs(messages, state, compress) {
|
|
|
8190
7971
|
for (const part of msg.parts || []) {
|
|
8191
7972
|
if (part.type === "text" && typeof part.text === "string") {
|
|
8192
7973
|
tokens += Math.round(part.text.length / 4);
|
|
8193
|
-
} else if (part.type === "file") {
|
|
8194
|
-
tokens += estimateImagePartTokens(part);
|
|
8195
7974
|
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
8196
7975
|
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
8197
7976
|
}
|
|
@@ -8609,7 +8388,7 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
8609
8388
|
);
|
|
8610
8389
|
preparedPlan.finalSummary = keepResult.summary;
|
|
8611
8390
|
const storedSummary = wrapCompressedSummary(blockId, preparedPlan.finalSummary);
|
|
8612
|
-
const summaryTokens =
|
|
8391
|
+
const summaryTokens = countTokens2(storedSummary);
|
|
8613
8392
|
const applied = applyCompressionState(
|
|
8614
8393
|
ctx.state,
|
|
8615
8394
|
{
|
|
@@ -8946,73 +8725,6 @@ var syncCompressPermissionState = (state, config, hostPermissions, messages) =>
|
|
|
8946
8725
|
);
|
|
8947
8726
|
};
|
|
8948
8727
|
|
|
8949
|
-
// lib/messages/large-images.ts
|
|
8950
|
-
var IMAGE_SURGE_DEDUP_CAP = 512;
|
|
8951
|
-
function detectNewLargeImages(messages, state, largeBytes) {
|
|
8952
|
-
if (!Number.isFinite(largeBytes) || largeBytes <= 0) return [];
|
|
8953
|
-
const nudged = state.imageSurgeNudged ??= {};
|
|
8954
|
-
const found = [];
|
|
8955
|
-
for (const message of messages) {
|
|
8956
|
-
const rawId = message.info.id;
|
|
8957
|
-
if (!rawId) continue;
|
|
8958
|
-
const ref = state.messageIds.byRawId.get(rawId);
|
|
8959
|
-
if (!ref) continue;
|
|
8960
|
-
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
8961
|
-
for (let partIndex = 0; partIndex < parts.length; partIndex++) {
|
|
8962
|
-
const part = parts[partIndex];
|
|
8963
|
-
if (part.type !== "file") continue;
|
|
8964
|
-
const parsed = parseImageDataUrl(part.url);
|
|
8965
|
-
if (!parsed) continue;
|
|
8966
|
-
if (!extForMediaType(parsed.mediaType)) continue;
|
|
8967
|
-
const bytes = decodedBytesFromBase64(parsed.base64);
|
|
8968
|
-
if (bytes < largeBytes) continue;
|
|
8969
|
-
const slotPrefix = `${sha8(rawId)}:p${partIndex}:`;
|
|
8970
|
-
let slotSeen = false;
|
|
8971
|
-
for (const key2 of Object.keys(nudged)) {
|
|
8972
|
-
if (key2.startsWith(slotPrefix)) {
|
|
8973
|
-
slotSeen = true;
|
|
8974
|
-
break;
|
|
8975
|
-
}
|
|
8976
|
-
}
|
|
8977
|
-
if (slotSeen) continue;
|
|
8978
|
-
const key = `${slotPrefix}${sha8(parsed.base64)}`;
|
|
8979
|
-
if (nudged[key] !== void 0) continue;
|
|
8980
|
-
nudged[key] = Date.now();
|
|
8981
|
-
found.push({
|
|
8982
|
-
ref,
|
|
8983
|
-
mediaType: parsed.mediaType,
|
|
8984
|
-
bytes,
|
|
8985
|
-
estimatedTokens: estimateImagePartTokens(part)
|
|
8986
|
-
});
|
|
8987
|
-
}
|
|
8988
|
-
}
|
|
8989
|
-
const keys = Object.keys(nudged);
|
|
8990
|
-
if (keys.length > IMAGE_SURGE_DEDUP_CAP) {
|
|
8991
|
-
for (const key of keys.slice(0, keys.length - IMAGE_SURGE_DEDUP_CAP)) {
|
|
8992
|
-
delete nudged[key];
|
|
8993
|
-
}
|
|
8994
|
-
}
|
|
8995
|
-
return found;
|
|
8996
|
-
}
|
|
8997
|
-
function formatBytes(bytes) {
|
|
8998
|
-
if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
8999
|
-
return `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
|
9000
|
-
}
|
|
9001
|
-
function formatLargeImageNotice(images) {
|
|
9002
|
-
const items = images.map(
|
|
9003
|
-
(image) => `- ${image.ref}: ${image.mediaType}, ${formatBytes(image.bytes)} (\u2248${formatTokens(image.estimatedTokens)} vision tokens; far more if your provider bills base64 as text)`
|
|
9004
|
-
).join("\n");
|
|
9005
|
-
return `
|
|
9006
|
-
|
|
9007
|
-
\u{1F5BC}\uFE0F Large image(s) just entered context:
|
|
9008
|
-
${items}
|
|
9009
|
-
Before doing anything else, make room: call the \`compress\` tool NOW on older conversation ranges you no longer need (acp_status lists them). Once you are done analyzing the image(s), compress the range(s) containing them \u2014 archived copies stay re-readable via their disk pointers.`;
|
|
9010
|
-
}
|
|
9011
|
-
function formatTokens(n) {
|
|
9012
|
-
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
9013
|
-
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
9014
|
-
}
|
|
9015
|
-
|
|
9016
8728
|
// lib/prompts/context-limit-nudge.ts
|
|
9017
8729
|
var CONTEXT_LIMIT_NUDGE = `
|
|
9018
8730
|
<system-reminder>
|
|
@@ -9830,21 +9542,6 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
9830
9542
|
let shouldInjectNotice = emergencyNoTargets && noticeCadenceMet;
|
|
9831
9543
|
let shouldInject = shouldInjectNudge || shouldInjectNotice;
|
|
9832
9544
|
let armedSmartPlan;
|
|
9833
|
-
const imagesConfig = config.compress?.images ?? DEFAULT_COMPRESS_IMAGES;
|
|
9834
|
-
let imageNoticeEmitted = false;
|
|
9835
|
-
if (suffixMessage && imagesConfig.nudge !== false && imagesConfig.largeBytes > 0) {
|
|
9836
|
-
const newLargeImages = detectNewLargeImages(messages, state, imagesConfig.largeBytes);
|
|
9837
|
-
if (newLargeImages.length > 0) {
|
|
9838
|
-
appendToLastTextPart(suffixMessage, formatLargeImageNotice(newLargeImages));
|
|
9839
|
-
imageNoticeEmitted = true;
|
|
9840
|
-
saveSessionState(state, logger).catch(() => {
|
|
9841
|
-
});
|
|
9842
|
-
logger.info("Large image notice injected", {
|
|
9843
|
-
session: state.sessionId,
|
|
9844
|
-
images: newLargeImages.map((image) => `${image.ref}:${image.mediaType}:${image.bytes}B`)
|
|
9845
|
-
});
|
|
9846
|
-
}
|
|
9847
|
-
}
|
|
9848
9545
|
if (shouldInjectNudge && config.compress.smartPlanRequired === true && state.sessionId) {
|
|
9849
9546
|
const planningRanges = candidatesEnabled ? candidatePlan.candidates.map((candidate) => ({
|
|
9850
9547
|
startRef: candidate.startRef,
|
|
@@ -9907,7 +9604,7 @@ ${ITERATION_CANDIDATE_GUIDANCE}` : ITERATION_CANDIDATE_GUIDANCE
|
|
|
9907
9604
|
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
9908
9605
|
baselineReEstablished = true;
|
|
9909
9606
|
}
|
|
9910
|
-
if (suffixMessage && !shouldInject
|
|
9607
|
+
if (suffixMessage && !shouldInject) {
|
|
9911
9608
|
const tierUsage = tierUsageEarly;
|
|
9912
9609
|
const tierChecks = [
|
|
9913
9610
|
{
|
|
@@ -10010,7 +9707,7 @@ ${rules}`;
|
|
|
10010
9707
|
break;
|
|
10011
9708
|
}
|
|
10012
9709
|
}
|
|
10013
|
-
state.nudges.shouldInjectThisTurn = shouldInject
|
|
9710
|
+
state.nudges.shouldInjectThisTurn = shouldInject;
|
|
10014
9711
|
const usagePct = currentTokens !== void 0 && modelContextLimit ? `${(currentTokens / modelContextLimit * 100).toFixed(1)}%` : void 0;
|
|
10015
9712
|
if (shouldInjectNudge) {
|
|
10016
9713
|
logger.info("Compression nudge injected", {
|
|
@@ -10070,7 +9767,7 @@ ${rules}`;
|
|
|
10070
9767
|
const efficiencyNote = effectiveTipsVariant !== "maxLimit" ? candidatesEnabled ? "\nThis is an efficiency nudge to compress early when content is no longer needed and keep context lean \u2014 not an overflow warning. A separate, stronger alert will appear if the context is actually full.\n\n" + COMPRESS_PHILOSOPHY : "\nThis is an efficiency nudge to compress early and keep context lean \u2014 not an overflow warning. A separate, stronger alert will appear if the context is actually full.\n\n" + COMPRESS_PHILOSOPHY : "";
|
|
10071
9768
|
const sysPart = composition.systemTokens > 0 ? `${fmt(composition.systemTokens)} system (${pct2(composition.systemTokens)}%) | ` : "";
|
|
10072
9769
|
let breakdown = `${efficiencyNote}
|
|
10073
|
-
Breakdown: ${sysPart}${fmt(composition.toolTokens)} tool (${pct2(composition.toolTokens)}%) | ${fmt(composition.summaryTokens)} summaries (${pct2(composition.summaryTokens)}%) | ${fmt(composition.codeTokens)} code (${pct2(composition.codeTokens)}%) | ${fmt(plainTextTokens)} text (${pct2(plainTextTokens)}%) | ${fmt(composition.reasoningTokens)} reasoning (${pct2(composition.reasoningTokens)}%)
|
|
9770
|
+
Breakdown: ${sysPart}${fmt(composition.toolTokens)} tool (${pct2(composition.toolTokens)}%) | ${fmt(composition.summaryTokens)} summaries (${pct2(composition.summaryTokens)}%) | ${fmt(composition.codeTokens)} code (${pct2(composition.codeTokens)}%) | ${fmt(plainTextTokens)} text (${pct2(plainTextTokens)}%) | ${fmt(composition.reasoningTokens)} reasoning (${pct2(composition.reasoningTokens)}%)${growthStr}`;
|
|
10074
9771
|
const compressibleTokens = composition.total - composition.systemTokens - composition.protectedTokens - composition.summaryTokens;
|
|
10075
9772
|
if (composition.protectedTokens > 0) {
|
|
10076
9773
|
breakdown += `
|
|
@@ -11030,7 +10727,7 @@ Use this tool to:
|
|
|
11030
10727
|
- Focus on ranges only (scope:"uncompressed")
|
|
11031
10728
|
- Find all messages of a specific tool type (scope:"uncompressed", view:"messages", tool:"bash")
|
|
11032
10729
|
- Check block details before decompressing (scope:"compressed")`;
|
|
11033
|
-
function
|
|
10730
|
+
function formatTokens(n) {
|
|
11034
10731
|
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
11035
10732
|
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
11036
10733
|
}
|
|
@@ -11074,7 +10771,7 @@ function tierBreakdown(blocks) {
|
|
|
11074
10771
|
const parts = [];
|
|
11075
10772
|
for (const t of [1, 2, 3]) {
|
|
11076
10773
|
if (tierTokens[t]) {
|
|
11077
|
-
parts.push(`T${t}: ${
|
|
10774
|
+
parts.push(`T${t}: ${formatTokens(tierTokens[t])}`);
|
|
11078
10775
|
}
|
|
11079
10776
|
}
|
|
11080
10777
|
return parts.join(" | ");
|
|
@@ -11100,13 +10797,10 @@ function collectVisibleMessages(rawMessages, ctx) {
|
|
|
11100
10797
|
if (!ref) return;
|
|
11101
10798
|
let tokens = 0;
|
|
11102
10799
|
let reasoning = 0;
|
|
11103
|
-
let image = 0;
|
|
11104
10800
|
let toolName = "";
|
|
11105
10801
|
for (const part of msg.parts || []) {
|
|
11106
10802
|
if (part.type === "text" && typeof part.text === "string") {
|
|
11107
10803
|
tokens += Math.round(part.text.length / 4);
|
|
11108
|
-
} else if (part.type === "file") {
|
|
11109
|
-
image += estimateImagePartTokens(part);
|
|
11110
10804
|
} else if (part.type === "tool") {
|
|
11111
10805
|
const raw = JSON.stringify(part);
|
|
11112
10806
|
tokens += Math.round(raw.length / 4);
|
|
@@ -11117,8 +10811,8 @@ function collectVisibleMessages(rawMessages, ctx) {
|
|
|
11117
10811
|
reasoning += Math.round(part.text.length / 4);
|
|
11118
10812
|
}
|
|
11119
10813
|
}
|
|
11120
|
-
if (tokens > 0 || reasoning > 0
|
|
11121
|
-
result.push({ ref, tokens, tool: toolName || "text", index: idx, reasoning
|
|
10814
|
+
if (tokens > 0 || reasoning > 0) {
|
|
10815
|
+
result.push({ ref, tokens, tool: toolName || "text", index: idx, reasoning });
|
|
11122
10816
|
}
|
|
11123
10817
|
});
|
|
11124
10818
|
return {
|
|
@@ -11141,17 +10835,15 @@ function renderOverview(visibleMessages, summaryTokens, systemTokens, blocks, fe
|
|
|
11141
10835
|
const totalTool = visibleMessages.filter((m) => m.tool !== "text" && m.tool !== "step-finish").reduce((s, m) => s + m.tokens, 0);
|
|
11142
10836
|
const totalText = visibleMessages.filter((m) => m.tool === "text").reduce((s, m) => s + m.tokens, 0);
|
|
11143
10837
|
const totalReasoning = visibleMessages.reduce((s, m) => s + m.reasoning, 0);
|
|
11144
|
-
const
|
|
11145
|
-
const total = systemTokens + totalTool + totalText + summaryTokens + totalReasoning + totalImage;
|
|
10838
|
+
const total = systemTokens + totalTool + totalText + summaryTokens + totalReasoning;
|
|
11146
10839
|
const sysPct = pct(systemTokens, total);
|
|
11147
10840
|
const toolPct = pct(totalTool, total);
|
|
11148
10841
|
const textPct = pct(totalText, total);
|
|
11149
10842
|
const summaryPct = pct(summaryTokens, total);
|
|
11150
10843
|
const reasoningPct = pct(totalReasoning, total);
|
|
11151
|
-
const imagePct = pct(totalImage, total);
|
|
11152
10844
|
lines.push("CONTEXT BREAKDOWN");
|
|
11153
10845
|
lines.push(
|
|
11154
|
-
` ${
|
|
10846
|
+
` ${formatTokens(systemTokens)} system (${sysPct}%) | ${formatTokens(totalTool)} tool (${toolPct}%) | ${formatTokens(totalText)} text (${textPct}%) | ${formatTokens(summaryTokens)} summaries (${summaryPct}%) | ${formatTokens(totalReasoning)} reasoning (${reasoningPct}%)`
|
|
11155
10847
|
);
|
|
11156
10848
|
const topTypes = Array.from(toolTypeMap.entries()).map(([tool6, tokens]) => ({ tool: tool6, tokens })).sort((a, b) => b.tokens - a.tokens).slice(0, 3);
|
|
11157
10849
|
if (topTypes.length > 0) {
|
|
@@ -11171,7 +10863,7 @@ function renderOverview(visibleMessages, summaryTokens, systemTokens, blocks, fe
|
|
|
11171
10863
|
(s, b) => s + getEffectiveCompressedTokens(b, blocksById),
|
|
11172
10864
|
0
|
|
11173
10865
|
);
|
|
11174
|
-
const header = `COMPRESSED BLOCKS \u2014 ${blocks.length} active (${
|
|
10866
|
+
const header = `COMPRESSED BLOCKS \u2014 ${blocks.length} active (${formatTokens(totalSummary)} summary, ${formatTokens(totalEffective)} original)`;
|
|
11175
10867
|
lines.push(header);
|
|
11176
10868
|
const breakdown = tierBreakdown(blocks);
|
|
11177
10869
|
if (breakdown) {
|
|
@@ -11190,7 +10882,7 @@ function renderOverview(visibleMessages, summaryTokens, systemTokens, blocks, fe
|
|
|
11190
10882
|
const tier = tierLabel(b);
|
|
11191
10883
|
const effTokens = getEffectiveCompressedTokens(b, blocksById);
|
|
11192
10884
|
lines.push(
|
|
11193
|
-
` b${b.blockId} (${tier}) ${
|
|
10885
|
+
` b${b.blockId} (${tier}) ${formatTokens(effTokens)}\u2192${formatTokens(b.summaryTokens)} ${ageStr} ${range} "${topic}"`
|
|
11194
10886
|
);
|
|
11195
10887
|
}
|
|
11196
10888
|
}
|
|
@@ -11251,7 +10943,7 @@ function renderUncompressedRanges(rawMessages, ctx) {
|
|
|
11251
10943
|
const totalMsgs = compressible.reduce((s, r) => s + r.count, 0);
|
|
11252
10944
|
const lines = [];
|
|
11253
10945
|
lines.push(
|
|
11254
|
-
`UNCOMPRESSED \u2014 ${
|
|
10946
|
+
`UNCOMPRESSED \u2014 ${formatTokens(totalTokens)} | ${totalMsgs} msgs in ${compressible.length} ranges`
|
|
11255
10947
|
);
|
|
11256
10948
|
lines.push("");
|
|
11257
10949
|
if (compressible.length === 0 && contextRanges.protected.length === 0) {
|
|
@@ -11308,7 +11000,7 @@ function renderUncompressedDrilldown(visibleMessages, toolFilter, sort, limit) {
|
|
|
11308
11000
|
if (toolFilter) {
|
|
11309
11001
|
filtered = filtered.filter((m) => m.tool === toolFilter);
|
|
11310
11002
|
}
|
|
11311
|
-
const sizeOf = (m) => m.tokens + m.reasoning
|
|
11003
|
+
const sizeOf = (m) => m.tokens + m.reasoning;
|
|
11312
11004
|
if (sort === "time") {
|
|
11313
11005
|
filtered.sort((a, b) => a.index - b.index);
|
|
11314
11006
|
} else if (sort === "tool") {
|
|
@@ -11318,13 +11010,13 @@ function renderUncompressedDrilldown(visibleMessages, toolFilter, sort, limit) {
|
|
|
11318
11010
|
}
|
|
11319
11011
|
const totalTokens = filtered.reduce((s, m) => s + sizeOf(m), 0);
|
|
11320
11012
|
const allTokens = visibleMessages.reduce((s, m) => s + sizeOf(m), 0);
|
|
11321
|
-
const header = toolFilter ? `UNCOMPRESSED \u2014 ${toolFilter}: ${
|
|
11013
|
+
const header = toolFilter ? `UNCOMPRESSED \u2014 ${toolFilter}: ${formatTokens(totalTokens)} | ${filtered.length} msgs | ${pct(totalTokens, allTokens)}% of visible` : `UNCOMPRESSED \u2014 ${formatTokens(totalTokens)} | ${filtered.length} msgs`;
|
|
11322
11014
|
lines.push(header);
|
|
11323
11015
|
lines.push(`Sorted by ${sort}`);
|
|
11324
11016
|
lines.push("");
|
|
11325
11017
|
const shown = filtered.slice(0, limit);
|
|
11326
11018
|
for (const m of shown) {
|
|
11327
|
-
lines.push(` ${m.ref} (${
|
|
11019
|
+
lines.push(` ${m.ref} (${formatTokens(sizeOf(m))}) ${m.tool}`);
|
|
11328
11020
|
}
|
|
11329
11021
|
if (filtered.length > shown.length) {
|
|
11330
11022
|
lines.push("");
|
|
@@ -11363,7 +11055,7 @@ function renderCompressedDrilldown(blocks, sort, limit, blocksById) {
|
|
|
11363
11055
|
0
|
|
11364
11056
|
);
|
|
11365
11057
|
lines.push(
|
|
11366
|
-
`COMPRESSED \u2014 ${sorted.length} blocks | ${
|
|
11058
|
+
`COMPRESSED \u2014 ${sorted.length} blocks | ${formatTokens(totalEffective)} original \u2192 ${formatTokens(totalSummary)} summary`
|
|
11367
11059
|
);
|
|
11368
11060
|
const breakdown = tierBreakdown(sorted);
|
|
11369
11061
|
if (breakdown) {
|
|
@@ -11388,7 +11080,7 @@ function renderCompressedDrilldown(blocks, sort, limit, blocksById) {
|
|
|
11388
11080
|
const tier = tierLabel(b);
|
|
11389
11081
|
const effTokens = getEffectiveCompressedTokens(b, blocksById);
|
|
11390
11082
|
lines.push(
|
|
11391
|
-
` b${b.blockId} (${tier}) ${
|
|
11083
|
+
` b${b.blockId} (${tier}) ${formatTokens(effTokens)}\u2192${formatTokens(b.summaryTokens)} ${formatAge(b.createdAt)} ${formatIdRange(b)} age=${survived} ${gen} eff=${effCount}${consumed}${status}`
|
|
11392
11084
|
);
|
|
11393
11085
|
lines.push(` "${topic}"`);
|
|
11394
11086
|
}
|
|
@@ -11666,7 +11358,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
11666
11358
|
import { join as join5 } from "path";
|
|
11667
11359
|
import { existsSync as existsSync5 } from "fs";
|
|
11668
11360
|
import { homedir as homedir3 } from "os";
|
|
11669
|
-
var LOG_VERSION = true ? "1.18.5
|
|
11361
|
+
var LOG_VERSION = true ? "1.18.5" : "dev";
|
|
11670
11362
|
var LEVEL_RANK = {
|
|
11671
11363
|
debug: 10,
|
|
11672
11364
|
info: 20,
|
|
@@ -12117,8 +11809,6 @@ NOTE: Message-mode blocks created in the same batch (same runId) are restored to
|
|
|
12117
11809
|
Decompressing one block from a batch restores all blocks in that batch.
|
|
12118
11810
|
</dcp-system-reminder>
|
|
12119
11811
|
`;
|
|
12120
|
-
var IMAGE_GUARD_SYSTEM_EXTENSION = `
|
|
12121
|
-
IMAGE HANDLING: Before reading an image file into context, make sure there is room: if context usage is high, compress older conversation ranges first. A large image consumes significant context for as long as it stays uncompressed. Once you are done analyzing an image, prefer compressing the range that contains it \u2014 archived images remain re-readable through their disk pointers.`;
|
|
12122
11812
|
|
|
12123
11813
|
// lib/prompts/store.ts
|
|
12124
11814
|
var PROMPT_DEFINITIONS = [
|
|
@@ -12455,7 +12145,7 @@ var PromptStore = class {
|
|
|
12455
12145
|
};
|
|
12456
12146
|
|
|
12457
12147
|
// lib/prompts/index.ts
|
|
12458
|
-
function renderSystemPrompt(prompts, protectedToolsExtension, subagent
|
|
12148
|
+
function renderSystemPrompt(prompts, protectedToolsExtension, subagent) {
|
|
12459
12149
|
const extensions = [];
|
|
12460
12150
|
if (protectedToolsExtension) {
|
|
12461
12151
|
extensions.push(protectedToolsExtension.trim());
|
|
@@ -12463,9 +12153,6 @@ function renderSystemPrompt(prompts, protectedToolsExtension, subagent, imageGua
|
|
|
12463
12153
|
if (subagent) {
|
|
12464
12154
|
extensions.push(prompts.subagentExtension.trim());
|
|
12465
12155
|
}
|
|
12466
|
-
if (imageGuardExtension) {
|
|
12467
|
-
extensions.push(imageGuardExtension.trim());
|
|
12468
|
-
}
|
|
12469
12156
|
extensions.push(prompts.decompressExtension.trim());
|
|
12470
12157
|
return [prompts.system.trim(), ...extensions].filter(Boolean).join("\n\n").replace(/\n([ \t]*\n)+/g, "\n\n").trim();
|
|
12471
12158
|
}
|
|
@@ -12531,7 +12218,7 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
12531
12218
|
if (content === void 0) continue;
|
|
12532
12219
|
if (content === "[Old tool result content cleared]") continue;
|
|
12533
12220
|
if (content.includes(TRUNCATION_MARKER)) continue;
|
|
12534
|
-
const tokens =
|
|
12221
|
+
const tokens = countTokens2(content);
|
|
12535
12222
|
if (tokens < MIN_OUTPUT_TOKENS) continue;
|
|
12536
12223
|
candidates.push({ part, content, tokens });
|
|
12537
12224
|
}
|
|
@@ -12552,7 +12239,7 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
12552
12239
|
|
|
12553
12240
|
` + suffix;
|
|
12554
12241
|
part.state.output = truncated;
|
|
12555
|
-
savedTokens += tokens -
|
|
12242
|
+
savedTokens += tokens - countTokens2(truncated);
|
|
12556
12243
|
truncatedCount++;
|
|
12557
12244
|
}
|
|
12558
12245
|
if (truncatedCount > 0) {
|
|
@@ -12706,7 +12393,7 @@ function enforceContextBudget(state, config, logger, messages) {
|
|
|
12706
12393
|
const content = extractCompletedToolOutput(part);
|
|
12707
12394
|
if (content === void 0) continue;
|
|
12708
12395
|
if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
12709
|
-
const tokens =
|
|
12396
|
+
const tokens = countTokens2(content);
|
|
12710
12397
|
if (tokens <= 0) continue;
|
|
12711
12398
|
candidates.push({ part, content, tokens, index: mi });
|
|
12712
12399
|
}
|
|
@@ -12728,20 +12415,20 @@ function enforceContextBudget(state, config, logger, messages) {
|
|
|
12728
12415
|
` + suffix;
|
|
12729
12416
|
if (truncated.length >= c.content.length) continue;
|
|
12730
12417
|
c.part.state.output = truncated;
|
|
12731
|
-
saved += c.tokens -
|
|
12418
|
+
saved += c.tokens - countTokens2(truncated);
|
|
12732
12419
|
truncatedCount++;
|
|
12733
12420
|
}
|
|
12734
12421
|
if (estimatedTokens - saved > budget) {
|
|
12735
12422
|
const clearable = candidates.filter((c) => {
|
|
12736
12423
|
const out = extractCompletedToolOutput(c.part);
|
|
12737
|
-
return out !== void 0 && out !== COMPACTED_TOOL_OUTPUT_PLACEHOLDER &&
|
|
12424
|
+
return out !== void 0 && out !== COMPACTED_TOOL_OUTPUT_PLACEHOLDER && countTokens2(out) > MIN_CLEAR_TOKENS;
|
|
12738
12425
|
}).sort((a, b) => a.index - b.index);
|
|
12739
12426
|
for (const c of clearable) {
|
|
12740
12427
|
if (estimatedTokens - saved <= budget) break;
|
|
12741
12428
|
const current = extractCompletedToolOutput(c.part);
|
|
12742
12429
|
if (current === void 0 || current === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
12743
12430
|
c.part.state.output = COMPACTED_TOOL_OUTPUT_PLACEHOLDER;
|
|
12744
|
-
saved +=
|
|
12431
|
+
saved += countTokens2(current) - countTokens2(COMPACTED_TOOL_OUTPUT_PLACEHOLDER);
|
|
12745
12432
|
clearedCount++;
|
|
12746
12433
|
}
|
|
12747
12434
|
}
|
|
@@ -12885,10 +12572,10 @@ function analyzeTokens(state, messages) {
|
|
|
12885
12572
|
breakdown.toolsInContextCount = toolsInContextCount;
|
|
12886
12573
|
breakdown.prunedToolCount = prunedToolIds.size;
|
|
12887
12574
|
breakdown.prunedMessageCount = prunedMessageCount;
|
|
12888
|
-
const firstUserTokens =
|
|
12889
|
-
breakdown.user =
|
|
12890
|
-
const toolInputTokens =
|
|
12891
|
-
const toolOutputTokens =
|
|
12575
|
+
const firstUserTokens = countTokens2(firstUserText);
|
|
12576
|
+
breakdown.user = countTokens2(userTextParts.join("\n"));
|
|
12577
|
+
const toolInputTokens = countTokens2(toolInputParts.join("\n"));
|
|
12578
|
+
const toolOutputTokens = countTokens2(toolOutputParts.join("\n"));
|
|
12892
12579
|
if (firstAssistant) {
|
|
12893
12580
|
const firstInput = (firstAssistant.tokens?.input || 0) + (firstAssistant.tokens?.cache?.read || 0) + (firstAssistant.tokens?.cache?.write || 0);
|
|
12894
12581
|
breakdown.system = Math.max(0, firstInput - firstUserTokens);
|
|
@@ -13100,7 +12787,7 @@ function resolveDefaultOutputPath(sessionId, cwd2) {
|
|
|
13100
12787
|
const short = (sessionId || "session").slice(0, 8);
|
|
13101
12788
|
return join7(cwd2, ".opencode", `acp-export-${short}.md`);
|
|
13102
12789
|
}
|
|
13103
|
-
function
|
|
12790
|
+
function formatTokens2(n) {
|
|
13104
12791
|
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
13105
12792
|
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
13106
12793
|
}
|
|
@@ -13153,7 +12840,7 @@ function renderExportMarkdown(params) {
|
|
|
13153
12840
|
const span = tokenSpans(blocks);
|
|
13154
12841
|
if (blocks.length > 0) {
|
|
13155
12842
|
out.push(
|
|
13156
|
-
`- **Coverage**: ${
|
|
12843
|
+
`- **Coverage**: ${formatTokens2(span.effective)} original \u2192 ${formatTokens2(span.summary)} summary`
|
|
13157
12844
|
);
|
|
13158
12845
|
}
|
|
13159
12846
|
out.push("");
|
|
@@ -13184,7 +12871,7 @@ function renderExportMarkdown(params) {
|
|
|
13184
12871
|
out.push(`- **Tier**: T${tier} \xB7 **Mode**: ${mode}`);
|
|
13185
12872
|
out.push(`- **Messages**: ${msgCount} (effective)`);
|
|
13186
12873
|
out.push(
|
|
13187
|
-
`- **Tokens**: ${
|
|
12874
|
+
`- **Tokens**: ${formatTokens2(effectiveTokens(block))} \u2192 ${formatTokens2(block.summaryTokens || 0)} summary`
|
|
13188
12875
|
);
|
|
13189
12876
|
out.push(`- **Created**: ${created}`);
|
|
13190
12877
|
out.push(`- **Age**: survived ${age} transform${age === 1 ? "" : "s"}`);
|
|
@@ -13654,7 +13341,7 @@ function mergeMarkedBlocks(state, markedIds, maxMergedLength) {
|
|
|
13654
13341
|
const mergedRaw = bodies.join("\n---\n");
|
|
13655
13342
|
const mergedBody = truncateMergedSummary(mergedRaw, maxMergedLength);
|
|
13656
13343
|
const newSummary = wrapCompressedSummary(newBlockId, mergedBody);
|
|
13657
|
-
const newSummaryTokens =
|
|
13344
|
+
const newSummaryTokens = countTokens2(newSummary);
|
|
13658
13345
|
const oldest = sourceBlocks[0];
|
|
13659
13346
|
const newest = sourceBlocks[sourceBlocks.length - 1];
|
|
13660
13347
|
const effectiveMessageIds = /* @__PURE__ */ new Set();
|
|
@@ -13830,8 +13517,7 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
13830
13517
|
const newPrompt = renderSystemPrompt(
|
|
13831
13518
|
runtimePrompts,
|
|
13832
13519
|
buildProtectedToolsExtension(config.compress.protectedTools),
|
|
13833
|
-
state.isSubAgent && config.allowSubAgents
|
|
13834
|
-
(config.compress.images ?? DEFAULT_COMPRESS_IMAGES).nudge !== false ? IMAGE_GUARD_SYSTEM_EXTENSION : void 0
|
|
13520
|
+
state.isSubAgent && config.allowSubAgents
|
|
13835
13521
|
);
|
|
13836
13522
|
if (output.system.length > 0) {
|
|
13837
13523
|
output.system[output.system.length - 1] += "\n\n" + newPrompt;
|
|
@@ -14470,7 +14156,7 @@ var server = (async (ctx) => {
|
|
|
14470
14156
|
}
|
|
14471
14157
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
14472
14158
|
logger.info("ACP plugin initialized", {
|
|
14473
|
-
version: true ? "1.18.5
|
|
14159
|
+
version: true ? "1.18.5" : "dev",
|
|
14474
14160
|
workspace: ctx.directory,
|
|
14475
14161
|
logLevel: logger.level,
|
|
14476
14162
|
debug: config.debug,
|