opencode-gbk-tools 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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}`);
@@ -16346,6 +16343,129 @@ function splitLinesWithNumbers(text, offset = 1, limit = 2e3) {
16346
16343
  content
16347
16344
  };
16348
16345
  }
16346
+ function detectNewlineStyle(text) {
16347
+ const crlfCount = (text.match(/\r\n/g) ?? []).length;
16348
+ const lfCount = (text.match(/(^|[^\r])\n/g) ?? []).length;
16349
+ if (crlfCount > 0 && lfCount === 0) {
16350
+ return "crlf";
16351
+ }
16352
+ if (lfCount > 0 && crlfCount === 0) {
16353
+ return "lf";
16354
+ }
16355
+ if (crlfCount > 0 && lfCount > 0) {
16356
+ return "mixed";
16357
+ }
16358
+ return "none";
16359
+ }
16360
+ function collectTailLines(text, limit) {
16361
+ assertPositiveInteger(limit, "limit");
16362
+ const lines = text.split(/\r?\n/);
16363
+ const totalLines = lines.length;
16364
+ const startLine = Math.max(1, totalLines - limit + 1);
16365
+ const selected = lines.slice(startLine - 1);
16366
+ return {
16367
+ startLine,
16368
+ endLine: totalLines,
16369
+ totalLines,
16370
+ content: selected.map((line, index) => `${startLine + index}: ${line}`).join("\n"),
16371
+ truncated: startLine > 1,
16372
+ tail: true,
16373
+ newlineStyle: detectNewlineStyle(text)
16374
+ };
16375
+ }
16376
+ function finalizeNewlineStyle(crlfCount, lfCount) {
16377
+ if (crlfCount > 0 && lfCount === 0) {
16378
+ return "crlf";
16379
+ }
16380
+ if (lfCount > 0 && crlfCount === 0) {
16381
+ return "lf";
16382
+ }
16383
+ if (crlfCount > 0 && lfCount > 0) {
16384
+ return "mixed";
16385
+ }
16386
+ return "none";
16387
+ }
16388
+ function applyLineRange(text, startLine, endLine) {
16389
+ if (startLine === void 0 && endLine === void 0) {
16390
+ return {
16391
+ selectedText: text,
16392
+ rangeStart: 0,
16393
+ rangeEnd: text.length
16394
+ };
16395
+ }
16396
+ const lines = text.split(/\r?\n/);
16397
+ const actualStartLine = startLine ?? 1;
16398
+ const actualEndLine = endLine ?? lines.length;
16399
+ assertPositiveInteger(actualStartLine, "startLine");
16400
+ assertPositiveInteger(actualEndLine, "endLine");
16401
+ if (actualEndLine < actualStartLine) {
16402
+ throw createGbkError("GBK_INVALID_ARGUMENT", "endLine \u4E0D\u80FD\u5C0F\u4E8E startLine");
16403
+ }
16404
+ let cursor = 0;
16405
+ let rangeStart = 0;
16406
+ let rangeEnd = text.length;
16407
+ for (let lineNumber = 1; lineNumber <= lines.length; lineNumber += 1) {
16408
+ if (lineNumber === actualStartLine) {
16409
+ rangeStart = cursor;
16410
+ }
16411
+ cursor += lines[lineNumber - 1].length;
16412
+ if (lineNumber < lines.length) {
16413
+ const newlineLength = text.startsWith("\r\n", cursor) ? 2 : 1;
16414
+ cursor += newlineLength;
16415
+ }
16416
+ if (lineNumber === actualEndLine) {
16417
+ rangeEnd = cursor;
16418
+ break;
16419
+ }
16420
+ }
16421
+ return {
16422
+ selectedText: text.slice(rangeStart, rangeEnd),
16423
+ rangeStart,
16424
+ rangeEnd
16425
+ };
16426
+ }
16427
+ function applyAnchors(text, startAnchor, endAnchor) {
16428
+ if (!startAnchor && !endAnchor) {
16429
+ return {
16430
+ selectedText: text,
16431
+ rangeStart: 0,
16432
+ rangeEnd: text.length
16433
+ };
16434
+ }
16435
+ let rangeStart = 0;
16436
+ let rangeEnd = text.length;
16437
+ if (startAnchor) {
16438
+ const found = text.indexOf(startAnchor);
16439
+ if (found === -1) {
16440
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\u70B9: ${startAnchor}`);
16441
+ }
16442
+ rangeStart = found + startAnchor.length;
16443
+ }
16444
+ if (endAnchor) {
16445
+ const found = text.indexOf(endAnchor, rangeStart);
16446
+ if (found === -1) {
16447
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u7ED3\u675F\u951A\u70B9: ${endAnchor}`);
16448
+ }
16449
+ rangeEnd = found;
16450
+ }
16451
+ if (rangeEnd < rangeStart) {
16452
+ throw createGbkError("GBK_INVALID_ARGUMENT", "\u951A\u70B9\u8303\u56F4\u65E0\u6548");
16453
+ }
16454
+ return {
16455
+ selectedText: text.slice(rangeStart, rangeEnd),
16456
+ rangeStart,
16457
+ rangeEnd
16458
+ };
16459
+ }
16460
+ function resolveEditScope(text, input) {
16461
+ const anchored = applyAnchors(text, input.startAnchor, input.endAnchor);
16462
+ const lineRanged = applyLineRange(anchored.selectedText, input.startLine, input.endLine);
16463
+ return {
16464
+ selectedText: lineRanged.selectedText,
16465
+ rangeStart: anchored.rangeStart + lineRanged.rangeStart,
16466
+ rangeEnd: anchored.rangeStart + lineRanged.rangeEnd
16467
+ };
16468
+ }
16349
16469
  function countOccurrences(text, target) {
16350
16470
  if (target.length === 0) {
16351
16471
  throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
@@ -16366,10 +16486,9 @@ async function readBufferAsText(buffer, encoding) {
16366
16486
  assertNotBinary(buffer);
16367
16487
  return import_iconv_lite.default.decode(buffer, encoding);
16368
16488
  }
16369
- async function readGbkFile(input) {
16489
+ async function resolveReadableGbkFile(input) {
16370
16490
  const encoding = input.encoding ?? "gbk";
16371
- const offset = input.offset ?? 1;
16372
- const limit = input.limit ?? 2e3;
16491
+ assertEncodingSupported(encoding);
16373
16492
  const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
16374
16493
  let stat;
16375
16494
  try {
@@ -16380,21 +16499,312 @@ async function readGbkFile(input) {
16380
16499
  if (stat.isDirectory()) {
16381
16500
  throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
16382
16501
  }
16502
+ return {
16503
+ filePath: candidatePath,
16504
+ encoding,
16505
+ stat
16506
+ };
16507
+ }
16508
+ async function readWholeGbkTextFile(input) {
16383
16509
  try {
16384
- const buffer = await fs2.readFile(candidatePath);
16385
- const text = await readBufferAsText(buffer, encoding);
16510
+ const buffer = await fs2.readFile(input.filePath);
16511
+ const content = await readBufferAsText(buffer, input.encoding);
16386
16512
  return {
16387
- filePath: candidatePath,
16388
- encoding,
16389
- ...splitLinesWithNumbers(text, offset, limit)
16513
+ filePath: input.filePath,
16514
+ encoding: input.encoding,
16515
+ content,
16516
+ bytesRead: buffer.byteLength
16390
16517
  };
16391
16518
  } catch (error45) {
16392
16519
  if (error45 instanceof Error && "code" in error45) {
16393
16520
  throw error45;
16394
16521
  }
16395
- throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${candidatePath}`, error45);
16522
+ throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
16523
+ }
16524
+ }
16525
+ async function visitDecodedTextChunks(input, visitor) {
16526
+ const decoder = import_iconv_lite.default.getDecoder(input.encoding);
16527
+ const stream = createReadStream(input.filePath);
16528
+ try {
16529
+ for await (const chunk of stream) {
16530
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
16531
+ assertNotBinary(buffer);
16532
+ const text = decoder.write(buffer);
16533
+ if (text.length > 0) {
16534
+ await visitor(text);
16535
+ }
16536
+ }
16537
+ const trailingText = decoder.end();
16538
+ if (trailingText && trailingText.length > 0) {
16539
+ await visitor(trailingText);
16540
+ }
16541
+ } catch (error45) {
16542
+ if (error45 instanceof Error && "code" in error45) {
16543
+ throw error45;
16544
+ }
16545
+ throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
16546
+ } finally {
16547
+ stream.destroy();
16548
+ }
16549
+ }
16550
+ function createLineCollector(offset, limit) {
16551
+ const lines = [];
16552
+ let pending = "";
16553
+ let totalLines = 0;
16554
+ let crlfCount = 0;
16555
+ let lfCount = 0;
16556
+ const emitLine = (line) => {
16557
+ totalLines += 1;
16558
+ if (totalLines >= offset && lines.length < limit) {
16559
+ lines.push(`${totalLines}: ${line}`);
16560
+ }
16561
+ };
16562
+ return {
16563
+ push(text) {
16564
+ if (text.length === 0) {
16565
+ return;
16566
+ }
16567
+ const combined = pending + text;
16568
+ let start = 0;
16569
+ while (true) {
16570
+ const newlineIndex = combined.indexOf("\n", start);
16571
+ if (newlineIndex === -1) {
16572
+ break;
16573
+ }
16574
+ let line = combined.slice(start, newlineIndex);
16575
+ if (line.endsWith("\r")) {
16576
+ line = line.slice(0, -1);
16577
+ crlfCount += 1;
16578
+ } else {
16579
+ lfCount += 1;
16580
+ }
16581
+ emitLine(line);
16582
+ start = newlineIndex + 1;
16583
+ }
16584
+ pending = combined.slice(start);
16585
+ },
16586
+ finish() {
16587
+ emitLine(pending);
16588
+ const endLine = lines.length === 0 ? totalLines : offset + lines.length - 1;
16589
+ return {
16590
+ startLine: offset,
16591
+ endLine,
16592
+ totalLines,
16593
+ content: lines.join("\n"),
16594
+ tail: false,
16595
+ truncated: endLine < totalLines,
16596
+ newlineStyle: finalizeNewlineStyle(crlfCount, lfCount)
16597
+ };
16598
+ }
16599
+ };
16600
+ }
16601
+ function createTailCollector(limit) {
16602
+ assertPositiveInteger(limit, "limit");
16603
+ const lines = [];
16604
+ let pending = "";
16605
+ let totalLines = 0;
16606
+ let crlfCount = 0;
16607
+ let lfCount = 0;
16608
+ const emitLine = (line) => {
16609
+ totalLines += 1;
16610
+ lines.push(line);
16611
+ if (lines.length > limit) {
16612
+ lines.shift();
16613
+ }
16614
+ };
16615
+ return {
16616
+ push(text) {
16617
+ if (text.length === 0) {
16618
+ return;
16619
+ }
16620
+ const combined = pending + text;
16621
+ let start = 0;
16622
+ while (true) {
16623
+ const newlineIndex = combined.indexOf("\n", start);
16624
+ if (newlineIndex === -1) {
16625
+ break;
16626
+ }
16627
+ let line = combined.slice(start, newlineIndex);
16628
+ if (line.endsWith("\r")) {
16629
+ line = line.slice(0, -1);
16630
+ crlfCount += 1;
16631
+ } else {
16632
+ lfCount += 1;
16633
+ }
16634
+ emitLine(line);
16635
+ start = newlineIndex + 1;
16636
+ }
16637
+ pending = combined.slice(start);
16638
+ },
16639
+ finish() {
16640
+ emitLine(pending);
16641
+ const startLine = Math.max(1, totalLines - lines.length + 1);
16642
+ return {
16643
+ startLine,
16644
+ endLine: totalLines,
16645
+ totalLines,
16646
+ content: lines.map((line, index) => `${startLine + index}: ${line}`).join("\n"),
16647
+ truncated: startLine > 1,
16648
+ tail: true,
16649
+ newlineStyle: finalizeNewlineStyle(crlfCount, lfCount)
16650
+ };
16651
+ }
16652
+ };
16653
+ }
16654
+ async function writeAll(handle, buffer) {
16655
+ let offset = 0;
16656
+ while (offset < buffer.length) {
16657
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.length - offset);
16658
+ offset += bytesWritten;
16659
+ }
16660
+ }
16661
+ async function writeEncodedText(handle, encoding, text) {
16662
+ if (text.length === 0) {
16663
+ return 0;
16664
+ }
16665
+ const buffer = import_iconv_lite.default.encode(text, encoding);
16666
+ await writeAll(handle, buffer);
16667
+ return buffer.byteLength;
16668
+ }
16669
+ async function replaceLargeGbkFileText(input) {
16670
+ const tempPath = path2.join(
16671
+ path2.dirname(input.filePath),
16672
+ `${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
16673
+ );
16674
+ const handle = await fs2.open(tempPath, "w");
16675
+ const carryLength = Math.max(input.oldString.length - 1, 0);
16676
+ let carry = "";
16677
+ let occurrencesBefore = 0;
16678
+ let bytesWritten = 0;
16679
+ const flushText = async (text, flush = false) => {
16680
+ const combined = carry + text;
16681
+ const splitAt = flush ? combined.length : Math.max(0, combined.length - carryLength);
16682
+ const processable = combined.slice(0, splitAt);
16683
+ carry = combined.slice(splitAt);
16684
+ if (processable.length === 0) {
16685
+ return;
16686
+ }
16687
+ const matchCount = countOccurrences(processable, input.oldString);
16688
+ const seenBefore = occurrencesBefore;
16689
+ occurrencesBefore += matchCount;
16690
+ let output = processable;
16691
+ if (input.replaceAll) {
16692
+ if (matchCount > 0) {
16693
+ output = processable.split(input.oldString).join(input.newString);
16694
+ }
16695
+ } else if (seenBefore === 0 && matchCount > 0) {
16696
+ output = processable.replace(input.oldString, input.newString);
16697
+ }
16698
+ bytesWritten += await writeEncodedText(handle, input.encoding, output);
16699
+ };
16700
+ try {
16701
+ await visitDecodedTextChunks(input, async (text) => {
16702
+ await flushText(text);
16703
+ });
16704
+ await flushText("", true);
16705
+ if (occurrencesBefore === 0) {
16706
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
16707
+ }
16708
+ if (!input.replaceAll && occurrencesBefore > 1) {
16709
+ throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
16710
+ }
16711
+ await handle.close();
16712
+ const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
16713
+ await fs2.chmod(tempPath, mode);
16714
+ await fs2.rename(tempPath, input.filePath);
16715
+ return {
16716
+ filePath: input.filePath,
16717
+ encoding: input.encoding,
16718
+ replacements: input.replaceAll ? occurrencesBefore : 1,
16719
+ occurrencesBefore,
16720
+ bytesRead: input.stat.size,
16721
+ bytesWritten
16722
+ };
16723
+ } catch (error45) {
16724
+ await handle.close().catch(() => void 0);
16725
+ await fs2.rm(tempPath, { force: true }).catch(() => void 0);
16726
+ throw error45;
16396
16727
  }
16397
16728
  }
16729
+ async function readGbkFile(input) {
16730
+ const offset = input.offset ?? 1;
16731
+ const limit = input.limit ?? 2e3;
16732
+ const tail = input.tail ?? false;
16733
+ const resolved = await resolveReadableGbkFile(input);
16734
+ if (resolved.stat.size < STREAMING_FILE_SIZE_THRESHOLD_BYTES) {
16735
+ const textFile = await readWholeGbkTextFile(resolved);
16736
+ const lineWindow2 = tail ? collectTailLines(textFile.content, limit) : {
16737
+ ...splitLinesWithNumbers(textFile.content, offset, limit),
16738
+ tail: false,
16739
+ truncated: false,
16740
+ newlineStyle: detectNewlineStyle(textFile.content)
16741
+ };
16742
+ if (!tail) {
16743
+ lineWindow2.truncated = lineWindow2.endLine < lineWindow2.totalLines;
16744
+ }
16745
+ return {
16746
+ filePath: textFile.filePath,
16747
+ encoding: textFile.encoding,
16748
+ bytesRead: textFile.bytesRead,
16749
+ fileSize: textFile.bytesRead,
16750
+ streamed: false,
16751
+ ...lineWindow2
16752
+ };
16753
+ }
16754
+ const collector = tail ? createTailCollector(limit) : createLineCollector(offset, limit);
16755
+ await visitDecodedTextChunks(resolved, (text) => {
16756
+ collector.push(text);
16757
+ });
16758
+ const lineWindow = collector.finish();
16759
+ return {
16760
+ filePath: resolved.filePath,
16761
+ encoding: resolved.encoding,
16762
+ bytesRead: resolved.stat.size,
16763
+ fileSize: resolved.stat.size,
16764
+ streamed: true,
16765
+ ...lineWindow
16766
+ };
16767
+ }
16768
+ async function replaceGbkFileText(input) {
16769
+ if (input.oldString.length === 0) {
16770
+ throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
16771
+ }
16772
+ const replaceAll = input.replaceAll ?? false;
16773
+ const resolved = await resolveReadableGbkFile(input);
16774
+ const hasScopedRange = input.startLine !== void 0 || input.endLine !== void 0 || input.startAnchor !== void 0 || input.endAnchor !== void 0;
16775
+ if (resolved.stat.size >= STREAMING_FILE_SIZE_THRESHOLD_BYTES && !hasScopedRange) {
16776
+ return await replaceLargeGbkFileText({
16777
+ ...resolved,
16778
+ oldString: input.oldString,
16779
+ newString: input.newString,
16780
+ replaceAll
16781
+ });
16782
+ }
16783
+ const current = await readWholeGbkTextFile(resolved);
16784
+ const scope = resolveEditScope(current.content, input);
16785
+ const occurrencesBefore = countOccurrences(scope.selectedText, input.oldString);
16786
+ if (replaceAll) {
16787
+ if (occurrencesBefore === 0) {
16788
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
16789
+ }
16790
+ } else if (occurrencesBefore === 0) {
16791
+ throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
16792
+ } else if (occurrencesBefore > 1) {
16793
+ throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
16794
+ }
16795
+ const replaced = replaceAll ? scope.selectedText.split(input.oldString).join(input.newString) : scope.selectedText.replace(input.oldString, input.newString);
16796
+ const outputText = `${current.content.slice(0, scope.rangeStart)}${replaced}${current.content.slice(scope.rangeEnd)}`;
16797
+ const buffer = import_iconv_lite.default.encode(outputText, current.encoding);
16798
+ await fs2.writeFile(current.filePath, buffer);
16799
+ return {
16800
+ filePath: current.filePath,
16801
+ encoding: current.encoding,
16802
+ replacements: replaceAll ? occurrencesBefore : 1,
16803
+ occurrencesBefore,
16804
+ bytesRead: current.bytesRead,
16805
+ bytesWritten: buffer.byteLength
16806
+ };
16807
+ }
16398
16808
  async function writeGbkFile(input) {
16399
16809
  const encoding = input.encoding ?? "gbk";
16400
16810
  const createDirectories = input.createDirectories ?? true;
@@ -16461,43 +16871,16 @@ var gbk_edit_default = tool({
16461
16871
  oldString: tool.schema.string().describe("Text to replace"),
16462
16872
  newString: tool.schema.string().describe("Replacement text"),
16463
16873
  replaceAll: tool.schema.boolean().optional().describe("Replace all occurrences"),
16874
+ startLine: tool.schema.number().int().positive().optional().describe("Restrict edit to 1-based start line"),
16875
+ endLine: tool.schema.number().int().positive().optional().describe("Restrict edit to 1-based end line"),
16876
+ startAnchor: tool.schema.string().optional().describe("Restrict edit to content after this anchor"),
16877
+ endAnchor: tool.schema.string().optional().describe("Restrict edit to content before this anchor"),
16464
16878
  encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
16465
16879
  allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
16466
16880
  },
16467
16881
  async execute(args, context) {
16468
- if (args.oldString.length === 0) {
16469
- throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
16470
- }
16471
- const encoding = args.encoding ?? "gbk";
16472
- const replaceAll = args.replaceAll ?? false;
16473
- const { candidatePath } = await assertPathAllowed(args.filePath, context, args.allowExternal ?? false);
16474
- const current = await readGbkFile({
16475
- filePath: candidatePath,
16476
- encoding,
16477
- context,
16478
- allowExternal: args.allowExternal
16479
- });
16480
- const sourceText = current.content.split("\n").map((line) => line.replace(/^\d+: /, "")).join("\n");
16481
- const occurrencesBefore = countOccurrences(sourceText, args.oldString);
16482
- if (replaceAll) {
16483
- if (occurrencesBefore === 0) {
16484
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${args.oldString}`);
16485
- }
16486
- } else if (occurrencesBefore === 0) {
16487
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${args.oldString}`);
16488
- } else if (occurrencesBefore > 1) {
16489
- throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${args.oldString}`);
16490
- }
16491
- const replaced = replaceAll ? sourceText.split(args.oldString).join(args.newString) : sourceText.replace(args.oldString, args.newString);
16492
- const buffer = import_iconv_lite2.default.encode(replaced, encoding);
16493
- await fs3.writeFile(candidatePath, buffer);
16494
- return JSON.stringify({
16495
- filePath: candidatePath,
16496
- encoding,
16497
- replacements: replaceAll ? occurrencesBefore : 1,
16498
- occurrencesBefore,
16499
- bytesWritten: buffer.byteLength
16500
- }, null, 2);
16882
+ const result = await replaceGbkFileText({ ...args, context });
16883
+ return JSON.stringify(result, null, 2);
16501
16884
  }
16502
16885
  });
16503
16886
 
@@ -16508,6 +16891,7 @@ var gbk_read_default = tool({
16508
16891
  filePath: tool.schema.string().describe("Target file path"),
16509
16892
  offset: tool.schema.number().int().positive().optional().describe("1-based start line"),
16510
16893
  limit: tool.schema.number().int().positive().optional().describe("Number of lines to read"),
16894
+ tail: tool.schema.boolean().optional().describe("Read last N lines instead of offset-based window"),
16511
16895
  encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
16512
16896
  allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
16513
16897
  },
@@ -1,30 +1,30 @@
1
1
  {
2
2
  "manifestVersion": 1,
3
3
  "packageName": "opencode-gbk-tools",
4
- "packageVersion": "0.1.1",
4
+ "packageVersion": "0.1.2",
5
5
  "artifacts": [
6
6
  {
7
7
  "relativePath": "tools/gbk_edit.js",
8
8
  "kind": "tool",
9
- "expectedHash": "e52297da0c7a3197df81e7081dc090ffbf9dd27682dabf2c41996616b297e816",
9
+ "expectedHash": "c90cdc75ece2c66d72750cb9243d20235a7dad82829e8f519a7c31f7432ef3ff",
10
10
  "hashAlgorithm": "sha256"
11
11
  },
12
12
  {
13
13
  "relativePath": "tools/gbk_read.js",
14
14
  "kind": "tool",
15
- "expectedHash": "e782cf10f4f01893b3bd87fada364b1097ad50a1bf31a2440a84589ac1b9f15b",
15
+ "expectedHash": "4ed95d744e6d5a70f578908fe0efc7b70b4a8c416e9a42fd2ed8726c02562cbe",
16
16
  "hashAlgorithm": "sha256"
17
17
  },
18
18
  {
19
19
  "relativePath": "tools/gbk_write.js",
20
20
  "kind": "tool",
21
- "expectedHash": "16b59a6a722a0e940eb107e30cebad4f0c60a5261c49eb0713ca9ef4c2d0a4d3",
21
+ "expectedHash": "fcb35107b599a9d2a60a1549372ef39f9b4e7f3759b5242faca3ed4b0170330f",
22
22
  "hashAlgorithm": "sha256"
23
23
  },
24
24
  {
25
25
  "relativePath": "agents/gbk-engine.md",
26
26
  "kind": "agent",
27
- "expectedHash": "07e1e126c49f22667d7f524760d7059d9e51d1842cf7c1d078c44e6f986110df",
27
+ "expectedHash": "755b6278033400bdc08bb0d7dc491eabe5aa3a0157c6bef4304c7a5f2038c0dd",
28
28
  "hashAlgorithm": "sha256"
29
29
  }
30
30
  ]