opencode-gbk-tools 0.1.22 → 0.1.24

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.
@@ -3816,6 +3816,100 @@ var require_lib = __commonJS({
3816
3816
  }
3817
3817
  });
3818
3818
 
3819
+ // src/lib/session-pressure.ts
3820
+ var AUTO_SUMMARIZE_PRESSURE_RATIO = 0.85;
3821
+ var AUTO_SUMMARIZE_COOLDOWN_MS = 6e4;
3822
+ var PRESSURE_CHECK_INTERVAL_MS = 15e3;
3823
+ var SESSION_PRESSURE_MESSAGE_LIMIT = 200;
3824
+ function estimateUnknownChars(value) {
3825
+ if (typeof value === "string") return value.length;
3826
+ if (typeof value === "number" || typeof value === "boolean") return String(value).length;
3827
+ if (Array.isArray(value)) {
3828
+ return value.reduce((total, item) => total + estimateUnknownChars(item), 0);
3829
+ }
3830
+ if (!value || typeof value !== "object") return 0;
3831
+ try {
3832
+ return JSON.stringify(value).length;
3833
+ } catch {
3834
+ return 0;
3835
+ }
3836
+ }
3837
+ function estimateDiffChars(summary) {
3838
+ let chars = (summary.title?.length ?? 0) + (summary.body?.length ?? 0);
3839
+ for (const diff of summary.diffs ?? []) {
3840
+ chars += diff.file.length + diff.before.length + diff.after.length;
3841
+ }
3842
+ return chars;
3843
+ }
3844
+ function estimateToolPartChars(part) {
3845
+ let chars = part.tool.length + estimateUnknownChars(part.metadata);
3846
+ switch (part.state.status) {
3847
+ case "pending":
3848
+ chars += estimateUnknownChars(part.state.input) + part.state.raw.length;
3849
+ break;
3850
+ case "running":
3851
+ chars += estimateUnknownChars(part.state.input);
3852
+ chars += part.state.title?.length ?? 0;
3853
+ chars += estimateUnknownChars(part.state.metadata);
3854
+ break;
3855
+ case "completed":
3856
+ chars += estimateUnknownChars(part.state.input);
3857
+ chars += part.state.output.length + part.state.title.length;
3858
+ chars += estimateUnknownChars(part.state.metadata);
3859
+ break;
3860
+ case "error":
3861
+ chars += estimateUnknownChars(part.state.input);
3862
+ chars += part.state.error.length + estimateUnknownChars(part.state.metadata);
3863
+ break;
3864
+ }
3865
+ return chars;
3866
+ }
3867
+ function estimatePartChars(part) {
3868
+ switch (part.type) {
3869
+ case "text":
3870
+ return part.text.length;
3871
+ case "reasoning":
3872
+ return Math.round(part.text.length * 0.25);
3873
+ case "file":
3874
+ return (part.filename?.length ?? 0) + (part.source?.text.value.length ?? 0);
3875
+ case "tool":
3876
+ return estimateToolPartChars(part);
3877
+ case "step-start":
3878
+ return part.snapshot?.length ?? 0;
3879
+ case "step-finish":
3880
+ return part.reason.length + (part.snapshot?.length ?? 0);
3881
+ case "snapshot":
3882
+ return part.snapshot.length;
3883
+ case "patch":
3884
+ return part.hash.length + part.files.join("\n").length;
3885
+ case "agent":
3886
+ return part.name.length + (part.source?.value.length ?? 0);
3887
+ case "retry":
3888
+ return estimateUnknownChars(part.error);
3889
+ case "compaction":
3890
+ return 32;
3891
+ case "subtask":
3892
+ return part.prompt.length + part.description.length + part.agent.length;
3893
+ }
3894
+ }
3895
+ function estimateMessageChars(message, parts) {
3896
+ let chars = 0;
3897
+ if (message.role === "user") {
3898
+ chars += message.system?.length ?? 0;
3899
+ if (message.summary) {
3900
+ chars += estimateDiffChars(message.summary);
3901
+ }
3902
+ }
3903
+ for (const part of parts) {
3904
+ chars += estimatePartChars(part);
3905
+ }
3906
+ return chars;
3907
+ }
3908
+ function estimateSessionTokens(messages) {
3909
+ const totalChars = messages.reduce((total, message) => total + estimateMessageChars(message.info, message.parts), 0);
3910
+ return Math.ceil(totalChars / 4);
3911
+ }
3912
+
3819
3913
  // node_modules/zod/v4/classic/external.js
3820
3914
  var external_exports = {};
3821
3915
  __export(external_exports, {
@@ -16238,6 +16332,132 @@ function tool(input) {
16238
16332
  }
16239
16333
  tool.schema = external_exports;
16240
16334
 
16335
+ // src/lib/model-context.ts
16336
+ var FALLBACK_MAX_OUTPUT_CHARS = 8e3;
16337
+ var MIN_BASE_MAX_OUTPUT_CHARS = 4e3;
16338
+ var MAX_BASE_OUTPUT_CHARS = 32e3;
16339
+ var MIN_PRESSURED_OUTPUT_CHARS = 1500;
16340
+ var sessionStates = /* @__PURE__ */ new Map();
16341
+ function getOrCreateSessionState(sessionID) {
16342
+ let state = sessionStates.get(sessionID);
16343
+ if (!state) {
16344
+ state = {
16345
+ contextTokens: null,
16346
+ compactionCount: 0,
16347
+ estimatedTokens: null,
16348
+ pressureRatio: null,
16349
+ lastPressureCheckedAt: null,
16350
+ autoSummarizeInFlight: false,
16351
+ lastAutoSummarizeAt: null,
16352
+ autoSummarizeCount: 0
16353
+ };
16354
+ sessionStates.set(sessionID, state);
16355
+ }
16356
+ return state;
16357
+ }
16358
+ function getSessionState(sessionID) {
16359
+ if (!sessionID) return null;
16360
+ return sessionStates.get(sessionID) ?? null;
16361
+ }
16362
+ function getCompactionPressureFactor(compactionCount) {
16363
+ if (compactionCount >= 3) return 0.35;
16364
+ if (compactionCount === 2) return 0.5;
16365
+ if (compactionCount === 1) return 0.75;
16366
+ return 1;
16367
+ }
16368
+ function getBaseMaxOutputChars(contextTokens, fallback) {
16369
+ if (contextTokens === null) return fallback;
16370
+ const computed = Math.round(contextTokens * 0.01 * 4);
16371
+ return Math.max(MIN_BASE_MAX_OUTPUT_CHARS, Math.min(MAX_BASE_OUTPUT_CHARS, computed));
16372
+ }
16373
+ function buildTruncationSuffix(sessionID, maxChars) {
16374
+ const state = getSessionState(sessionID);
16375
+ if (!state || state.compactionCount === 0) {
16376
+ return `\u8F93\u51FA\u5DF2\u622A\u65AD\uFF0C\u8D85\u51FA ${maxChars} \u5B57\u7B26\u4E0A\u9650`;
16377
+ }
16378
+ return `\u8F93\u51FA\u5DF2\u622A\u65AD\uFF0C\u8D85\u51FA ${maxChars} \u5B57\u7B26\u4E0A\u9650\uFF1B\u5F53\u524D\u4F1A\u8BDD\u5DF2\u538B\u7F29 ${state.compactionCount} \u6B21\uFF0C\u5DF2\u81EA\u52A8\u6536\u7D27\u5DE5\u5177\u8F93\u51FA\u9884\u7B97`;
16379
+ }
16380
+ function setCurrentContextTokens(sessionID, tokens) {
16381
+ if (!sessionID || tokens <= 0) return;
16382
+ const state = getOrCreateSessionState(sessionID);
16383
+ state.contextTokens = tokens;
16384
+ }
16385
+ function markSessionCompacted(sessionID) {
16386
+ if (!sessionID) return;
16387
+ const state = getOrCreateSessionState(sessionID);
16388
+ state.compactionCount += 1;
16389
+ state.estimatedTokens = null;
16390
+ state.pressureRatio = null;
16391
+ state.lastPressureCheckedAt = null;
16392
+ }
16393
+ function getSessionCompactionCount(sessionID) {
16394
+ return getSessionState(sessionID)?.compactionCount ?? 0;
16395
+ }
16396
+ function updateSessionPressure(sessionID, estimatedTokens, pressureRatio, checkedAt = Date.now()) {
16397
+ if (!sessionID) return;
16398
+ const state = getOrCreateSessionState(sessionID);
16399
+ state.estimatedTokens = estimatedTokens;
16400
+ state.pressureRatio = pressureRatio;
16401
+ state.lastPressureCheckedAt = checkedAt;
16402
+ }
16403
+ function clearSessionPressure(sessionID) {
16404
+ if (!sessionID) return;
16405
+ const state = getOrCreateSessionState(sessionID);
16406
+ state.estimatedTokens = null;
16407
+ state.pressureRatio = null;
16408
+ state.lastPressureCheckedAt = null;
16409
+ }
16410
+ function getSessionPressure(sessionID) {
16411
+ const state = getSessionState(sessionID);
16412
+ if (!state || state.estimatedTokens === null || state.pressureRatio === null || state.lastPressureCheckedAt === null) {
16413
+ return null;
16414
+ }
16415
+ return {
16416
+ estimatedTokens: state.estimatedTokens,
16417
+ pressureRatio: state.pressureRatio,
16418
+ checkedAt: state.lastPressureCheckedAt
16419
+ };
16420
+ }
16421
+ function isAutoSummarizeInFlight(sessionID) {
16422
+ return getSessionState(sessionID)?.autoSummarizeInFlight ?? false;
16423
+ }
16424
+ function markAutoSummarizeStarted(sessionID) {
16425
+ if (!sessionID) return;
16426
+ const state = getOrCreateSessionState(sessionID);
16427
+ state.autoSummarizeInFlight = true;
16428
+ }
16429
+ function markAutoSummarizeFinished(sessionID, summarized, finishedAt = Date.now()) {
16430
+ if (!sessionID) return;
16431
+ const state = getOrCreateSessionState(sessionID);
16432
+ state.autoSummarizeInFlight = false;
16433
+ if (summarized) {
16434
+ state.lastAutoSummarizeAt = finishedAt;
16435
+ state.autoSummarizeCount += 1;
16436
+ state.estimatedTokens = null;
16437
+ state.pressureRatio = null;
16438
+ state.lastPressureCheckedAt = null;
16439
+ }
16440
+ }
16441
+ function getLastAutoSummarizeAt(sessionID) {
16442
+ return getSessionState(sessionID)?.lastAutoSummarizeAt ?? null;
16443
+ }
16444
+ function getAutoSummarizeCount(sessionID) {
16445
+ return getSessionState(sessionID)?.autoSummarizeCount ?? 0;
16446
+ }
16447
+ function getMaxOutputChars(sessionID, fallback = FALLBACK_MAX_OUTPUT_CHARS) {
16448
+ const state = getSessionState(sessionID);
16449
+ const base = getBaseMaxOutputChars(state?.contextTokens ?? null, fallback);
16450
+ const pressured = Math.round(base * getCompactionPressureFactor(state?.compactionCount ?? 0));
16451
+ return Math.max(MIN_PRESSURED_OUTPUT_CHARS, pressured);
16452
+ }
16453
+ function truncateToolOutput(content, sessionID, fallback = FALLBACK_MAX_OUTPUT_CHARS) {
16454
+ const maxChars = getMaxOutputChars(sessionID, fallback);
16455
+ if (content.length <= maxChars) return content;
16456
+ const truncated = content.slice(0, maxChars);
16457
+ return `${truncated}
16458
+ \x1B[2m... (${buildTruncationSuffix(sessionID, maxChars)})\x1B[0m`;
16459
+ }
16460
+
16241
16461
  // src/lib/gbk-file.ts
16242
16462
  var import_iconv_lite = __toESM(require_lib(), 1);
16243
16463
  import crypto from "crypto";
@@ -16308,29 +16528,40 @@ async function assertPathAllowed(filePath, context, allowExternal = false) {
16308
16528
  var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
16309
16529
  var STREAM_READ_CHUNK_SIZE_BYTES = 1024 * 1024;
16310
16530
  var gbkLineIndexCache = /* @__PURE__ */ new Map();
16531
+ var ANSI_RED = "\x1B[31m";
16532
+ var ANSI_GREEN = "\x1B[32m";
16533
+ var ANSI_DIM = "\x1B[2m";
16534
+ var ANSI_RESET = "\x1B[0m";
16535
+ var MAX_DIFF_LINES = 80;
16311
16536
  function buildGbkLineDiffPreview(filePath, encoding, beforeText, afterText) {
16312
16537
  const beforeLines = normalizeNewlines(beforeText).split("\n");
16313
16538
  const afterLines = normalizeNewlines(afterText).split("\n");
16314
16539
  const maxLines = Math.max(beforeLines.length, afterLines.length);
16315
- const lines = [
16316
- `--- ${path2.basename(filePath)} (${encoding})`,
16317
- `+++ ${path2.basename(filePath)} (${encoding})`
16540
+ const header = [
16541
+ `${ANSI_DIM}--- ${path2.basename(filePath)} (${encoding})${ANSI_RESET}`,
16542
+ `${ANSI_DIM}+++ ${path2.basename(filePath)} (${encoding})${ANSI_RESET}`
16318
16543
  ];
16544
+ const body = [];
16319
16545
  for (let index = 0; index < maxLines; index += 1) {
16320
16546
  const before = beforeLines[index];
16321
16547
  const after = afterLines[index];
16322
16548
  if (before !== void 0 && after !== void 0 && before === after) {
16323
- lines.push(` ${before}`);
16549
+ body.push(` ${before}`);
16324
16550
  continue;
16325
16551
  }
16326
16552
  if (before !== void 0) {
16327
- lines.push(`-${before}`);
16553
+ body.push(`${ANSI_RED}-${before}${ANSI_RESET}`);
16328
16554
  }
16329
16555
  if (after !== void 0) {
16330
- lines.push(`+${after}`);
16556
+ body.push(`${ANSI_GREEN}+${after}${ANSI_RESET}`);
16331
16557
  }
16332
16558
  }
16333
- return lines.join("\n");
16559
+ if (header.length + body.length <= MAX_DIFF_LINES) {
16560
+ return "\n" + [...header, ...body].join("\n");
16561
+ }
16562
+ const changedOnly = body.filter((l) => l.startsWith(ANSI_RED) || l.startsWith(ANSI_GREEN));
16563
+ const notice = `${ANSI_DIM}... (\u4E0A\u4E0B\u6587\u5DF2\u7701\u7565\uFF0C\u4EC5\u663E\u793A\u53D8\u66F4\u884C)${ANSI_RESET}`;
16564
+ return "\n" + [...header, notice, ...changedOnly].join("\n");
16334
16565
  }
16335
16566
  function toSafeNumber(value) {
16336
16567
  return typeof value === "bigint" ? Number(value) : value;
@@ -16354,10 +16585,10 @@ function assertInsertArguments(input) {
16354
16585
  throw createGbkError("GBK_INVALID_ARGUMENT", "content \u4E0D\u80FD\u4E3A\u7A7A");
16355
16586
  }
16356
16587
  if (input.replaceAll !== void 0) {
16357
- throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\uFFFD\uFFFD? replaceAll");
16588
+ throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\uFFFD? replaceAll");
16358
16589
  }
16359
16590
  if (input.startLine !== void 0 || input.endLine !== void 0 || input.startAnchor !== void 0 || input.endAnchor !== void 0) {
16360
- throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\uFFFD\uFFFD? startLine/endLine/startAnchor/endAnchor");
16591
+ throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\uFFFD? startLine/endLine/startAnchor/endAnchor");
16361
16592
  }
16362
16593
  }
16363
16594
  function findOccurrenceIndex(text, token, occurrence) {
@@ -16381,9 +16612,9 @@ function findOccurrenceIndex(text, token, occurrence) {
16381
16612
  searchFrom = index + token.length;
16382
16613
  }
16383
16614
  if (count === 0) {
16384
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\uFFFD\uFFFD?: ${token}`);
16615
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\uFFFD?: ${token}`);
16385
16616
  }
16386
- throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\uFFFD\uFFFD? ${count} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\uFFFD\uFFFD? ${occurrence} \u5904`);
16617
+ throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\uFFFD? ${count} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\uFFFD? ${occurrence} \u5904`);
16387
16618
  }
16388
16619
  function insertByAnchor(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
16389
16620
  const alignedContent = newlineStyle === "crlf" ? content.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n") : newlineStyle === "lf" ? content.replace(/\r\n/g, "\n") : content;
@@ -16392,7 +16623,7 @@ function insertByAnchor(text, mode, anchor, content, occurrence, ifExists, newli
16392
16623
  const alreadyExists = mode === "insertAfter" ? text.slice(insertionPoint, insertionPoint + alignedContent.length) === alignedContent : text.slice(Math.max(0, insertionPoint - alignedContent.length), insertionPoint) === alignedContent;
16393
16624
  if (alreadyExists) {
16394
16625
  if (ifExists === "error") {
16395
- throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\uFFFD\uFFFD?");
16626
+ throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\uFFFD?");
16396
16627
  }
16397
16628
  if (ifExists === "skip") {
16398
16629
  return {
@@ -16433,7 +16664,7 @@ function normalizeOptionalPositiveInteger(value, name) {
16433
16664
  }
16434
16665
  function assertNotBinary(buffer) {
16435
16666
  if (buffer.includes(0)) {
16436
- throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\uFFFD\uFFFD? GBK \u6587\u672C\u5904\u7406");
16667
+ throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\uFFFD? GBK \u6587\u672C\u5904\u7406");
16437
16668
  }
16438
16669
  }
16439
16670
  function splitLinesWithNumbers(text, offset = 1, limit = 2e3) {
@@ -16563,14 +16794,14 @@ function applyAnchors(text, startAnchor, endAnchor) {
16563
16794
  if (startAnchor) {
16564
16795
  const found = text.indexOf(startAnchor);
16565
16796
  if (found === -1) {
16566
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\uFFFD\uFFFD?: ${startAnchor}`);
16797
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\uFFFD?: ${startAnchor}`);
16567
16798
  }
16568
16799
  rangeStart = found + startAnchor.length;
16569
16800
  }
16570
16801
  if (endAnchor) {
16571
16802
  const found = text.indexOf(endAnchor, rangeStart);
16572
16803
  if (found === -1) {
16573
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u7ED3\u675F\u951A\uFFFD\uFFFD?: ${endAnchor}`);
16804
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u7ED3\u675F\u951A\uFFFD?: ${endAnchor}`);
16574
16805
  }
16575
16806
  rangeEnd = found;
16576
16807
  }
@@ -16623,9 +16854,9 @@ function getNearestContext(content, oldString) {
16623
16854
  }
16624
16855
  function buildNoMatchMessage(content, oldString) {
16625
16856
  return [
16626
- "\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\uFFFD\uFFFD?",
16627
- "oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\uFF0C\u6216\u4EC5\u5728\u6362\uFFFD\uFFFD?/\u5C3E\u968F\u7A7A\u884C\u4E0A\u5B58\u5728\u8F7B\u5FAE\u5DEE\u5F02\uFFFD\uFFFD?",
16628
- "\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFFFD\uFFFD?",
16857
+ "\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\uFFFD?",
16858
+ "oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\uFF0C\u6216\u4EC5\u5728\u6362\uFFFD?/\u5C3E\u968F\u7A7A\u884C\u4E0A\u5B58\u5728\u8F7B\u5FAE\u5DEE\u5F02\uFFFD?",
16859
+ "\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFFFD?",
16629
16860
  getNearestContext(content, oldString)
16630
16861
  ].join("\n");
16631
16862
  }
@@ -16748,10 +16979,10 @@ async function resolveReadableGbkFile(input) {
16748
16979
  try {
16749
16980
  stat = await fs2.stat(candidatePath);
16750
16981
  } catch (error45) {
16751
- throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\uFFFD\uFFFD?: ${candidatePath}`, error45);
16982
+ throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\uFFFD?: ${candidatePath}`, error45);
16752
16983
  }
16753
16984
  if (stat.isDirectory()) {
16754
- throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\uFFFD\uFFFD?: ${candidatePath}`);
16985
+ throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\uFFFD?: ${candidatePath}`);
16755
16986
  }
16756
16987
  return {
16757
16988
  filePath: candidatePath,
@@ -16956,7 +17187,7 @@ function replaceScopedTextContent(scopeText, oldString, newString, replaceAll, n
16956
17187
  } else if (occurrencesBefore === 0) {
16957
17188
  throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scopeText, oldString));
16958
17189
  } else if (occurrencesBefore > 1) {
16959
- throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\uFFFD\uFFFD?: ${oldString}`);
17190
+ throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\uFFFD?: ${oldString}`);
16960
17191
  }
16961
17192
  const alignedNewString = alignTextToNewlineStyle(newString, newlineStyle);
16962
17193
  return {
@@ -17061,7 +17292,7 @@ async function replaceLargeGbkFileByAnchor(input) {
17061
17292
  const alreadyExists = input.mode === "insertAfter" ? after.startsWith(alignedContent) : before.endsWith(alignedContent);
17062
17293
  if (alreadyExists) {
17063
17294
  if (input.ifExists === "error") {
17064
- throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\uFFFD\uFFFD?");
17295
+ throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\uFFFD?");
17065
17296
  }
17066
17297
  if (input.ifExists === "skip") {
17067
17298
  skipped = true;
@@ -17108,7 +17339,7 @@ async function replaceLargeGbkFileByAnchor(input) {
17108
17339
  const alreadyExists = input.mode === "insertAfter" ? after.startsWith(alignedContent) : before.endsWith(alignedContent);
17109
17340
  if (alreadyExists) {
17110
17341
  if (input.ifExists === "error") {
17111
- throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\uFFFD\uFFFD?");
17342
+ throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\uFFFD?");
17112
17343
  }
17113
17344
  if (input.ifExists === "skip") {
17114
17345
  skipped = true;
@@ -17122,10 +17353,10 @@ async function replaceLargeGbkFileByAnchor(input) {
17122
17353
  }
17123
17354
  }
17124
17355
  if (!inserted && totalMatches === 0) {
17125
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\uFFFD\uFFFD?: ${input.anchor}`);
17356
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\uFFFD?: ${input.anchor}`);
17126
17357
  }
17127
17358
  if (!inserted && totalMatches > 0) {
17128
- throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${input.anchor} \u53EA\u627E\uFFFD\uFFFD? ${totalMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\uFFFD\uFFFD? ${input.occurrence} \u5904`);
17359
+ throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${input.anchor} \u53EA\u627E\uFFFD? ${totalMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\uFFFD? ${input.occurrence} \u5904`);
17129
17360
  }
17130
17361
  await finalizeInserted();
17131
17362
  await handle.close();
@@ -17188,10 +17419,10 @@ async function replaceLargeGbkFileText(input) {
17188
17419
  });
17189
17420
  await flushText("", true);
17190
17421
  if (occurrencesBefore === 0) {
17191
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\uFFFD\uFFFD?: ${input.oldString}`);
17422
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\uFFFD?: ${input.oldString}`);
17192
17423
  }
17193
17424
  if (!input.replaceAll && occurrencesBefore > 1) {
17194
- throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\uFFFD\uFFFD?: ${input.oldString}`);
17425
+ throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\uFFFD?: ${input.oldString}`);
17195
17426
  }
17196
17427
  await handle.close();
17197
17428
  const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
@@ -17417,7 +17648,7 @@ async function replaceGbkFileText(input) {
17417
17648
  } else if (occurrencesBefore === 0) {
17418
17649
  throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, effectiveOldString));
17419
17650
  } else if (occurrencesBefore > 1) {
17420
- throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\uFFFD\uFFFD?: ${effectiveOldString}`);
17651
+ throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\uFFFD?: ${effectiveOldString}`);
17421
17652
  }
17422
17653
  const fileNewlineStyle = detectNewlineStyle(current.content);
17423
17654
  const alignedNewString = fileNewlineStyle === "crlf" ? input.newString.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n") : fileNewlineStyle === "lf" ? input.newString.replace(/\r\n/g, "\n") : input.newString;
@@ -17550,10 +17781,10 @@ async function writeGbkFile(input) {
17550
17781
  try {
17551
17782
  const stat = await fs2.stat(candidatePath);
17552
17783
  if (stat.isDirectory()) {
17553
- throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\uFFFD\uFFFD?: ${candidatePath}`);
17784
+ throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\uFFFD?: ${candidatePath}`);
17554
17785
  }
17555
17786
  if (!overwrite) {
17556
- throw createGbkError("GBK_FILE_EXISTS", `\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\uFFFD\uFFFD?: ${candidatePath}`);
17787
+ throw createGbkError("GBK_FILE_EXISTS", `\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\uFFFD?: ${candidatePath}`);
17557
17788
  }
17558
17789
  } catch (error45) {
17559
17790
  if (error45 instanceof Error && "code" in error45) {
@@ -17669,8 +17900,8 @@ Insert mode:
17669
17900
  diffPreview
17670
17901
  }
17671
17902
  });
17672
- if (diffPreview) return diffPreview;
17673
- return JSON.stringify(result, null, 2);
17903
+ if (diffPreview) return truncateToolOutput(diffPreview, context.sessionID);
17904
+ return truncateToolOutput(JSON.stringify(result, null, 2), context.sessionID);
17674
17905
  }
17675
17906
  });
17676
17907
 
@@ -18051,29 +18282,40 @@ function assertInsertArguments2(input) {
18051
18282
  throw createTextError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 startLine/endLine/startAnchor/endAnchor");
18052
18283
  }
18053
18284
  }
18285
+ var ANSI_RED2 = "\x1B[31m";
18286
+ var ANSI_GREEN2 = "\x1B[32m";
18287
+ var ANSI_DIM2 = "\x1B[2m";
18288
+ var ANSI_RESET2 = "\x1B[0m";
18289
+ var MAX_DIFF_LINES2 = 80;
18054
18290
  function buildLineDiffPreview(filePath, encoding, beforeText, afterText) {
18055
18291
  const beforeLines = normalizeNewlines2(beforeText).split("\n");
18056
18292
  const afterLines = normalizeNewlines2(afterText).split("\n");
18057
18293
  const maxLines = Math.max(beforeLines.length, afterLines.length);
18058
- const lines = [
18059
- `--- ${path3.basename(filePath)} (${encoding})`,
18060
- `+++ ${path3.basename(filePath)} (${encoding})`
18294
+ const header = [
18295
+ `${ANSI_DIM2}--- ${path3.basename(filePath)} (${encoding})${ANSI_RESET2}`,
18296
+ `${ANSI_DIM2}+++ ${path3.basename(filePath)} (${encoding})${ANSI_RESET2}`
18061
18297
  ];
18298
+ const body = [];
18062
18299
  for (let index = 0; index < maxLines; index += 1) {
18063
18300
  const before = beforeLines[index];
18064
18301
  const after = afterLines[index];
18065
18302
  if (before !== void 0 && after !== void 0 && before === after) {
18066
- lines.push(` ${before}`);
18303
+ body.push(` ${before}`);
18067
18304
  continue;
18068
18305
  }
18069
18306
  if (before !== void 0) {
18070
- lines.push(`-${before}`);
18307
+ body.push(`${ANSI_RED2}-${before}${ANSI_RESET2}`);
18071
18308
  }
18072
18309
  if (after !== void 0) {
18073
- lines.push(`+${after}`);
18310
+ body.push(`${ANSI_GREEN2}+${after}${ANSI_RESET2}`);
18074
18311
  }
18075
18312
  }
18076
- return lines.join("\n");
18313
+ if (header.length + body.length <= MAX_DIFF_LINES2) {
18314
+ return "\n" + [...header, ...body].join("\n");
18315
+ }
18316
+ const changedOnly = body.filter((l) => l.startsWith(ANSI_RED2) || l.startsWith(ANSI_GREEN2));
18317
+ const notice = `${ANSI_DIM2}... (\u4E0A\u4E0B\u6587\u5DF2\u7701\u7565\uFF0C\u4EC5\u663E\u793A\u53D8\u66F4\u884C)${ANSI_RESET2}`;
18318
+ return "\n" + [...header, notice, ...changedOnly].join("\n");
18077
18319
  }
18078
18320
  function buildInsertOutput(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
18079
18321
  const alignedContent = alignTextToNewlineStyle2(content, newlineStyle);
@@ -18720,7 +18962,7 @@ async function replaceTextFileText(input) {
18720
18962
  bytesRead: loaded.bytesRead,
18721
18963
  bytesWritten: buffer.byteLength,
18722
18964
  newlineStyle: detectNewlineStyle(outputText),
18723
- diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding, scope.selectedText, replaced)
18965
+ diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding, input.oldString, alignedNewString)
18724
18966
  };
18725
18967
  }
18726
18968
 
@@ -18775,8 +19017,8 @@ var text_edit_default = tool({
18775
19017
  diffPreview
18776
19018
  }
18777
19019
  });
18778
- if (diffPreview) return diffPreview;
18779
- return JSON.stringify(result, null, 2);
19020
+ if (diffPreview) return truncateToolOutput(diffPreview, context.sessionID);
19021
+ return truncateToolOutput(JSON.stringify(result, null, 2), context.sessionID);
18780
19022
  }
18781
19023
  });
18782
19024
 
@@ -18857,7 +19099,66 @@ var text_write_default = tool({
18857
19099
  });
18858
19100
 
18859
19101
  // src/plugin/index.ts
18860
- function createOpencodeGbkHooks() {
19102
+ var MANAGED_TOOL_IDS = /* @__PURE__ */ new Set([
19103
+ "gbk_read",
19104
+ "gbk_write",
19105
+ "gbk_edit",
19106
+ "gbk_search",
19107
+ "text_read",
19108
+ "text_write",
19109
+ "text_edit"
19110
+ ]);
19111
+ function truncateMetadataPreview(value, sessionID) {
19112
+ const previewMaxChars = Math.max(800, Math.min(2e3, Math.floor(getMaxOutputChars(sessionID) / 2)));
19113
+ if (value.length <= previewMaxChars) return value;
19114
+ const truncated = value.slice(0, previewMaxChars);
19115
+ return `${truncated}
19116
+ \x1B[2m... (metadata diffPreview \u5DF2\u622A\u65AD\uFF0C\u8D85\u51FA ${previewMaxChars} \u5B57\u7B26\u4E0A\u9650)\x1B[0m`;
19117
+ }
19118
+ async function maybeAutoSummarizeSession(client, directory, input) {
19119
+ if (!client?.session?.messages || !client.session.summarize) return;
19120
+ if (isAutoSummarizeInFlight(input.sessionID)) return;
19121
+ const contextTokens = input.model.limit?.context;
19122
+ if (typeof contextTokens !== "number" || contextTokens <= 0) return;
19123
+ const now = Date.now();
19124
+ const lastAutoSummarizeAt = getLastAutoSummarizeAt(input.sessionID);
19125
+ if (lastAutoSummarizeAt !== null && now - lastAutoSummarizeAt < AUTO_SUMMARIZE_COOLDOWN_MS) {
19126
+ return;
19127
+ }
19128
+ let pressure = getSessionPressure(input.sessionID);
19129
+ if (!pressure || now - pressure.checkedAt >= PRESSURE_CHECK_INTERVAL_MS) {
19130
+ const response = await client.session.messages({
19131
+ path: { id: input.sessionID },
19132
+ query: {
19133
+ directory,
19134
+ limit: SESSION_PRESSURE_MESSAGE_LIMIT
19135
+ },
19136
+ throwOnError: true
19137
+ });
19138
+ const estimatedTokens = estimateSessionTokens(Array.isArray(response.data) ? response.data : []);
19139
+ const pressureRatio = estimatedTokens / contextTokens;
19140
+ updateSessionPressure(input.sessionID, estimatedTokens, pressureRatio, now);
19141
+ pressure = getSessionPressure(input.sessionID);
19142
+ }
19143
+ if (!pressure || pressure.pressureRatio < AUTO_SUMMARIZE_PRESSURE_RATIO) return;
19144
+ markAutoSummarizeStarted(input.sessionID);
19145
+ try {
19146
+ await client.session.summarize({
19147
+ path: { id: input.sessionID },
19148
+ body: {
19149
+ providerID: input.model.providerID,
19150
+ modelID: input.model.id
19151
+ },
19152
+ query: { directory },
19153
+ throwOnError: true
19154
+ });
19155
+ clearSessionPressure(input.sessionID);
19156
+ markAutoSummarizeFinished(input.sessionID, true);
19157
+ } catch {
19158
+ markAutoSummarizeFinished(input.sessionID, false);
19159
+ }
19160
+ }
19161
+ function createOpencodeGbkHooks(client, directory) {
18861
19162
  return {
18862
19163
  tool: {
18863
19164
  gbk_read: gbk_read_default,
@@ -18868,15 +19169,63 @@ function createOpencodeGbkHooks() {
18868
19169
  text_write: text_write_default,
18869
19170
  text_edit: text_edit_default
18870
19171
  },
19172
+ async event(input) {
19173
+ if (input.event.type === "session.compacted") {
19174
+ const sessionID = input.event.properties.sessionID;
19175
+ if (sessionID) {
19176
+ markSessionCompacted(sessionID);
19177
+ }
19178
+ }
19179
+ },
19180
+ async "chat.params"(input) {
19181
+ const contextTokens = input.model?.limit?.context;
19182
+ if (typeof contextTokens === "number" && contextTokens > 0) {
19183
+ setCurrentContextTokens(input.sessionID, contextTokens);
19184
+ await maybeAutoSummarizeSession(client, directory, input);
19185
+ }
19186
+ },
19187
+ async "tool.execute.after"(input, output) {
19188
+ if (!MANAGED_TOOL_IDS.has(input.tool)) return;
19189
+ const maxOutputChars = getMaxOutputChars(input.sessionID);
19190
+ const compactionCount = getSessionCompactionCount(input.sessionID);
19191
+ const sessionPressure = getSessionPressure(input.sessionID);
19192
+ const autoSummarizeCount = getAutoSummarizeCount(input.sessionID);
19193
+ const metadata = output.metadata && typeof output.metadata === "object" ? { ...output.metadata } : {};
19194
+ const nextOutput = truncateToolOutput(output.output, input.sessionID);
19195
+ if (nextOutput !== output.output) {
19196
+ metadata.outputTruncated = true;
19197
+ output.output = nextOutput;
19198
+ }
19199
+ if (typeof metadata.diffPreview === "string") {
19200
+ metadata.diffPreview = truncateMetadataPreview(metadata.diffPreview, input.sessionID);
19201
+ }
19202
+ metadata.maxOutputChars = maxOutputChars;
19203
+ if (compactionCount > 0) {
19204
+ metadata.sessionCompactions = compactionCount;
19205
+ }
19206
+ if (sessionPressure) {
19207
+ metadata.estimatedSessionTokens = sessionPressure.estimatedTokens;
19208
+ metadata.sessionPressureRatio = Number(sessionPressure.pressureRatio.toFixed(3));
19209
+ }
19210
+ if (autoSummarizeCount > 0) {
19211
+ metadata.autoSummarizeCount = autoSummarizeCount;
19212
+ }
19213
+ output.metadata = metadata;
19214
+ },
18871
19215
  async "experimental.chat.system.transform"(_input, output) {
18872
19216
  appendTextToolSystemPrompt(output.system);
19217
+ },
19218
+ async "experimental.session.compacting"(_input, output) {
19219
+ output.context.push(
19220
+ "Aggressively compress prior tool outputs. Keep only unresolved tasks, final decisions, exact file paths or line ranges, and the smallest snippets needed to continue. Drop repeated raw file content, full JSON payloads, verbose logs, and duplicated diff previews."
19221
+ );
18873
19222
  }
18874
19223
  };
18875
19224
  }
18876
19225
 
18877
19226
  // src/local-plugin/opencode-gbk-tools.ts
18878
- var OpencodeGbkToolsLocalPlugin = async () => {
18879
- return createOpencodeGbkHooks();
19227
+ var OpencodeGbkToolsLocalPlugin = async (ctx) => {
19228
+ return createOpencodeGbkHooks(ctx.client, ctx.directory);
18880
19229
  };
18881
19230
  export {
18882
19231
  OpencodeGbkToolsLocalPlugin
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "manifestVersion": 1,
3
3
  "packageName": "opencode-gbk-tools",
4
- "packageVersion": "0.1.21",
4
+ "packageVersion": "0.1.24",
5
5
  "artifacts": [
6
6
  {
7
7
  "relativePath": "plugins/opencode-gbk-tools.js",
8
8
  "kind": "plugin",
9
- "expectedHash": "d0407b86c21d2515bf4642c3bbede9bc26083224d316f19c3d40decb7fea4461",
9
+ "expectedHash": "55f2eae379bf3bd5a9129fbc413f255a4ffd9186d90a4fe26ecb3cc75b5a4c68",
10
10
  "hashAlgorithm": "sha256"
11
11
  }
12
12
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gbk-tools",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Auto-encoding text tools plus GBK/GB18030 tools for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin/index.js",