opencode-acp 1.18.3 → 1.18.4-pr.483.216
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 +45 -34
- package/README.zh-CN.md +43 -33
- package/dist/index.js +353 -53
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/image-archive.d.ts +43 -0
- package/dist/lib/compress/image-archive.d.ts.map +1 -0
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +36 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/index.d.ts +1 -0
- package/dist/lib/messages/index.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/messages/reasoning-canonicalize.d.ts +37 -0
- package/dist/lib/messages/reasoning-canonicalize.d.ts.map +1 -0
- package/dist/lib/state/persistence.d.ts +3 -1
- 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 +24 -0
- package/dist/lib/state/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -916,6 +916,10 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
916
916
|
"compress.reasoning",
|
|
917
917
|
"compress.reasoning.drop",
|
|
918
918
|
"compress.reasoning.threshold",
|
|
919
|
+
"compress.images",
|
|
920
|
+
"compress.images.enabled",
|
|
921
|
+
"compress.images.minBytes",
|
|
922
|
+
"compress.images.ttlDays",
|
|
919
923
|
"compress.completionReserveTokens",
|
|
920
924
|
"gc",
|
|
921
925
|
"gc.algorithm",
|
|
@@ -1308,6 +1312,37 @@ function validateConfigTypes(config) {
|
|
|
1308
1312
|
}
|
|
1309
1313
|
}
|
|
1310
1314
|
}
|
|
1315
|
+
if (compress.images !== void 0) {
|
|
1316
|
+
if (typeof compress.images !== "object" || compress.images === null || Array.isArray(compress.images)) {
|
|
1317
|
+
errors.push({
|
|
1318
|
+
key: "compress.images",
|
|
1319
|
+
expected: "object { enabled?: boolean; minBytes?: integer (>= 0); ttlDays?: number (> 0) }",
|
|
1320
|
+
actual: JSON.stringify(compress.images)
|
|
1321
|
+
});
|
|
1322
|
+
} else {
|
|
1323
|
+
if (compress.images.enabled !== void 0 && typeof compress.images.enabled !== "boolean") {
|
|
1324
|
+
errors.push({
|
|
1325
|
+
key: "compress.images.enabled",
|
|
1326
|
+
expected: "boolean",
|
|
1327
|
+
actual: typeof compress.images.enabled
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
if (compress.images.minBytes !== void 0 && (typeof compress.images.minBytes !== "number" || !Number.isInteger(compress.images.minBytes) || compress.images.minBytes < 0)) {
|
|
1331
|
+
errors.push({
|
|
1332
|
+
key: "compress.images.minBytes",
|
|
1333
|
+
expected: "integer (>= 0)",
|
|
1334
|
+
actual: JSON.stringify(compress.images.minBytes)
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
if (compress.images.ttlDays !== void 0 && (typeof compress.images.ttlDays !== "number" || !Number.isFinite(compress.images.ttlDays) || compress.images.ttlDays <= 0)) {
|
|
1338
|
+
errors.push({
|
|
1339
|
+
key: "compress.images.ttlDays",
|
|
1340
|
+
expected: "number (> 0)",
|
|
1341
|
+
actual: JSON.stringify(compress.images.ttlDays)
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1311
1346
|
if (compress.completionReserveTokens !== void 0 && typeof compress.completionReserveTokens !== "number") {
|
|
1312
1347
|
errors.push({
|
|
1313
1348
|
key: "compress.completionReserveTokens",
|
|
@@ -1695,6 +1730,11 @@ var DEFAULT_COMPRESS_REASONING = {
|
|
|
1695
1730
|
drop: true,
|
|
1696
1731
|
threshold: 2048
|
|
1697
1732
|
};
|
|
1733
|
+
var DEFAULT_COMPRESS_IMAGES = {
|
|
1734
|
+
enabled: true,
|
|
1735
|
+
minBytes: 16384,
|
|
1736
|
+
ttlDays: 7
|
|
1737
|
+
};
|
|
1698
1738
|
var DEFAULT_PROTECTED_TOOLS = [
|
|
1699
1739
|
"task",
|
|
1700
1740
|
"skill",
|
|
@@ -1793,7 +1833,8 @@ var defaultConfig = {
|
|
|
1793
1833
|
preserveRecentMessages: 5,
|
|
1794
1834
|
preserveRecentTokens: 5e3,
|
|
1795
1835
|
preserveLastUserMessage: true,
|
|
1796
|
-
reasoning: { ...DEFAULT_COMPRESS_REASONING }
|
|
1836
|
+
reasoning: { ...DEFAULT_COMPRESS_REASONING },
|
|
1837
|
+
images: { ...DEFAULT_COMPRESS_IMAGES }
|
|
1797
1838
|
},
|
|
1798
1839
|
gc: {
|
|
1799
1840
|
algorithm: "truncate",
|
|
@@ -1971,6 +2012,11 @@ function mergeCompress(base, override) {
|
|
|
1971
2012
|
drop: override.reasoning?.drop ?? base.reasoning?.drop ?? DEFAULT_COMPRESS_REASONING.drop,
|
|
1972
2013
|
threshold: override.reasoning?.threshold ?? base.reasoning?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
1973
2014
|
},
|
|
2015
|
+
images: {
|
|
2016
|
+
enabled: override.images?.enabled ?? base.images?.enabled ?? DEFAULT_COMPRESS_IMAGES.enabled,
|
|
2017
|
+
minBytes: override.images?.minBytes ?? base.images?.minBytes ?? DEFAULT_COMPRESS_IMAGES.minBytes,
|
|
2018
|
+
ttlDays: override.images?.ttlDays ?? base.images?.ttlDays ?? DEFAULT_COMPRESS_IMAGES.ttlDays
|
|
2019
|
+
},
|
|
1974
2020
|
completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens
|
|
1975
2021
|
};
|
|
1976
2022
|
}
|
|
@@ -2908,7 +2954,8 @@ function saveSessionState(sessionState, logger, sessionName) {
|
|
|
2908
2954
|
lastCompaction: sessionState.lastCompaction,
|
|
2909
2955
|
modelContextLimit: sessionState.modelContextLimit,
|
|
2910
2956
|
modelProviderID: sessionState.modelProviderID,
|
|
2911
|
-
modelID: sessionState.modelID
|
|
2957
|
+
modelID: sessionState.modelID,
|
|
2958
|
+
imageArchive: sessionState.imageArchive
|
|
2912
2959
|
};
|
|
2913
2960
|
const key = saveQueueKey(sessionState.sessionId, sessionState.storageDir);
|
|
2914
2961
|
let queue = saveQueues.get(key);
|
|
@@ -5075,7 +5122,9 @@ function createSessionState() {
|
|
|
5075
5122
|
systemPromptTokens: void 0,
|
|
5076
5123
|
storageDir: void 0,
|
|
5077
5124
|
qualityGateRetryPending: false,
|
|
5078
|
-
noContextLimitWarned: false
|
|
5125
|
+
noContextLimitWarned: false,
|
|
5126
|
+
imageArchive: {},
|
|
5127
|
+
imagePruneDone: false
|
|
5079
5128
|
};
|
|
5080
5129
|
}
|
|
5081
5130
|
function resetSessionState(state) {
|
|
@@ -5119,6 +5168,8 @@ function resetSessionState(state) {
|
|
|
5119
5168
|
state.storageDir = void 0;
|
|
5120
5169
|
state.qualityGateRetryPending = false;
|
|
5121
5170
|
state.noContextLimitWarned = false;
|
|
5171
|
+
state.imageArchive = {};
|
|
5172
|
+
state.imagePruneDone = false;
|
|
5122
5173
|
}
|
|
5123
5174
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
|
|
5124
5175
|
if (state.sessionId === sessionId) {
|
|
@@ -5197,8 +5248,21 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
5197
5248
|
state.nudges.compressBaselineSet = persisted.nudges.compressBaselineSet ?? false;
|
|
5198
5249
|
state.stats = {
|
|
5199
5250
|
pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0,
|
|
5200
|
-
totalPruneTokens: persisted.stats?.totalPruneTokens || 0
|
|
5251
|
+
totalPruneTokens: persisted.stats?.totalPruneTokens || 0,
|
|
5252
|
+
imagesArchived: persisted.stats?.imagesArchived,
|
|
5253
|
+
imageBytesArchived: persisted.stats?.imageBytesArchived
|
|
5201
5254
|
};
|
|
5255
|
+
if (persisted.imageArchive && typeof persisted.imageArchive === "object") {
|
|
5256
|
+
for (const [rawId, entries] of Object.entries(persisted.imageArchive)) {
|
|
5257
|
+
if (!Array.isArray(entries)) continue;
|
|
5258
|
+
const valid = entries.filter(
|
|
5259
|
+
(e) => !!e && typeof e.path === "string" && typeof e.mediaType === "string" && typeof e.bytes === "number"
|
|
5260
|
+
);
|
|
5261
|
+
if (valid.length > 0) {
|
|
5262
|
+
state.imageArchive[rawId] = valid;
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5202
5266
|
const persistedAny = persisted;
|
|
5203
5267
|
if (persistedAny._persistedMessageIds) {
|
|
5204
5268
|
state.messageIds = {
|
|
@@ -6611,6 +6675,156 @@ function buildPhantomSkipNotice(identification) {
|
|
|
6611
6675
|
return `Skipped ${identification.phantomIndices.length} already-compressed ${noun} (${skippedNums}) \u2014 they are no-ops; the remaining entries were compressed.`;
|
|
6612
6676
|
}
|
|
6613
6677
|
|
|
6678
|
+
// lib/compress/image-archive.ts
|
|
6679
|
+
import { createHash as createHash2 } from "crypto";
|
|
6680
|
+
import {
|
|
6681
|
+
existsSync as existsSync4,
|
|
6682
|
+
mkdirSync as mkdirSync2,
|
|
6683
|
+
readdirSync,
|
|
6684
|
+
rmdirSync,
|
|
6685
|
+
statSync as statSync2,
|
|
6686
|
+
unlinkSync,
|
|
6687
|
+
writeFileSync as writeFileSync2
|
|
6688
|
+
} from "fs";
|
|
6689
|
+
import { join as join4 } from "path";
|
|
6690
|
+
var DATA_URL_REGEX = /^data:(image\/[a-z0-9.+-]+);base64,(.+)$/is;
|
|
6691
|
+
var IMAGE_EXTENSIONS = {
|
|
6692
|
+
"image/png": "png",
|
|
6693
|
+
"image/jpeg": "jpg",
|
|
6694
|
+
"image/jpg": "jpg",
|
|
6695
|
+
"image/gif": "gif",
|
|
6696
|
+
"image/webp": "webp",
|
|
6697
|
+
"image/avif": "avif",
|
|
6698
|
+
"image/bmp": "bmp"
|
|
6699
|
+
};
|
|
6700
|
+
function sha8(input) {
|
|
6701
|
+
return createHash2("sha256").update(input).digest("hex").slice(0, 8);
|
|
6702
|
+
}
|
|
6703
|
+
function parseImageDataUrl(url) {
|
|
6704
|
+
if (typeof url !== "string") return void 0;
|
|
6705
|
+
const match = DATA_URL_REGEX.exec(url);
|
|
6706
|
+
if (!match) return void 0;
|
|
6707
|
+
return { mediaType: match[1].toLowerCase(), base64: match[2] };
|
|
6708
|
+
}
|
|
6709
|
+
function extForMediaType(mediaType) {
|
|
6710
|
+
return IMAGE_EXTENSIONS[mediaType.toLowerCase()];
|
|
6711
|
+
}
|
|
6712
|
+
function archiveDirName(sessionId) {
|
|
6713
|
+
let name = sessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 100);
|
|
6714
|
+
if (name !== sessionId) {
|
|
6715
|
+
name = `${name}-${sha8(sessionId)}`;
|
|
6716
|
+
}
|
|
6717
|
+
return name || "-";
|
|
6718
|
+
}
|
|
6719
|
+
function archiveRoot(storageDir, sessionId) {
|
|
6720
|
+
return join4(storageDir ?? getDefaultStorageDir(), "images", archiveDirName(sessionId));
|
|
6721
|
+
}
|
|
6722
|
+
function archiveSessionImages(state, messages, images, logger) {
|
|
6723
|
+
if (!images.enabled || !state.sessionId) return 0;
|
|
6724
|
+
const root = archiveRoot(state.storageDir, state.sessionId);
|
|
6725
|
+
let archived = 0;
|
|
6726
|
+
for (const message of messages) {
|
|
6727
|
+
const rawId = message.info.id;
|
|
6728
|
+
if (!rawId) continue;
|
|
6729
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
6730
|
+
for (let partIndex = 0; partIndex < parts.length; partIndex++) {
|
|
6731
|
+
const part = parts[partIndex];
|
|
6732
|
+
if (part.type !== "file") continue;
|
|
6733
|
+
const parsed = parseImageDataUrl(part.url);
|
|
6734
|
+
if (!parsed) continue;
|
|
6735
|
+
const ext = extForMediaType(parsed.mediaType);
|
|
6736
|
+
if (!ext) continue;
|
|
6737
|
+
const fileName = `${sha8(rawId)}-p${partIndex}-${sha8(parsed.base64)}.${ext}`;
|
|
6738
|
+
const filePath = join4(root, fileName);
|
|
6739
|
+
const existing = state.imageArchive[rawId]?.find((e) => e.path === filePath);
|
|
6740
|
+
if (existing) continue;
|
|
6741
|
+
let bytes;
|
|
6742
|
+
try {
|
|
6743
|
+
if (existsSync4(filePath)) {
|
|
6744
|
+
bytes = statSync2(filePath).size;
|
|
6745
|
+
} else {
|
|
6746
|
+
const buffer = Buffer.from(parsed.base64, "base64");
|
|
6747
|
+
bytes = buffer.byteLength;
|
|
6748
|
+
if (bytes < images.minBytes) continue;
|
|
6749
|
+
mkdirSync2(root, { recursive: true });
|
|
6750
|
+
writeFileSync2(filePath, buffer, { mode: 384 });
|
|
6751
|
+
}
|
|
6752
|
+
} catch (error) {
|
|
6753
|
+
logger.warn(
|
|
6754
|
+
`[#478] failed to archive image ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
6755
|
+
);
|
|
6756
|
+
continue;
|
|
6757
|
+
}
|
|
6758
|
+
const entry = {
|
|
6759
|
+
path: filePath,
|
|
6760
|
+
mediaType: parsed.mediaType,
|
|
6761
|
+
bytes
|
|
6762
|
+
};
|
|
6763
|
+
if (!state.imageArchive[rawId]) {
|
|
6764
|
+
state.imageArchive[rawId] = [];
|
|
6765
|
+
}
|
|
6766
|
+
state.imageArchive[rawId].push(entry);
|
|
6767
|
+
state.stats.imagesArchived = (state.stats.imagesArchived ?? 0) + 1;
|
|
6768
|
+
state.stats.imageBytesArchived = (state.stats.imageBytesArchived ?? 0) + bytes;
|
|
6769
|
+
archived++;
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6772
|
+
return archived;
|
|
6773
|
+
}
|
|
6774
|
+
function pruneImageArchive(state, images, logger) {
|
|
6775
|
+
if (state.imagePruneDone || !state.sessionId) return;
|
|
6776
|
+
state.imagePruneDone = true;
|
|
6777
|
+
try {
|
|
6778
|
+
const root = archiveRoot(state.storageDir, state.sessionId);
|
|
6779
|
+
if (!existsSync4(root)) return;
|
|
6780
|
+
const cutoff = Date.now() - images.ttlDays * 864e5;
|
|
6781
|
+
const prunedPaths = /* @__PURE__ */ new Set();
|
|
6782
|
+
for (const name of readdirSync(root)) {
|
|
6783
|
+
const filePath = join4(root, name);
|
|
6784
|
+
try {
|
|
6785
|
+
if (statSync2(filePath).mtimeMs < cutoff) {
|
|
6786
|
+
unlinkSync(filePath);
|
|
6787
|
+
prunedPaths.add(filePath);
|
|
6788
|
+
}
|
|
6789
|
+
} catch {
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
if (prunedPaths.size > 0) {
|
|
6793
|
+
for (const [rawId, entries] of Object.entries(state.imageArchive)) {
|
|
6794
|
+
const kept = entries.filter((e) => !prunedPaths.has(e.path));
|
|
6795
|
+
if (kept.length === 0) {
|
|
6796
|
+
delete state.imageArchive[rawId];
|
|
6797
|
+
} else if (kept.length !== entries.length) {
|
|
6798
|
+
state.imageArchive[rawId] = kept;
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
logger.info(`[#478] TTL sweep removed ${prunedPaths.size} expired archived image(s)`);
|
|
6802
|
+
}
|
|
6803
|
+
if (readdirSync(root).length === 0) {
|
|
6804
|
+
rmdirSync(root);
|
|
6805
|
+
}
|
|
6806
|
+
} catch (error) {
|
|
6807
|
+
logger.warn(
|
|
6808
|
+
`[#478] image TTL sweep failed: ${error instanceof Error ? error.message : String(error)}`
|
|
6809
|
+
);
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
function appendArchivedImagePointers(summary, selection, state, enabled) {
|
|
6813
|
+
if (!enabled) return summary;
|
|
6814
|
+
const lines = [];
|
|
6815
|
+
for (const messageId of selection.messageIds) {
|
|
6816
|
+
const entries = state.imageArchive[messageId];
|
|
6817
|
+
if (!entries || entries.length === 0) continue;
|
|
6818
|
+
const ref = state.messageIds.byRawId.get(messageId) ?? messageId;
|
|
6819
|
+
for (const entry of entries) {
|
|
6820
|
+
lines.push(`- ${ref}: ${entry.path} (${entry.mediaType}, ${entry.bytes} bytes)`);
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
if (lines.length === 0) return summary;
|
|
6824
|
+
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:";
|
|
6825
|
+
return summary + heading + "\n" + lines.join("\n");
|
|
6826
|
+
}
|
|
6827
|
+
|
|
6614
6828
|
// lib/compress/keep-markers.ts
|
|
6615
6829
|
var KEEP_REGEX = /\[\[KEEP:(m\d+)\]\]/g;
|
|
6616
6830
|
var REF_REGEX = /\[\[REF:(m\d+)\|([^\]]+)\]\]/g;
|
|
@@ -6785,12 +6999,12 @@ ${afterClose}`;
|
|
|
6785
6999
|
}
|
|
6786
7000
|
|
|
6787
7001
|
// lib/messages/utils.ts
|
|
6788
|
-
import { createHash as
|
|
7002
|
+
import { createHash as createHash3 } from "crypto";
|
|
6789
7003
|
var SUMMARY_ID_HASH_LENGTH = 16;
|
|
6790
7004
|
var DCP_PAIRED_TAG_REGEX = /<(?:dcp|acp)[^>]*>[\s\S]*?<\/(?:dcp|acp)[^>]*>/gi;
|
|
6791
7005
|
var DCP_UNPAIRED_TAG_REGEX = /<\/?(?:dcp|acp)[^>]*>/gi;
|
|
6792
7006
|
var generateStableId = (prefix, seed) => {
|
|
6793
|
-
const hash =
|
|
7007
|
+
const hash = createHash3("sha256").update(seed).digest("hex").slice(0, SUMMARY_ID_HASH_LENGTH);
|
|
6794
7008
|
return `${prefix}_${hash}`;
|
|
6795
7009
|
};
|
|
6796
7010
|
var createSyntheticMessage = (baseMessage, content, stableSeed, role = "user") => {
|
|
@@ -7203,7 +7417,8 @@ function resolveMinNudgeFloorTokens(config, modelContextLimit, providerId, model
|
|
|
7203
7417
|
}
|
|
7204
7418
|
var OVERRIDE_BLANKET_APPLY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
7205
7419
|
"maxContextLimit",
|
|
7206
|
-
"reasoning"
|
|
7420
|
+
"reasoning",
|
|
7421
|
+
"images"
|
|
7207
7422
|
]);
|
|
7208
7423
|
function applyCompressOverrides(config, providerId, modelId) {
|
|
7209
7424
|
const overrides = resolveCompressOverrides(config, providerId, modelId);
|
|
@@ -8057,7 +8272,12 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
8057
8272
|
entry: plan.entry,
|
|
8058
8273
|
selection: plan.selection,
|
|
8059
8274
|
anchorMessageId: plan.anchorMessageId,
|
|
8060
|
-
finalSummary:
|
|
8275
|
+
finalSummary: appendArchivedImagePointers(
|
|
8276
|
+
completedSummary.expandedSummary,
|
|
8277
|
+
plan.selection,
|
|
8278
|
+
ctx.state,
|
|
8279
|
+
ctx.config.compress.images?.enabled !== false
|
|
8280
|
+
),
|
|
8061
8281
|
consumedBlockIds: mergeConsumedBlockIds
|
|
8062
8282
|
});
|
|
8063
8283
|
}
|
|
@@ -9733,6 +9953,66 @@ function dropCompressReasoning(messages, threshold) {
|
|
|
9733
9953
|
return removed;
|
|
9734
9954
|
}
|
|
9735
9955
|
|
|
9956
|
+
// lib/messages/reasoning-canonicalize.ts
|
|
9957
|
+
var CANONICAL_OPEN = "<thinking>\n";
|
|
9958
|
+
var CANONICAL_CLOSE = "\n</thinking>";
|
|
9959
|
+
function canonicalizeReasoningShape(messages) {
|
|
9960
|
+
let normalized = 0;
|
|
9961
|
+
for (const message of messages) {
|
|
9962
|
+
if (!message || message.info.role !== "assistant") {
|
|
9963
|
+
continue;
|
|
9964
|
+
}
|
|
9965
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
9966
|
+
let allReasoning = "";
|
|
9967
|
+
for (const part of parts) {
|
|
9968
|
+
if (part.type === "reasoning") {
|
|
9969
|
+
allReasoning += part.text;
|
|
9970
|
+
}
|
|
9971
|
+
}
|
|
9972
|
+
if (allReasoning.length === 0) {
|
|
9973
|
+
continue;
|
|
9974
|
+
}
|
|
9975
|
+
let allText = "";
|
|
9976
|
+
for (const part of parts) {
|
|
9977
|
+
if (part.type === "text") {
|
|
9978
|
+
allText += part.text;
|
|
9979
|
+
}
|
|
9980
|
+
}
|
|
9981
|
+
const merged = allText.length > 0 ? `${CANONICAL_OPEN}${allReasoning}${CANONICAL_CLOSE}
|
|
9982
|
+
${allText}` : `${CANONICAL_OPEN}${allReasoning}${CANONICAL_CLOSE}`;
|
|
9983
|
+
const rebuilt = [];
|
|
9984
|
+
let placed = false;
|
|
9985
|
+
const placeMerged = (carrier) => {
|
|
9986
|
+
if (placed) {
|
|
9987
|
+
return;
|
|
9988
|
+
}
|
|
9989
|
+
placed = true;
|
|
9990
|
+
const part = {
|
|
9991
|
+
id: carrier.id,
|
|
9992
|
+
sessionID: message.info.sessionID,
|
|
9993
|
+
messageID: message.info.id,
|
|
9994
|
+
type: "text",
|
|
9995
|
+
text: merged
|
|
9996
|
+
};
|
|
9997
|
+
rebuilt.push(part);
|
|
9998
|
+
};
|
|
9999
|
+
for (const part of parts) {
|
|
10000
|
+
if (part.type === "reasoning") {
|
|
10001
|
+
placeMerged(part);
|
|
10002
|
+
continue;
|
|
10003
|
+
}
|
|
10004
|
+
if (part.type === "text") {
|
|
10005
|
+
placeMerged(part);
|
|
10006
|
+
continue;
|
|
10007
|
+
}
|
|
10008
|
+
rebuilt.push(part);
|
|
10009
|
+
}
|
|
10010
|
+
message.parts = rebuilt;
|
|
10011
|
+
normalized++;
|
|
10012
|
+
}
|
|
10013
|
+
return normalized;
|
|
10014
|
+
}
|
|
10015
|
+
|
|
9736
10016
|
// lib/messages/priority.ts
|
|
9737
10017
|
function buildPriorityMap(_config, _state, _messages) {
|
|
9738
10018
|
return /* @__PURE__ */ new Map();
|
|
@@ -11106,10 +11386,10 @@ function hideFailedCompressCalls(messages) {
|
|
|
11106
11386
|
|
|
11107
11387
|
// lib/logger.ts
|
|
11108
11388
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
11109
|
-
import { join as
|
|
11110
|
-
import { existsSync as
|
|
11389
|
+
import { join as join5 } from "path";
|
|
11390
|
+
import { existsSync as existsSync5 } from "fs";
|
|
11111
11391
|
import { homedir as homedir3 } from "os";
|
|
11112
|
-
var LOG_VERSION = true ? "1.18.
|
|
11392
|
+
var LOG_VERSION = true ? "1.18.4-pr.483.216" : "dev";
|
|
11113
11393
|
var LEVEL_RANK = {
|
|
11114
11394
|
debug: 10,
|
|
11115
11395
|
info: 20,
|
|
@@ -11129,8 +11409,8 @@ var Logger = class {
|
|
|
11129
11409
|
constructor(enabled, level) {
|
|
11130
11410
|
const resolved = level !== void 0 && LEVEL_RANK[level] !== void 0 ? level : void 0;
|
|
11131
11411
|
this.level = resolved ?? (enabled ? "debug" : "warn");
|
|
11132
|
-
const configHome = process.env.XDG_CONFIG_HOME ||
|
|
11133
|
-
this.logDir =
|
|
11412
|
+
const configHome = process.env.XDG_CONFIG_HOME || join5(homedir3(), ".config");
|
|
11413
|
+
this.logDir = join5(configHome, "opencode", "logs", "acp");
|
|
11134
11414
|
}
|
|
11135
11415
|
/** Back-compat view of the old flag: true only at full debug verbosity. */
|
|
11136
11416
|
get enabled() {
|
|
@@ -11140,7 +11420,7 @@ var Logger = class {
|
|
|
11140
11420
|
return LEVEL_RANK[level] >= LEVEL_RANK[this.level];
|
|
11141
11421
|
}
|
|
11142
11422
|
async ensureLogDir() {
|
|
11143
|
-
if (!
|
|
11423
|
+
if (!existsSync5(this.logDir)) {
|
|
11144
11424
|
await mkdir2(this.logDir, { recursive: true });
|
|
11145
11425
|
}
|
|
11146
11426
|
}
|
|
@@ -11192,11 +11472,11 @@ var Logger = class {
|
|
|
11192
11472
|
const dataStr = this.formatData(data);
|
|
11193
11473
|
const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? " | " + dataStr : ""} | v=${LOG_VERSION}
|
|
11194
11474
|
`;
|
|
11195
|
-
const dailyLogDir =
|
|
11196
|
-
if (!
|
|
11475
|
+
const dailyLogDir = join5(this.logDir, "daily");
|
|
11476
|
+
if (!existsSync5(dailyLogDir)) {
|
|
11197
11477
|
await mkdir2(dailyLogDir, { recursive: true });
|
|
11198
11478
|
}
|
|
11199
|
-
const logFile =
|
|
11479
|
+
const logFile = join5(dailyLogDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`);
|
|
11200
11480
|
await writeFile2(logFile, logLine, { flag: "a" });
|
|
11201
11481
|
} catch (error) {
|
|
11202
11482
|
}
|
|
@@ -11307,18 +11587,18 @@ var Logger = class {
|
|
|
11307
11587
|
async saveContext(sessionId, messages) {
|
|
11308
11588
|
if (this.level !== "debug") return;
|
|
11309
11589
|
try {
|
|
11310
|
-
const contextDir =
|
|
11311
|
-
if (!
|
|
11590
|
+
const contextDir = join5(this.logDir, "context", sessionId);
|
|
11591
|
+
if (!existsSync5(contextDir)) {
|
|
11312
11592
|
await mkdir2(contextDir, { recursive: true });
|
|
11313
11593
|
}
|
|
11314
11594
|
const minimized = this.minimizeForDebug(messages).filter(
|
|
11315
11595
|
(msg) => msg.parts && msg.parts.length > 0
|
|
11316
11596
|
);
|
|
11317
11597
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
11318
|
-
const contextFile =
|
|
11598
|
+
const contextFile = join5(contextDir, `${timestamp}.json`);
|
|
11319
11599
|
await writeFile2(contextFile, JSON.stringify(minimized, null, 2));
|
|
11320
|
-
const versionFile =
|
|
11321
|
-
if (!
|
|
11600
|
+
const versionFile = join5(contextDir, "_version");
|
|
11601
|
+
if (!existsSync5(versionFile)) {
|
|
11322
11602
|
await writeFile2(versionFile, LOG_VERSION);
|
|
11323
11603
|
}
|
|
11324
11604
|
} catch (error) {
|
|
@@ -11327,8 +11607,8 @@ var Logger = class {
|
|
|
11327
11607
|
};
|
|
11328
11608
|
|
|
11329
11609
|
// lib/prompts/store.ts
|
|
11330
|
-
import { existsSync as
|
|
11331
|
-
import { join as
|
|
11610
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3, statSync as statSync3 } from "fs";
|
|
11611
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
11332
11612
|
import { homedir as homedir4 } from "os";
|
|
11333
11613
|
|
|
11334
11614
|
// lib/prompts/system.ts
|
|
@@ -11636,10 +11916,10 @@ function createBundledRuntimePrompts(candidatesEnabled) {
|
|
|
11636
11916
|
function findOpencodeDir2(startDir) {
|
|
11637
11917
|
let current = startDir;
|
|
11638
11918
|
while (current !== "/") {
|
|
11639
|
-
const candidate =
|
|
11640
|
-
if (
|
|
11919
|
+
const candidate = join6(current, ".opencode");
|
|
11920
|
+
if (existsSync6(candidate)) {
|
|
11641
11921
|
try {
|
|
11642
|
-
if (
|
|
11922
|
+
if (statSync3(candidate).isDirectory()) {
|
|
11643
11923
|
return candidate;
|
|
11644
11924
|
}
|
|
11645
11925
|
} catch {
|
|
@@ -11654,13 +11934,13 @@ function findOpencodeDir2(startDir) {
|
|
|
11654
11934
|
return null;
|
|
11655
11935
|
}
|
|
11656
11936
|
function resolvePromptPaths(workingDirectory) {
|
|
11657
|
-
const configHome = process.env.XDG_CONFIG_HOME ||
|
|
11658
|
-
const globalRoot =
|
|
11659
|
-
const defaultsDir =
|
|
11660
|
-
const globalOverridesDir =
|
|
11661
|
-
const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ?
|
|
11937
|
+
const configHome = process.env.XDG_CONFIG_HOME || join6(homedir4(), ".config");
|
|
11938
|
+
const globalRoot = join6(configHome, "opencode", "acp-prompts");
|
|
11939
|
+
const defaultsDir = join6(globalRoot, "defaults");
|
|
11940
|
+
const globalOverridesDir = join6(globalRoot, "overrides");
|
|
11941
|
+
const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ? join6(process.env.OPENCODE_CONFIG_DIR, "acp-prompts", "overrides") : null;
|
|
11662
11942
|
const opencodeDir = findOpencodeDir2(workingDirectory);
|
|
11663
|
-
const projectOverridesDir = opencodeDir ?
|
|
11943
|
+
const projectOverridesDir = opencodeDir ? join6(opencodeDir, "acp-prompts", "overrides") : null;
|
|
11664
11944
|
return {
|
|
11665
11945
|
defaultsDir,
|
|
11666
11946
|
globalOverridesDir,
|
|
@@ -11760,7 +12040,7 @@ function buildDefaultsReadmeContent() {
|
|
|
11760
12040
|
`;
|
|
11761
12041
|
}
|
|
11762
12042
|
function readFileIfExists(filePath) {
|
|
11763
|
-
if (!
|
|
12043
|
+
if (!existsSync6(filePath)) {
|
|
11764
12044
|
return null;
|
|
11765
12045
|
}
|
|
11766
12046
|
try {
|
|
@@ -11833,23 +12113,23 @@ var PromptStore = class {
|
|
|
11833
12113
|
const candidates = [];
|
|
11834
12114
|
if (this.paths.projectOverridesDir) {
|
|
11835
12115
|
candidates.push({
|
|
11836
|
-
path:
|
|
12116
|
+
path: join6(this.paths.projectOverridesDir, fileName)
|
|
11837
12117
|
});
|
|
11838
12118
|
}
|
|
11839
12119
|
if (this.paths.configDirOverridesDir) {
|
|
11840
12120
|
candidates.push({
|
|
11841
|
-
path:
|
|
12121
|
+
path: join6(this.paths.configDirOverridesDir, fileName)
|
|
11842
12122
|
});
|
|
11843
12123
|
}
|
|
11844
12124
|
candidates.push({
|
|
11845
|
-
path:
|
|
12125
|
+
path: join6(this.paths.globalOverridesDir, fileName)
|
|
11846
12126
|
});
|
|
11847
12127
|
return candidates;
|
|
11848
12128
|
}
|
|
11849
12129
|
ensureDefaultFiles() {
|
|
11850
12130
|
try {
|
|
11851
|
-
|
|
11852
|
-
|
|
12131
|
+
mkdirSync3(this.paths.defaultsDir, { recursive: true });
|
|
12132
|
+
mkdirSync3(this.paths.globalOverridesDir, { recursive: true });
|
|
11853
12133
|
} catch {
|
|
11854
12134
|
this.logger.warn("Failed to initialize prompt directories", {
|
|
11855
12135
|
defaultsDir: this.paths.defaultsDir,
|
|
@@ -11866,13 +12146,13 @@ var PromptStore = class {
|
|
|
11866
12146
|
const managedContent = buildDefaultPromptFileContent(
|
|
11867
12147
|
bundledEditable || bundledPrompts[definition.runtimeField]
|
|
11868
12148
|
);
|
|
11869
|
-
const filePath =
|
|
12149
|
+
const filePath = join6(this.paths.defaultsDir, definition.fileName);
|
|
11870
12150
|
try {
|
|
11871
12151
|
const existing = readFileIfExists(filePath);
|
|
11872
12152
|
if (existing === managedContent) {
|
|
11873
12153
|
continue;
|
|
11874
12154
|
}
|
|
11875
|
-
|
|
12155
|
+
writeFileSync3(filePath, managedContent, "utf-8");
|
|
11876
12156
|
} catch {
|
|
11877
12157
|
this.logger.warn("Failed to write default prompt file", {
|
|
11878
12158
|
key: definition.key,
|
|
@@ -11880,12 +12160,12 @@ var PromptStore = class {
|
|
|
11880
12160
|
});
|
|
11881
12161
|
}
|
|
11882
12162
|
}
|
|
11883
|
-
const readmePath =
|
|
12163
|
+
const readmePath = join6(this.paths.defaultsDir, DEFAULTS_README_FILE);
|
|
11884
12164
|
const readmeContent = buildDefaultsReadmeContent();
|
|
11885
12165
|
try {
|
|
11886
12166
|
const existing = readFileIfExists(readmePath);
|
|
11887
12167
|
if (existing !== readmeContent) {
|
|
11888
|
-
|
|
12168
|
+
writeFileSync3(readmePath, readmeContent, "utf-8");
|
|
11889
12169
|
}
|
|
11890
12170
|
} catch {
|
|
11891
12171
|
this.logger.warn("Failed to write defaults README", {
|
|
@@ -12353,8 +12633,8 @@ ${report}`;
|
|
|
12353
12633
|
|
|
12354
12634
|
// lib/commands/export.ts
|
|
12355
12635
|
import * as fs2 from "fs/promises";
|
|
12356
|
-
import { existsSync as
|
|
12357
|
-
import { dirname as dirname3, isAbsolute as isAbsolute2, join as
|
|
12636
|
+
import { existsSync as existsSync7 } from "fs";
|
|
12637
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join7, resolve } from "path";
|
|
12358
12638
|
var ALL_TIERS = [1, 2, 3];
|
|
12359
12639
|
var TIER_NAMES = {
|
|
12360
12640
|
1: "Tier 1 \u2014 Capture",
|
|
@@ -12467,7 +12747,7 @@ function parseTiers(raw) {
|
|
|
12467
12747
|
}
|
|
12468
12748
|
function resolveDefaultOutputPath(sessionId, cwd2) {
|
|
12469
12749
|
const short = (sessionId || "session").slice(0, 8);
|
|
12470
|
-
return
|
|
12750
|
+
return join7(cwd2, ".opencode", `acp-export-${short}.md`);
|
|
12471
12751
|
}
|
|
12472
12752
|
function formatTokens2(n) {
|
|
12473
12753
|
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
@@ -12598,11 +12878,11 @@ async function handleExportCommand(ctx, args) {
|
|
|
12598
12878
|
const targetPath = options.outputPath !== "" ? isAbsolute2(options.outputPath) ? options.outputPath : resolve(cwd2, options.outputPath) : resolveDefaultOutputPath(sessionId, cwd2);
|
|
12599
12879
|
try {
|
|
12600
12880
|
const dir = dirname3(targetPath);
|
|
12601
|
-
if (!
|
|
12881
|
+
if (!existsSync7(dir)) {
|
|
12602
12882
|
await fs2.mkdir(dir, { recursive: true });
|
|
12603
12883
|
}
|
|
12604
12884
|
const flag = options.append ? "a" : "w";
|
|
12605
|
-
if (options.append &&
|
|
12885
|
+
if (options.append && existsSync7(targetPath)) {
|
|
12606
12886
|
await fs2.appendFile(targetPath, `
|
|
12607
12887
|
|
|
12608
12888
|
---
|
|
@@ -13308,6 +13588,12 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
13308
13588
|
});
|
|
13309
13589
|
}
|
|
13310
13590
|
}
|
|
13591
|
+
const canonicalizedReasoning = canonicalizeReasoningShape(output.messages);
|
|
13592
|
+
if (canonicalizedReasoning > 0) {
|
|
13593
|
+
logger.debug("reasoning-canonicalize: pinned historical thinking shape", {
|
|
13594
|
+
messages: canonicalizedReasoning
|
|
13595
|
+
});
|
|
13596
|
+
}
|
|
13311
13597
|
ensureBuiltinFiltersRegistered();
|
|
13312
13598
|
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
13313
13599
|
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
@@ -13317,6 +13603,20 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
13317
13603
|
});
|
|
13318
13604
|
cacheSystemPromptTokens(state, output.messages);
|
|
13319
13605
|
assignMessageRefs(state, output.messages);
|
|
13606
|
+
const imagesConfig = config.compress.images ?? DEFAULT_COMPRESS_IMAGES;
|
|
13607
|
+
if (imagesConfig.enabled) {
|
|
13608
|
+
const archivedImages = archiveSessionImages(
|
|
13609
|
+
state,
|
|
13610
|
+
output.messages,
|
|
13611
|
+
imagesConfig,
|
|
13612
|
+
logger
|
|
13613
|
+
);
|
|
13614
|
+
if (archivedImages > 0) {
|
|
13615
|
+
saveSessionState(state, logger).catch(() => {
|
|
13616
|
+
});
|
|
13617
|
+
}
|
|
13618
|
+
pruneImageArchive(state, imagesConfig, logger);
|
|
13619
|
+
}
|
|
13320
13620
|
const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
|
|
13321
13621
|
const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
|
|
13322
13622
|
if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
|
|
@@ -13594,7 +13894,7 @@ function configureClientAuth(client) {
|
|
|
13594
13894
|
|
|
13595
13895
|
// lib/update.ts
|
|
13596
13896
|
import { readFile as readFile2, rm } from "fs/promises";
|
|
13597
|
-
import { basename, dirname as dirname4, join as
|
|
13897
|
+
import { basename, dirname as dirname4, join as join8 } from "path";
|
|
13598
13898
|
import { fileURLToPath } from "url";
|
|
13599
13899
|
var PACKAGE_NAME = "opencode-acp";
|
|
13600
13900
|
function startAutoUpdate(ctx, enabled, logger) {
|
|
@@ -13641,7 +13941,7 @@ async function checkAutoUpdate(signal, logger) {
|
|
|
13641
13941
|
logger?.info("Auto-update skipped: package dir not found");
|
|
13642
13942
|
return { updated: false };
|
|
13643
13943
|
}
|
|
13644
|
-
const pkg = await readPackageJson(
|
|
13944
|
+
const pkg = await readPackageJson(join8(packageDir, "package.json"));
|
|
13645
13945
|
if (!pkg?.name || !pkg.version) {
|
|
13646
13946
|
logger?.info("Auto-update skipped: package.json unreadable", { packageDir });
|
|
13647
13947
|
return { updated: false };
|
|
@@ -13682,7 +13982,7 @@ async function checkAutoUpdate(signal, logger) {
|
|
|
13682
13982
|
async function findPackageDir(name) {
|
|
13683
13983
|
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
13684
13984
|
for (; ; ) {
|
|
13685
|
-
const pkg = await readPackageJson(
|
|
13985
|
+
const pkg = await readPackageJson(join8(dir, "package.json"));
|
|
13686
13986
|
if (pkg?.name === name) return dir;
|
|
13687
13987
|
const parent = dirname4(dir);
|
|
13688
13988
|
if (parent === dir) return void 0;
|
|
@@ -13694,7 +13994,7 @@ async function updateTarget(packageDir, name) {
|
|
|
13694
13994
|
const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname4(packageParent) : packageParent;
|
|
13695
13995
|
if (basename(nodeModulesDir) !== "node_modules") return void 0;
|
|
13696
13996
|
const wrapperDir = dirname4(nodeModulesDir);
|
|
13697
|
-
const wrapperPkg = await readPackageJson(
|
|
13997
|
+
const wrapperPkg = await readPackageJson(join8(wrapperDir, "package.json"));
|
|
13698
13998
|
const spec = wrapperSpec(wrapperDir, name) ?? wrapperPkg?.dependencies?.[name];
|
|
13699
13999
|
if (!spec || !isAutoUpdatableSpec(spec)) return void 0;
|
|
13700
14000
|
return { removeDir: wrapperDir, spec };
|
|
@@ -13805,7 +14105,7 @@ var server = (async (ctx) => {
|
|
|
13805
14105
|
}
|
|
13806
14106
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
13807
14107
|
logger.info("ACP plugin initialized", {
|
|
13808
|
-
version: true ? "1.18.
|
|
14108
|
+
version: true ? "1.18.4-pr.483.216" : "dev",
|
|
13809
14109
|
workspace: ctx.directory,
|
|
13810
14110
|
logLevel: logger.level,
|
|
13811
14111
|
debug: config.debug,
|