opencode-gbk-tools 0.1.28 → 0.1.30

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,9 @@ var require_lib = __commonJS({
3816
3816
  }
3817
3817
  });
3818
3818
 
3819
+ // src/plugin/index.ts
3820
+ import path5 from "path";
3821
+
3819
3822
  // src/lib/session-pressure.ts
3820
3823
  var AUTO_SUMMARIZE_PRESSURE_RATIO = 0.85;
3821
3824
  var AUTO_SUMMARIZE_COOLDOWN_MS = 6e4;
@@ -3910,6 +3913,200 @@ function estimateSessionTokens(messages) {
3910
3913
  return Math.ceil(totalChars / 4);
3911
3914
  }
3912
3915
 
3916
+ // src/lib/encoding-memory.ts
3917
+ import fs from "fs/promises";
3918
+ import os from "os";
3919
+ import path from "path";
3920
+ var ENCODING_MEMORY_VERSION = 1;
3921
+ var ENCODING_MEMORY_FILE_NAME = "encoding-memory.json";
3922
+ var CONFIG_DIR_ENV = "OPENCODE_GBK_TOOLS_CONFIG_DIR";
3923
+ var memoryCache = null;
3924
+ var loadingPromise = null;
3925
+ function resolveConfigDirectory() {
3926
+ const override = process.env[CONFIG_DIR_ENV];
3927
+ if (typeof override === "string" && override.trim().length > 0) {
3928
+ return path.resolve(override);
3929
+ }
3930
+ if (process.platform === "win32") {
3931
+ return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "opencode-gbk-tools");
3932
+ }
3933
+ if (process.platform === "darwin") {
3934
+ return path.join(os.homedir(), "Library", "Application Support", "opencode-gbk-tools");
3935
+ }
3936
+ return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "opencode-gbk-tools");
3937
+ }
3938
+ function normalizeFilePath(filePath) {
3939
+ const resolved = path.normalize(path.resolve(filePath));
3940
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
3941
+ }
3942
+ function toStoredFilePath(filePath) {
3943
+ return path.normalize(path.resolve(filePath));
3944
+ }
3945
+ function isRememberedEntry(value) {
3946
+ if (!value || typeof value !== "object") {
3947
+ return false;
3948
+ }
3949
+ const entry = value;
3950
+ return typeof entry.filePath === "string" && isRememberedGbkEncoding(entry.encoding) && typeof entry.mtimeMs === "number" && Number.isFinite(entry.mtimeMs) && typeof entry.size === "number" && Number.isFinite(entry.size) && typeof entry.lastConfirmedAt === "number" && Number.isFinite(entry.lastConfirmedAt);
3951
+ }
3952
+ async function readEncodingMemoryMap() {
3953
+ if (memoryCache) {
3954
+ return memoryCache;
3955
+ }
3956
+ if (loadingPromise) {
3957
+ return loadingPromise;
3958
+ }
3959
+ loadingPromise = (async () => {
3960
+ const map2 = /* @__PURE__ */ new Map();
3961
+ try {
3962
+ const raw = await fs.readFile(getEncodingMemoryFilePath(), "utf8");
3963
+ const parsed = JSON.parse(raw);
3964
+ const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
3965
+ for (const entry of entries) {
3966
+ if (isRememberedEntry(entry)) {
3967
+ map2.set(normalizeFilePath(entry.filePath), entry);
3968
+ }
3969
+ }
3970
+ } catch (error45) {
3971
+ if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
3972
+ throw error45;
3973
+ }
3974
+ }
3975
+ memoryCache = map2;
3976
+ loadingPromise = null;
3977
+ return map2;
3978
+ })();
3979
+ return loadingPromise;
3980
+ }
3981
+ async function persistEncodingMemoryMap(map2) {
3982
+ const entries = [...map2.values()].sort((a, b) => a.filePath.localeCompare(b.filePath));
3983
+ const payload = {
3984
+ version: ENCODING_MEMORY_VERSION,
3985
+ entries
3986
+ };
3987
+ const filePath = getEncodingMemoryFilePath();
3988
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
3989
+ await fs.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
3990
+ }
3991
+ async function statRegularFile(filePath) {
3992
+ try {
3993
+ const stat = await fs.stat(filePath);
3994
+ return stat.isFile() ? stat : null;
3995
+ } catch (error45) {
3996
+ if (error45 instanceof Error && "code" in error45 && error45.code === "ENOENT") {
3997
+ return null;
3998
+ }
3999
+ throw error45;
4000
+ }
4001
+ }
4002
+ function isRememberedGbkEncoding(encoding) {
4003
+ return encoding === "gbk" || encoding === "gb18030";
4004
+ }
4005
+ function getEncodingMemoryFilePath() {
4006
+ return path.join(resolveConfigDirectory(), ENCODING_MEMORY_FILE_NAME);
4007
+ }
4008
+ async function rememberGbkEncoding(filePath, encoding) {
4009
+ const stat = await statRegularFile(filePath);
4010
+ if (!stat) {
4011
+ return null;
4012
+ }
4013
+ const map2 = await readEncodingMemoryMap();
4014
+ const entry = {
4015
+ filePath: toStoredFilePath(filePath),
4016
+ encoding,
4017
+ mtimeMs: stat.mtimeMs,
4018
+ size: Number(stat.size),
4019
+ lastConfirmedAt: Date.now()
4020
+ };
4021
+ map2.set(normalizeFilePath(filePath), entry);
4022
+ await persistEncodingMemoryMap(map2);
4023
+ return entry;
4024
+ }
4025
+ async function forgetRememberedEncoding(filePath) {
4026
+ const map2 = await readEncodingMemoryMap();
4027
+ const deleted = map2.delete(normalizeFilePath(filePath));
4028
+ if (deleted) {
4029
+ await persistEncodingMemoryMap(map2);
4030
+ }
4031
+ return deleted;
4032
+ }
4033
+ async function getRememberedGbkEncoding(filePath) {
4034
+ const map2 = await readEncodingMemoryMap();
4035
+ const key = normalizeFilePath(filePath);
4036
+ const entry = map2.get(key);
4037
+ if (!entry) {
4038
+ return null;
4039
+ }
4040
+ const stat = await statRegularFile(filePath);
4041
+ if (!stat || stat.mtimeMs !== entry.mtimeMs || Number(stat.size) !== entry.size) {
4042
+ map2.delete(key);
4043
+ await persistEncodingMemoryMap(map2);
4044
+ return null;
4045
+ }
4046
+ return entry;
4047
+ }
4048
+
4049
+ // src/lib/path-sandbox.ts
4050
+ import fs2 from "fs/promises";
4051
+ import path2 from "path";
4052
+
4053
+ // src/lib/errors.ts
4054
+ var GbkToolError = class extends Error {
4055
+ code;
4056
+ cause;
4057
+ constructor(code, message, cause) {
4058
+ super(message);
4059
+ this.name = "GbkToolError";
4060
+ this.code = code;
4061
+ this.cause = cause;
4062
+ }
4063
+ };
4064
+ function createGbkError(code, message, cause) {
4065
+ return new GbkToolError(code, message, cause);
4066
+ }
4067
+ var createTextError = createGbkError;
4068
+
4069
+ // src/lib/path-sandbox.ts
4070
+ function resolveBaseDirectory(context) {
4071
+ const value = context.directory ?? process.cwd();
4072
+ return path2.isAbsolute(value) ? value : path2.resolve(process.cwd(), value);
4073
+ }
4074
+ function resolveWorkspaceRoot(context) {
4075
+ const value = context.worktree ?? context.directory ?? process.cwd();
4076
+ return path2.isAbsolute(value) ? value : path2.resolve(process.cwd(), value);
4077
+ }
4078
+ function resolveCandidatePath(filePath, context) {
4079
+ return path2.resolve(resolveBaseDirectory(context), filePath);
4080
+ }
4081
+ async function resolveExistingAnchor(filePath) {
4082
+ let current = filePath;
4083
+ while (true) {
4084
+ try {
4085
+ return await fs2.realpath(current);
4086
+ } catch {
4087
+ const parent = path2.dirname(current);
4088
+ if (parent === current) {
4089
+ throw createGbkError("GBK_IO_ERROR", `\u65E0\u6CD5\u89E3\u6790\u8DEF\u5F84\u951A\u70B9: ${filePath}`);
4090
+ }
4091
+ current = parent;
4092
+ }
4093
+ }
4094
+ }
4095
+ async function assertPathAllowed(filePath, context, allowExternal = false) {
4096
+ const candidatePath = resolveCandidatePath(filePath, context);
4097
+ const workspaceRoot = resolveWorkspaceRoot(context);
4098
+ if (!allowExternal) {
4099
+ const realWorkspaceRoot = await fs2.realpath(workspaceRoot);
4100
+ const realCandidateAnchor = await resolveExistingAnchor(candidatePath);
4101
+ const relative = path2.relative(realWorkspaceRoot, realCandidateAnchor);
4102
+ if (relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative)) {
4103
+ return { candidatePath, workspaceRoot };
4104
+ }
4105
+ throw createGbkError("GBK_PATH_OUTSIDE_ROOT", `\u76EE\u6807\u8DEF\u5F84\u8D85\u51FA\u5DE5\u4F5C\u76EE\u5F55\u8303\u56F4: ${candidatePath}`);
4106
+ }
4107
+ return { candidatePath, workspaceRoot };
4108
+ }
4109
+
3913
4110
  // node_modules/zod/v4/classic/external.js
3914
4111
  var external_exports = {};
3915
4112
  __export(external_exports, {
@@ -4641,10 +4838,10 @@ function mergeDefs(...defs) {
4641
4838
  function cloneDef(schema) {
4642
4839
  return mergeDefs(schema._zod.def);
4643
4840
  }
4644
- function getElementAtPath(obj, path4) {
4645
- if (!path4)
4841
+ function getElementAtPath(obj, path6) {
4842
+ if (!path6)
4646
4843
  return obj;
4647
- return path4.reduce((acc, key) => acc?.[key], obj);
4844
+ return path6.reduce((acc, key) => acc?.[key], obj);
4648
4845
  }
4649
4846
  function promiseAllObject(promisesObj) {
4650
4847
  const keys = Object.keys(promisesObj);
@@ -5005,11 +5202,11 @@ function aborted(x, startIndex = 0) {
5005
5202
  }
5006
5203
  return false;
5007
5204
  }
5008
- function prefixIssues(path4, issues) {
5205
+ function prefixIssues(path6, issues) {
5009
5206
  return issues.map((iss) => {
5010
5207
  var _a;
5011
5208
  (_a = iss).path ?? (_a.path = []);
5012
- iss.path.unshift(path4);
5209
+ iss.path.unshift(path6);
5013
5210
  return iss;
5014
5211
  });
5015
5212
  }
@@ -5177,7 +5374,7 @@ function treeifyError(error45, _mapper) {
5177
5374
  return issue2.message;
5178
5375
  };
5179
5376
  const result = { errors: [] };
5180
- const processError = (error46, path4 = []) => {
5377
+ const processError = (error46, path6 = []) => {
5181
5378
  var _a, _b;
5182
5379
  for (const issue2 of error46.issues) {
5183
5380
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -5187,7 +5384,7 @@ function treeifyError(error45, _mapper) {
5187
5384
  } else if (issue2.code === "invalid_element") {
5188
5385
  processError({ issues: issue2.issues }, issue2.path);
5189
5386
  } else {
5190
- const fullpath = [...path4, ...issue2.path];
5387
+ const fullpath = [...path6, ...issue2.path];
5191
5388
  if (fullpath.length === 0) {
5192
5389
  result.errors.push(mapper(issue2));
5193
5390
  continue;
@@ -5219,8 +5416,8 @@ function treeifyError(error45, _mapper) {
5219
5416
  }
5220
5417
  function toDotPath(_path) {
5221
5418
  const segs = [];
5222
- const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5223
- for (const seg of path4) {
5419
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5420
+ for (const seg of path6) {
5224
5421
  if (typeof seg === "number")
5225
5422
  segs.push(`[${seg}]`);
5226
5423
  else if (typeof seg === "symbol")
@@ -16462,69 +16659,8 @@ function truncateToolOutput(content, sessionID, fallback = FALLBACK_MAX_OUTPUT_C
16462
16659
  var import_iconv_lite = __toESM(require_lib(), 1);
16463
16660
  import crypto from "crypto";
16464
16661
  import { createReadStream } from "fs";
16465
- import fs2 from "fs/promises";
16466
- import path2 from "path";
16467
-
16468
- // src/lib/errors.ts
16469
- var GbkToolError = class extends Error {
16470
- code;
16471
- cause;
16472
- constructor(code, message, cause) {
16473
- super(message);
16474
- this.name = "GbkToolError";
16475
- this.code = code;
16476
- this.cause = cause;
16477
- }
16478
- };
16479
- function createGbkError(code, message, cause) {
16480
- return new GbkToolError(code, message, cause);
16481
- }
16482
- var createTextError = createGbkError;
16483
-
16484
- // src/lib/path-sandbox.ts
16485
- import fs from "fs/promises";
16486
- import path from "path";
16487
- function resolveBaseDirectory(context) {
16488
- const value = context.directory ?? process.cwd();
16489
- return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
16490
- }
16491
- function resolveWorkspaceRoot(context) {
16492
- const value = context.worktree ?? context.directory ?? process.cwd();
16493
- return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
16494
- }
16495
- function resolveCandidatePath(filePath, context) {
16496
- return path.resolve(resolveBaseDirectory(context), filePath);
16497
- }
16498
- async function resolveExistingAnchor(filePath) {
16499
- let current = filePath;
16500
- while (true) {
16501
- try {
16502
- return await fs.realpath(current);
16503
- } catch {
16504
- const parent = path.dirname(current);
16505
- if (parent === current) {
16506
- throw createGbkError("GBK_IO_ERROR", `\u65E0\u6CD5\u89E3\u6790\u8DEF\u5F84\u951A\u70B9: ${filePath}`);
16507
- }
16508
- current = parent;
16509
- }
16510
- }
16511
- }
16512
- async function assertPathAllowed(filePath, context, allowExternal = false) {
16513
- const candidatePath = resolveCandidatePath(filePath, context);
16514
- const workspaceRoot = resolveWorkspaceRoot(context);
16515
- if (!allowExternal) {
16516
- const realWorkspaceRoot = await fs.realpath(workspaceRoot);
16517
- const realCandidateAnchor = await resolveExistingAnchor(candidatePath);
16518
- const relative = path.relative(realWorkspaceRoot, realCandidateAnchor);
16519
- if (relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative)) {
16520
- return { candidatePath, workspaceRoot };
16521
- }
16522
- throw createGbkError("GBK_PATH_OUTSIDE_ROOT", `\u76EE\u6807\u8DEF\u5F84\u8D85\u51FA\u5DE5\u4F5C\u76EE\u5F55\u8303\u56F4: ${candidatePath}`);
16523
- }
16524
- return { candidatePath, workspaceRoot };
16525
- }
16526
-
16527
- // src/lib/gbk-file.ts
16662
+ import fs3 from "fs/promises";
16663
+ import path3 from "path";
16528
16664
  var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
16529
16665
  var STREAM_READ_CHUNK_SIZE_BYTES = 1024 * 1024;
16530
16666
  var gbkLineIndexCache = /* @__PURE__ */ new Map();
@@ -16538,8 +16674,8 @@ function buildGbkLineDiffPreview(filePath, encoding, beforeText, afterText) {
16538
16674
  const afterLines = normalizeNewlines(afterText).split("\n");
16539
16675
  const maxLines = Math.max(beforeLines.length, afterLines.length);
16540
16676
  const header = [
16541
- `${ANSI_DIM}--- ${path2.basename(filePath)} (${encoding})${ANSI_RESET}`,
16542
- `${ANSI_DIM}+++ ${path2.basename(filePath)} (${encoding})${ANSI_RESET}`
16677
+ `${ANSI_DIM}--- ${path3.basename(filePath)} (${encoding})${ANSI_RESET}`,
16678
+ `${ANSI_DIM}+++ ${path3.basename(filePath)} (${encoding})${ANSI_RESET}`
16543
16679
  ];
16544
16680
  const body = [];
16545
16681
  for (let index = 0; index < maxLines; index += 1) {
@@ -17033,7 +17169,7 @@ async function resolveReadableGbkFile(input) {
17033
17169
  const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
17034
17170
  let stat;
17035
17171
  try {
17036
- stat = await fs2.stat(candidatePath);
17172
+ stat = await fs3.stat(candidatePath);
17037
17173
  } catch (error45) {
17038
17174
  throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
17039
17175
  }
@@ -17048,7 +17184,7 @@ async function resolveReadableGbkFile(input) {
17048
17184
  }
17049
17185
  async function readWholeGbkTextFile(input) {
17050
17186
  try {
17051
- const buffer = await fs2.readFile(input.filePath);
17187
+ const buffer = await fs3.readFile(input.filePath);
17052
17188
  const content = await readBufferAsText(buffer, input.encoding);
17053
17189
  return {
17054
17190
  filePath: input.filePath,
@@ -17192,7 +17328,7 @@ async function appendEncodedText(filePath, encoding, text) {
17192
17328
  return 0;
17193
17329
  }
17194
17330
  const buffer = import_iconv_lite.default.encode(text, encoding);
17195
- await fs2.appendFile(filePath, buffer);
17331
+ await fs3.appendFile(filePath, buffer);
17196
17332
  return buffer.byteLength;
17197
17333
  }
17198
17334
  async function copyFileByteRangeToHandle(sourcePath, handle, start, endExclusive) {
@@ -17271,11 +17407,11 @@ async function replaceLargeGbkFileTextInLineRange(input) {
17271
17407
  input.replaceAll,
17272
17408
  lineIndex.newlineStyle
17273
17409
  );
17274
- const tempPath = path2.join(
17275
- path2.dirname(input.filePath),
17276
- `${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
17410
+ const tempPath = path3.join(
17411
+ path3.dirname(input.filePath),
17412
+ `${path3.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
17277
17413
  );
17278
- const handle = await fs2.open(tempPath, "w");
17414
+ const handle = await fs3.open(tempPath, "w");
17279
17415
  let bytesWritten = 0;
17280
17416
  try {
17281
17417
  bytesWritten += await copyFileByteRangeToHandle(input.filePath, handle, 0, rangeStart);
@@ -17283,8 +17419,8 @@ async function replaceLargeGbkFileTextInLineRange(input) {
17283
17419
  bytesWritten += await copyFileByteRangeToHandle(input.filePath, handle, rangeEnd, toSafeNumber(input.stat.size));
17284
17420
  await handle.close();
17285
17421
  const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
17286
- await fs2.chmod(tempPath, mode);
17287
- await fs2.rename(tempPath, input.filePath);
17422
+ await fs3.chmod(tempPath, mode);
17423
+ await fs3.rename(tempPath, input.filePath);
17288
17424
  return {
17289
17425
  mode: "replace",
17290
17426
  filePath: input.filePath,
@@ -17296,18 +17432,18 @@ async function replaceLargeGbkFileTextInLineRange(input) {
17296
17432
  };
17297
17433
  } catch (error45) {
17298
17434
  await handle.close().catch(() => void 0);
17299
- await fs2.rm(tempPath, { force: true }).catch(() => void 0);
17435
+ await fs3.rm(tempPath, { force: true }).catch(() => void 0);
17300
17436
  throw error45;
17301
17437
  }
17302
17438
  }
17303
17439
  async function replaceLargeGbkFileByAnchor(input) {
17304
17440
  const lineIndex = await getGbkLineIndex(input);
17305
17441
  const newlineStyle = lineIndex.newlineStyle;
17306
- const tempPath = path2.join(
17307
- path2.dirname(input.filePath),
17308
- `${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
17442
+ const tempPath = path3.join(
17443
+ path3.dirname(input.filePath),
17444
+ `${path3.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
17309
17445
  );
17310
- const handle = await fs2.open(tempPath, "w");
17446
+ const handle = await fs3.open(tempPath, "w");
17311
17447
  const alignedContent = alignTextToNewlineStyle(input.content, newlineStyle);
17312
17448
  const anchorVariants = buildFlexibleSearchVariants(input.anchor, newlineStyle);
17313
17449
  const maxAnchorLength = anchorVariants.reduce((maxLength, candidate) => Math.max(maxLength, candidate.length), input.anchor.length);
@@ -17420,8 +17556,8 @@ async function replaceLargeGbkFileByAnchor(input) {
17420
17556
  await finalizeInserted();
17421
17557
  await handle.close();
17422
17558
  const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
17423
- await fs2.chmod(tempPath, mode);
17424
- await fs2.rename(tempPath, input.filePath);
17559
+ await fs3.chmod(tempPath, mode);
17560
+ await fs3.rename(tempPath, input.filePath);
17425
17561
  invalidateGbkLineIndex(input.filePath);
17426
17562
  return {
17427
17563
  mode: input.mode,
@@ -17437,18 +17573,18 @@ async function replaceLargeGbkFileByAnchor(input) {
17437
17573
  };
17438
17574
  } catch (error45) {
17439
17575
  await handle.close().catch(() => void 0);
17440
- await fs2.rm(tempPath, { force: true }).catch(() => void 0);
17576
+ await fs3.rm(tempPath, { force: true }).catch(() => void 0);
17441
17577
  throw error45;
17442
17578
  }
17443
17579
  }
17444
17580
  async function replaceLargeGbkFileText(input) {
17445
17581
  const lineIndex = await getGbkLineIndex(input);
17446
17582
  const newlineStyle = lineIndex.newlineStyle;
17447
- const tempPath = path2.join(
17448
- path2.dirname(input.filePath),
17449
- `${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
17583
+ const tempPath = path3.join(
17584
+ path3.dirname(input.filePath),
17585
+ `${path3.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
17450
17586
  );
17451
- const handle = await fs2.open(tempPath, "w");
17587
+ const handle = await fs3.open(tempPath, "w");
17452
17588
  const carryLength = Math.max(input.oldString.length - 1, 0);
17453
17589
  const alignedNewString = alignTextToNewlineStyle(input.newString, newlineStyle);
17454
17590
  let carry = "";
@@ -17507,8 +17643,8 @@ async function replaceLargeGbkFileText(input) {
17507
17643
  }
17508
17644
  await handle.close();
17509
17645
  const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
17510
- await fs2.chmod(tempPath, mode);
17511
- await fs2.rename(tempPath, input.filePath);
17646
+ await fs3.chmod(tempPath, mode);
17647
+ await fs3.rename(tempPath, input.filePath);
17512
17648
  invalidateGbkLineIndex(input.filePath);
17513
17649
  return {
17514
17650
  filePath: input.filePath,
@@ -17520,7 +17656,7 @@ async function replaceLargeGbkFileText(input) {
17520
17656
  };
17521
17657
  } catch (error45) {
17522
17658
  await handle.close().catch(() => void 0);
17523
- await fs2.rm(tempPath, { force: true }).catch(() => void 0);
17659
+ await fs3.rm(tempPath, { force: true }).catch(() => void 0);
17524
17660
  throw error45;
17525
17661
  }
17526
17662
  }
@@ -17648,7 +17784,7 @@ async function replaceGbkFileText(input) {
17648
17784
  };
17649
17785
  }
17650
17786
  const buffer2 = import_iconv_lite.default.encode(insertResult.outputText, current2.encoding);
17651
- await fs2.writeFile(current2.filePath, buffer2);
17787
+ await fs3.writeFile(current2.filePath, buffer2);
17652
17788
  return {
17653
17789
  mode,
17654
17790
  filePath: current2.filePath,
@@ -17708,7 +17844,7 @@ async function replaceGbkFileText(input) {
17708
17844
  if (loose !== null) {
17709
17845
  const outputText2 = `${current.content.slice(0, scope.rangeStart)}${loose.content}${current.content.slice(scope.rangeEnd)}`;
17710
17846
  const buffer2 = import_iconv_lite.default.encode(outputText2, current.encoding);
17711
- await fs2.writeFile(current.filePath, buffer2);
17847
+ await fs3.writeFile(current.filePath, buffer2);
17712
17848
  invalidateGbkLineIndex(current.filePath);
17713
17849
  return {
17714
17850
  mode: "replace",
@@ -17736,7 +17872,7 @@ async function replaceGbkFileText(input) {
17736
17872
  const replaced = replaceAll ? scope.selectedText.split(effectiveOldString).join(alignedNewString) : scope.selectedText.replace(effectiveOldString, alignedNewString);
17737
17873
  const outputText = `${current.content.slice(0, scope.rangeStart)}${replaced}${current.content.slice(scope.rangeEnd)}`;
17738
17874
  const buffer = import_iconv_lite.default.encode(outputText, current.encoding);
17739
- await fs2.writeFile(current.filePath, buffer);
17875
+ await fs3.writeFile(current.filePath, buffer);
17740
17876
  invalidateGbkLineIndex(current.filePath);
17741
17877
  return {
17742
17878
  mode: "replace",
@@ -17821,11 +17957,11 @@ async function writeGbkFile(input) {
17821
17957
  const overwrite = input.overwrite ?? false;
17822
17958
  const append = input.append ?? false;
17823
17959
  const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
17824
- const parent = path2.dirname(candidatePath);
17960
+ const parent = path3.dirname(candidatePath);
17825
17961
  assertEncodingSupported(encoding);
17826
17962
  if (append) {
17827
17963
  try {
17828
- const parentStat = await fs2.stat(parent);
17964
+ const parentStat = await fs3.stat(parent);
17829
17965
  if (!parentStat.isDirectory()) {
17830
17966
  throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
17831
17967
  }
@@ -17834,14 +17970,14 @@ async function writeGbkFile(input) {
17834
17970
  if (!createDirectories) {
17835
17971
  throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
17836
17972
  }
17837
- await fs2.mkdir(parent, { recursive: true });
17973
+ await fs3.mkdir(parent, { recursive: true });
17838
17974
  } else if (error45 instanceof Error && "code" in error45) {
17839
17975
  throw error45;
17840
17976
  }
17841
17977
  }
17842
17978
  let existed = false;
17843
17979
  try {
17844
- await fs2.stat(candidatePath);
17980
+ await fs3.stat(candidatePath);
17845
17981
  existed = true;
17846
17982
  } catch (error45) {
17847
17983
  if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
@@ -17860,7 +17996,7 @@ async function writeGbkFile(input) {
17860
17996
  };
17861
17997
  }
17862
17998
  try {
17863
- const stat = await fs2.stat(candidatePath);
17999
+ const stat = await fs3.stat(candidatePath);
17864
18000
  if (stat.isDirectory()) {
17865
18001
  throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
17866
18002
  }
@@ -17877,7 +18013,7 @@ async function writeGbkFile(input) {
17877
18013
  }
17878
18014
  }
17879
18015
  try {
17880
- const parentStat = await fs2.stat(parent);
18016
+ const parentStat = await fs3.stat(parent);
17881
18017
  if (!parentStat.isDirectory()) {
17882
18018
  throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
17883
18019
  }
@@ -17886,15 +18022,15 @@ async function writeGbkFile(input) {
17886
18022
  if (!createDirectories) {
17887
18023
  throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
17888
18024
  }
17889
- await fs2.mkdir(parent, { recursive: true });
18025
+ await fs3.mkdir(parent, { recursive: true });
17890
18026
  } else if (error45 instanceof Error && "code" in error45) {
17891
18027
  throw error45;
17892
18028
  }
17893
18029
  }
17894
18030
  try {
17895
- const existed = await fs2.stat(candidatePath).then(() => true).catch(() => false);
18031
+ const existed = await fs3.stat(candidatePath).then(() => true).catch(() => false);
17896
18032
  const buffer = import_iconv_lite.default.encode(input.content, encoding);
17897
- await fs2.writeFile(candidatePath, buffer);
18033
+ await fs3.writeFile(candidatePath, buffer);
17898
18034
  invalidateGbkLineIndex(candidatePath);
17899
18035
  return {
17900
18036
  filePath: candidatePath,
@@ -18110,39 +18246,12 @@ var gbk_write_default = tool({
18110
18246
  }
18111
18247
  });
18112
18248
 
18113
- // src/plugin/text-guidance.ts
18114
- var TEXT_TOOL_SYSTEM_MARKER = "[opencode-gbk-tools:text-rules]";
18115
- var TEXT_TOOL_SYSTEM_PROMPT = [
18116
- TEXT_TOOL_SYSTEM_MARKER,
18117
- "\u6587\u672C\u6587\u4EF6\u5904\u7406\u89C4\u5219\uFF1A",
18118
- "- \u5904\u7406\u6587\u672C\u6587\u4EF6\u65F6\uFF0C\u9ED8\u8BA4\u4F18\u5148\u4F7F\u7528 gbk_read\u3001gbk_write\u3001gbk_edit\u3001gbk_search\u3002",
18119
- "- gbk_* \u9002\u5408\u5F53\u524D\u4ED3\u5E93\u7684\u81EA\u52A8\u7F16\u7801\u6587\u672C\u4E0E GBK/GB18030 \u6587\u4EF6\uFF1B\u5373\u4F7F\u4E0D\u662F\u660E\u786E\u7684 GBK \u6587\u4EF6\uFF0C\u4E5F\u4F18\u5148\u8D70\u8FD9\u7EC4\u5DE5\u5177\u3002",
18120
- "- \u65B0\u5EFA .txt \u6587\u4EF6\u5728 encoding=auto \u4E0B\u9ED8\u8BA4\u4F7F\u7528 GBK\uFF1B\u5176\u4ED6\u65B0\u6587\u4EF6\u8BF7\u663E\u5F0F\u6307\u5B9A encoding\u3002",
18121
- "- \u5982\u679C\u610F\u56FE\u662F\u2018\u5728\u67D0\u6807\u7B7E\u524D\u540E\u63D2\u5165\u5185\u5BB9\u2019\uFF0C\u4F18\u5148\u4F7F\u7528 mode=insertAfter \u6216 mode=insertBefore\uFF0C\u5E76\u4F20 anchor/content\u3002",
18122
- "- \u53EA\u6709\u5728\u660E\u786E\u505A\u7CBE\u786E\u66FF\u6362\u65F6\uFF0C\u624D\u4F7F\u7528 oldString/newString\u3002",
18123
- "- anchor\u3001startAnchor\u3001endAnchor\u3001oldString \u82E5\u76F4\u63A5\u590D\u5236\u81EA\u8BFB\u53D6\u7ED3\u679C\uFF0C\u53EF\u4FDD\u7559 LF \u6362\u884C\uFF1B\u5DE5\u5177\u4F1A\u5C3D\u91CF\u6309\u6587\u4EF6\u6362\u884C\u98CE\u683C\u81EA\u52A8\u5BF9\u9F50\u3002",
18124
- '- \u82E5\u8BFB\u53D6\u7ED3\u679C\u5E26\u6709 "N: " \u884C\u53F7\u524D\u7F00\uFF0Cgbk_edit \u4F1A\u5C3D\u91CF\u81EA\u52A8\u5265\u79BB\u8FD9\u4E9B\u524D\u7F00\u540E\u518D\u5339\u914D\u3002',
18125
- "- text_read\u3001text_write\u3001text_edit \u4EC5\u4F5C\u4E3A\u517C\u5BB9\u5DE5\u5177\uFF0C\u4E0D\u518D\u4F5C\u4E3A\u9ED8\u8BA4\u63A8\u8350\u8DEF\u5F84\u3002"
18126
- ].join("\n");
18127
- function appendTextToolSystemPrompt(system) {
18128
- if (system.some((item) => item.includes(TEXT_TOOL_SYSTEM_MARKER))) {
18129
- return;
18130
- }
18131
- if (system.length === 0) {
18132
- system.push(TEXT_TOOL_SYSTEM_PROMPT);
18133
- return;
18134
- }
18135
- system[0] = `${system[0]}
18136
-
18137
- ${TEXT_TOOL_SYSTEM_PROMPT}`;
18138
- }
18139
-
18140
18249
  // src/lib/text-file.ts
18141
18250
  var import_iconv_lite2 = __toESM(require_lib(), 1);
18142
18251
  import crypto2 from "crypto";
18143
18252
  import { createReadStream as createReadStream2 } from "fs";
18144
- import fs3 from "fs/promises";
18145
- import path3 from "path";
18253
+ import fs4 from "fs/promises";
18254
+ import path4 from "path";
18146
18255
  var TEXT_STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
18147
18256
  var TEXT_DETECTION_SAMPLE_BYTES = 64 * 1024;
18148
18257
  var UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
@@ -18178,7 +18287,7 @@ function resolveExplicitTextEncoding(value, fallback) {
18178
18287
  return requested === "auto" ? fallback : requested;
18179
18288
  }
18180
18289
  function shouldDefaultNewTextFileToGbk(filePath) {
18181
- return path3.extname(filePath).toLowerCase() === ".txt";
18290
+ return path4.extname(filePath).toLowerCase() === ".txt";
18182
18291
  }
18183
18292
  function getBomPrefix(encoding, hasBom) {
18184
18293
  if (!hasBom) {
@@ -18474,8 +18583,8 @@ function buildLineDiffPreview(filePath, encoding, beforeText, afterText) {
18474
18583
  const afterLines = normalizeNewlines2(afterText).split("\n");
18475
18584
  const maxLines = Math.max(beforeLines.length, afterLines.length);
18476
18585
  const header = [
18477
- `${ANSI_DIM2}--- ${path3.basename(filePath)} (${encoding})${ANSI_RESET2}`,
18478
- `${ANSI_DIM2}+++ ${path3.basename(filePath)} (${encoding})${ANSI_RESET2}`
18586
+ `${ANSI_DIM2}--- ${path4.basename(filePath)} (${encoding})${ANSI_RESET2}`,
18587
+ `${ANSI_DIM2}+++ ${path4.basename(filePath)} (${encoding})${ANSI_RESET2}`
18479
18588
  ];
18480
18589
  const body = [];
18481
18590
  for (let index = 0; index < maxLines; index += 1) {
@@ -18591,7 +18700,7 @@ async function resolveReadableTextFile(input) {
18591
18700
  const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
18592
18701
  let stat;
18593
18702
  try {
18594
- stat = await fs3.stat(candidatePath);
18703
+ stat = await fs4.stat(candidatePath);
18595
18704
  } catch (error45) {
18596
18705
  throw createTextError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
18597
18706
  }
@@ -18601,7 +18710,7 @@ async function resolveReadableTextFile(input) {
18601
18710
  return { filePath: candidatePath, stat };
18602
18711
  }
18603
18712
  async function readDetectionBuffer(filePath, sampleSize = TEXT_DETECTION_SAMPLE_BYTES) {
18604
- const handle = await fs3.open(filePath, "r");
18713
+ const handle = await fs4.open(filePath, "r");
18605
18714
  try {
18606
18715
  const buffer = Buffer.alloc(sampleSize);
18607
18716
  const { bytesRead } = await handle.read(buffer, 0, sampleSize, 0);
@@ -18764,8 +18873,8 @@ async function visitDecodedTextChunks2(resolved, visitor) {
18764
18873
  }
18765
18874
  }
18766
18875
  async function writeLargeTextFile(filePath, encoding, hasBom, producer) {
18767
- const tempPath = path3.join(path3.dirname(filePath), `${path3.basename(filePath)}.opencode-text-${crypto2.randomUUID()}.tmp`);
18768
- const handle = await fs3.open(tempPath, "w");
18876
+ const tempPath = path4.join(path4.dirname(filePath), `${path4.basename(filePath)}.opencode-text-${crypto2.randomUUID()}.tmp`);
18877
+ const handle = await fs4.open(tempPath, "w");
18769
18878
  try {
18770
18879
  if (hasBom) {
18771
18880
  const bom = getBomPrefix(encoding, hasBom);
@@ -18775,11 +18884,11 @@ async function writeLargeTextFile(filePath, encoding, hasBom, producer) {
18775
18884
  }
18776
18885
  const bytesWritten = await producer(handle);
18777
18886
  await handle.close();
18778
- await fs3.rename(tempPath, filePath);
18887
+ await fs4.rename(tempPath, filePath);
18779
18888
  return bytesWritten + getBomPrefix(encoding, hasBom).length;
18780
18889
  } catch (error45) {
18781
18890
  await handle.close().catch(() => void 0);
18782
- await fs3.rm(tempPath, { force: true }).catch(() => void 0);
18891
+ await fs4.rm(tempPath, { force: true }).catch(() => void 0);
18783
18892
  throw error45;
18784
18893
  }
18785
18894
  }
@@ -18874,7 +18983,7 @@ async function replaceLargeTextFileText(input) {
18874
18983
  async function readWholeTextFile(input) {
18875
18984
  const resolved = await resolveReadableTextFile(input);
18876
18985
  try {
18877
- const buffer = await fs3.readFile(resolved.filePath);
18986
+ const buffer = await fs4.readFile(resolved.filePath);
18878
18987
  assertLikelyTextBuffer(buffer);
18879
18988
  const detected = detectTextEncodingFromBuffer(buffer, input.encoding ?? "auto");
18880
18989
  const content = decodeText(buffer, detected.detectedEncoding);
@@ -19070,7 +19179,7 @@ function ensureLossless(input, encoding, hasBom = encoding === "utf8-bom") {
19070
19179
  }
19071
19180
  async function ensureParentDirectory(parent, createDirectories) {
19072
19181
  try {
19073
- const parentStat = await fs3.stat(parent);
19182
+ const parentStat = await fs4.stat(parent);
19074
19183
  if (!parentStat.isDirectory()) {
19075
19184
  throw createTextError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
19076
19185
  }
@@ -19079,12 +19188,26 @@ async function ensureParentDirectory(parent, createDirectories) {
19079
19188
  if (!createDirectories) {
19080
19189
  throw createTextError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
19081
19190
  }
19082
- await fs3.mkdir(parent, { recursive: true });
19191
+ await fs4.mkdir(parent, { recursive: true });
19083
19192
  return;
19084
19193
  }
19085
19194
  throw error45;
19086
19195
  }
19087
19196
  }
19197
+ async function detectTextFileEncoding(input) {
19198
+ const detected = await detectReadableTextFile(input);
19199
+ const fileSize = typeof detected.stat.size === "bigint" ? Number(detected.stat.size) : detected.stat.size;
19200
+ return {
19201
+ filePath: detected.filePath,
19202
+ encoding: detected.encoding,
19203
+ requestedEncoding: detected.requestedEncoding,
19204
+ detectedEncoding: detected.detectedEncoding,
19205
+ confidence: detected.confidence,
19206
+ hasBom: detected.hasBom,
19207
+ bytesRead: Math.min(fileSize, TEXT_DETECTION_SAMPLE_BYTES),
19208
+ newlineStyle: "unknown"
19209
+ };
19210
+ }
19088
19211
  async function readTextFile(input) {
19089
19212
  const offset = normalizeOptionalPositiveInteger(input.offset, "offset") ?? 1;
19090
19213
  const limit = normalizeOptionalPositiveInteger(input.limit, "limit") ?? 2e3;
@@ -19142,7 +19265,7 @@ async function writeTextFile(input) {
19142
19265
  const overwrite = input.overwrite ?? false;
19143
19266
  const append = input.append ?? false;
19144
19267
  const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
19145
- const parent = path3.dirname(candidatePath);
19268
+ const parent = path4.dirname(candidatePath);
19146
19269
  await ensureParentDirectory(parent, createDirectories);
19147
19270
  let existing = null;
19148
19271
  try {
@@ -19171,7 +19294,7 @@ async function writeTextFile(input) {
19171
19294
  const outputContent = existing && preserveNewlineStyle ? alignTextToNewlineStyle2(rawContent, existing.newlineStyle) : rawContent;
19172
19295
  ensureLossless(outputContent, targetEncoding, targetHasBom);
19173
19296
  const buffer = encodeText(outputContent, targetEncoding, targetHasBom);
19174
- await fs3.writeFile(candidatePath, buffer);
19297
+ await fs4.writeFile(candidatePath, buffer);
19175
19298
  return {
19176
19299
  filePath: candidatePath,
19177
19300
  encoding: targetEncoding,
@@ -19235,7 +19358,7 @@ async function replaceTextFileText(input) {
19235
19358
  const targetHasBom2 = normalizedInput.preserveEncoding === false ? targetEncoding2 === "utf8-bom" || targetEncoding2 === "utf16le" || targetEncoding2 === "utf16be" : loaded2.hasBom;
19236
19359
  ensureLossless(insertResult.outputText, targetEncoding2, targetHasBom2);
19237
19360
  const buffer2 = encodeText(insertResult.outputText, targetEncoding2, targetHasBom2);
19238
- await fs3.writeFile(loaded2.filePath, buffer2);
19361
+ await fs4.writeFile(loaded2.filePath, buffer2);
19239
19362
  return {
19240
19363
  mode,
19241
19364
  filePath: loaded2.filePath,
@@ -19312,7 +19435,7 @@ async function replaceTextFileText(input) {
19312
19435
  const outputText2 = `${loaded.content.slice(0, scope.rangeStart)}${loose.content}${loaded.content.slice(scope.rangeEnd)}`;
19313
19436
  ensureLossless(outputText2, targetEncoding, targetHasBom);
19314
19437
  const buffer2 = encodeText(outputText2, targetEncoding, targetHasBom);
19315
- await fs3.writeFile(loaded.filePath, buffer2);
19438
+ await fs4.writeFile(loaded.filePath, buffer2);
19316
19439
  return {
19317
19440
  mode: "replace",
19318
19441
  filePath: loaded.filePath,
@@ -19344,7 +19467,7 @@ async function replaceTextFileText(input) {
19344
19467
  const outputText = `${loaded.content.slice(0, scope.rangeStart)}${replaced}${loaded.content.slice(scope.rangeEnd)}`;
19345
19468
  ensureLossless(outputText, targetEncoding, targetHasBom);
19346
19469
  const buffer = encodeText(outputText, targetEncoding, targetHasBom);
19347
- await fs3.writeFile(loaded.filePath, buffer);
19470
+ await fs4.writeFile(loaded.filePath, buffer);
19348
19471
  return {
19349
19472
  mode: "replace",
19350
19473
  filePath: loaded.filePath,
@@ -19362,6 +19485,35 @@ async function replaceTextFileText(input) {
19362
19485
  };
19363
19486
  }
19364
19487
 
19488
+ // src/plugin/text-guidance.ts
19489
+ var TEXT_TOOL_SYSTEM_MARKER = "[opencode-gbk-tools:text-rules]";
19490
+ var TEXT_TOOL_SYSTEM_PROMPT = [
19491
+ TEXT_TOOL_SYSTEM_MARKER,
19492
+ "\u6587\u672C\u6587\u4EF6\u5904\u7406\u89C4\u5219\uFF1A",
19493
+ "- \u666E\u901A UTF-8 / UTF-8 BOM / UTF-16 \u6587\u672C\uFF0C\u4F18\u5148\u4F7F\u7528 OpenCode \u5185\u7F6E read\u3001write\u3001edit\u3002",
19494
+ "- \u9047\u5230 GBK / GB18030 \u6587\u4EF6\u3001\u4E2D\u6587\u4E71\u7801\u3001\u975E UTF-8 \u65E7\u6587\u672C\uFF0C\u4F18\u5148\u4F7F\u7528 gbk_read\u3001gbk_write\u3001gbk_edit\u3001gbk_search\uFF0C\u4E0D\u8981\u5148\u5C1D\u8BD5 text_* \u6216\u5185\u7F6E read/write/edit\u3002",
19495
+ "- \u65B0\u5EFA .txt \u6587\u4EF6\u5728 encoding=auto \u4E0B\u9ED8\u8BA4\u4F7F\u7528 GBK\uFF1B\u5176\u4ED6\u65B0\u6587\u4EF6\u8BF7\u663E\u5F0F\u6307\u5B9A encoding\u3002",
19496
+ "- \u5BF9\u73B0\u6709 .txt / .cfg / .ini / .log \u7B49\u65E7\u6587\u672C\u6587\u4EF6\uFF0C\u53EA\u8981\u6000\u7591\u662F\u4E2D\u6587\u672C\u5730\u7F16\u7801\uFF0C\u4F18\u5148\u5148\u7528 gbk_read \u5224\u65AD\uFF0C\u4E0D\u8981\u5148 edit \u518D\u56E0\u4E3A\u5339\u914D\u5931\u8D25\u6216\u6587\u4EF6\u65F6\u95F4\u6233\u53D8\u5316\u800C\u56DE\u9000\u3002",
19497
+ "- \u5DF2\u786E\u8BA4\u6216\u9996\u6B21\u68C0\u6D4B\u5230\u662F GBK/GB18030 \u7684\u6587\u4EF6\u4F1A\u88AB\u63D2\u4EF6\u6301\u4E45\u8BB0\u5FC6\uFF1B\u518D\u6B21\u64CD\u4F5C\u540C\u4E00\u8DEF\u5F84\u65F6\uFF0C\u4F1A\u76F4\u63A5\u62E6\u622A\u5185\u7F6E read/write/edit \u548C text_*\uFF0C\u5E76\u8981\u6C42\u6539\u7528 gbk_*\u3002",
19498
+ "- \u5982\u679C\u610F\u56FE\u662F\u2018\u5728\u67D0\u6807\u7B7E\u524D\u540E\u63D2\u5165\u5185\u5BB9\u2019\uFF0C\u4F18\u5148\u4F7F\u7528 mode=insertAfter \u6216 mode=insertBefore\uFF0C\u5E76\u4F20 anchor/content\u3002",
19499
+ "- \u53EA\u6709\u5728\u660E\u786E\u505A\u7CBE\u786E\u66FF\u6362\u65F6\uFF0C\u624D\u4F7F\u7528 oldString/newString\u3002",
19500
+ "- anchor\u3001startAnchor\u3001endAnchor\u3001oldString \u82E5\u76F4\u63A5\u590D\u5236\u81EA\u8BFB\u53D6\u7ED3\u679C\uFF0C\u53EF\u4FDD\u7559 LF \u6362\u884C\uFF1Bgbk_edit / text_edit \u4F1A\u5C3D\u91CF\u6309\u6587\u4EF6\u6362\u884C\u98CE\u683C\u81EA\u52A8\u5BF9\u9F50\u3002",
19501
+ '- \u82E5\u8BFB\u53D6\u7ED3\u679C\u5E26\u6709 "N: " \u884C\u53F7\u524D\u7F00\uFF0Cgbk_edit / text_edit \u4F1A\u5C3D\u91CF\u81EA\u52A8\u5265\u79BB\u8FD9\u4E9B\u524D\u7F00\u540E\u518D\u5339\u914D\u3002',
19502
+ "- gbk-engine \u662F\u5F3A\u5236 GBK \u4E13\u5C5E\u6A21\u5F0F\uFF1A\u53EA\u5141\u8BB8 gbk_*\uFF0C\u4E0D\u8D70\u5185\u7F6E\u8BFB\u5199\u7F16\u8F91\u5DE5\u5177\u3002"
19503
+ ].join("\n");
19504
+ function appendTextToolSystemPrompt(system) {
19505
+ if (system.some((item) => item.includes(TEXT_TOOL_SYSTEM_MARKER))) {
19506
+ return;
19507
+ }
19508
+ if (system.length === 0) {
19509
+ system.push(TEXT_TOOL_SYSTEM_PROMPT);
19510
+ return;
19511
+ }
19512
+ system[0] = `${system[0]}
19513
+
19514
+ ${TEXT_TOOL_SYSTEM_PROMPT}`;
19515
+ }
19516
+
19365
19517
  // src/tools/text_edit.ts
19366
19518
  var text_edit_default = tool({
19367
19519
  description: `Edit text files with automatic encoding detection and preservation.
@@ -19505,6 +19657,58 @@ var MANAGED_TOOL_IDS = /* @__PURE__ */ new Set([
19505
19657
  "text_write",
19506
19658
  "text_edit"
19507
19659
  ]);
19660
+ var BUILTIN_TEXT_TOOL_IDS = /* @__PURE__ */ new Set(["read", "write", "edit"]);
19661
+ var TEXT_TOOL_IDS = /* @__PURE__ */ new Set(["text_read", "text_write", "text_edit"]);
19662
+ var ROUTED_TEXT_TOOL_IDS = /* @__PURE__ */ new Set([...BUILTIN_TEXT_TOOL_IDS, ...TEXT_TOOL_IDS]);
19663
+ function getToolFilePath(args) {
19664
+ if (!args || typeof args !== "object") {
19665
+ return null;
19666
+ }
19667
+ const filePath = args.filePath;
19668
+ return typeof filePath === "string" ? filePath : null;
19669
+ }
19670
+ function getToolAllowExternal(args) {
19671
+ if (!args || typeof args !== "object") {
19672
+ return false;
19673
+ }
19674
+ return args.allowExternal === true;
19675
+ }
19676
+ function normalizeSessionFilePath(filePath, directory, worktree) {
19677
+ const resolved = path5.normalize(resolveCandidatePath(filePath, { directory, worktree }));
19678
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
19679
+ }
19680
+ function buildGbkRoutingMessage(filePath, encoding) {
19681
+ return `\u6587\u4EF6\u68C0\u6D4B\u4E3A ${encoding.toUpperCase()} \u7F16\u7801\uFF0C\u8BF7\u76F4\u63A5\u6539\u7528 gbk_read\u3001gbk_write\u3001gbk_edit \u6216 gbk_search\uFF0C\u4E0D\u8981\u5148\u4F7F\u7528\u5185\u7F6E read/write/edit \u6216 text_*\uFF1A${filePath}`;
19682
+ }
19683
+ function buildTextEditSessionRoutingMessage(filePath) {
19684
+ return `\u5F53\u524D\u4F1A\u8BDD\u5DF2\u5BF9\u8BE5\u6587\u4EF6\u4F7F\u7528 text_edit\uFF0C\u8BF7\u7EE7\u7EED\u4F7F\u7528 text_read\u3001text_write\u3001text_edit \u6216\u76F4\u63A5\u6539\u7528 gbk_*\uFF1B\u4E0D\u8981\u518D\u5207\u56DE\u5185\u7F6E read/write/edit\uFF0C\u4EE5\u514D\u89E6\u53D1\u6587\u4EF6\u65B0\u9C9C\u5EA6\u68C0\u67E5\u51B2\u7A81\uFF1A${filePath}`;
19685
+ }
19686
+ async function detectExistingGbkEncoding(filePath, allowExternal, directory, worktree) {
19687
+ try {
19688
+ const detected = await detectTextFileEncoding({
19689
+ filePath,
19690
+ encoding: "auto",
19691
+ allowExternal,
19692
+ context: { directory, worktree }
19693
+ });
19694
+ return isRememberedGbkEncoding(detected.encoding) ? detected.encoding : null;
19695
+ } catch {
19696
+ return null;
19697
+ }
19698
+ }
19699
+ async function maybePersistRememberedEncoding(metadata) {
19700
+ const filePath = typeof metadata.filePath === "string" ? metadata.filePath : null;
19701
+ const encoding = metadata.encoding;
19702
+ if (!filePath) {
19703
+ return;
19704
+ }
19705
+ if (isRememberedGbkEncoding(encoding)) {
19706
+ await rememberGbkEncoding(filePath, encoding);
19707
+ metadata.rememberedEncoding = encoding;
19708
+ return;
19709
+ }
19710
+ await forgetRememberedEncoding(filePath);
19711
+ }
19508
19712
  function truncateMetadataPreview(value, sessionID) {
19509
19713
  const previewMaxChars = Math.max(800, Math.min(2e3, Math.floor(getMaxOutputChars(sessionID) / 2)));
19510
19714
  if (value.length <= previewMaxChars) return value;
@@ -19555,7 +19759,25 @@ async function maybeAutoSummarizeSession(client, directory, input) {
19555
19759
  markAutoSummarizeFinished(input.sessionID, false);
19556
19760
  }
19557
19761
  }
19558
- function createOpencodeGbkHooks(client, directory) {
19762
+ function createOpencodeGbkHooks(client, directory, worktree) {
19763
+ const sessionTextEditedFiles = /* @__PURE__ */ new Map();
19764
+ function rememberSessionTextEditFile(sessionID, normalizedFilePath) {
19765
+ if (!sessionID) {
19766
+ return;
19767
+ }
19768
+ let files = sessionTextEditedFiles.get(sessionID);
19769
+ if (!files) {
19770
+ files = /* @__PURE__ */ new Set();
19771
+ sessionTextEditedFiles.set(sessionID, files);
19772
+ }
19773
+ files.add(normalizedFilePath);
19774
+ }
19775
+ function hasSessionTextEditFile(sessionID, normalizedFilePath) {
19776
+ if (!sessionID) {
19777
+ return false;
19778
+ }
19779
+ return sessionTextEditedFiles.get(sessionID)?.has(normalizedFilePath) ?? false;
19780
+ }
19559
19781
  return {
19560
19782
  tool: {
19561
19783
  gbk_read: gbk_read_default,
@@ -19581,6 +19803,38 @@ function createOpencodeGbkHooks(client, directory) {
19581
19803
  await maybeAutoSummarizeSession(client, directory, input);
19582
19804
  }
19583
19805
  },
19806
+ async "tool.execute.before"(input, output) {
19807
+ const filePath = getToolFilePath(output.args);
19808
+ if (!filePath) {
19809
+ return;
19810
+ }
19811
+ if (!ROUTED_TEXT_TOOL_IDS.has(input.tool)) {
19812
+ return;
19813
+ }
19814
+ const normalizedFilePath = normalizeSessionFilePath(filePath, directory, worktree);
19815
+ if (BUILTIN_TEXT_TOOL_IDS.has(input.tool) && hasSessionTextEditFile(input.sessionID, normalizedFilePath)) {
19816
+ throw new Error(buildTextEditSessionRoutingMessage(filePath));
19817
+ }
19818
+ const resolvedFilePath = resolveCandidatePath(filePath, { directory, worktree });
19819
+ const remembered = await getRememberedGbkEncoding(resolvedFilePath);
19820
+ if (remembered) {
19821
+ throw new Error(buildGbkRoutingMessage(filePath, remembered.encoding));
19822
+ }
19823
+ const detectedEncoding = await detectExistingGbkEncoding(
19824
+ resolvedFilePath,
19825
+ getToolAllowExternal(output.args),
19826
+ directory,
19827
+ worktree
19828
+ );
19829
+ if (!detectedEncoding) {
19830
+ return;
19831
+ }
19832
+ try {
19833
+ await rememberGbkEncoding(resolvedFilePath, detectedEncoding);
19834
+ } catch {
19835
+ }
19836
+ throw new Error(buildGbkRoutingMessage(filePath, detectedEncoding));
19837
+ },
19584
19838
  async "tool.execute.after"(input, output) {
19585
19839
  if (!MANAGED_TOOL_IDS.has(input.tool)) return;
19586
19840
  const maxOutputChars = getMaxOutputChars(input.sessionID);
@@ -19596,6 +19850,17 @@ function createOpencodeGbkHooks(client, directory) {
19596
19850
  if (typeof metadata.diffPreview === "string") {
19597
19851
  metadata.diffPreview = truncateMetadataPreview(metadata.diffPreview, input.sessionID);
19598
19852
  }
19853
+ if (input.tool === "text_edit" && typeof metadata.filePath === "string") {
19854
+ rememberSessionTextEditFile(
19855
+ input.sessionID,
19856
+ normalizeSessionFilePath(metadata.filePath, directory, worktree)
19857
+ );
19858
+ }
19859
+ try {
19860
+ await maybePersistRememberedEncoding(metadata);
19861
+ } catch {
19862
+ metadata.encodingMemoryWarning = "\u8BB0\u5FC6\u6587\u4EF6\u7F16\u7801\u5931\u8D25\uFF0C\u5DF2\u5FFD\u7565";
19863
+ }
19599
19864
  metadata.maxOutputChars = maxOutputChars;
19600
19865
  if (compactionCount > 0) {
19601
19866
  metadata.sessionCompactions = compactionCount;