opencode-gbk-tools 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -182,7 +182,7 @@ var require_internal = __commonJS({
182
182
  // Codec.
183
183
  _internal: InternalCodec
184
184
  };
185
- function InternalCodec(codecOptions, iconv3) {
185
+ function InternalCodec(codecOptions, iconv2) {
186
186
  this.enc = codecOptions.encodingName;
187
187
  this.bomAware = codecOptions.bomAware;
188
188
  if (this.enc === "base64") {
@@ -194,7 +194,7 @@ var require_internal = __commonJS({
194
194
  this.encoder = InternalEncoderCesu8;
195
195
  if (Buffer2.from("eda0bdedb2a9", "hex").toString() !== "\u{1F4A9}") {
196
196
  this.decoder = InternalDecoderCesu8;
197
- this.defaultCharUnicode = iconv3.defaultCharUnicode;
197
+ this.defaultCharUnicode = iconv2.defaultCharUnicode;
198
198
  }
199
199
  }
200
200
  }
@@ -351,8 +351,8 @@ var require_utf32 = __commonJS({
351
351
  "use strict";
352
352
  var Buffer2 = require_safer().Buffer;
353
353
  exports._utf32 = Utf32Codec;
354
- function Utf32Codec(codecOptions, iconv3) {
355
- this.iconv = iconv3;
354
+ function Utf32Codec(codecOptions, iconv2) {
355
+ this.iconv = iconv2;
356
356
  this.bomAware = true;
357
357
  this.isLE = codecOptions.isLE;
358
358
  }
@@ -476,8 +476,8 @@ var require_utf32 = __commonJS({
476
476
  };
477
477
  exports.utf32 = Utf32AutoCodec;
478
478
  exports.ucs4 = "utf32";
479
- function Utf32AutoCodec(options, iconv3) {
480
- this.iconv = iconv3;
479
+ function Utf32AutoCodec(options, iconv2) {
480
+ this.iconv = iconv2;
481
481
  }
482
482
  Utf32AutoCodec.prototype.encoder = Utf32AutoEncoder;
483
483
  Utf32AutoCodec.prototype.decoder = Utf32AutoDecoder;
@@ -627,8 +627,8 @@ var require_utf16 = __commonJS({
627
627
  this.overflowByte = -1;
628
628
  };
629
629
  exports.utf16 = Utf16Codec;
630
- function Utf16Codec(codecOptions, iconv3) {
631
- this.iconv = iconv3;
630
+ function Utf16Codec(codecOptions, iconv2) {
631
+ this.iconv = iconv2;
632
632
  }
633
633
  Utf16Codec.prototype.encoder = Utf16Encoder;
634
634
  Utf16Codec.prototype.decoder = Utf16Decoder;
@@ -726,8 +726,8 @@ var require_utf7 = __commonJS({
726
726
  var Buffer2 = require_safer().Buffer;
727
727
  exports.utf7 = Utf7Codec;
728
728
  exports.unicode11utf7 = "utf7";
729
- function Utf7Codec(codecOptions, iconv3) {
730
- this.iconv = iconv3;
729
+ function Utf7Codec(codecOptions, iconv2) {
730
+ this.iconv = iconv2;
731
731
  }
732
732
  Utf7Codec.prototype.encoder = Utf7Encoder;
733
733
  Utf7Codec.prototype.decoder = Utf7Decoder;
@@ -809,8 +809,8 @@ var require_utf7 = __commonJS({
809
809
  return res;
810
810
  };
811
811
  exports.utf7imap = Utf7IMAPCodec;
812
- function Utf7IMAPCodec(codecOptions, iconv3) {
813
- this.iconv = iconv3;
812
+ function Utf7IMAPCodec(codecOptions, iconv2) {
813
+ this.iconv = iconv2;
814
814
  }
815
815
  Utf7IMAPCodec.prototype.encoder = Utf7IMAPEncoder;
816
816
  Utf7IMAPCodec.prototype.decoder = Utf7IMAPDecoder;
@@ -943,7 +943,7 @@ var require_sbcs_codec = __commonJS({
943
943
  "use strict";
944
944
  var Buffer2 = require_safer().Buffer;
945
945
  exports._sbcs = SBCSCodec;
946
- function SBCSCodec(codecOptions, iconv3) {
946
+ function SBCSCodec(codecOptions, iconv2) {
947
947
  if (!codecOptions) {
948
948
  throw new Error("SBCS codec is called without the data.");
949
949
  }
@@ -958,7 +958,7 @@ var require_sbcs_codec = __commonJS({
958
958
  codecOptions.chars = asciiString + codecOptions.chars;
959
959
  }
960
960
  this.decodeBuf = Buffer2.from(codecOptions.chars, "ucs2");
961
- var encodeBuf = Buffer2.alloc(65536, iconv3.defaultCharSingleByte.charCodeAt(0));
961
+ var encodeBuf = Buffer2.alloc(65536, iconv2.defaultCharSingleByte.charCodeAt(0));
962
962
  for (var i = 0; i < codecOptions.chars.length; i++) {
963
963
  encodeBuf[codecOptions.chars.charCodeAt(i)] = i;
964
964
  }
@@ -1623,7 +1623,7 @@ var require_dbcs_codec = __commonJS({
1623
1623
  UNASSIGNED_NODE[i] = UNASSIGNED;
1624
1624
  }
1625
1625
  var i;
1626
- function DBCSCodec(codecOptions, iconv3) {
1626
+ function DBCSCodec(codecOptions, iconv2) {
1627
1627
  this.encodingName = codecOptions.encodingName;
1628
1628
  if (!codecOptions) {
1629
1629
  throw new Error("DBCS codec is called without the data.");
@@ -1672,7 +1672,7 @@ var require_dbcs_codec = __commonJS({
1672
1672
  }
1673
1673
  }
1674
1674
  }
1675
- this.defaultCharUnicode = iconv3.defaultCharUnicode;
1675
+ this.defaultCharUnicode = iconv2.defaultCharUnicode;
1676
1676
  this.encodeTable = [];
1677
1677
  this.encodeTableSeq = [];
1678
1678
  var skipEncodeChars = {};
@@ -1696,7 +1696,7 @@ var require_dbcs_codec = __commonJS({
1696
1696
  }
1697
1697
  }
1698
1698
  }
1699
- this.defCharSB = this.encodeTable[0][iconv3.defaultCharSingleByte.charCodeAt(0)];
1699
+ this.defCharSB = this.encodeTable[0][iconv2.defaultCharSingleByte.charCodeAt(0)];
1700
1700
  if (this.defCharSB === UNASSIGNED) this.defCharSB = this.encodeTable[0]["?"];
1701
1701
  if (this.defCharSB === UNASSIGNED) this.defCharSB = "?".charCodeAt(0);
1702
1702
  }
@@ -3816,9 +3816,6 @@ var require_lib = __commonJS({
3816
3816
  }
3817
3817
  });
3818
3818
 
3819
- // src/tools/gbk_edit.ts
3820
- import fs3 from "fs/promises";
3821
-
3822
3819
  // node_modules/zod/v4/classic/external.js
3823
3820
  var external_exports = {};
3824
3821
  __export(external_exports, {
@@ -16241,11 +16238,10 @@ function tool(input) {
16241
16238
  }
16242
16239
  tool.schema = external_exports;
16243
16240
 
16244
- // src/tools/gbk_edit.ts
16245
- var import_iconv_lite2 = __toESM(require_lib(), 1);
16246
-
16247
16241
  // src/lib/gbk-file.ts
16248
16242
  var import_iconv_lite = __toESM(require_lib(), 1);
16243
+ import crypto from "crypto";
16244
+ import { createReadStream } from "fs";
16249
16245
  import fs2 from "fs/promises";
16250
16246
  import path2 from "path";
16251
16247
 
@@ -16308,6 +16304,7 @@ async function assertPathAllowed(filePath, context, allowExternal = false) {
16308
16304
  }
16309
16305
 
16310
16306
  // src/lib/gbk-file.ts
16307
+ var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
16311
16308
  function assertEncodingSupported(encoding) {
16312
16309
  if (encoding !== "gbk" && encoding !== "gb18030") {
16313
16310
  throw createGbkError("GBK_INVALID_ENCODING", `\u4E0D\u652F\u6301\u7684\u7F16\u7801: ${encoding}`);
@@ -16323,29 +16320,179 @@ function assertNotBinary(buffer) {
16323
16320
  throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u6309 GBK \u6587\u672C\u5904\u7406");
16324
16321
  }
16325
16322
  }
16326
- function splitLinesWithNumbers(text, offset = 1, limit = 2e3) {
16327
- assertPositiveInteger(offset, "offset");
16328
- assertPositiveInteger(limit, "limit");
16323
+ function detectNewlineStyle(text) {
16324
+ const crlfCount = (text.match(/\r\n/g) ?? []).length;
16325
+ const lfCount = (text.match(/(^|[^\r])\n/g) ?? []).length;
16326
+ if (crlfCount > 0 && lfCount === 0) {
16327
+ return "crlf";
16328
+ }
16329
+ if (lfCount > 0 && crlfCount === 0) {
16330
+ return "lf";
16331
+ }
16332
+ if (crlfCount > 0 && lfCount > 0) {
16333
+ return "mixed";
16334
+ }
16335
+ return "none";
16336
+ }
16337
+ function applyLineRange(text, startLine, endLine) {
16338
+ if (startLine === void 0 && endLine === void 0) {
16339
+ return {
16340
+ selectedText: text,
16341
+ rangeStart: 0,
16342
+ rangeEnd: text.length
16343
+ };
16344
+ }
16329
16345
  const lines = text.split(/\r?\n/);
16330
- const totalLines = lines.length;
16331
- if (offset > totalLines) {
16346
+ const actualStartLine = startLine ?? 1;
16347
+ const actualEndLine = endLine ?? lines.length;
16348
+ assertPositiveInteger(actualStartLine, "startLine");
16349
+ assertPositiveInteger(actualEndLine, "endLine");
16350
+ if (actualEndLine < actualStartLine) {
16351
+ throw createGbkError("GBK_INVALID_ARGUMENT", "endLine \u4E0D\u80FD\u5C0F\u4E8E startLine");
16352
+ }
16353
+ let cursor = 0;
16354
+ let rangeStart = 0;
16355
+ let rangeEnd = text.length;
16356
+ for (let lineNumber = 1; lineNumber <= lines.length; lineNumber += 1) {
16357
+ if (lineNumber === actualStartLine) {
16358
+ rangeStart = cursor;
16359
+ }
16360
+ cursor += lines[lineNumber - 1].length;
16361
+ if (lineNumber < lines.length) {
16362
+ const newlineLength = text.startsWith("\r\n", cursor) ? 2 : 1;
16363
+ cursor += newlineLength;
16364
+ }
16365
+ if (lineNumber === actualEndLine) {
16366
+ rangeEnd = cursor;
16367
+ break;
16368
+ }
16369
+ }
16370
+ return {
16371
+ selectedText: text.slice(rangeStart, rangeEnd),
16372
+ rangeStart,
16373
+ rangeEnd
16374
+ };
16375
+ }
16376
+ function applyAnchors(text, startAnchor, endAnchor) {
16377
+ if (!startAnchor && !endAnchor) {
16332
16378
  return {
16333
- startLine: offset,
16334
- endLine: totalLines,
16335
- totalLines,
16336
- content: ""
16379
+ selectedText: text,
16380
+ rangeStart: 0,
16381
+ rangeEnd: text.length
16337
16382
  };
16338
16383
  }
16339
- const startIndex = offset - 1;
16340
- const endIndex = Math.min(startIndex + limit, totalLines);
16341
- const content = lines.slice(startIndex, endIndex).map((line, index) => `${offset + index}: ${line}`).join("\n");
16384
+ let rangeStart = 0;
16385
+ let rangeEnd = text.length;
16386
+ if (startAnchor) {
16387
+ const found = text.indexOf(startAnchor);
16388
+ if (found === -1) {
16389
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\u70B9: ${startAnchor}`);
16390
+ }
16391
+ rangeStart = found + startAnchor.length;
16392
+ }
16393
+ if (endAnchor) {
16394
+ const found = text.indexOf(endAnchor, rangeStart);
16395
+ if (found === -1) {
16396
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u7ED3\u675F\u951A\u70B9: ${endAnchor}`);
16397
+ }
16398
+ rangeEnd = found;
16399
+ }
16400
+ if (rangeEnd < rangeStart) {
16401
+ throw createGbkError("GBK_INVALID_ARGUMENT", "\u951A\u70B9\u8303\u56F4\u65E0\u6548");
16402
+ }
16403
+ return {
16404
+ selectedText: text.slice(rangeStart, rangeEnd),
16405
+ rangeStart,
16406
+ rangeEnd
16407
+ };
16408
+ }
16409
+ function resolveEditScope(text, input) {
16410
+ const anchored = applyAnchors(text, input.startAnchor, input.endAnchor);
16411
+ const lineRanged = applyLineRange(anchored.selectedText, input.startLine, input.endLine);
16342
16412
  return {
16343
- startLine: offset,
16344
- endLine: offset + (endIndex - startIndex) - 1,
16345
- totalLines,
16346
- content
16413
+ selectedText: lineRanged.selectedText,
16414
+ rangeStart: anchored.rangeStart + lineRanged.rangeStart,
16415
+ rangeEnd: anchored.rangeStart + lineRanged.rangeEnd
16347
16416
  };
16348
16417
  }
16418
+ function normalizeNewlines(text) {
16419
+ return text.replace(/\r\n/g, "\n");
16420
+ }
16421
+ function trimRightSpaces(text) {
16422
+ return text.replace(/[ \t]+$/g, "");
16423
+ }
16424
+ function splitNormalizedLines(text) {
16425
+ return normalizeNewlines(text).split("\n");
16426
+ }
16427
+ function trimTrailingEmptyLines(lines) {
16428
+ const result = [...lines];
16429
+ while (result.length > 0 && trimRightSpaces(result[result.length - 1]) === "") {
16430
+ result.pop();
16431
+ }
16432
+ return result;
16433
+ }
16434
+ function getNearestContext(content, oldString) {
16435
+ const lines = content.split(/\r?\n/);
16436
+ const oldLines = normalizeNewlines(oldString).split("\n").filter(Boolean);
16437
+ const firstToken = oldLines[0] || oldString.trim();
16438
+ for (let index = 0; index < lines.length; index += 1) {
16439
+ if (lines[index].includes(firstToken)) {
16440
+ return lines.slice(index, index + 4).join("\n");
16441
+ }
16442
+ }
16443
+ return lines.slice(0, 4).join("\n");
16444
+ }
16445
+ function buildNoMatchMessage(content, oldString) {
16446
+ return [
16447
+ "\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
16448
+ "oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\uFF0C\u6216\u4EC5\u5728\u6362\u884C/\u5C3E\u968F\u7A7A\u884C\u4E0A\u5B58\u5728\u8F7B\u5FAE\u5DEE\u5F02\u3002",
16449
+ "\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
16450
+ getNearestContext(content, oldString)
16451
+ ].join("\n");
16452
+ }
16453
+ function tryLooseBlockReplace(content, oldString, newString) {
16454
+ const normalizedContent = normalizeNewlines(content);
16455
+ const contentLines = splitNormalizedLines(content);
16456
+ const oldLines = trimTrailingEmptyLines(splitNormalizedLines(oldString));
16457
+ const newLines = splitNormalizedLines(newString);
16458
+ if (oldLines.length === 0) {
16459
+ return null;
16460
+ }
16461
+ for (let start = 0; start < contentLines.length; start += 1) {
16462
+ let contentIndex = start;
16463
+ let oldIndex = 0;
16464
+ while (oldIndex < oldLines.length && contentIndex < contentLines.length) {
16465
+ const expected = trimRightSpaces(oldLines[oldIndex]);
16466
+ const actual = trimRightSpaces(contentLines[contentIndex]);
16467
+ if (expected === actual) {
16468
+ oldIndex += 1;
16469
+ contentIndex += 1;
16470
+ continue;
16471
+ }
16472
+ if (actual === "") {
16473
+ contentIndex += 1;
16474
+ continue;
16475
+ }
16476
+ if (expected === "") {
16477
+ oldIndex += 1;
16478
+ continue;
16479
+ }
16480
+ break;
16481
+ }
16482
+ if (oldIndex === oldLines.length) {
16483
+ const replacedNormalized = [
16484
+ ...contentLines.slice(0, start),
16485
+ ...newLines,
16486
+ ...contentLines.slice(contentIndex)
16487
+ ].join("\n");
16488
+ return {
16489
+ occurrencesBefore: 1,
16490
+ content: detectNewlineStyle(content) === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
16491
+ };
16492
+ }
16493
+ }
16494
+ return null;
16495
+ }
16349
16496
  function countOccurrences(text, target) {
16350
16497
  if (target.length === 0) {
16351
16498
  throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
@@ -16366,10 +16513,9 @@ async function readBufferAsText(buffer, encoding) {
16366
16513
  assertNotBinary(buffer);
16367
16514
  return import_iconv_lite.default.decode(buffer, encoding);
16368
16515
  }
16369
- async function readGbkFile(input) {
16516
+ async function resolveReadableGbkFile(input) {
16370
16517
  const encoding = input.encoding ?? "gbk";
16371
- const offset = input.offset ?? 1;
16372
- const limit = input.limit ?? 2e3;
16518
+ assertEncodingSupported(encoding);
16373
16519
  const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
16374
16520
  let stat;
16375
16521
  try {
@@ -16380,21 +16526,185 @@ async function readGbkFile(input) {
16380
16526
  if (stat.isDirectory()) {
16381
16527
  throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
16382
16528
  }
16529
+ return {
16530
+ filePath: candidatePath,
16531
+ encoding,
16532
+ stat
16533
+ };
16534
+ }
16535
+ async function readWholeGbkTextFile(input) {
16383
16536
  try {
16384
- const buffer = await fs2.readFile(candidatePath);
16385
- const text = await readBufferAsText(buffer, encoding);
16537
+ const buffer = await fs2.readFile(input.filePath);
16538
+ const content = await readBufferAsText(buffer, input.encoding);
16386
16539
  return {
16387
- filePath: candidatePath,
16388
- encoding,
16389
- ...splitLinesWithNumbers(text, offset, limit)
16540
+ filePath: input.filePath,
16541
+ encoding: input.encoding,
16542
+ content,
16543
+ bytesRead: buffer.byteLength
16390
16544
  };
16391
16545
  } catch (error45) {
16392
16546
  if (error45 instanceof Error && "code" in error45) {
16393
16547
  throw error45;
16394
16548
  }
16395
- throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${candidatePath}`, error45);
16549
+ throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
16396
16550
  }
16397
16551
  }
16552
+ async function visitDecodedTextChunks(input, visitor) {
16553
+ const decoder = import_iconv_lite.default.getDecoder(input.encoding);
16554
+ const stream = createReadStream(input.filePath);
16555
+ try {
16556
+ for await (const chunk of stream) {
16557
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
16558
+ assertNotBinary(buffer);
16559
+ const text = decoder.write(buffer);
16560
+ if (text.length > 0) {
16561
+ await visitor(text);
16562
+ }
16563
+ }
16564
+ const trailingText = decoder.end();
16565
+ if (trailingText && trailingText.length > 0) {
16566
+ await visitor(trailingText);
16567
+ }
16568
+ } catch (error45) {
16569
+ if (error45 instanceof Error && "code" in error45) {
16570
+ throw error45;
16571
+ }
16572
+ throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
16573
+ } finally {
16574
+ stream.destroy();
16575
+ }
16576
+ }
16577
+ async function writeAll(handle, buffer) {
16578
+ let offset = 0;
16579
+ while (offset < buffer.length) {
16580
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.length - offset);
16581
+ offset += bytesWritten;
16582
+ }
16583
+ }
16584
+ async function writeEncodedText(handle, encoding, text) {
16585
+ if (text.length === 0) {
16586
+ return 0;
16587
+ }
16588
+ const buffer = import_iconv_lite.default.encode(text, encoding);
16589
+ await writeAll(handle, buffer);
16590
+ return buffer.byteLength;
16591
+ }
16592
+ async function replaceLargeGbkFileText(input) {
16593
+ const tempPath = path2.join(
16594
+ path2.dirname(input.filePath),
16595
+ `${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
16596
+ );
16597
+ const handle = await fs2.open(tempPath, "w");
16598
+ const carryLength = Math.max(input.oldString.length - 1, 0);
16599
+ let carry = "";
16600
+ let occurrencesBefore = 0;
16601
+ let bytesWritten = 0;
16602
+ const flushText = async (text, flush = false) => {
16603
+ const combined = carry + text;
16604
+ const splitAt = flush ? combined.length : Math.max(0, combined.length - carryLength);
16605
+ const processable = combined.slice(0, splitAt);
16606
+ carry = combined.slice(splitAt);
16607
+ if (processable.length === 0) {
16608
+ return;
16609
+ }
16610
+ const matchCount = countOccurrences(processable, input.oldString);
16611
+ const seenBefore = occurrencesBefore;
16612
+ occurrencesBefore += matchCount;
16613
+ let output = processable;
16614
+ if (input.replaceAll) {
16615
+ if (matchCount > 0) {
16616
+ output = processable.split(input.oldString).join(input.newString);
16617
+ }
16618
+ } else if (seenBefore === 0 && matchCount > 0) {
16619
+ output = processable.replace(input.oldString, input.newString);
16620
+ }
16621
+ bytesWritten += await writeEncodedText(handle, input.encoding, output);
16622
+ };
16623
+ try {
16624
+ await visitDecodedTextChunks(input, async (text) => {
16625
+ await flushText(text);
16626
+ });
16627
+ await flushText("", true);
16628
+ if (occurrencesBefore === 0) {
16629
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
16630
+ }
16631
+ if (!input.replaceAll && occurrencesBefore > 1) {
16632
+ throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
16633
+ }
16634
+ await handle.close();
16635
+ const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
16636
+ await fs2.chmod(tempPath, mode);
16637
+ await fs2.rename(tempPath, input.filePath);
16638
+ return {
16639
+ filePath: input.filePath,
16640
+ encoding: input.encoding,
16641
+ replacements: input.replaceAll ? occurrencesBefore : 1,
16642
+ occurrencesBefore,
16643
+ bytesRead: input.stat.size,
16644
+ bytesWritten
16645
+ };
16646
+ } catch (error45) {
16647
+ await handle.close().catch(() => void 0);
16648
+ await fs2.rm(tempPath, { force: true }).catch(() => void 0);
16649
+ throw error45;
16650
+ }
16651
+ }
16652
+ async function replaceGbkFileText(input) {
16653
+ if (input.oldString.length === 0) {
16654
+ throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
16655
+ }
16656
+ const replaceAll = input.replaceAll ?? false;
16657
+ const resolved = await resolveReadableGbkFile(input);
16658
+ const hasScopedRange = input.startLine !== void 0 || input.endLine !== void 0 || input.startAnchor !== void 0 || input.endAnchor !== void 0;
16659
+ if (resolved.stat.size >= STREAMING_FILE_SIZE_THRESHOLD_BYTES && !hasScopedRange) {
16660
+ return await replaceLargeGbkFileText({
16661
+ ...resolved,
16662
+ oldString: input.oldString,
16663
+ newString: input.newString,
16664
+ replaceAll
16665
+ });
16666
+ }
16667
+ const current = await readWholeGbkTextFile(resolved);
16668
+ const scope = resolveEditScope(current.content, input);
16669
+ const occurrencesBefore = countOccurrences(scope.selectedText, input.oldString);
16670
+ if (!replaceAll && occurrencesBefore === 0) {
16671
+ const loose = tryLooseBlockReplace(scope.selectedText, input.oldString, input.newString);
16672
+ if (loose !== null) {
16673
+ const outputText2 = `${current.content.slice(0, scope.rangeStart)}${loose.content}${current.content.slice(scope.rangeEnd)}`;
16674
+ const buffer2 = import_iconv_lite.default.encode(outputText2, current.encoding);
16675
+ await fs2.writeFile(current.filePath, buffer2);
16676
+ return {
16677
+ filePath: current.filePath,
16678
+ encoding: current.encoding,
16679
+ replacements: 1,
16680
+ occurrencesBefore: loose.occurrencesBefore,
16681
+ bytesRead: current.bytesRead,
16682
+ bytesWritten: buffer2.byteLength
16683
+ };
16684
+ }
16685
+ }
16686
+ if (replaceAll) {
16687
+ if (occurrencesBefore === 0) {
16688
+ throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
16689
+ }
16690
+ } else if (occurrencesBefore === 0) {
16691
+ throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
16692
+ } else if (occurrencesBefore > 1) {
16693
+ throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
16694
+ }
16695
+ const replaced = replaceAll ? scope.selectedText.split(input.oldString).join(input.newString) : scope.selectedText.replace(input.oldString, input.newString);
16696
+ const outputText = `${current.content.slice(0, scope.rangeStart)}${replaced}${current.content.slice(scope.rangeEnd)}`;
16697
+ const buffer = import_iconv_lite.default.encode(outputText, current.encoding);
16698
+ await fs2.writeFile(current.filePath, buffer);
16699
+ return {
16700
+ filePath: current.filePath,
16701
+ encoding: current.encoding,
16702
+ replacements: replaceAll ? occurrencesBefore : 1,
16703
+ occurrencesBefore,
16704
+ bytesRead: current.bytesRead,
16705
+ bytesWritten: buffer.byteLength
16706
+ };
16707
+ }
16398
16708
 
16399
16709
  // src/tools/gbk_edit.ts
16400
16710
  var gbk_edit_default = tool({
@@ -16404,43 +16714,16 @@ var gbk_edit_default = tool({
16404
16714
  oldString: tool.schema.string().describe("Text to replace"),
16405
16715
  newString: tool.schema.string().describe("Replacement text"),
16406
16716
  replaceAll: tool.schema.boolean().optional().describe("Replace all occurrences"),
16717
+ startLine: tool.schema.number().int().positive().optional().describe("Restrict edit to 1-based start line"),
16718
+ endLine: tool.schema.number().int().positive().optional().describe("Restrict edit to 1-based end line"),
16719
+ startAnchor: tool.schema.string().optional().describe("Restrict edit to content after this anchor"),
16720
+ endAnchor: tool.schema.string().optional().describe("Restrict edit to content before this anchor"),
16407
16721
  encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
16408
16722
  allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
16409
16723
  },
16410
16724
  async execute(args, context) {
16411
- if (args.oldString.length === 0) {
16412
- throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
16413
- }
16414
- const encoding = args.encoding ?? "gbk";
16415
- const replaceAll = args.replaceAll ?? false;
16416
- const { candidatePath } = await assertPathAllowed(args.filePath, context, args.allowExternal ?? false);
16417
- const current = await readGbkFile({
16418
- filePath: candidatePath,
16419
- encoding,
16420
- context,
16421
- allowExternal: args.allowExternal
16422
- });
16423
- const sourceText = current.content.split("\n").map((line) => line.replace(/^\d+: /, "")).join("\n");
16424
- const occurrencesBefore = countOccurrences(sourceText, args.oldString);
16425
- if (replaceAll) {
16426
- if (occurrencesBefore === 0) {
16427
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${args.oldString}`);
16428
- }
16429
- } else if (occurrencesBefore === 0) {
16430
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${args.oldString}`);
16431
- } else if (occurrencesBefore > 1) {
16432
- throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${args.oldString}`);
16433
- }
16434
- const replaced = replaceAll ? sourceText.split(args.oldString).join(args.newString) : sourceText.replace(args.oldString, args.newString);
16435
- const buffer = import_iconv_lite2.default.encode(replaced, encoding);
16436
- await fs3.writeFile(candidatePath, buffer);
16437
- return JSON.stringify({
16438
- filePath: candidatePath,
16439
- encoding,
16440
- replacements: replaceAll ? occurrencesBefore : 1,
16441
- occurrencesBefore,
16442
- bytesWritten: buffer.byteLength
16443
- }, null, 2);
16725
+ const result = await replaceGbkFileText({ ...args, context });
16726
+ return JSON.stringify(result, null, 2);
16444
16727
  }
16445
16728
  });
16446
16729
  export {