opencode-acp 1.12.10 → 1.13.2
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/NOTICE +29 -0
- package/README.md +123 -37
- package/README.zh-CN.md +100 -20
- package/dist/index.js +917 -54
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/pipeline.d.ts.map +1 -1
- package/dist/lib/compress/quality-gate/algorithms/index.d.ts +3 -0
- package/dist/lib/compress/quality-gate/algorithms/index.d.ts.map +1 -0
- package/dist/lib/compress/quality-gate/evaluate.d.ts +8 -0
- package/dist/lib/compress/quality-gate/evaluate.d.ts.map +1 -0
- package/dist/lib/compress/quality-gate/index.d.ts +5 -0
- package/dist/lib/compress/quality-gate/index.d.ts.map +1 -0
- package/dist/lib/compress/quality-gate/registry.d.ts +6 -0
- package/dist/lib/compress/quality-gate/registry.d.ts.map +1 -0
- package/dist/lib/compress/quality-gate/types.d.ts +51 -0
- package/dist/lib/compress/quality-gate/types.d.ts.map +1 -0
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/messages/inject/policy/index.d.ts +5 -0
- package/dist/lib/messages/inject/policy/index.d.ts.map +1 -0
- package/dist/lib/messages/inject/policy/registry.d.ts +8 -0
- package/dist/lib/messages/inject/policy/registry.d.ts.map +1 -0
- package/dist/lib/messages/inject/policy/types.d.ts +2 -0
- package/dist/lib/messages/inject/policy/types.d.ts.map +1 -0
- package/dist/lib/messages/inject/utils.d.ts +0 -9
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/messages/utils.d.ts.map +1 -1
- package/dist/lib/ui/notification.d.ts.map +1 -1
- package/package.json +5 -2
- package/dist/lib/prompts/compression-rules.d.ts +0 -20
- package/dist/lib/prompts/compression-rules.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -926,7 +926,11 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
926
926
|
"strategies.purgeErrors",
|
|
927
927
|
"strategies.purgeErrors.enabled",
|
|
928
928
|
"strategies.purgeErrors.turns",
|
|
929
|
-
"strategies.purgeErrors.protectedTools"
|
|
929
|
+
"strategies.purgeErrors.protectedTools",
|
|
930
|
+
"qualityGate",
|
|
931
|
+
"qualityGate.enabled",
|
|
932
|
+
"qualityGate.algorithm",
|
|
933
|
+
"qualityGate.algorithms"
|
|
930
934
|
]);
|
|
931
935
|
function getConfigKeyPaths(obj, prefix = "") {
|
|
932
936
|
const keys = [];
|
|
@@ -1553,8 +1557,11 @@ var defaultConfig = {
|
|
|
1553
1557
|
enabled: true,
|
|
1554
1558
|
autoUpdate: true,
|
|
1555
1559
|
debug: false,
|
|
1556
|
-
pruneNotification: "
|
|
1557
|
-
|
|
1560
|
+
pruneNotification: "off",
|
|
1561
|
+
// [FIX #20] Default to toast — chat-mode notifications inject an empty
|
|
1562
|
+
// user message that freezes the session on providers that reject empty
|
|
1563
|
+
// messages (zhipuai-lb code 1214). See lib/ui/notification.ts.
|
|
1564
|
+
pruneNotificationType: "toast",
|
|
1558
1565
|
commands: {
|
|
1559
1566
|
enabled: true,
|
|
1560
1567
|
protectedTools: [...DEFAULT_PROTECTED_TOOLS]
|
|
@@ -1586,7 +1593,7 @@ var defaultConfig = {
|
|
|
1586
1593
|
protectedTools: [...COMPRESS_DEFAULT_PROTECTED_TOOLS],
|
|
1587
1594
|
protectTags: false,
|
|
1588
1595
|
protectUserMessages: false,
|
|
1589
|
-
maxSummaryLengthHard:
|
|
1596
|
+
maxSummaryLengthHard: 2e4,
|
|
1590
1597
|
minCompressRange: 5e3,
|
|
1591
1598
|
minNudgeGrowthRatio: 0.45,
|
|
1592
1599
|
minNudgeGrowthFloor: 5e3,
|
|
@@ -1618,6 +1625,18 @@ var defaultConfig = {
|
|
|
1618
1625
|
highThreshold: "75%",
|
|
1619
1626
|
forceThreshold: "90%"
|
|
1620
1627
|
}
|
|
1628
|
+
},
|
|
1629
|
+
qualityGate: {
|
|
1630
|
+
enabled: false,
|
|
1631
|
+
algorithm: "rouge-recall-v1",
|
|
1632
|
+
algorithms: {
|
|
1633
|
+
"rouge-recall-v1": {
|
|
1634
|
+
layer1MinChars: 200,
|
|
1635
|
+
layer1MinRetentionPct: 1,
|
|
1636
|
+
layer2MaxRougeF1: 0.05,
|
|
1637
|
+
layer2MaxTop20Recall: 0.2
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1621
1640
|
}
|
|
1622
1641
|
};
|
|
1623
1642
|
var GLOBAL_CONFIG_DIR = process.env.XDG_CONFIG_HOME ? join(process.env.XDG_CONFIG_HOME, "opencode") : join(homedir(), ".config", "opencode");
|
|
@@ -1815,6 +1834,11 @@ function deepCloneConfig(config) {
|
|
|
1815
1834
|
gc: {
|
|
1816
1835
|
...config.gc,
|
|
1817
1836
|
batchCleanup: { ...config.gc.batchCleanup }
|
|
1837
|
+
},
|
|
1838
|
+
qualityGate: {
|
|
1839
|
+
enabled: config.qualityGate.enabled,
|
|
1840
|
+
algorithm: config.qualityGate.algorithm,
|
|
1841
|
+
algorithms: { ...config.qualityGate.algorithms }
|
|
1818
1842
|
}
|
|
1819
1843
|
};
|
|
1820
1844
|
}
|
|
@@ -1828,6 +1852,14 @@ function mergeGC(base, override) {
|
|
|
1828
1852
|
batchCleanup: { ...base.batchCleanup, ...override.batchCleanup ?? {} }
|
|
1829
1853
|
};
|
|
1830
1854
|
}
|
|
1855
|
+
function mergeQualityGate(base, override) {
|
|
1856
|
+
if (!override) return base;
|
|
1857
|
+
return {
|
|
1858
|
+
enabled: override.enabled ?? base.enabled,
|
|
1859
|
+
algorithm: override.algorithm ?? base.algorithm,
|
|
1860
|
+
algorithms: { ...base.algorithms, ...override.algorithms ?? {} }
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1831
1863
|
function mergeLayer(config, data) {
|
|
1832
1864
|
return {
|
|
1833
1865
|
enabled: data.enabled ?? config.enabled,
|
|
@@ -1847,7 +1879,8 @@ function mergeLayer(config, data) {
|
|
|
1847
1879
|
],
|
|
1848
1880
|
compress: mergeCompress(config.compress, data.compress),
|
|
1849
1881
|
gc: mergeGC(config.gc, data.gc),
|
|
1850
|
-
strategies: mergeStrategies(config.strategies, data.strategies)
|
|
1882
|
+
strategies: mergeStrategies(config.strategies, data.strategies),
|
|
1883
|
+
qualityGate: mergeQualityGate(config.qualityGate, data.qualityGate)
|
|
1851
1884
|
};
|
|
1852
1885
|
}
|
|
1853
1886
|
function scheduleParseWarning(ctx, title, message) {
|
|
@@ -3287,6 +3320,8 @@ var SoftIssue = class extends Error {
|
|
|
3287
3320
|
this.kind = kind;
|
|
3288
3321
|
this.messageId = messageId;
|
|
3289
3322
|
}
|
|
3323
|
+
kind;
|
|
3324
|
+
messageId;
|
|
3290
3325
|
};
|
|
3291
3326
|
function validateArgs(args) {
|
|
3292
3327
|
if (typeof args.topic !== "string" || args.topic.trim().length === 0) {
|
|
@@ -5043,10 +5078,25 @@ function formatContextTransition(tokensBefore, tokensAfter) {
|
|
|
5043
5078
|
return `Context ${beforeStr} \u2192 ${afterStr}`;
|
|
5044
5079
|
}
|
|
5045
5080
|
async function sendCompressNotification(client, logger, config, state, sessionId, entries, batchTopic, sessionMessageIds, params, contextTokensBefore) {
|
|
5046
|
-
if (
|
|
5081
|
+
if (entries.length === 0) {
|
|
5047
5082
|
return false;
|
|
5048
5083
|
}
|
|
5049
|
-
|
|
5084
|
+
const logBlockIds = entries.map((e) => e.blockId);
|
|
5085
|
+
const logTopics = entries.map((e) => state.prune.messages.blocksById.get(e.blockId)?.topic ?? "?");
|
|
5086
|
+
const logCompressedTokens = entries.reduce((sum, e) => {
|
|
5087
|
+
const block = state.prune.messages.blocksById.get(e.blockId);
|
|
5088
|
+
return sum + (block?.compressedTokens ?? 0);
|
|
5089
|
+
}, 0);
|
|
5090
|
+
const logSummaryTokens = entries.reduce((sum, e) => sum + e.summaryTokens, 0);
|
|
5091
|
+
logger.info("Compression completed", {
|
|
5092
|
+
sessionId,
|
|
5093
|
+
blockIds: logBlockIds,
|
|
5094
|
+
topics: logTopics,
|
|
5095
|
+
compressedTokens: logCompressedTokens,
|
|
5096
|
+
summaryTokens: logSummaryTokens,
|
|
5097
|
+
contextTokensBefore
|
|
5098
|
+
});
|
|
5099
|
+
if (config.pruneNotification === "off") {
|
|
5050
5100
|
return false;
|
|
5051
5101
|
}
|
|
5052
5102
|
let message;
|
|
@@ -5145,20 +5195,22 @@ ${progressBar}`;
|
|
|
5145
5195
|
\u2192 Compression (~${summaryTokensStr}): ${displaySummary}`;
|
|
5146
5196
|
}
|
|
5147
5197
|
}
|
|
5148
|
-
if (config.pruneNotificationType === "
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
title: "ACP: Compress Notification",
|
|
5154
|
-
message: toastMessage,
|
|
5155
|
-
variant: "info",
|
|
5156
|
-
duration: 5e3
|
|
5157
|
-
}
|
|
5158
|
-
});
|
|
5159
|
-
return true;
|
|
5198
|
+
if (config.pruneNotificationType === "chat") {
|
|
5199
|
+
logger.warn(
|
|
5200
|
+
"compress.pruneNotificationType 'chat' is no longer supported (it injects an empty user message that causes provider 400 errors); falling back to toast. Set pruneNotificationType to 'toast' (or pruneNotification to 'off') to silence this warning.",
|
|
5201
|
+
{ sessionId }
|
|
5202
|
+
);
|
|
5160
5203
|
}
|
|
5161
|
-
|
|
5204
|
+
let toastMessage = message;
|
|
5205
|
+
toastMessage = config.pruneNotification === "minimal" ? toastMessage : truncateToastBody(toastMessage);
|
|
5206
|
+
await client.tui.showToast({
|
|
5207
|
+
body: {
|
|
5208
|
+
title: "ACP: Compress Notification",
|
|
5209
|
+
message: toastMessage,
|
|
5210
|
+
variant: "info",
|
|
5211
|
+
duration: 5e3
|
|
5212
|
+
}
|
|
5213
|
+
});
|
|
5162
5214
|
return true;
|
|
5163
5215
|
}
|
|
5164
5216
|
async function sendIgnoredMessage(client, sessionID, text, params, logger) {
|
|
@@ -5192,6 +5244,737 @@ async function sendIgnoredMessage(client, sessionID, text, params, logger) {
|
|
|
5192
5244
|
}
|
|
5193
5245
|
}
|
|
5194
5246
|
|
|
5247
|
+
// lib/compress/quality-gate/registry.ts
|
|
5248
|
+
var registry = /* @__PURE__ */ new Map();
|
|
5249
|
+
function registerQualityGate(gate) {
|
|
5250
|
+
if (registry.has(gate.name)) {
|
|
5251
|
+
const existing = registry.get(gate.name);
|
|
5252
|
+
if (existing !== gate && existing.version !== gate.version) {
|
|
5253
|
+
throw new Error(
|
|
5254
|
+
`Quality gate "${gate.name}" already registered with version ${existing.version} (attempted ${gate.version})`
|
|
5255
|
+
);
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
registry.set(gate.name, gate);
|
|
5259
|
+
}
|
|
5260
|
+
function getQualityGate(name) {
|
|
5261
|
+
return registry.get(name);
|
|
5262
|
+
}
|
|
5263
|
+
|
|
5264
|
+
// node_modules/context-compress-algorithms/dist/chunk-E6LYOQFY.js
|
|
5265
|
+
var ENGLISH_WORD_RE = /[a-z][a-z0-9_]+/g;
|
|
5266
|
+
var CJK_RE = /[\u4e00-\u9fff]/g;
|
|
5267
|
+
var FILE_PATH_RE = /(?:[a-zA-Z0-9_-]+\/){1,}[a-zA-Z0-9_-]+\.[a-zA-Z]{1,5}/g;
|
|
5268
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
5269
|
+
"the",
|
|
5270
|
+
"that",
|
|
5271
|
+
"this",
|
|
5272
|
+
"these",
|
|
5273
|
+
"those",
|
|
5274
|
+
"there",
|
|
5275
|
+
"their",
|
|
5276
|
+
"them",
|
|
5277
|
+
"then",
|
|
5278
|
+
"than",
|
|
5279
|
+
"thats",
|
|
5280
|
+
"with",
|
|
5281
|
+
"will",
|
|
5282
|
+
"would",
|
|
5283
|
+
"could",
|
|
5284
|
+
"should",
|
|
5285
|
+
"from",
|
|
5286
|
+
"have",
|
|
5287
|
+
"has",
|
|
5288
|
+
"had",
|
|
5289
|
+
"having",
|
|
5290
|
+
"were",
|
|
5291
|
+
"what",
|
|
5292
|
+
"which",
|
|
5293
|
+
"when",
|
|
5294
|
+
"where",
|
|
5295
|
+
"while",
|
|
5296
|
+
"your",
|
|
5297
|
+
"yours",
|
|
5298
|
+
"theirs",
|
|
5299
|
+
"ours",
|
|
5300
|
+
"mine",
|
|
5301
|
+
"hers",
|
|
5302
|
+
"whose",
|
|
5303
|
+
"they",
|
|
5304
|
+
"them",
|
|
5305
|
+
"those",
|
|
5306
|
+
"these",
|
|
5307
|
+
"this",
|
|
5308
|
+
"that",
|
|
5309
|
+
"such",
|
|
5310
|
+
"some",
|
|
5311
|
+
"same",
|
|
5312
|
+
"other",
|
|
5313
|
+
"another",
|
|
5314
|
+
"each",
|
|
5315
|
+
"into",
|
|
5316
|
+
"onto",
|
|
5317
|
+
"upon",
|
|
5318
|
+
"over",
|
|
5319
|
+
"under",
|
|
5320
|
+
"between",
|
|
5321
|
+
"through",
|
|
5322
|
+
"during",
|
|
5323
|
+
"before",
|
|
5324
|
+
"after",
|
|
5325
|
+
"above",
|
|
5326
|
+
"below",
|
|
5327
|
+
"among",
|
|
5328
|
+
"across",
|
|
5329
|
+
"along",
|
|
5330
|
+
"around",
|
|
5331
|
+
"about",
|
|
5332
|
+
"because",
|
|
5333
|
+
"since",
|
|
5334
|
+
"unless",
|
|
5335
|
+
"although",
|
|
5336
|
+
"though",
|
|
5337
|
+
"whereas",
|
|
5338
|
+
"whether",
|
|
5339
|
+
"either",
|
|
5340
|
+
"neither",
|
|
5341
|
+
"both",
|
|
5342
|
+
"also",
|
|
5343
|
+
"only",
|
|
5344
|
+
"just",
|
|
5345
|
+
"very",
|
|
5346
|
+
"more",
|
|
5347
|
+
"most",
|
|
5348
|
+
"much",
|
|
5349
|
+
"many",
|
|
5350
|
+
"less",
|
|
5351
|
+
"least",
|
|
5352
|
+
"several",
|
|
5353
|
+
"enough",
|
|
5354
|
+
"make",
|
|
5355
|
+
"made",
|
|
5356
|
+
"makes",
|
|
5357
|
+
"making",
|
|
5358
|
+
"take",
|
|
5359
|
+
"took",
|
|
5360
|
+
"taken",
|
|
5361
|
+
"takes",
|
|
5362
|
+
"taking",
|
|
5363
|
+
"get",
|
|
5364
|
+
"got",
|
|
5365
|
+
"getting",
|
|
5366
|
+
"gets",
|
|
5367
|
+
"give",
|
|
5368
|
+
"gave",
|
|
5369
|
+
"given",
|
|
5370
|
+
"gives",
|
|
5371
|
+
"giving",
|
|
5372
|
+
"come",
|
|
5373
|
+
"came",
|
|
5374
|
+
"comes",
|
|
5375
|
+
"coming",
|
|
5376
|
+
"were",
|
|
5377
|
+
"been",
|
|
5378
|
+
"being",
|
|
5379
|
+
"have",
|
|
5380
|
+
"make",
|
|
5381
|
+
"want",
|
|
5382
|
+
"need",
|
|
5383
|
+
"using",
|
|
5384
|
+
"true",
|
|
5385
|
+
"false",
|
|
5386
|
+
"null",
|
|
5387
|
+
"undefined",
|
|
5388
|
+
"void",
|
|
5389
|
+
"return",
|
|
5390
|
+
"returns",
|
|
5391
|
+
"returning",
|
|
5392
|
+
"function",
|
|
5393
|
+
"const",
|
|
5394
|
+
"class",
|
|
5395
|
+
"interface",
|
|
5396
|
+
"type",
|
|
5397
|
+
"typeof",
|
|
5398
|
+
"instanceof",
|
|
5399
|
+
"import",
|
|
5400
|
+
"export",
|
|
5401
|
+
"require",
|
|
5402
|
+
"module",
|
|
5403
|
+
"default",
|
|
5404
|
+
"async",
|
|
5405
|
+
"await",
|
|
5406
|
+
"static",
|
|
5407
|
+
"public",
|
|
5408
|
+
"private",
|
|
5409
|
+
"protected",
|
|
5410
|
+
"readonly",
|
|
5411
|
+
"partial",
|
|
5412
|
+
"abstract",
|
|
5413
|
+
"virtual",
|
|
5414
|
+
"override",
|
|
5415
|
+
"final",
|
|
5416
|
+
"super",
|
|
5417
|
+
"value",
|
|
5418
|
+
"values",
|
|
5419
|
+
"param",
|
|
5420
|
+
"params",
|
|
5421
|
+
"name",
|
|
5422
|
+
"names",
|
|
5423
|
+
"test",
|
|
5424
|
+
"tests",
|
|
5425
|
+
"testing",
|
|
5426
|
+
"tested",
|
|
5427
|
+
"expect",
|
|
5428
|
+
"expected",
|
|
5429
|
+
"actual",
|
|
5430
|
+
"actuals",
|
|
5431
|
+
"result",
|
|
5432
|
+
"results",
|
|
5433
|
+
"output",
|
|
5434
|
+
"outputs",
|
|
5435
|
+
"input",
|
|
5436
|
+
"inputs",
|
|
5437
|
+
"data",
|
|
5438
|
+
"record",
|
|
5439
|
+
"records",
|
|
5440
|
+
"item",
|
|
5441
|
+
"items",
|
|
5442
|
+
"like",
|
|
5443
|
+
"want",
|
|
5444
|
+
"need",
|
|
5445
|
+
"used",
|
|
5446
|
+
"uses",
|
|
5447
|
+
"said",
|
|
5448
|
+
"say",
|
|
5449
|
+
"says",
|
|
5450
|
+
"went",
|
|
5451
|
+
"goes",
|
|
5452
|
+
"here",
|
|
5453
|
+
"there",
|
|
5454
|
+
"where",
|
|
5455
|
+
"when",
|
|
5456
|
+
"what",
|
|
5457
|
+
"who",
|
|
5458
|
+
"how",
|
|
5459
|
+
"why",
|
|
5460
|
+
"which",
|
|
5461
|
+
"whose",
|
|
5462
|
+
"whom",
|
|
5463
|
+
"yourself",
|
|
5464
|
+
"myself",
|
|
5465
|
+
"itself",
|
|
5466
|
+
"themselves",
|
|
5467
|
+
"ourselves",
|
|
5468
|
+
"himself",
|
|
5469
|
+
"herself",
|
|
5470
|
+
"yeah",
|
|
5471
|
+
"okay",
|
|
5472
|
+
"ok",
|
|
5473
|
+
"yes",
|
|
5474
|
+
"no",
|
|
5475
|
+
"not",
|
|
5476
|
+
"nor",
|
|
5477
|
+
"or",
|
|
5478
|
+
"and",
|
|
5479
|
+
"but",
|
|
5480
|
+
"if",
|
|
5481
|
+
"then",
|
|
5482
|
+
"else",
|
|
5483
|
+
"elif",
|
|
5484
|
+
"when",
|
|
5485
|
+
"while",
|
|
5486
|
+
"for",
|
|
5487
|
+
"to",
|
|
5488
|
+
"of",
|
|
5489
|
+
"in",
|
|
5490
|
+
"on",
|
|
5491
|
+
"at",
|
|
5492
|
+
"by",
|
|
5493
|
+
"with",
|
|
5494
|
+
"from",
|
|
5495
|
+
"into",
|
|
5496
|
+
"onto",
|
|
5497
|
+
"\u7684",
|
|
5498
|
+
"\u662F",
|
|
5499
|
+
"\u4E86",
|
|
5500
|
+
"\u5728",
|
|
5501
|
+
"\u548C",
|
|
5502
|
+
"\u4E0E",
|
|
5503
|
+
"\u6216",
|
|
5504
|
+
"\u4E5F",
|
|
5505
|
+
"\u90FD",
|
|
5506
|
+
"\u5C31",
|
|
5507
|
+
"\u8FD8",
|
|
5508
|
+
"\u53C8",
|
|
5509
|
+
"\u624D",
|
|
5510
|
+
"\u518D",
|
|
5511
|
+
"\u5DF2",
|
|
5512
|
+
"\u5C06",
|
|
5513
|
+
"\u4F1A",
|
|
5514
|
+
"\u80FD",
|
|
5515
|
+
"\u53EF",
|
|
5516
|
+
"\u53EF\u4EE5",
|
|
5517
|
+
"\u8981",
|
|
5518
|
+
"\u60F3",
|
|
5519
|
+
"\u9700\u8981",
|
|
5520
|
+
"\u5E94\u8BE5",
|
|
5521
|
+
"\u5FC5\u987B",
|
|
5522
|
+
"\u6CA1",
|
|
5523
|
+
"\u6CA1\u6709",
|
|
5524
|
+
"\u4E0D",
|
|
5525
|
+
"\u975E",
|
|
5526
|
+
"\u65E0",
|
|
5527
|
+
"\u83AB",
|
|
5528
|
+
"\u6211",
|
|
5529
|
+
"\u4F60",
|
|
5530
|
+
"\u4ED6",
|
|
5531
|
+
"\u5979",
|
|
5532
|
+
"\u5B83",
|
|
5533
|
+
"\u6211\u4EEC",
|
|
5534
|
+
"\u4F60\u4EEC",
|
|
5535
|
+
"\u4ED6\u4EEC",
|
|
5536
|
+
"\u5979\u4EEC",
|
|
5537
|
+
"\u5B83\u4EEC",
|
|
5538
|
+
"\u54B1",
|
|
5539
|
+
"\u54B1\u4EEC",
|
|
5540
|
+
"\u81EA\u5DF1",
|
|
5541
|
+
"\u8FD9",
|
|
5542
|
+
"\u90A3",
|
|
5543
|
+
"\u8FD9\u4E2A",
|
|
5544
|
+
"\u90A3\u4E2A",
|
|
5545
|
+
"\u8FD9\u4E9B",
|
|
5546
|
+
"\u90A3\u4E9B",
|
|
5547
|
+
"\u8FD9\u6837",
|
|
5548
|
+
"\u90A3\u6837",
|
|
5549
|
+
"\u8FD9\u91CC",
|
|
5550
|
+
"\u90A3\u91CC",
|
|
5551
|
+
"\u8FD9\u4E48",
|
|
5552
|
+
"\u90A3\u4E48",
|
|
5553
|
+
"\u4EC0\u4E48",
|
|
5554
|
+
"\u600E\u4E48",
|
|
5555
|
+
"\u4E3A\u4EC0\u4E48",
|
|
5556
|
+
"\u54EA",
|
|
5557
|
+
"\u54EA\u4E2A",
|
|
5558
|
+
"\u54EA\u4E9B",
|
|
5559
|
+
"\u54EA\u91CC",
|
|
5560
|
+
"\u600E\u6837",
|
|
5561
|
+
"\u591A\u5C11",
|
|
5562
|
+
"\u51E0",
|
|
5563
|
+
"\u591A",
|
|
5564
|
+
"\u5C11",
|
|
5565
|
+
"\u4E8E",
|
|
5566
|
+
"\u4ECE",
|
|
5567
|
+
"\u5411",
|
|
5568
|
+
"\u5F80",
|
|
5569
|
+
"\u5230",
|
|
5570
|
+
"\u81F3",
|
|
5571
|
+
"\u4E3A",
|
|
5572
|
+
"\u5BF9\u4E8E",
|
|
5573
|
+
"\u5173\u4E8E",
|
|
5574
|
+
"\u81F3\u4E8E",
|
|
5575
|
+
"\u7531\u4E8E",
|
|
5576
|
+
"\u56E0\u4E3A",
|
|
5577
|
+
"\u6240\u4EE5",
|
|
5578
|
+
"\u4F46\u662F",
|
|
5579
|
+
"\u4F46",
|
|
5580
|
+
"\u4E0D\u8FC7",
|
|
5581
|
+
"\u7136\u800C",
|
|
5582
|
+
"\u53EF\u662F",
|
|
5583
|
+
"\u53EA\u662F",
|
|
5584
|
+
"\u53EA\u6709",
|
|
5585
|
+
"\u9664\u4E86",
|
|
5586
|
+
"\u9664\u975E",
|
|
5587
|
+
"\u65E0\u8BBA",
|
|
5588
|
+
"\u4E0D\u7BA1",
|
|
5589
|
+
"\u5C3D\u7BA1",
|
|
5590
|
+
"\u867D\u7136",
|
|
5591
|
+
"\u867D\u8BF4",
|
|
5592
|
+
"\u5373\u4F7F",
|
|
5593
|
+
"\u5373\u4FBF",
|
|
5594
|
+
"\u54EA\u6015",
|
|
5595
|
+
"\u4E00\u65E6",
|
|
5596
|
+
"\u5982\u679C",
|
|
5597
|
+
"\u8981\u662F",
|
|
5598
|
+
"\u5047\u5982",
|
|
5599
|
+
"\u5047\u4F7F",
|
|
5600
|
+
"\u5018\u82E5",
|
|
5601
|
+
"\u4E4B",
|
|
5602
|
+
"\u5176",
|
|
5603
|
+
"\u5176\u4E2D",
|
|
5604
|
+
"\u5176\u4ED6",
|
|
5605
|
+
"\u5176\u5B83",
|
|
5606
|
+
"\u5176\u4F59",
|
|
5607
|
+
"\u53E6\u4E00",
|
|
5608
|
+
"\u53E6\u5916",
|
|
5609
|
+
"\u6B64\u5916",
|
|
5610
|
+
"\u5E76\u4E14",
|
|
5611
|
+
"\u5E76",
|
|
5612
|
+
"\u4E14",
|
|
5613
|
+
"\u7740",
|
|
5614
|
+
"\u8FC7",
|
|
5615
|
+
"\u5427",
|
|
5616
|
+
"\u5417",
|
|
5617
|
+
"\u5462",
|
|
5618
|
+
"\u554A",
|
|
5619
|
+
"\u54E6",
|
|
5620
|
+
"\u55EF",
|
|
5621
|
+
"\u5440",
|
|
5622
|
+
"\u54C7",
|
|
5623
|
+
"\u54C8",
|
|
5624
|
+
"\u561B",
|
|
5625
|
+
"\u54AF",
|
|
5626
|
+
"\u54DF"
|
|
5627
|
+
]);
|
|
5628
|
+
var ZH_STOPWORD_BIGRAMS = /* @__PURE__ */ new Set([
|
|
5629
|
+
"\u6211\u4EEC",
|
|
5630
|
+
"\u4F60\u4EEC",
|
|
5631
|
+
"\u4ED6\u4EEC",
|
|
5632
|
+
"\u5979\u4EEC",
|
|
5633
|
+
"\u5B83\u4EEC",
|
|
5634
|
+
"\u54B1\u4EEC",
|
|
5635
|
+
"\u8FD9\u4E2A",
|
|
5636
|
+
"\u90A3\u4E2A",
|
|
5637
|
+
"\u8FD9\u4E9B",
|
|
5638
|
+
"\u90A3\u4E9B",
|
|
5639
|
+
"\u4EC0\u4E48",
|
|
5640
|
+
"\u600E\u4E48",
|
|
5641
|
+
"\u4E3A\u4EC0\u4E48",
|
|
5642
|
+
"\u5982\u4F55",
|
|
5643
|
+
"\u53EF\u4EE5",
|
|
5644
|
+
"\u5E94\u8BE5",
|
|
5645
|
+
"\u4F46\u662F",
|
|
5646
|
+
"\u56E0\u4E3A",
|
|
5647
|
+
"\u6240\u4EE5",
|
|
5648
|
+
"\u5982\u679C",
|
|
5649
|
+
"\u867D\u7136",
|
|
5650
|
+
"\u5373\u4F7F",
|
|
5651
|
+
"\u5C3D\u7BA1",
|
|
5652
|
+
"\u4E3A\u4E86",
|
|
5653
|
+
"\u7531\u4E8E",
|
|
5654
|
+
"\u4E0D\u4F46",
|
|
5655
|
+
"\u800C\u4E14",
|
|
5656
|
+
"\u5E76\u4E14",
|
|
5657
|
+
"\u6216\u8005",
|
|
5658
|
+
"\u8FD8\u662F",
|
|
5659
|
+
"\u4EE5\u53CA",
|
|
5660
|
+
"\u4EE5\u4E3A",
|
|
5661
|
+
"\u4E8E\u662F",
|
|
5662
|
+
"\u7136\u800C",
|
|
5663
|
+
"\u5176\u5B9E",
|
|
5664
|
+
"\u5C31\u662F",
|
|
5665
|
+
"\u53EA\u662F",
|
|
5666
|
+
"\u53EA\u6709",
|
|
5667
|
+
"\u9664\u4E86",
|
|
5668
|
+
"\u9664\u975E",
|
|
5669
|
+
"\u8FD9\u6837",
|
|
5670
|
+
"\u90A3\u6837",
|
|
5671
|
+
"\u8FD9\u4E48",
|
|
5672
|
+
"\u90A3\u4E48",
|
|
5673
|
+
"\u8FD9\u4E9B",
|
|
5674
|
+
"\u90A3\u4E9B",
|
|
5675
|
+
"\u8FD9\u91CC",
|
|
5676
|
+
"\u90A3\u91CC",
|
|
5677
|
+
"\u73B0\u5728",
|
|
5678
|
+
"\u4EE5\u540E",
|
|
5679
|
+
"\u4EE5\u524D",
|
|
5680
|
+
"\u4E4B\u540E",
|
|
5681
|
+
"\u4E4B\u524D",
|
|
5682
|
+
"\u7136\u540E",
|
|
5683
|
+
"\u5F53\u7136",
|
|
5684
|
+
"\u53EF\u80FD",
|
|
5685
|
+
"\u4E00\u4E9B",
|
|
5686
|
+
"\u8BB8\u591A",
|
|
5687
|
+
"\u975E\u5E38",
|
|
5688
|
+
"\u5341\u5206",
|
|
5689
|
+
"\u6BD4\u8F83",
|
|
5690
|
+
"\u66F4\u52A0",
|
|
5691
|
+
"\u6700\u4E3A",
|
|
5692
|
+
"\u4E5F\u662F",
|
|
5693
|
+
"\u8FD8\u662F",
|
|
5694
|
+
"\u5C31\u662F",
|
|
5695
|
+
"\u4E0D\u8FC7",
|
|
5696
|
+
"\u4E0D\u8981",
|
|
5697
|
+
"\u4E0D\u80FD",
|
|
5698
|
+
"\u4E0D\u4F1A",
|
|
5699
|
+
"\u6CA1\u6709",
|
|
5700
|
+
"\u4E0D\u662F",
|
|
5701
|
+
"\u4E0D\u7528",
|
|
5702
|
+
"\u4E0D\u5FC5",
|
|
5703
|
+
"\u4E00\u76F4",
|
|
5704
|
+
"\u5DF2\u7ECF",
|
|
5705
|
+
"\u6B63\u5728",
|
|
5706
|
+
"\u9A6C\u4E0A"
|
|
5707
|
+
]);
|
|
5708
|
+
var DEFAULT_OPTS = {
|
|
5709
|
+
english: true,
|
|
5710
|
+
zhUnigrams: true,
|
|
5711
|
+
zhBigrams: true
|
|
5712
|
+
};
|
|
5713
|
+
function tokenize(text, opts = {}) {
|
|
5714
|
+
if (!text || typeof text !== "string") return [];
|
|
5715
|
+
const options = { ...DEFAULT_OPTS, ...opts };
|
|
5716
|
+
const tokens = [];
|
|
5717
|
+
const lower = text.toLowerCase();
|
|
5718
|
+
if (options.english) {
|
|
5719
|
+
const matches = lower.match(ENGLISH_WORD_RE);
|
|
5720
|
+
if (matches) {
|
|
5721
|
+
for (const w of matches) {
|
|
5722
|
+
if (w.length >= 4 && !STOPWORDS.has(w) && !/^\d+$/.test(w)) {
|
|
5723
|
+
tokens.push(w);
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
if (options.zhUnigrams || options.zhBigrams) {
|
|
5729
|
+
const cjkChars = text.match(CJK_RE);
|
|
5730
|
+
if (cjkChars && cjkChars.length > 0) {
|
|
5731
|
+
const cjkStr = cjkChars.join("");
|
|
5732
|
+
if (options.zhUnigrams) {
|
|
5733
|
+
for (const c of cjkStr) {
|
|
5734
|
+
if (!STOPWORDS.has(c)) tokens.push(c);
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
if (options.zhBigrams) {
|
|
5738
|
+
for (let i = 0; i < cjkStr.length - 1; i++) {
|
|
5739
|
+
const bg = cjkStr.slice(i, i + 2);
|
|
5740
|
+
if (!ZH_STOPWORD_BIGRAMS.has(bg) && !STOPWORDS.has(bg[0]) && !STOPWORDS.has(bg[1])) {
|
|
5741
|
+
tokens.push(bg);
|
|
5742
|
+
}
|
|
5743
|
+
}
|
|
5744
|
+
}
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
return tokens;
|
|
5748
|
+
}
|
|
5749
|
+
function termFrequency(tokens) {
|
|
5750
|
+
const tf = /* @__PURE__ */ new Map();
|
|
5751
|
+
for (const t of tokens) {
|
|
5752
|
+
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
5753
|
+
}
|
|
5754
|
+
return tf;
|
|
5755
|
+
}
|
|
5756
|
+
function topKByTf(tokens, k) {
|
|
5757
|
+
if (tokens.length === 0 || k <= 0) return [];
|
|
5758
|
+
const tf = termFrequency(tokens);
|
|
5759
|
+
const sorted = [...tf.entries()].sort((a, b) => {
|
|
5760
|
+
if (b[1] !== a[1]) return b[1] - a[1];
|
|
5761
|
+
return a[0].localeCompare(b[0]);
|
|
5762
|
+
});
|
|
5763
|
+
return sorted.slice(0, k).map((e) => e[0]);
|
|
5764
|
+
}
|
|
5765
|
+
function extractFilePaths(text) {
|
|
5766
|
+
if (!text) return /* @__PURE__ */ new Set();
|
|
5767
|
+
const paths = /* @__PURE__ */ new Set();
|
|
5768
|
+
const matches = text.match(FILE_PATH_RE);
|
|
5769
|
+
if (matches) {
|
|
5770
|
+
for (const m of matches) paths.add(m);
|
|
5771
|
+
}
|
|
5772
|
+
return paths;
|
|
5773
|
+
}
|
|
5774
|
+
function rouge1Recall(summaryTokens, originalTokens) {
|
|
5775
|
+
if (originalTokens.length === 0) return 0;
|
|
5776
|
+
const summarySet = new Set(summaryTokens);
|
|
5777
|
+
const originalSet = new Set(originalTokens);
|
|
5778
|
+
let hit = 0;
|
|
5779
|
+
for (const t of originalSet) if (summarySet.has(t)) hit++;
|
|
5780
|
+
return hit / originalSet.size;
|
|
5781
|
+
}
|
|
5782
|
+
function rouge1Precision(summaryTokens, originalTokens) {
|
|
5783
|
+
if (summaryTokens.length === 0) return 0;
|
|
5784
|
+
const summarySet = new Set(summaryTokens);
|
|
5785
|
+
const originalSet = new Set(originalTokens);
|
|
5786
|
+
let hit = 0;
|
|
5787
|
+
for (const t of summarySet) if (originalSet.has(t)) hit++;
|
|
5788
|
+
return hit / summarySet.size;
|
|
5789
|
+
}
|
|
5790
|
+
function rouge1F1(summaryTokens, originalTokens) {
|
|
5791
|
+
const r = rouge1Recall(summaryTokens, originalTokens);
|
|
5792
|
+
const p = rouge1Precision(summaryTokens, originalTokens);
|
|
5793
|
+
if (r + p === 0) return 0;
|
|
5794
|
+
return 2 * r * p / (r + p);
|
|
5795
|
+
}
|
|
5796
|
+
function topKRecall(summaryTokens, originalTokens, k) {
|
|
5797
|
+
if (originalTokens.length === 0 || k <= 0) return 0;
|
|
5798
|
+
const top = topKByTf(originalTokens, k);
|
|
5799
|
+
if (top.length === 0) return 0;
|
|
5800
|
+
const summarySet = new Set(summaryTokens);
|
|
5801
|
+
let hit = 0;
|
|
5802
|
+
for (const t of top) if (summarySet.has(t)) hit++;
|
|
5803
|
+
return hit / top.length;
|
|
5804
|
+
}
|
|
5805
|
+
var DEFAULT_ROUGE_RECALL_V1_CONFIG = {
|
|
5806
|
+
layer1MinChars: 200,
|
|
5807
|
+
layer1MinRetentionPct: 1,
|
|
5808
|
+
layer2MaxRougeF1: 0.05,
|
|
5809
|
+
layer2MaxTop20Recall: 0.2
|
|
5810
|
+
};
|
|
5811
|
+
var TOP_K = 20;
|
|
5812
|
+
var ORIGINAL_TOKEN_ESTIMATE_CHARS_PER_TOKEN = 4;
|
|
5813
|
+
function resolveConfig(input) {
|
|
5814
|
+
if (!input || typeof input !== "object") return DEFAULT_ROUGE_RECALL_V1_CONFIG;
|
|
5815
|
+
const c = input;
|
|
5816
|
+
return {
|
|
5817
|
+
layer1MinChars: typeof c.layer1MinChars === "number" && c.layer1MinChars > 0 ? c.layer1MinChars : DEFAULT_ROUGE_RECALL_V1_CONFIG.layer1MinChars,
|
|
5818
|
+
layer1MinRetentionPct: typeof c.layer1MinRetentionPct === "number" && c.layer1MinRetentionPct >= 0 ? c.layer1MinRetentionPct : DEFAULT_ROUGE_RECALL_V1_CONFIG.layer1MinRetentionPct,
|
|
5819
|
+
layer2MaxRougeF1: typeof c.layer2MaxRougeF1 === "number" && c.layer2MaxRougeF1 >= 0 ? c.layer2MaxRougeF1 : DEFAULT_ROUGE_RECALL_V1_CONFIG.layer2MaxRougeF1,
|
|
5820
|
+
layer2MaxTop20Recall: typeof c.layer2MaxTop20Recall === "number" && c.layer2MaxTop20Recall >= 0 ? c.layer2MaxTop20Recall : DEFAULT_ROUGE_RECALL_V1_CONFIG.layer2MaxTop20Recall
|
|
5821
|
+
};
|
|
5822
|
+
}
|
|
5823
|
+
var rougeRecallV1 = {
|
|
5824
|
+
name: "rouge-recall-v1",
|
|
5825
|
+
version: "1.0.0",
|
|
5826
|
+
description: "Two-layer gate: length floor (L1) then ROUGE-1 F1 AND top-20 keyword recall (L2)",
|
|
5827
|
+
evaluate(ctx, rawConfig) {
|
|
5828
|
+
const cfg = resolveConfig(rawConfig);
|
|
5829
|
+
const summaryLen = ctx.summary.length;
|
|
5830
|
+
const originalChars = ctx.originalText.length;
|
|
5831
|
+
const retentionPct = ctx.block.compressedTokens > 0 ? summaryLen / (ctx.block.compressedTokens * ORIGINAL_TOKEN_ESTIMATE_CHARS_PER_TOKEN) * 100 : 0;
|
|
5832
|
+
const baseMetrics = [
|
|
5833
|
+
{ name: "summaryLen", value: summaryLen },
|
|
5834
|
+
{ name: "retentionPct", value: +retentionPct.toFixed(2), format: "percent" },
|
|
5835
|
+
{ name: "originalTokens", value: ctx.block.compressedTokens }
|
|
5836
|
+
];
|
|
5837
|
+
if (summaryLen < cfg.layer1MinChars || originalChars > 0 && retentionPct < cfg.layer1MinRetentionPct) {
|
|
5838
|
+
return {
|
|
5839
|
+
passed: false,
|
|
5840
|
+
layer: "L1-length",
|
|
5841
|
+
reason: `Summary too short: ${summaryLen} chars, ${retentionPct.toFixed(2)}% retention (threshold: ${cfg.layer1MinChars} chars OR ${cfg.layer1MinRetentionPct}% retention)`,
|
|
5842
|
+
metrics: baseMetrics
|
|
5843
|
+
};
|
|
5844
|
+
}
|
|
5845
|
+
if (ctx.originalText.length === 0) {
|
|
5846
|
+
return { passed: true, metrics: baseMetrics };
|
|
5847
|
+
}
|
|
5848
|
+
const summaryTokens = tokenize(ctx.summary);
|
|
5849
|
+
const originalTokens = tokenize(ctx.originalText);
|
|
5850
|
+
const rougeF1 = rouge1F1(summaryTokens, originalTokens);
|
|
5851
|
+
const rougeRecall = rouge1Recall(summaryTokens, originalTokens);
|
|
5852
|
+
const top20 = topKRecall(summaryTokens, originalTokens, TOP_K);
|
|
5853
|
+
const summaryPaths = extractFilePaths(ctx.summary);
|
|
5854
|
+
const originalPaths = extractFilePaths(ctx.originalText);
|
|
5855
|
+
const pathCoverage = originalPaths.size >= 5 ? [...summaryPaths].filter((p) => originalPaths.has(p)).length / originalPaths.size : -1;
|
|
5856
|
+
const contentMetrics = [
|
|
5857
|
+
...baseMetrics,
|
|
5858
|
+
{ name: "rougeF1", value: +rougeF1.toFixed(4), format: "ratio" },
|
|
5859
|
+
{ name: "rougeRecall", value: +rougeRecall.toFixed(4), format: "ratio" },
|
|
5860
|
+
{ name: "top20Recall", value: +top20.toFixed(4), format: "ratio" },
|
|
5861
|
+
{ name: "nOriginalPaths", value: originalPaths.size },
|
|
5862
|
+
{ name: "nSummaryPaths", value: summaryPaths.size }
|
|
5863
|
+
];
|
|
5864
|
+
if (pathCoverage >= 0) {
|
|
5865
|
+
contentMetrics.push({ name: "pathCoverage", value: +pathCoverage.toFixed(3), format: "ratio" });
|
|
5866
|
+
}
|
|
5867
|
+
if (rougeF1 < cfg.layer2MaxRougeF1 && top20 < cfg.layer2MaxTop20Recall) {
|
|
5868
|
+
return {
|
|
5869
|
+
passed: false,
|
|
5870
|
+
layer: "L2-recall",
|
|
5871
|
+
reason: `Content coverage too low: rougeF1=${rougeF1.toFixed(3)}, top20Recall=${top20.toFixed(3)} (threshold: rougeF1<${cfg.layer2MaxRougeF1} AND top20<${cfg.layer2MaxTop20Recall})`,
|
|
5872
|
+
metrics: contentMetrics
|
|
5873
|
+
};
|
|
5874
|
+
}
|
|
5875
|
+
return { passed: true, metrics: contentMetrics };
|
|
5876
|
+
}
|
|
5877
|
+
};
|
|
5878
|
+
|
|
5879
|
+
// lib/compress/quality-gate/algorithms/index.ts
|
|
5880
|
+
function ensureBuiltinGatesRegistered() {
|
|
5881
|
+
registerQualityGate(rougeRecallV1);
|
|
5882
|
+
}
|
|
5883
|
+
|
|
5884
|
+
// lib/compress/quality-gate/evaluate.ts
|
|
5885
|
+
var CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
5886
|
+
var TOOL_OUTPUT_MAX_CHARS = 1500;
|
|
5887
|
+
var TOOL_INPUT_MAX_CHARS = 500;
|
|
5888
|
+
function extractMessageText(parts) {
|
|
5889
|
+
if (!parts || !Array.isArray(parts)) return "";
|
|
5890
|
+
let text = "";
|
|
5891
|
+
for (const part of parts) {
|
|
5892
|
+
if (!part || typeof part !== "object") continue;
|
|
5893
|
+
if (part.type === "text") {
|
|
5894
|
+
text += part.text + "\n";
|
|
5895
|
+
} else if (part.type === "tool") {
|
|
5896
|
+
const state = part.state;
|
|
5897
|
+
const input = state.status === "completed" && typeof state.input === "object" ? JSON.stringify(state.input).slice(0, TOOL_INPUT_MAX_CHARS) : "";
|
|
5898
|
+
const output = state.status === "completed" && typeof state.output === "string" ? state.output.slice(0, TOOL_OUTPUT_MAX_CHARS) : state.status === "completed" && typeof state.output === "object" ? JSON.stringify(state.output).slice(0, TOOL_OUTPUT_MAX_CHARS) : "";
|
|
5899
|
+
text += `[tool:${part.tool}] ${input}
|
|
5900
|
+
${output}
|
|
5901
|
+
`;
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
return text;
|
|
5905
|
+
}
|
|
5906
|
+
function buildContext(block, rawMessages) {
|
|
5907
|
+
const directIds = block.directMessageIds;
|
|
5908
|
+
if (!directIds || directIds.length === 0) return null;
|
|
5909
|
+
const idToMsg = /* @__PURE__ */ new Map();
|
|
5910
|
+
for (const m of rawMessages) {
|
|
5911
|
+
const id = m?.info?.id;
|
|
5912
|
+
if (typeof id === "string") idToMsg.set(id, m);
|
|
5913
|
+
}
|
|
5914
|
+
const chunks = [];
|
|
5915
|
+
for (const id of directIds) {
|
|
5916
|
+
const m = idToMsg.get(id);
|
|
5917
|
+
if (!m) continue;
|
|
5918
|
+
chunks.push(extractMessageText(m.parts));
|
|
5919
|
+
}
|
|
5920
|
+
if (chunks.length === 0) return null;
|
|
5921
|
+
const originalText = chunks.join("\n");
|
|
5922
|
+
return {
|
|
5923
|
+
block,
|
|
5924
|
+
summary: block.summary ?? "",
|
|
5925
|
+
originalChunks: chunks,
|
|
5926
|
+
originalText,
|
|
5927
|
+
originalTokens: Math.ceil(originalText.length / CHARS_PER_TOKEN_ESTIMATE)
|
|
5928
|
+
};
|
|
5929
|
+
}
|
|
5930
|
+
function evaluateBlockQuality(state, rawMessages, entry, config, logger) {
|
|
5931
|
+
const qg = config.qualityGate;
|
|
5932
|
+
if (!qg || qg.enabled !== true) return null;
|
|
5933
|
+
ensureBuiltinGatesRegistered();
|
|
5934
|
+
const algoName = qg.algorithm;
|
|
5935
|
+
if (!algoName) {
|
|
5936
|
+
logger.warn("Quality gate enabled but no algorithm specified", {});
|
|
5937
|
+
return null;
|
|
5938
|
+
}
|
|
5939
|
+
const gate = getQualityGate(algoName);
|
|
5940
|
+
if (!gate) {
|
|
5941
|
+
logger.warn("Quality gate algorithm not found in registry", { algorithm: algoName });
|
|
5942
|
+
return null;
|
|
5943
|
+
}
|
|
5944
|
+
const block = state.prune.messages.blocksById.get(entry.blockId);
|
|
5945
|
+
if (!block) {
|
|
5946
|
+
logger.warn("Quality gate: block not found", { blockId: entry.blockId });
|
|
5947
|
+
return null;
|
|
5948
|
+
}
|
|
5949
|
+
const ctx = buildContext(block, rawMessages);
|
|
5950
|
+
if (!ctx) return null;
|
|
5951
|
+
const algoConfig = (qg.algorithms && qg.algorithms[algoName]) ?? {};
|
|
5952
|
+
try {
|
|
5953
|
+
return gate.evaluate(ctx, algoConfig);
|
|
5954
|
+
} catch (err) {
|
|
5955
|
+
logger.warn("Quality gate threw \u2014 treating as pass", {
|
|
5956
|
+
gate: gate.name,
|
|
5957
|
+
blockId: entry.blockId,
|
|
5958
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5959
|
+
});
|
|
5960
|
+
return { passed: true, metrics: [] };
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
function evaluateBatchQuality(state, rawMessages, entries, config, logger) {
|
|
5964
|
+
const failures = [];
|
|
5965
|
+
for (const entry of entries) {
|
|
5966
|
+
const result = evaluateBlockQuality(state, rawMessages, entry, config, logger);
|
|
5967
|
+
if (result && !result.passed) {
|
|
5968
|
+
failures.push({ blockId: entry.blockId, result });
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
return {
|
|
5972
|
+
total: entries.length,
|
|
5973
|
+
passed: entries.length - failures.length,
|
|
5974
|
+
failures
|
|
5975
|
+
};
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5195
5978
|
// lib/compress/pipeline.ts
|
|
5196
5979
|
function snapshotCompressionState(state) {
|
|
5197
5980
|
return {
|
|
@@ -5240,6 +6023,27 @@ async function finalizeSession(ctx, toolCtx, rawMessages, entries, batchTopic) {
|
|
|
5240
6023
|
ctx.state.manualMode = ctx.state.manualMode ? "active" : false;
|
|
5241
6024
|
applyPendingCompressionDurations(ctx.state);
|
|
5242
6025
|
await saveSessionState(ctx.state, ctx.logger);
|
|
6026
|
+
if (entries.length > 0) {
|
|
6027
|
+
const qualityReport = evaluateBatchQuality(
|
|
6028
|
+
ctx.state,
|
|
6029
|
+
rawMessages,
|
|
6030
|
+
entries,
|
|
6031
|
+
ctx.config,
|
|
6032
|
+
ctx.logger
|
|
6033
|
+
);
|
|
6034
|
+
for (const failure of qualityReport.failures) {
|
|
6035
|
+
const metrics = Object.fromEntries(
|
|
6036
|
+
failure.result.metrics.map((m) => [m.name, m.value])
|
|
6037
|
+
);
|
|
6038
|
+
ctx.logger.warn("Compression quality gate FAILED", {
|
|
6039
|
+
blockId: failure.blockId,
|
|
6040
|
+
algorithm: ctx.config.qualityGate.algorithm,
|
|
6041
|
+
layer: failure.result.layer,
|
|
6042
|
+
reason: failure.result.reason,
|
|
6043
|
+
...metrics
|
|
6044
|
+
});
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
5243
6047
|
const params = getCurrentParams(ctx.state, rawMessages, ctx.logger);
|
|
5244
6048
|
const sessionMessageIds = rawMessages.filter((msg) => !isIgnoredUserMessage(msg)).map((msg) => msg.info.id);
|
|
5245
6049
|
const contextTokensBefore = getCurrentTokenUsage(ctx.state, rawMessages);
|
|
@@ -5880,15 +6684,29 @@ var filterCompressedRanges = (state, messages) => {
|
|
|
5880
6684
|
if (state.prune.messages.byMessageId.size === 0) {
|
|
5881
6685
|
return;
|
|
5882
6686
|
}
|
|
6687
|
+
const survive = messages.map((msg) => {
|
|
6688
|
+
const pruneEntry = state.prune.messages.byMessageId.get(msg.info.id);
|
|
6689
|
+
if (!pruneEntry || pruneEntry.activeBlockIds.length === 0) {
|
|
6690
|
+
return true;
|
|
6691
|
+
}
|
|
6692
|
+
return false;
|
|
6693
|
+
});
|
|
6694
|
+
const anyUserSurvives = messages.some(
|
|
6695
|
+
(msg, i) => survive[i] && msg.info.role === "user"
|
|
6696
|
+
);
|
|
6697
|
+
if (!anyUserSurvives) {
|
|
6698
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
6699
|
+
if (messages[i].info.role === "user" && !survive[i]) {
|
|
6700
|
+
survive[i] = true;
|
|
6701
|
+
break;
|
|
6702
|
+
}
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
5883
6705
|
const result = [];
|
|
5884
6706
|
for (let i = 0; i < messages.length; i++) {
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
const pruneEntry = state.prune.messages.byMessageId.get(msgId);
|
|
5888
|
-
if (pruneEntry && pruneEntry.activeBlockIds.length > 0) {
|
|
5889
|
-
continue;
|
|
6707
|
+
if (survive[i]) {
|
|
6708
|
+
result.push(messages[i]);
|
|
5890
6709
|
}
|
|
5891
|
-
result.push(msg);
|
|
5892
6710
|
}
|
|
5893
6711
|
messages.length = 0;
|
|
5894
6712
|
messages.push(...result);
|
|
@@ -6220,7 +7038,7 @@ var dropEmptyMessages = (messages) => {
|
|
|
6220
7038
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
6221
7039
|
const parts = Array.isArray(messages[i].parts) ? messages[i].parts : [];
|
|
6222
7040
|
const isEmpty = parts.every(
|
|
6223
|
-
(part) => part.type === "text" && (typeof part.text !== "string" || part.text.trim().length === 0)
|
|
7041
|
+
(part) => part.type === "text" && (typeof part.text !== "string" || part.text.trim().length === 0 || part.ignored === true)
|
|
6224
7042
|
);
|
|
6225
7043
|
if (isEmpty) {
|
|
6226
7044
|
messages.splice(i, 1);
|
|
@@ -6388,6 +7206,62 @@ function listPriorityRefsBeforeIndex(messages, priorities, anchorIndex, priority
|
|
|
6388
7206
|
return refs;
|
|
6389
7207
|
}
|
|
6390
7208
|
|
|
7209
|
+
// node_modules/context-compress-algorithms/dist/chunk-UX2UW5FY.js
|
|
7210
|
+
var NUDGE_GROWTH_FLOOR = 6e3;
|
|
7211
|
+
var NUDGE_GROWTH_CAP = 5e4;
|
|
7212
|
+
var NUDGE_GROWTH_RATIO = 0.05;
|
|
7213
|
+
function computeShouldNudge(params) {
|
|
7214
|
+
const { currentTokens, overMinLimit, overMaxLimit } = params;
|
|
7215
|
+
if (currentTokens === void 0) {
|
|
7216
|
+
return { shouldNudge: false, tipsVariant: null };
|
|
7217
|
+
}
|
|
7218
|
+
if (params.lastNudgeTokens === void 0) {
|
|
7219
|
+
return { shouldNudge: false, tipsVariant: null };
|
|
7220
|
+
}
|
|
7221
|
+
const growthSinceLastNudge = currentTokens - params.lastNudgeTokens;
|
|
7222
|
+
const shouldNudge = growthSinceLastNudge >= params.nudgeGrowthTokens || overMaxLimit;
|
|
7223
|
+
if (!shouldNudge) {
|
|
7224
|
+
return { shouldNudge: false, tipsVariant: null };
|
|
7225
|
+
}
|
|
7226
|
+
const tipsVariant = overMaxLimit ? "maxLimit" : overMinLimit ? "minLimit" : "normal";
|
|
7227
|
+
return { shouldNudge: true, tipsVariant };
|
|
7228
|
+
}
|
|
7229
|
+
function resolveAdaptiveNudgeGrowth(modelContextLimit) {
|
|
7230
|
+
if (!modelContextLimit || modelContextLimit <= 0) return NUDGE_GROWTH_FLOOR;
|
|
7231
|
+
return Math.min(
|
|
7232
|
+
NUDGE_GROWTH_CAP,
|
|
7233
|
+
Math.max(NUDGE_GROWTH_FLOOR, Math.round(modelContextLimit * NUDGE_GROWTH_RATIO))
|
|
7234
|
+
);
|
|
7235
|
+
}
|
|
7236
|
+
var defaultTriggerPolicy = {
|
|
7237
|
+
name: "context-compress-algorithms-trigger",
|
|
7238
|
+
version: "1.0.0",
|
|
7239
|
+
description: "Growth-only cadence: nudge when context growth since last nudge exceeds adaptive threshold, or when over max limit.",
|
|
7240
|
+
computeShouldNudge,
|
|
7241
|
+
resolveAdaptiveNudgeGrowth
|
|
7242
|
+
};
|
|
7243
|
+
|
|
7244
|
+
// lib/messages/inject/policy/registry.ts
|
|
7245
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
7246
|
+
var defaultPolicy = null;
|
|
7247
|
+
function registerTriggerPolicy2(policy) {
|
|
7248
|
+
if (!policy.name) {
|
|
7249
|
+
throw new Error("TriggerPolicy must have a name");
|
|
7250
|
+
}
|
|
7251
|
+
registry2.set(policy.name, policy);
|
|
7252
|
+
if (!defaultPolicy) {
|
|
7253
|
+
defaultPolicy = policy;
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
function getDefaultTriggerPolicy() {
|
|
7257
|
+
return defaultPolicy;
|
|
7258
|
+
}
|
|
7259
|
+
|
|
7260
|
+
// lib/messages/inject/policy/index.ts
|
|
7261
|
+
function ensureBuiltinTriggerPolicyRegistered() {
|
|
7262
|
+
registerTriggerPolicy2(defaultTriggerPolicy);
|
|
7263
|
+
}
|
|
7264
|
+
|
|
6391
7265
|
// lib/messages/inject/utils.ts
|
|
6392
7266
|
var MESSAGE_MODE_NUDGE_PRIORITY = "high";
|
|
6393
7267
|
function getNudgeFrequency(config) {
|
|
@@ -6500,31 +7374,20 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
|
6500
7374
|
modelContextLimit: state.modelContextLimit
|
|
6501
7375
|
};
|
|
6502
7376
|
}
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
}
|
|
6508
|
-
if (params.lastNudgeTokens === void 0) {
|
|
7377
|
+
ensureBuiltinTriggerPolicyRegistered();
|
|
7378
|
+
function computeShouldNudge2(params) {
|
|
7379
|
+
const policy = getDefaultTriggerPolicy();
|
|
7380
|
+
if (!policy) {
|
|
6509
7381
|
return { shouldNudge: false, tipsVariant: null };
|
|
6510
7382
|
}
|
|
6511
|
-
|
|
6512
|
-
const shouldNudge = growthSinceLastNudge >= params.nudgeGrowthTokens || overMaxLimit;
|
|
6513
|
-
if (!shouldNudge) {
|
|
6514
|
-
return { shouldNudge: false, tipsVariant: null };
|
|
6515
|
-
}
|
|
6516
|
-
const tipsVariant = overMaxLimit ? "maxLimit" : overMinLimit ? "minLimit" : "normal";
|
|
6517
|
-
return { shouldNudge: true, tipsVariant };
|
|
7383
|
+
return policy.computeShouldNudge(params);
|
|
6518
7384
|
}
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
return
|
|
6525
|
-
NUDGE_GROWTH_CAP,
|
|
6526
|
-
Math.max(NUDGE_GROWTH_FLOOR, Math.round(modelContextLimit * NUDGE_GROWTH_RATIO))
|
|
6527
|
-
);
|
|
7385
|
+
function resolveAdaptiveNudgeGrowth2(modelContextLimit) {
|
|
7386
|
+
const policy = getDefaultTriggerPolicy();
|
|
7387
|
+
if (!policy) {
|
|
7388
|
+
return 6e3;
|
|
7389
|
+
}
|
|
7390
|
+
return policy.resolveAdaptiveNudgeGrowth(modelContextLimit);
|
|
6528
7391
|
}
|
|
6529
7392
|
function addAnchor(anchorMessageIds, anchorMessageId, anchorMessageIndex, messages, interval) {
|
|
6530
7393
|
if (anchorMessageIndex < 0) {
|
|
@@ -7100,7 +7963,7 @@ ${lines2.join("\n")}`;
|
|
|
7100
7963
|
${lines.join("\n")}`;
|
|
7101
7964
|
}
|
|
7102
7965
|
|
|
7103
|
-
//
|
|
7966
|
+
// node_modules/context-compress-algorithms/dist/chunk-ZRHPFN6B.js
|
|
7104
7967
|
var COMPRESS_PHILOSOPHY = `Compression Philosophy:
|
|
7105
7968
|
- All compression serves the primary task, but be frugal.
|
|
7106
7969
|
- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
|
|
@@ -7276,7 +8139,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
7276
8139
|
}
|
|
7277
8140
|
}
|
|
7278
8141
|
const suffixMessage = createSuffixMessage(messages);
|
|
7279
|
-
const nudgeGrowthTokens = config.compress?.nudgeGrowthTokens ??
|
|
8142
|
+
const nudgeGrowthTokens = config.compress?.nudgeGrowthTokens ?? resolveAdaptiveNudgeGrowth2(modelContextLimit);
|
|
7280
8143
|
const growthFloor = Math.max(
|
|
7281
8144
|
config.compress?.minNudgeGrowthFloor ?? 5e3,
|
|
7282
8145
|
(config.compress?.minNudgeGrowthRatio ?? 0.45) * nudgeGrowthTokens
|
|
@@ -7291,7 +8154,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
7291
8154
|
const hasPendingNudge = state.nudges.lastNudgeShownTokens !== void 0;
|
|
7292
8155
|
const effectiveThreshold = hasPendingNudge ? Math.floor(nudgeGrowthTokens / 2) : nudgeGrowthTokens;
|
|
7293
8156
|
const growthReference = state.nudges.lastNudgeShownTokens ?? state.nudges.lastPerMessageNudgeTokens;
|
|
7294
|
-
const decision =
|
|
8157
|
+
const decision = computeShouldNudge2({
|
|
7295
8158
|
currentTokens,
|
|
7296
8159
|
modelContextLimit,
|
|
7297
8160
|
overMinLimit,
|
|
@@ -8025,7 +8888,7 @@ function buildSchema3() {
|
|
|
8025
8888
|
function extractMessageId(m) {
|
|
8026
8889
|
return m.id ?? m.messageId ?? "";
|
|
8027
8890
|
}
|
|
8028
|
-
function
|
|
8891
|
+
function extractMessageText2(m) {
|
|
8029
8892
|
const msg = m;
|
|
8030
8893
|
const role = msg.role || msg.type || "unknown";
|
|
8031
8894
|
const content = typeof msg.content === "string" ? msg.content : typeof msg.text === "string" ? msg.text : JSON.stringify(msg.content || msg.text || "");
|
|
@@ -8078,7 +8941,7 @@ function createDecompressTool(ctx) {
|
|
|
8078
8941
|
}
|
|
8079
8942
|
}
|
|
8080
8943
|
const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
|
|
8081
|
-
const lines2 = blockMessages.map(
|
|
8944
|
+
const lines2 = blockMessages.map(extractMessageText2);
|
|
8082
8945
|
const { writeFile: writeFile3 } = await import("fs/promises");
|
|
8083
8946
|
const fileContent = lines2.length > 0 ? lines2.join("\n\n---\n\n") : activeBlocks[0]?.summary ?? "(no content available)";
|
|
8084
8947
|
await writeFile3(targetPath, fileContent, "utf-8");
|