@lexwdex-org/opencode-dcp 3.3.4 → 3.4.3
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.en.md +290 -0
- package/README.md +112 -56
- package/dist/index.js +350 -74
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/external-inference.d.ts +15 -0
- package/dist/lib/compress/external-inference.d.ts.map +1 -0
- package/dist/lib/compress/message-utils.d.ts.map +1 -1
- package/dist/lib/compress/message.d.ts.map +1 -1
- package/dist/lib/compress/range-utils.d.ts.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/compress/search.d.ts.map +1 -1
- package/dist/lib/compress/types.d.ts +2 -2
- package/dist/lib/compress/types.d.ts.map +1 -1
- package/dist/lib/config-env-override.d.ts +3 -0
- package/dist/lib/config-env-override.d.ts.map +1 -0
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/extensions/tool.d.ts +4 -2
- package/dist/lib/prompts/extensions/tool.d.ts.map +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/system.d.ts +1 -1
- package/dist/lib/prompts/system.d.ts.map +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -862,6 +862,37 @@ var ParseErrorCode;
|
|
|
862
862
|
ParseErrorCode2[ParseErrorCode2["InvalidCharacter"] = 16] = "InvalidCharacter";
|
|
863
863
|
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
864
864
|
|
|
865
|
+
// lib/config-env-override.ts
|
|
866
|
+
function applyExternalModelEnvOverride(config) {
|
|
867
|
+
const envUrl = process.env.OPENCODE_DCP_EXTERNAL_COMPRESS_URL;
|
|
868
|
+
const envApiKey = process.env.OPENCODE_DCP_EXTERNAL_COMPRESS_KEY;
|
|
869
|
+
const envModel = process.env.OPENCODE_DCP_EXTERNAL_COMPRESS_MODEL;
|
|
870
|
+
const envTimeout = process.env.OPENCODE_DCP_EXTERNAL_COMPRESS_TIMEOUT;
|
|
871
|
+
const envRetries = process.env.OPENCODE_DCP_EXTERNAL_COMPRESS_RETRIES;
|
|
872
|
+
if (envUrl && envModel) {
|
|
873
|
+
const externalModel = {
|
|
874
|
+
url: envUrl,
|
|
875
|
+
model: envModel
|
|
876
|
+
};
|
|
877
|
+
if (envApiKey) {
|
|
878
|
+
externalModel.apiKey = envApiKey;
|
|
879
|
+
}
|
|
880
|
+
if (envTimeout) {
|
|
881
|
+
const parsed = parseInt(envTimeout, 10);
|
|
882
|
+
if (parsed > 0) {
|
|
883
|
+
externalModel.timeout = parsed;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (envRetries) {
|
|
887
|
+
const parsed = parseInt(envRetries, 10);
|
|
888
|
+
if (parsed >= 0) {
|
|
889
|
+
externalModel.retries = parsed;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
config.compress.externalModel = externalModel;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
865
896
|
// lib/config.ts
|
|
866
897
|
var DEFAULT_PROTECTED_TOOLS = [
|
|
867
898
|
"task",
|
|
@@ -912,6 +943,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
912
943
|
"compress.protectedTools",
|
|
913
944
|
"compress.protectTags",
|
|
914
945
|
"compress.protectUserMessages",
|
|
946
|
+
"compress.externalModel",
|
|
915
947
|
"strategies",
|
|
916
948
|
"strategies.deduplication",
|
|
917
949
|
"strategies.deduplication.enabled",
|
|
@@ -1213,6 +1245,30 @@ function validateConfigTypes(config) {
|
|
|
1213
1245
|
actual: JSON.stringify(compress.permission)
|
|
1214
1246
|
});
|
|
1215
1247
|
}
|
|
1248
|
+
if (compress.externalModel !== void 0) {
|
|
1249
|
+
if (typeof compress.externalModel !== "object" || compress.externalModel === null || Array.isArray(compress.externalModel)) {
|
|
1250
|
+
errors.push({
|
|
1251
|
+
key: "compress.externalModel",
|
|
1252
|
+
expected: "object with url and model",
|
|
1253
|
+
actual: typeof compress.externalModel
|
|
1254
|
+
});
|
|
1255
|
+
} else {
|
|
1256
|
+
if (typeof compress.externalModel.url !== "string") {
|
|
1257
|
+
errors.push({
|
|
1258
|
+
key: "compress.externalModel.url",
|
|
1259
|
+
expected: "string",
|
|
1260
|
+
actual: typeof compress.externalModel.url
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
if (typeof compress.externalModel.model !== "string") {
|
|
1264
|
+
errors.push({
|
|
1265
|
+
key: "compress.externalModel.model",
|
|
1266
|
+
expected: "string",
|
|
1267
|
+
actual: typeof compress.externalModel.model
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1216
1272
|
if (compress.showCompression !== void 0 && typeof compress.showCompression !== "boolean") {
|
|
1217
1273
|
errors.push({
|
|
1218
1274
|
key: "compress.showCompression",
|
|
@@ -1342,7 +1398,8 @@ var defaultConfig = {
|
|
|
1342
1398
|
nudgeForce: "strong",
|
|
1343
1399
|
protectedTools: [...COMPRESS_DEFAULT_PROTECTED_TOOLS],
|
|
1344
1400
|
protectTags: false,
|
|
1345
|
-
protectUserMessages: false
|
|
1401
|
+
protectUserMessages: false,
|
|
1402
|
+
externalModel: void 0
|
|
1346
1403
|
},
|
|
1347
1404
|
strategies: {
|
|
1348
1405
|
deduplication: {
|
|
@@ -1465,7 +1522,8 @@ function mergeCompress(base, override) {
|
|
|
1465
1522
|
nudgeForce: override.nudgeForce ?? base.nudgeForce,
|
|
1466
1523
|
protectedTools: [.../* @__PURE__ */ new Set([...base.protectedTools, ...override.protectedTools ?? []])],
|
|
1467
1524
|
protectTags: override.protectTags ?? base.protectTags,
|
|
1468
|
-
protectUserMessages: override.protectUserMessages ?? base.protectUserMessages
|
|
1525
|
+
protectUserMessages: override.protectUserMessages ?? base.protectUserMessages,
|
|
1526
|
+
externalModel: override.externalModel ?? base.externalModel
|
|
1469
1527
|
};
|
|
1470
1528
|
}
|
|
1471
1529
|
function mergeCommands(base, override) {
|
|
@@ -1509,7 +1567,8 @@ function deepCloneConfig(config) {
|
|
|
1509
1567
|
...config.compress,
|
|
1510
1568
|
modelMaxLimits: { ...config.compress.modelMaxLimits },
|
|
1511
1569
|
modelMinLimits: { ...config.compress.modelMinLimits },
|
|
1512
|
-
protectedTools: [...config.compress.protectedTools]
|
|
1570
|
+
protectedTools: [...config.compress.protectedTools],
|
|
1571
|
+
externalModel: config.compress.externalModel ? { ...config.compress.externalModel } : void 0
|
|
1513
1572
|
},
|
|
1514
1573
|
strategies: {
|
|
1515
1574
|
deduplication: {
|
|
@@ -1591,6 +1650,7 @@ Using previous/default values`
|
|
|
1591
1650
|
showConfigWarnings(ctx, layer.path, result.data, layer.isProject);
|
|
1592
1651
|
config = mergeLayer(config, result.data);
|
|
1593
1652
|
}
|
|
1653
|
+
applyExternalModelEnvOverride(config);
|
|
1594
1654
|
return config;
|
|
1595
1655
|
}
|
|
1596
1656
|
|
|
@@ -1793,7 +1853,12 @@ function countAllMessageTokens(msg) {
|
|
|
1793
1853
|
}
|
|
1794
1854
|
|
|
1795
1855
|
// lib/prompts/extensions/tool.ts
|
|
1796
|
-
|
|
1856
|
+
function buildRangeFormatExtension(externalModelEnabled) {
|
|
1857
|
+
const summaryField = externalModelEnabled ? `summary?: string // \u66FF\u6362\u8303\u56F4\u5185\u6240\u6709\u5185\u5BB9\u7684\u5B8C\u6574\u6280\u672F\u6458\u8981\uFF08\u53EF\u7701\u7565\uFF1B\u5916\u90E8\u6A21\u578B\u751F\u6210\uFF09` : `summary: string // \u66FF\u6362\u8303\u56F4\u5185\u6240\u6709\u5185\u5BB9\u7684\u5B8C\u6574\u6280\u672F\u6458\u8981`;
|
|
1858
|
+
const externalHint = externalModelEnabled ? `
|
|
1859
|
+
|
|
1860
|
+
\u5F53\u542F\u7528\u5916\u90E8\u6A21\u578B\u538B\u7F29\u65F6\uFF0Csummary \u5B57\u6BB5\u53EF\u7701\u7565\uFF1B\u63D2\u4EF6\u5C06\u81EA\u52A8\u8C03\u7528\u5916\u90E8\u6A21\u578B\u751F\u6210\u3002` : "";
|
|
1861
|
+
return `
|
|
1797
1862
|
\u538B\u7F29\u683C\u5F0F
|
|
1798
1863
|
|
|
1799
1864
|
\`\`\`
|
|
@@ -1803,12 +1868,20 @@ var RANGE_FORMAT_EXTENSION = `
|
|
|
1803
1868
|
{
|
|
1804
1869
|
startId: string, // \u8303\u56F4\u5F00\u59CB\u7684\u8FB9\u754C ID\uFF1AmNNNN \u6216 bN
|
|
1805
1870
|
endId: string, // \u8303\u56F4\u7ED3\u675F\u7684\u8FB9\u754C ID\uFF1AmNNNN \u6216 bN
|
|
1806
|
-
|
|
1871
|
+
${summaryField}
|
|
1807
1872
|
}
|
|
1808
1873
|
]
|
|
1809
1874
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1875
|
+
\`\`\`
|
|
1876
|
+
|
|
1877
|
+
\u8FB9\u754C ID \u5FC5\u987B\u4ECE\u5F53\u524D\u53EF\u89C1\u4E0A\u4E0B\u6587\u4E2D\u5DF2\u6709\u7684\u6CE8\u5165 ID \u7CBE\u786E\u9009\u53D6\uFF1B\u4E0D\u8981\u53D1\u660E ID\uFF0C\u4E5F\u4E0D\u8981\u6839\u636E\u987A\u5E8F\u5916\u63A8\u4E0D\u5B58\u5728\u7684 ID\u3002${externalHint}`;
|
|
1878
|
+
}
|
|
1879
|
+
function buildMessageFormatExtension(externalModelEnabled) {
|
|
1880
|
+
const summaryField = externalModelEnabled ? `summary?: string // \u66FF\u6362\u8BE5\u6761\u6D88\u606F\u7684\u5B8C\u6574\u6280\u672F\u6458\u8981\uFF08\u53EF\u7701\u7565\uFF1B\u5916\u90E8\u6A21\u578B\u751F\u6210\uFF09` : `summary: string // \u66FF\u6362\u8BE5\u6761\u6D88\u606F\u7684\u5B8C\u6574\u6280\u672F\u6458\u8981`;
|
|
1881
|
+
const externalHint = externalModelEnabled ? `
|
|
1882
|
+
|
|
1883
|
+
\u5F53\u542F\u7528\u5916\u90E8\u6A21\u578B\u538B\u7F29\u65F6\uFF0Csummary \u5B57\u6BB5\u53EF\u7701\u7565\uFF1B\u63D2\u4EF6\u5C06\u81EA\u52A8\u8C03\u7528\u5916\u90E8\u6A21\u578B\u751F\u6210\u3002` : "";
|
|
1884
|
+
return `
|
|
1812
1885
|
\u538B\u7F29\u683C\u5F0F
|
|
1813
1886
|
|
|
1814
1887
|
\`\`\`
|
|
@@ -1818,11 +1891,14 @@ var MESSAGE_FORMAT_EXTENSION = `
|
|
|
1818
1891
|
{
|
|
1819
1892
|
messageId: string, // \u4EC5\u539F\u59CB\u6D88\u606F ID\uFF1AmNNNN\uFF08\u5FFD\u7565 priority \u7B49\u5143\u6570\u636E\u5C5E\u6027\uFF09
|
|
1820
1893
|
topic: string, // \u6B64\u5355\u6761\u6D88\u606F\u6458\u8981\u7684\u77ED\u6807\u7B7E\uFF083-5 \u4E2A\u8BCD\uFF09
|
|
1821
|
-
|
|
1894
|
+
${summaryField}
|
|
1822
1895
|
}
|
|
1823
1896
|
]
|
|
1824
1897
|
}
|
|
1825
|
-
|
|
1898
|
+
\`\`\`${externalHint}`;
|
|
1899
|
+
}
|
|
1900
|
+
var RANGE_FORMAT_EXTENSION = buildRangeFormatExtension(false);
|
|
1901
|
+
var MESSAGE_FORMAT_EXTENSION = buildMessageFormatExtension(false);
|
|
1826
1902
|
|
|
1827
1903
|
// lib/message-ids.ts
|
|
1828
1904
|
var MESSAGE_REF_REGEX = /^m(\d{4})$/;
|
|
@@ -1985,11 +2061,17 @@ function resolveBoundaryIds(context, state, startId, endId) {
|
|
|
1985
2061
|
const issues = [];
|
|
1986
2062
|
const parsedStartId = parseBoundaryId(startId);
|
|
1987
2063
|
const parsedEndId = parseBoundaryId(endId);
|
|
2064
|
+
const hint = formatAvailableBoundaryHint(lookup);
|
|
2065
|
+
const guidance = formatBoundaryUsageGuidance();
|
|
1988
2066
|
if (parsedStartId === null) {
|
|
1989
|
-
issues.push(
|
|
2067
|
+
issues.push(
|
|
2068
|
+
`startId is invalid. Use an injected message ID (mNNNN) or block ID (bN).${hint}${guidance}`
|
|
2069
|
+
);
|
|
1990
2070
|
}
|
|
1991
2071
|
if (parsedEndId === null) {
|
|
1992
|
-
issues.push(
|
|
2072
|
+
issues.push(
|
|
2073
|
+
`endId is invalid. Use an injected message ID (mNNNN) or block ID (bN).${hint}${guidance}`
|
|
2074
|
+
);
|
|
1993
2075
|
}
|
|
1994
2076
|
if (issues.length > 0) {
|
|
1995
2077
|
throw new Error(
|
|
@@ -2002,28 +2084,18 @@ function resolveBoundaryIds(context, state, startId, endId) {
|
|
|
2002
2084
|
const startReference = lookup.get(parsedStartId.ref);
|
|
2003
2085
|
const endReference = lookup.get(parsedEndId.ref);
|
|
2004
2086
|
if (!startReference || !endReference) {
|
|
2005
|
-
const allKeys = Array.from(lookup.keys());
|
|
2006
|
-
const availableBlockRefs = allKeys.filter((key) => key.startsWith("b")).sort(
|
|
2007
|
-
(a, b) => Number.parseInt(a.slice(1), 10) - Number.parseInt(b.slice(1), 10)
|
|
2008
|
-
);
|
|
2009
|
-
const availableMsgRefs = allKeys.filter((key) => key.startsWith("m")).sort(
|
|
2010
|
-
(a, b) => Number.parseInt(a.slice(1), 10) - Number.parseInt(b.slice(1), 10)
|
|
2011
|
-
);
|
|
2012
|
-
const blockHint = availableBlockRefs.length ? ` Available block IDs: ${availableBlockRefs.join(", ")}.` : " No block IDs available.";
|
|
2013
|
-
const msgHint = availableMsgRefs.length === 0 ? " No message IDs available." : availableMsgRefs.length <= 10 ? ` Available message IDs: ${availableMsgRefs.join(", ")}.` : ` Available message IDs: ${availableMsgRefs[0]} to ${availableMsgRefs[availableMsgRefs.length - 1]} (${availableMsgRefs.length} total).`;
|
|
2014
|
-
const hint = `${msgHint}${blockHint}`;
|
|
2015
2087
|
if (!startReference) {
|
|
2016
2088
|
const wasKnown = state.messageIds.byRef.has(parsedStartId.ref);
|
|
2017
2089
|
const reason = wasKnown ? " (message was likely compacted out of the conversation)" : " (invalid ID)";
|
|
2018
2090
|
issues.push(
|
|
2019
|
-
`startId ${parsedStartId.ref} is not available${reason}. Choose an injected ID visible in context.${hint}`
|
|
2091
|
+
`startId ${parsedStartId.ref} is not available${reason}. Choose an injected ID visible in context.${hint}${guidance}`
|
|
2020
2092
|
);
|
|
2021
2093
|
}
|
|
2022
2094
|
if (!endReference) {
|
|
2023
2095
|
const wasKnown = state.messageIds.byRef.has(parsedEndId.ref);
|
|
2024
2096
|
const reason = wasKnown ? " (message was likely compacted out of the conversation)" : " (invalid ID)";
|
|
2025
2097
|
issues.push(
|
|
2026
|
-
`endId ${parsedEndId.ref} is not available${reason}. Choose an injected ID visible in context.${hint}`
|
|
2098
|
+
`endId ${parsedEndId.ref} is not available${reason}. Choose an injected ID visible in context.${hint}${guidance}`
|
|
2027
2099
|
);
|
|
2028
2100
|
}
|
|
2029
2101
|
}
|
|
@@ -2129,6 +2201,17 @@ function resolveAnchorMessageId(startReference) {
|
|
|
2129
2201
|
}
|
|
2130
2202
|
return startReference.messageId;
|
|
2131
2203
|
}
|
|
2204
|
+
function formatAvailableBoundaryHint(lookup) {
|
|
2205
|
+
const allKeys = Array.from(lookup.keys());
|
|
2206
|
+
const availableBlockRefs = allKeys.filter((key) => key.startsWith("b")).sort((a, b) => Number.parseInt(a.slice(1), 10) - Number.parseInt(b.slice(1), 10));
|
|
2207
|
+
const availableMsgRefs = allKeys.filter((key) => key.startsWith("m")).sort((a, b) => Number.parseInt(a.slice(1), 10) - Number.parseInt(b.slice(1), 10));
|
|
2208
|
+
const blockHint = availableBlockRefs.length ? ` Available block IDs: ${availableBlockRefs.join(", ")}.` : " No block IDs available.";
|
|
2209
|
+
const msgHint = availableMsgRefs.length === 0 ? " No message IDs available." : availableMsgRefs.length <= 10 ? ` Available message IDs: ${availableMsgRefs.join(", ")}.` : ` Available message IDs: ${availableMsgRefs[0]} to ${availableMsgRefs[availableMsgRefs.length - 1]} (${availableMsgRefs.length} total).`;
|
|
2210
|
+
return `${msgHint}${blockHint}`;
|
|
2211
|
+
}
|
|
2212
|
+
function formatBoundaryUsageGuidance() {
|
|
2213
|
+
return " Use exactly one of the listed injected IDs. For already compressed content, use its bN block ID.";
|
|
2214
|
+
}
|
|
2132
2215
|
function buildBoundaryLookup(context, state) {
|
|
2133
2216
|
const lookup = /* @__PURE__ */ new Map();
|
|
2134
2217
|
for (const [messageRef, messageId] of state.messageIds.byRef) {
|
|
@@ -2446,8 +2529,10 @@ function validateArgs(args) {
|
|
|
2446
2529
|
if (typeof entry?.topic !== "string" || entry.topic.trim().length === 0) {
|
|
2447
2530
|
throw new Error(`${prefix}.topic is required and must be a non-empty string`);
|
|
2448
2531
|
}
|
|
2449
|
-
if (
|
|
2450
|
-
|
|
2532
|
+
if (entry?.summary !== void 0) {
|
|
2533
|
+
if (typeof entry.summary !== "string" || entry.summary.trim().length === 0) {
|
|
2534
|
+
throw new Error(`${prefix}.summary must be a non-empty string when provided`);
|
|
2535
|
+
}
|
|
2451
2536
|
}
|
|
2452
2537
|
}
|
|
2453
2538
|
}
|
|
@@ -4165,8 +4250,118 @@ ${output}`);
|
|
|
4165
4250
|
return summary + heading + protectedOutputs.join("");
|
|
4166
4251
|
}
|
|
4167
4252
|
|
|
4253
|
+
// lib/compress/external-inference.ts
|
|
4254
|
+
var ExternalModelError = class extends Error {
|
|
4255
|
+
constructor(message, kind, cause) {
|
|
4256
|
+
super(message);
|
|
4257
|
+
this.kind = kind;
|
|
4258
|
+
this.cause = cause;
|
|
4259
|
+
this.name = "ExternalModelError";
|
|
4260
|
+
}
|
|
4261
|
+
};
|
|
4262
|
+
function resolveCompressBaseUrl(url) {
|
|
4263
|
+
const normalized = url.replace(/\/$/, "");
|
|
4264
|
+
if (normalized.endsWith("/chat/completions")) {
|
|
4265
|
+
return normalized;
|
|
4266
|
+
}
|
|
4267
|
+
return `${normalized}/chat/completions`;
|
|
4268
|
+
}
|
|
4269
|
+
async function callExternalModel(endpoint, cfg, req) {
|
|
4270
|
+
const controller = new AbortController();
|
|
4271
|
+
const timeoutMs = cfg.timeout ?? 12e4;
|
|
4272
|
+
const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
|
|
4273
|
+
let response;
|
|
4274
|
+
try {
|
|
4275
|
+
response = await fetch(endpoint, {
|
|
4276
|
+
method: "POST",
|
|
4277
|
+
headers: {
|
|
4278
|
+
"Content-Type": "application/json",
|
|
4279
|
+
...cfg.apiKey ? { Authorization: `Bearer ${cfg.apiKey}` } : {}
|
|
4280
|
+
},
|
|
4281
|
+
body: JSON.stringify({
|
|
4282
|
+
model: cfg.model,
|
|
4283
|
+
messages: [
|
|
4284
|
+
{ role: "system", content: req.systemPrompt },
|
|
4285
|
+
{ role: "user", content: req.userContent }
|
|
4286
|
+
],
|
|
4287
|
+
temperature: 0
|
|
4288
|
+
}),
|
|
4289
|
+
signal: controller.signal
|
|
4290
|
+
});
|
|
4291
|
+
} catch (error) {
|
|
4292
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
4293
|
+
throw new ExternalModelError(
|
|
4294
|
+
`External model request timed out after ${timeoutMs}ms`,
|
|
4295
|
+
"timeout",
|
|
4296
|
+
error
|
|
4297
|
+
);
|
|
4298
|
+
}
|
|
4299
|
+
throw new ExternalModelError(
|
|
4300
|
+
`External model request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
4301
|
+
"network",
|
|
4302
|
+
error
|
|
4303
|
+
);
|
|
4304
|
+
} finally {
|
|
4305
|
+
clearTimeout(timeoutHandle);
|
|
4306
|
+
}
|
|
4307
|
+
if (!response.ok) {
|
|
4308
|
+
let errorBody = "";
|
|
4309
|
+
try {
|
|
4310
|
+
errorBody = await response.text();
|
|
4311
|
+
} catch {
|
|
4312
|
+
}
|
|
4313
|
+
throw new ExternalModelError(
|
|
4314
|
+
`External model returned HTTP ${response.status}: ${errorBody.slice(0, 200)}`,
|
|
4315
|
+
"http",
|
|
4316
|
+
{ status: response.status, body: errorBody }
|
|
4317
|
+
);
|
|
4318
|
+
}
|
|
4319
|
+
let parsed;
|
|
4320
|
+
try {
|
|
4321
|
+
parsed = await response.json();
|
|
4322
|
+
} catch (error) {
|
|
4323
|
+
throw new ExternalModelError("External model returned invalid JSON", "parse", error);
|
|
4324
|
+
}
|
|
4325
|
+
const content = parsed?.choices?.[0]?.message?.content;
|
|
4326
|
+
if (typeof content !== "string" || content.trim().length === 0) {
|
|
4327
|
+
throw new ExternalModelError(
|
|
4328
|
+
"External model returned empty or missing summary content",
|
|
4329
|
+
"empty",
|
|
4330
|
+
parsed
|
|
4331
|
+
);
|
|
4332
|
+
}
|
|
4333
|
+
return content;
|
|
4334
|
+
}
|
|
4335
|
+
async function generateSummaryViaExternal(cfg, req) {
|
|
4336
|
+
const endpoint = resolveCompressBaseUrl(cfg.url);
|
|
4337
|
+
const maxRetries = cfg.retries ?? 1;
|
|
4338
|
+
let lastError;
|
|
4339
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
4340
|
+
try {
|
|
4341
|
+
return await callExternalModel(endpoint, cfg, req);
|
|
4342
|
+
} catch (error) {
|
|
4343
|
+
lastError = error;
|
|
4344
|
+
}
|
|
4345
|
+
}
|
|
4346
|
+
throw lastError;
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4168
4349
|
// lib/compress/message.ts
|
|
4169
|
-
function
|
|
4350
|
+
function extractMessageText(messageId, searchContext) {
|
|
4351
|
+
const msg = searchContext.rawMessagesById.get(messageId);
|
|
4352
|
+
if (!msg) return "";
|
|
4353
|
+
const parts = [];
|
|
4354
|
+
for (const part of msg.parts) {
|
|
4355
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
4356
|
+
parts.push(part.text);
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
return parts.join("\n\n");
|
|
4360
|
+
}
|
|
4361
|
+
function buildSchema(externalModelEnabled) {
|
|
4362
|
+
const summaryField = externalModelEnabled ? tool.schema.string().optional().describe(
|
|
4363
|
+
"Complete technical summary replacing that one message (optional when external model is configured)"
|
|
4364
|
+
) : tool.schema.string().describe("Complete technical summary replacing that one message");
|
|
4170
4365
|
return {
|
|
4171
4366
|
topic: tool.schema.string().describe(
|
|
4172
4367
|
"Short label (3-5 words) for the overall batch - e.g., 'Closed Research Notes'"
|
|
@@ -4175,7 +4370,7 @@ function buildSchema() {
|
|
|
4175
4370
|
tool.schema.object({
|
|
4176
4371
|
messageId: tool.schema.string().describe("Raw message ID to compress (e.g. m0001)"),
|
|
4177
4372
|
topic: tool.schema.string().describe("Short label (3-5 words) for this one message summary"),
|
|
4178
|
-
summary:
|
|
4373
|
+
summary: summaryField
|
|
4179
4374
|
})
|
|
4180
4375
|
).describe("Batch of individual message summaries to create in one tool call")
|
|
4181
4376
|
};
|
|
@@ -4183,9 +4378,10 @@ function buildSchema() {
|
|
|
4183
4378
|
function createCompressMessageTool(ctx) {
|
|
4184
4379
|
ctx.prompts.reload();
|
|
4185
4380
|
const runtimePrompts = ctx.prompts.getRuntimePrompts();
|
|
4381
|
+
const externalModelEnabled = ctx.config.compress.externalModel !== void 0;
|
|
4186
4382
|
return tool({
|
|
4187
|
-
description: runtimePrompts.compressMessage +
|
|
4188
|
-
args: buildSchema(),
|
|
4383
|
+
description: runtimePrompts.compressMessage + buildMessageFormatExtension(externalModelEnabled),
|
|
4384
|
+
args: buildSchema(externalModelEnabled),
|
|
4189
4385
|
async execute(args, toolCtx) {
|
|
4190
4386
|
const input = args;
|
|
4191
4387
|
validateArgs(input);
|
|
@@ -4204,9 +4400,29 @@ function createCompressMessageTool(ctx) {
|
|
|
4204
4400
|
if (plans.length === 0 && skippedCount > 0) {
|
|
4205
4401
|
throw new Error(formatIssues(skippedIssues, skippedCount));
|
|
4206
4402
|
}
|
|
4403
|
+
if (ctx.config.compress.externalModel) {
|
|
4404
|
+
for (const plan of plans) {
|
|
4405
|
+
if (plan.entry.summary === void 0) {
|
|
4406
|
+
const userContent = extractMessageText(plan.entry.messageId, searchContext);
|
|
4407
|
+
const generated = await generateSummaryViaExternal(
|
|
4408
|
+
ctx.config.compress.externalModel,
|
|
4409
|
+
{
|
|
4410
|
+
systemPrompt: runtimePrompts.compressMessage,
|
|
4411
|
+
userContent
|
|
4412
|
+
}
|
|
4413
|
+
);
|
|
4414
|
+
plan.entry.summary = generated;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4207
4418
|
const notifications = [];
|
|
4208
4419
|
const preparedPlans = [];
|
|
4209
4420
|
for (const plan of plans) {
|
|
4421
|
+
if (plan.entry.summary === void 0) {
|
|
4422
|
+
throw new Error(
|
|
4423
|
+
"\u7F3A\u5C11 summary\uFF1A\u672A\u914D\u7F6E\u5916\u90E8\u6A21\u578B\uFF0C\u8BF7\u63D0\u4F9B summary \u6216\u914D\u7F6E OPENCODE_DCP_EXTERNAL_COMPRESS_URL/MODEL"
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4210
4426
|
const summaryWithPromptInfo = appendProtectedPromptInfo(
|
|
4211
4427
|
plan.entry.summary,
|
|
4212
4428
|
plan.selection,
|
|
@@ -4287,8 +4503,10 @@ function validateArgs2(args) {
|
|
|
4287
4503
|
if (typeof entry?.endId !== "string" || entry.endId.trim().length === 0) {
|
|
4288
4504
|
throw new Error(`${prefix}.endId is required and must be a non-empty string`);
|
|
4289
4505
|
}
|
|
4290
|
-
if (
|
|
4291
|
-
|
|
4506
|
+
if (entry?.summary !== void 0) {
|
|
4507
|
+
if (typeof entry.summary !== "string" || entry.summary.trim().length === 0) {
|
|
4508
|
+
throw new Error(`${prefix}.summary must be a non-empty string when provided`);
|
|
4509
|
+
}
|
|
4292
4510
|
}
|
|
4293
4511
|
}
|
|
4294
4512
|
}
|
|
@@ -4494,16 +4712,34 @@ ${right}`;
|
|
|
4494
4712
|
}
|
|
4495
4713
|
|
|
4496
4714
|
// lib/compress/range.ts
|
|
4497
|
-
function
|
|
4715
|
+
function extractRangeText(selection, searchContext) {
|
|
4716
|
+
const parts = [];
|
|
4717
|
+
for (const messageId of selection.messageIds) {
|
|
4718
|
+
const msg = searchContext.rawMessagesById.get(messageId);
|
|
4719
|
+
if (!msg) continue;
|
|
4720
|
+
for (const part of msg.parts) {
|
|
4721
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
4722
|
+
parts.push(part.text);
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
return parts.join("\n\n");
|
|
4727
|
+
}
|
|
4728
|
+
function buildSchema2(externalModelEnabled) {
|
|
4729
|
+
const summaryField = externalModelEnabled ? tool2.schema.string().optional().describe(
|
|
4730
|
+
"Complete technical summary replacing all content in range (optional when external model is configured)"
|
|
4731
|
+
) : tool2.schema.string().describe("Complete technical summary replacing all content in range");
|
|
4498
4732
|
return {
|
|
4499
4733
|
topic: tool2.schema.string().describe("Short label (3-5 words) for display - e.g., 'Auth System Exploration'"),
|
|
4500
4734
|
content: tool2.schema.array(
|
|
4501
4735
|
tool2.schema.object({
|
|
4502
4736
|
startId: tool2.schema.string().describe(
|
|
4503
|
-
"
|
|
4737
|
+
"Injected message or block ID visible in the current context marking the beginning of the range. Do not invent IDs; use an exact visible mNNNN or bN boundary."
|
|
4738
|
+
),
|
|
4739
|
+
endId: tool2.schema.string().describe(
|
|
4740
|
+
"Injected message or block ID visible in the current context marking the end of the range. Do not invent IDs; use an exact visible mNNNN or bN boundary."
|
|
4504
4741
|
),
|
|
4505
|
-
|
|
4506
|
-
summary: tool2.schema.string().describe("Complete technical summary replacing all content in range")
|
|
4742
|
+
summary: summaryField
|
|
4507
4743
|
})
|
|
4508
4744
|
).describe(
|
|
4509
4745
|
"One or more ranges to compress, each with start/end boundaries and a summary"
|
|
@@ -4513,9 +4749,10 @@ function buildSchema2() {
|
|
|
4513
4749
|
function createCompressRangeTool(ctx) {
|
|
4514
4750
|
ctx.prompts.reload();
|
|
4515
4751
|
const runtimePrompts = ctx.prompts.getRuntimePrompts();
|
|
4752
|
+
const externalModelEnabled = ctx.config.compress.externalModel !== void 0;
|
|
4516
4753
|
return tool2({
|
|
4517
|
-
description: runtimePrompts.compressRange +
|
|
4518
|
-
args: buildSchema2(),
|
|
4754
|
+
description: runtimePrompts.compressRange + buildRangeFormatExtension(externalModelEnabled),
|
|
4755
|
+
args: buildSchema2(externalModelEnabled),
|
|
4519
4756
|
async execute(args, toolCtx) {
|
|
4520
4757
|
const input = args;
|
|
4521
4758
|
validateArgs2(input);
|
|
@@ -4527,10 +4764,30 @@ function createCompressRangeTool(ctx) {
|
|
|
4527
4764
|
);
|
|
4528
4765
|
const resolvedPlans = resolveRanges(input, searchContext, ctx.state);
|
|
4529
4766
|
validateNonOverlapping(resolvedPlans);
|
|
4767
|
+
if (ctx.config.compress.externalModel) {
|
|
4768
|
+
for (const plan of resolvedPlans) {
|
|
4769
|
+
if (plan.entry.summary === void 0) {
|
|
4770
|
+
const userContent = extractRangeText(plan.selection, searchContext);
|
|
4771
|
+
const generated = await generateSummaryViaExternal(
|
|
4772
|
+
ctx.config.compress.externalModel,
|
|
4773
|
+
{
|
|
4774
|
+
systemPrompt: runtimePrompts.compressRange,
|
|
4775
|
+
userContent
|
|
4776
|
+
}
|
|
4777
|
+
);
|
|
4778
|
+
plan.entry.summary = generated;
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4530
4782
|
const notifications = [];
|
|
4531
4783
|
const preparedPlans = [];
|
|
4532
4784
|
let totalCompressedMessages = 0;
|
|
4533
4785
|
for (const plan of resolvedPlans) {
|
|
4786
|
+
if (plan.entry.summary === void 0) {
|
|
4787
|
+
throw new Error(
|
|
4788
|
+
"\u7F3A\u5C11 summary\uFF1A\u672A\u914D\u7F6E\u5916\u90E8\u6A21\u578B\uFF0C\u8BF7\u63D0\u4F9B summary \u6216\u914D\u7F6E OPENCODE_DCP_EXTERNAL_COMPRESS_URL/MODEL"
|
|
4789
|
+
);
|
|
4790
|
+
}
|
|
4534
4791
|
const parsedPlaceholders = parseBlockPlaceholders(plan.entry.summary);
|
|
4535
4792
|
const missingBlockIds = validateSummaryPlaceholders(
|
|
4536
4793
|
parsedPlaceholders,
|
|
@@ -4882,44 +5139,51 @@ import { homedir as homedir4 } from "os";
|
|
|
4882
5139
|
|
|
4883
5140
|
// lib/prompts/system.ts
|
|
4884
5141
|
var SYSTEM = `
|
|
4885
|
-
|
|
5142
|
+
You run in a context-limited environment. Manage context actively to avoid accumulation and keep retrieval quality high.
|
|
5143
|
+
|
|
5144
|
+
Your only context-management tool is \`compress\`. It replaces older conversation content with a technical summary you generate.
|
|
5145
|
+
|
|
5146
|
+
Hard constraints
|
|
5147
|
+
- Context management is done ONLY by calling \`compress\`. Never produce summaries in plain text.
|
|
5148
|
+
- NEVER output \`<summary>\` or \`<analysis>\` XML tags in text responses \u2014 they cause system errors.
|
|
5149
|
+
- If context needs compression, call \`compress\`. Do not write summaries inline.
|
|
5150
|
+
- Use Markdown headings (e.g. \`## Analysis\`, \`## Summary\`) to organize information. Never use XML tags.
|
|
4886
5151
|
|
|
4887
|
-
|
|
5152
|
+
\`\` and \`\` are environment-injected metadata. Do not output them.
|
|
4888
5153
|
|
|
4889
|
-
|
|
4890
|
-
- \u4E0A\u4E0B\u6587\u7BA1\u7406\u4EC5\u901A\u8FC7\u8C03\u7528 \`compress\` \u5DE5\u5177\u5B8C\u6210\u3002\u7EDD\u4E0D\u4EE5\u7EAF\u6587\u672C\u5F62\u5F0F\u751F\u6210\u6458\u8981\u3002
|
|
4891
|
-
- **\u4E25\u683C\u7981\u6B62**\u5728\u6587\u672C\u54CD\u5E94\u4E2D\u8F93\u51FA \`<summary>\` \u6216 \`<analysis>\` XML \u6807\u7B7E\u3002\u8FD9\u4E9B\u6807\u7B7E\u4F1A\u5BFC\u81F4\u7CFB\u7EDF\u9519\u8BEF\u3002
|
|
4892
|
-
- \u5982\u679C\u4F60\u8BA4\u4E3A\u4E0A\u4E0B\u6587\u9700\u8981\u538B\u7F29\uFF0C\u8C03\u7528 \`compress\` \u5DE5\u5177\u3002\u4E0D\u8981\u5185\u8054\u7F16\u5199\u6458\u8981\u3002
|
|
4893
|
-
- \u6240\u6709\u8F93\u51FA\u5FC5\u987B\u4F7F\u7528**\u4E2D\u6587**\u3002
|
|
4894
|
-
- \u5982\u679C\u9700\u8981\u5728\u6587\u672C\u4E2D\u7EC4\u7EC7\u4FE1\u606F\uFF0C\u4F7F\u7528 Markdown \u683C\u5F0F\uFF08\u5982 \`## \u5206\u6790\`\u3001\`## \u603B\u7ED3\`\uFF09\uFF0C\u7EDD\u4E0D\u4F7F\u7528 XML \u6807\u7B7E\u3002
|
|
5154
|
+
When to compress
|
|
4895
5155
|
|
|
4896
|
-
|
|
5156
|
+
HIGHEST-PRIORITY HARD TRIGGER \u2014 call \`compress\` immediately whenever the conversation TOPIC changes. Compress the previous topic's finished context into a summary.
|
|
4897
5157
|
|
|
4898
|
-
|
|
4899
|
-
\`compress\` \u5C06\u5BF9\u8BDD\u5185\u5BB9\u8F6C\u6362\u4E3A\u5BC6\u96C6\u3001\u9AD8\u4FDD\u771F\u7684\u6458\u8981\u3002\u8FD9\u4E0D\u662F\u6E05\u7406\u2014\u2014\u800C\u662F\u7ED3\u6676\u3002\u4F60\u7684\u6458\u8981\u6210\u4E3A\u6240\u53D1\u751F\u4E8B\u60C5\u7684\u6743\u5A01\u8BB0\u5F55\u3002
|
|
5158
|
+
A topic change is an objective, low-judgment signal, not a subjective call. Any of these counts:
|
|
4900
5159
|
|
|
4901
|
-
|
|
5160
|
+
- The user raises a task or question unrelated to the previous one
|
|
5161
|
+
- The focus shifts from one module/file/feature to another
|
|
5162
|
+
- The goal shifts from explore/research \u2192 implement, or implement \u2192 debug/verify
|
|
5163
|
+
- A distinct problem is resolved and you move to the next
|
|
4902
5164
|
|
|
4903
|
-
|
|
5165
|
+
When any occurs, compress the finished range of the previous topic at once. Do not wait. This is the single most effective way to keep context clear.
|
|
4904
5166
|
|
|
4905
|
-
|
|
5167
|
+
Also compress (when a section genuinely closes even without a topic change):
|
|
4906
5168
|
|
|
4907
|
-
-
|
|
4908
|
-
-
|
|
4909
|
-
-
|
|
4910
|
-
-
|
|
5169
|
+
- Research is done and findings are settled
|
|
5170
|
+
- Implementation is complete and verified
|
|
5171
|
+
- Exploration is exhausted and patterns understood
|
|
5172
|
+
- Dead-end noise can be dropped without waiting for the whole section to close
|
|
4911
5173
|
|
|
4912
|
-
|
|
5174
|
+
Do NOT compress when:
|
|
4913
5175
|
|
|
4914
|
-
-
|
|
4915
|
-
-
|
|
4916
|
-
-
|
|
5176
|
+
- Raw context is still needed for editing or exact reference
|
|
5177
|
+
- The target work is actively in progress
|
|
5178
|
+
- You may need the exact code, error message, or file content in the next steps
|
|
4917
5179
|
|
|
4918
|
-
|
|
5180
|
+
Ask yourself before compressing: _"Is this section closed enough to be summary-only?"_
|
|
4919
5181
|
|
|
4920
|
-
\
|
|
5182
|
+
If the answer is "the topic switched, the old content is no longer needed" \u2014 the answer is always yes.
|
|
4921
5183
|
|
|
4922
|
-
|
|
5184
|
+
Periodically assess the conversation's signal-to-noise ratio. Use \`compress\` deliberately and provide high-quality summaries. Prioritize stale content to maintain a high-signal context window.
|
|
5185
|
+
|
|
5186
|
+
Keeping a sharp, high-quality context window for best performance is your responsibility.
|
|
4923
5187
|
`;
|
|
4924
5188
|
|
|
4925
5189
|
// lib/prompts/compress-range.ts
|
|
@@ -5056,32 +5320,44 @@ var COMPRESS_MESSAGE = `\u5C06\u5BF9\u8BDD\u4E2D\u9009\u5B9A\u7684\u5355\u6761\u
|
|
|
5056
5320
|
|
|
5057
5321
|
// lib/prompts/context-limit-nudge.ts
|
|
5058
5322
|
var CONTEXT_LIMIT_NUDGE = `<dcp-system-reminder>
|
|
5059
|
-
|
|
5323
|
+
CRITICAL: max context limit reached.
|
|
5060
5324
|
|
|
5061
|
-
|
|
5325
|
+
You are at or beyond the configured max context threshold. This is an emergency context-recovery moment.
|
|
5062
5326
|
|
|
5063
|
-
|
|
5327
|
+
You MUST call \`compress\` now. Do not continue normal exploration until compression is done.
|
|
5064
5328
|
|
|
5065
|
-
|
|
5329
|
+
If mid-atomic-step, finish that one step, then compress immediately.
|
|
5066
5330
|
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5331
|
+
Selection
|
|
5332
|
+
Start from older, already-finished history and capture as much stale context as possible in one pass.
|
|
5333
|
+
Do not select the latest working messages unless they are clearly concluded.
|
|
5070
5334
|
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5335
|
+
Summary requirements
|
|
5336
|
+
Your summary must cover all key details in the selected messages so work can continue.
|
|
5337
|
+
If the range contains user messages, preserve their intent fully. For short user messages, prefer direct quotes to avoid semantic drift.
|
|
5074
5338
|
</dcp-system-reminder>
|
|
5075
5339
|
`;
|
|
5076
5340
|
|
|
5077
5341
|
// lib/prompts/turn-nudge.ts
|
|
5078
5342
|
var TURN_NUDGE = `
|
|
5079
|
-
|
|
5343
|
+
New user turn. This is the moment topic changes are most likely.
|
|
5344
|
+
|
|
5345
|
+
Is this turn's request a different topic/task from the previous one?
|
|
5346
|
+
- New or independent topic: call \`compress\` now to fold the previous topic's finished context into a summary. Its details no longer need verbatim retention.
|
|
5347
|
+
- Continuation of the same topic: you may keep it, but still watch for stale accumulation.
|
|
5348
|
+
|
|
5349
|
+
Topic change is the clearest, most objective compression trigger \u2014 do not miss it.
|
|
5080
5350
|
`;
|
|
5081
5351
|
|
|
5082
5352
|
// lib/prompts/iteration-nudge.ts
|
|
5083
5353
|
var ITERATION_NUDGE = `
|
|
5084
|
-
|
|
5354
|
+
Many iterations elapsed without new user input.
|
|
5355
|
+
|
|
5356
|
+
Has the current iteration's task effectively finished, or has its topic drifted away from earlier steps?
|
|
5357
|
+
- If earlier steps belong to a resolved problem or a superseded exploration, call \`compress\` now to fold them into a summary.
|
|
5358
|
+
- If the iteration still continues the same unfinished goal and early context is still needed, keep it.
|
|
5359
|
+
|
|
5360
|
+
Do not let finished exploration directions keep occupying context. Topic drift is the best moment to compress.
|
|
5085
5361
|
`;
|
|
5086
5362
|
|
|
5087
5363
|
// lib/prompts/extensions/system.ts
|