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.
- package/README.md +33 -0
- package/dist/agents/gbk-engine.md +2 -0
- package/dist/cli/index.js +65 -5
- package/dist/opencode-tools/gbk_edit.js +364 -81
- package/dist/opencode-tools/gbk_read.js +230 -9
- package/dist/opencode-tools/gbk_write.js +3 -0
- package/dist/plugin/index.js +543 -65
- package/dist/release-manifest.json +5 -5
- package/package.json +3 -2
package/dist/plugin/index.js
CHANGED
|
@@ -182,7 +182,7 @@ var require_internal = __commonJS({
|
|
|
182
182
|
// Codec.
|
|
183
183
|
_internal: InternalCodec
|
|
184
184
|
};
|
|
185
|
-
function InternalCodec(codecOptions,
|
|
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 =
|
|
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,
|
|
355
|
-
this.iconv =
|
|
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,
|
|
480
|
-
this.iconv =
|
|
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,
|
|
631
|
-
this.iconv =
|
|
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,
|
|
730
|
-
this.iconv =
|
|
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,
|
|
813
|
-
this.iconv =
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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][
|
|
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,207 @@ 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
|
+
}
|
|
16469
|
+
function normalizeNewlines(text) {
|
|
16470
|
+
return text.replace(/\r\n/g, "\n");
|
|
16471
|
+
}
|
|
16472
|
+
function trimRightSpaces(text) {
|
|
16473
|
+
return text.replace(/[ \t]+$/g, "");
|
|
16474
|
+
}
|
|
16475
|
+
function splitNormalizedLines(text) {
|
|
16476
|
+
return normalizeNewlines(text).split("\n");
|
|
16477
|
+
}
|
|
16478
|
+
function trimTrailingEmptyLines(lines) {
|
|
16479
|
+
const result = [...lines];
|
|
16480
|
+
while (result.length > 0 && trimRightSpaces(result[result.length - 1]) === "") {
|
|
16481
|
+
result.pop();
|
|
16482
|
+
}
|
|
16483
|
+
return result;
|
|
16484
|
+
}
|
|
16485
|
+
function getNearestContext(content, oldString) {
|
|
16486
|
+
const lines = content.split(/\r?\n/);
|
|
16487
|
+
const oldLines = normalizeNewlines(oldString).split("\n").filter(Boolean);
|
|
16488
|
+
const firstToken = oldLines[0] || oldString.trim();
|
|
16489
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
16490
|
+
if (lines[index].includes(firstToken)) {
|
|
16491
|
+
return lines.slice(index, index + 4).join("\n");
|
|
16492
|
+
}
|
|
16493
|
+
}
|
|
16494
|
+
return lines.slice(0, 4).join("\n");
|
|
16495
|
+
}
|
|
16496
|
+
function buildNoMatchMessage(content, oldString) {
|
|
16497
|
+
return [
|
|
16498
|
+
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
|
|
16499
|
+
"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",
|
|
16500
|
+
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
|
|
16501
|
+
getNearestContext(content, oldString)
|
|
16502
|
+
].join("\n");
|
|
16503
|
+
}
|
|
16504
|
+
function tryLooseBlockReplace(content, oldString, newString) {
|
|
16505
|
+
const normalizedContent = normalizeNewlines(content);
|
|
16506
|
+
const contentLines = splitNormalizedLines(content);
|
|
16507
|
+
const oldLines = trimTrailingEmptyLines(splitNormalizedLines(oldString));
|
|
16508
|
+
const newLines = splitNormalizedLines(newString);
|
|
16509
|
+
if (oldLines.length === 0) {
|
|
16510
|
+
return null;
|
|
16511
|
+
}
|
|
16512
|
+
for (let start = 0; start < contentLines.length; start += 1) {
|
|
16513
|
+
let contentIndex = start;
|
|
16514
|
+
let oldIndex = 0;
|
|
16515
|
+
while (oldIndex < oldLines.length && contentIndex < contentLines.length) {
|
|
16516
|
+
const expected = trimRightSpaces(oldLines[oldIndex]);
|
|
16517
|
+
const actual = trimRightSpaces(contentLines[contentIndex]);
|
|
16518
|
+
if (expected === actual) {
|
|
16519
|
+
oldIndex += 1;
|
|
16520
|
+
contentIndex += 1;
|
|
16521
|
+
continue;
|
|
16522
|
+
}
|
|
16523
|
+
if (actual === "") {
|
|
16524
|
+
contentIndex += 1;
|
|
16525
|
+
continue;
|
|
16526
|
+
}
|
|
16527
|
+
if (expected === "") {
|
|
16528
|
+
oldIndex += 1;
|
|
16529
|
+
continue;
|
|
16530
|
+
}
|
|
16531
|
+
break;
|
|
16532
|
+
}
|
|
16533
|
+
if (oldIndex === oldLines.length) {
|
|
16534
|
+
const replacedNormalized = [
|
|
16535
|
+
...contentLines.slice(0, start),
|
|
16536
|
+
...newLines,
|
|
16537
|
+
...contentLines.slice(contentIndex)
|
|
16538
|
+
].join("\n");
|
|
16539
|
+
return {
|
|
16540
|
+
occurrencesBefore: 1,
|
|
16541
|
+
content: detectNewlineStyle(content) === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
|
|
16542
|
+
};
|
|
16543
|
+
}
|
|
16544
|
+
}
|
|
16545
|
+
return null;
|
|
16546
|
+
}
|
|
16349
16547
|
function countOccurrences(text, target) {
|
|
16350
16548
|
if (target.length === 0) {
|
|
16351
16549
|
throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
|
|
@@ -16366,10 +16564,9 @@ async function readBufferAsText(buffer, encoding) {
|
|
|
16366
16564
|
assertNotBinary(buffer);
|
|
16367
16565
|
return import_iconv_lite.default.decode(buffer, encoding);
|
|
16368
16566
|
}
|
|
16369
|
-
async function
|
|
16567
|
+
async function resolveReadableGbkFile(input) {
|
|
16370
16568
|
const encoding = input.encoding ?? "gbk";
|
|
16371
|
-
|
|
16372
|
-
const limit = input.limit ?? 2e3;
|
|
16569
|
+
assertEncodingSupported(encoding);
|
|
16373
16570
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
16374
16571
|
let stat;
|
|
16375
16572
|
try {
|
|
@@ -16380,21 +16577,328 @@ async function readGbkFile(input) {
|
|
|
16380
16577
|
if (stat.isDirectory()) {
|
|
16381
16578
|
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
16382
16579
|
}
|
|
16580
|
+
return {
|
|
16581
|
+
filePath: candidatePath,
|
|
16582
|
+
encoding,
|
|
16583
|
+
stat
|
|
16584
|
+
};
|
|
16585
|
+
}
|
|
16586
|
+
async function readWholeGbkTextFile(input) {
|
|
16383
16587
|
try {
|
|
16384
|
-
const buffer = await fs2.readFile(
|
|
16385
|
-
const
|
|
16588
|
+
const buffer = await fs2.readFile(input.filePath);
|
|
16589
|
+
const content = await readBufferAsText(buffer, input.encoding);
|
|
16386
16590
|
return {
|
|
16387
|
-
filePath:
|
|
16388
|
-
encoding,
|
|
16389
|
-
|
|
16591
|
+
filePath: input.filePath,
|
|
16592
|
+
encoding: input.encoding,
|
|
16593
|
+
content,
|
|
16594
|
+
bytesRead: buffer.byteLength
|
|
16390
16595
|
};
|
|
16391
16596
|
} catch (error45) {
|
|
16392
16597
|
if (error45 instanceof Error && "code" in error45) {
|
|
16393
16598
|
throw error45;
|
|
16394
16599
|
}
|
|
16395
|
-
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${
|
|
16600
|
+
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
|
|
16601
|
+
}
|
|
16602
|
+
}
|
|
16603
|
+
async function visitDecodedTextChunks(input, visitor) {
|
|
16604
|
+
const decoder = import_iconv_lite.default.getDecoder(input.encoding);
|
|
16605
|
+
const stream = createReadStream(input.filePath);
|
|
16606
|
+
try {
|
|
16607
|
+
for await (const chunk of stream) {
|
|
16608
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
16609
|
+
assertNotBinary(buffer);
|
|
16610
|
+
const text = decoder.write(buffer);
|
|
16611
|
+
if (text.length > 0) {
|
|
16612
|
+
await visitor(text);
|
|
16613
|
+
}
|
|
16614
|
+
}
|
|
16615
|
+
const trailingText = decoder.end();
|
|
16616
|
+
if (trailingText && trailingText.length > 0) {
|
|
16617
|
+
await visitor(trailingText);
|
|
16618
|
+
}
|
|
16619
|
+
} catch (error45) {
|
|
16620
|
+
if (error45 instanceof Error && "code" in error45) {
|
|
16621
|
+
throw error45;
|
|
16622
|
+
}
|
|
16623
|
+
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
|
|
16624
|
+
} finally {
|
|
16625
|
+
stream.destroy();
|
|
16396
16626
|
}
|
|
16397
16627
|
}
|
|
16628
|
+
function createLineCollector(offset, limit) {
|
|
16629
|
+
const lines = [];
|
|
16630
|
+
let pending = "";
|
|
16631
|
+
let totalLines = 0;
|
|
16632
|
+
let crlfCount = 0;
|
|
16633
|
+
let lfCount = 0;
|
|
16634
|
+
const emitLine = (line) => {
|
|
16635
|
+
totalLines += 1;
|
|
16636
|
+
if (totalLines >= offset && lines.length < limit) {
|
|
16637
|
+
lines.push(`${totalLines}: ${line}`);
|
|
16638
|
+
}
|
|
16639
|
+
};
|
|
16640
|
+
return {
|
|
16641
|
+
push(text) {
|
|
16642
|
+
if (text.length === 0) {
|
|
16643
|
+
return;
|
|
16644
|
+
}
|
|
16645
|
+
const combined = pending + text;
|
|
16646
|
+
let start = 0;
|
|
16647
|
+
while (true) {
|
|
16648
|
+
const newlineIndex = combined.indexOf("\n", start);
|
|
16649
|
+
if (newlineIndex === -1) {
|
|
16650
|
+
break;
|
|
16651
|
+
}
|
|
16652
|
+
let line = combined.slice(start, newlineIndex);
|
|
16653
|
+
if (line.endsWith("\r")) {
|
|
16654
|
+
line = line.slice(0, -1);
|
|
16655
|
+
crlfCount += 1;
|
|
16656
|
+
} else {
|
|
16657
|
+
lfCount += 1;
|
|
16658
|
+
}
|
|
16659
|
+
emitLine(line);
|
|
16660
|
+
start = newlineIndex + 1;
|
|
16661
|
+
}
|
|
16662
|
+
pending = combined.slice(start);
|
|
16663
|
+
},
|
|
16664
|
+
finish() {
|
|
16665
|
+
emitLine(pending);
|
|
16666
|
+
const endLine = lines.length === 0 ? totalLines : offset + lines.length - 1;
|
|
16667
|
+
return {
|
|
16668
|
+
startLine: offset,
|
|
16669
|
+
endLine,
|
|
16670
|
+
totalLines,
|
|
16671
|
+
content: lines.join("\n"),
|
|
16672
|
+
tail: false,
|
|
16673
|
+
truncated: endLine < totalLines,
|
|
16674
|
+
newlineStyle: finalizeNewlineStyle(crlfCount, lfCount)
|
|
16675
|
+
};
|
|
16676
|
+
}
|
|
16677
|
+
};
|
|
16678
|
+
}
|
|
16679
|
+
function createTailCollector(limit) {
|
|
16680
|
+
assertPositiveInteger(limit, "limit");
|
|
16681
|
+
const lines = [];
|
|
16682
|
+
let pending = "";
|
|
16683
|
+
let totalLines = 0;
|
|
16684
|
+
let crlfCount = 0;
|
|
16685
|
+
let lfCount = 0;
|
|
16686
|
+
const emitLine = (line) => {
|
|
16687
|
+
totalLines += 1;
|
|
16688
|
+
lines.push(line);
|
|
16689
|
+
if (lines.length > limit) {
|
|
16690
|
+
lines.shift();
|
|
16691
|
+
}
|
|
16692
|
+
};
|
|
16693
|
+
return {
|
|
16694
|
+
push(text) {
|
|
16695
|
+
if (text.length === 0) {
|
|
16696
|
+
return;
|
|
16697
|
+
}
|
|
16698
|
+
const combined = pending + text;
|
|
16699
|
+
let start = 0;
|
|
16700
|
+
while (true) {
|
|
16701
|
+
const newlineIndex = combined.indexOf("\n", start);
|
|
16702
|
+
if (newlineIndex === -1) {
|
|
16703
|
+
break;
|
|
16704
|
+
}
|
|
16705
|
+
let line = combined.slice(start, newlineIndex);
|
|
16706
|
+
if (line.endsWith("\r")) {
|
|
16707
|
+
line = line.slice(0, -1);
|
|
16708
|
+
crlfCount += 1;
|
|
16709
|
+
} else {
|
|
16710
|
+
lfCount += 1;
|
|
16711
|
+
}
|
|
16712
|
+
emitLine(line);
|
|
16713
|
+
start = newlineIndex + 1;
|
|
16714
|
+
}
|
|
16715
|
+
pending = combined.slice(start);
|
|
16716
|
+
},
|
|
16717
|
+
finish() {
|
|
16718
|
+
emitLine(pending);
|
|
16719
|
+
const startLine = Math.max(1, totalLines - lines.length + 1);
|
|
16720
|
+
return {
|
|
16721
|
+
startLine,
|
|
16722
|
+
endLine: totalLines,
|
|
16723
|
+
totalLines,
|
|
16724
|
+
content: lines.map((line, index) => `${startLine + index}: ${line}`).join("\n"),
|
|
16725
|
+
truncated: startLine > 1,
|
|
16726
|
+
tail: true,
|
|
16727
|
+
newlineStyle: finalizeNewlineStyle(crlfCount, lfCount)
|
|
16728
|
+
};
|
|
16729
|
+
}
|
|
16730
|
+
};
|
|
16731
|
+
}
|
|
16732
|
+
async function writeAll(handle, buffer) {
|
|
16733
|
+
let offset = 0;
|
|
16734
|
+
while (offset < buffer.length) {
|
|
16735
|
+
const { bytesWritten } = await handle.write(buffer, offset, buffer.length - offset);
|
|
16736
|
+
offset += bytesWritten;
|
|
16737
|
+
}
|
|
16738
|
+
}
|
|
16739
|
+
async function writeEncodedText(handle, encoding, text) {
|
|
16740
|
+
if (text.length === 0) {
|
|
16741
|
+
return 0;
|
|
16742
|
+
}
|
|
16743
|
+
const buffer = import_iconv_lite.default.encode(text, encoding);
|
|
16744
|
+
await writeAll(handle, buffer);
|
|
16745
|
+
return buffer.byteLength;
|
|
16746
|
+
}
|
|
16747
|
+
async function replaceLargeGbkFileText(input) {
|
|
16748
|
+
const tempPath = path2.join(
|
|
16749
|
+
path2.dirname(input.filePath),
|
|
16750
|
+
`${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
16751
|
+
);
|
|
16752
|
+
const handle = await fs2.open(tempPath, "w");
|
|
16753
|
+
const carryLength = Math.max(input.oldString.length - 1, 0);
|
|
16754
|
+
let carry = "";
|
|
16755
|
+
let occurrencesBefore = 0;
|
|
16756
|
+
let bytesWritten = 0;
|
|
16757
|
+
const flushText = async (text, flush = false) => {
|
|
16758
|
+
const combined = carry + text;
|
|
16759
|
+
const splitAt = flush ? combined.length : Math.max(0, combined.length - carryLength);
|
|
16760
|
+
const processable = combined.slice(0, splitAt);
|
|
16761
|
+
carry = combined.slice(splitAt);
|
|
16762
|
+
if (processable.length === 0) {
|
|
16763
|
+
return;
|
|
16764
|
+
}
|
|
16765
|
+
const matchCount = countOccurrences(processable, input.oldString);
|
|
16766
|
+
const seenBefore = occurrencesBefore;
|
|
16767
|
+
occurrencesBefore += matchCount;
|
|
16768
|
+
let output = processable;
|
|
16769
|
+
if (input.replaceAll) {
|
|
16770
|
+
if (matchCount > 0) {
|
|
16771
|
+
output = processable.split(input.oldString).join(input.newString);
|
|
16772
|
+
}
|
|
16773
|
+
} else if (seenBefore === 0 && matchCount > 0) {
|
|
16774
|
+
output = processable.replace(input.oldString, input.newString);
|
|
16775
|
+
}
|
|
16776
|
+
bytesWritten += await writeEncodedText(handle, input.encoding, output);
|
|
16777
|
+
};
|
|
16778
|
+
try {
|
|
16779
|
+
await visitDecodedTextChunks(input, async (text) => {
|
|
16780
|
+
await flushText(text);
|
|
16781
|
+
});
|
|
16782
|
+
await flushText("", true);
|
|
16783
|
+
if (occurrencesBefore === 0) {
|
|
16784
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
|
|
16785
|
+
}
|
|
16786
|
+
if (!input.replaceAll && occurrencesBefore > 1) {
|
|
16787
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
16788
|
+
}
|
|
16789
|
+
await handle.close();
|
|
16790
|
+
const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
|
|
16791
|
+
await fs2.chmod(tempPath, mode);
|
|
16792
|
+
await fs2.rename(tempPath, input.filePath);
|
|
16793
|
+
return {
|
|
16794
|
+
filePath: input.filePath,
|
|
16795
|
+
encoding: input.encoding,
|
|
16796
|
+
replacements: input.replaceAll ? occurrencesBefore : 1,
|
|
16797
|
+
occurrencesBefore,
|
|
16798
|
+
bytesRead: input.stat.size,
|
|
16799
|
+
bytesWritten
|
|
16800
|
+
};
|
|
16801
|
+
} catch (error45) {
|
|
16802
|
+
await handle.close().catch(() => void 0);
|
|
16803
|
+
await fs2.rm(tempPath, { force: true }).catch(() => void 0);
|
|
16804
|
+
throw error45;
|
|
16805
|
+
}
|
|
16806
|
+
}
|
|
16807
|
+
async function readGbkFile(input) {
|
|
16808
|
+
const offset = input.offset ?? 1;
|
|
16809
|
+
const limit = input.limit ?? 2e3;
|
|
16810
|
+
const tail = input.tail ?? false;
|
|
16811
|
+
const resolved = await resolveReadableGbkFile(input);
|
|
16812
|
+
if (resolved.stat.size < STREAMING_FILE_SIZE_THRESHOLD_BYTES) {
|
|
16813
|
+
const textFile = await readWholeGbkTextFile(resolved);
|
|
16814
|
+
const lineWindow2 = tail ? collectTailLines(textFile.content, limit) : {
|
|
16815
|
+
...splitLinesWithNumbers(textFile.content, offset, limit),
|
|
16816
|
+
tail: false,
|
|
16817
|
+
truncated: false,
|
|
16818
|
+
newlineStyle: detectNewlineStyle(textFile.content)
|
|
16819
|
+
};
|
|
16820
|
+
if (!tail) {
|
|
16821
|
+
lineWindow2.truncated = lineWindow2.endLine < lineWindow2.totalLines;
|
|
16822
|
+
}
|
|
16823
|
+
return {
|
|
16824
|
+
filePath: textFile.filePath,
|
|
16825
|
+
encoding: textFile.encoding,
|
|
16826
|
+
bytesRead: textFile.bytesRead,
|
|
16827
|
+
fileSize: textFile.bytesRead,
|
|
16828
|
+
streamed: false,
|
|
16829
|
+
...lineWindow2
|
|
16830
|
+
};
|
|
16831
|
+
}
|
|
16832
|
+
const collector = tail ? createTailCollector(limit) : createLineCollector(offset, limit);
|
|
16833
|
+
await visitDecodedTextChunks(resolved, (text) => {
|
|
16834
|
+
collector.push(text);
|
|
16835
|
+
});
|
|
16836
|
+
const lineWindow = collector.finish();
|
|
16837
|
+
return {
|
|
16838
|
+
filePath: resolved.filePath,
|
|
16839
|
+
encoding: resolved.encoding,
|
|
16840
|
+
bytesRead: resolved.stat.size,
|
|
16841
|
+
fileSize: resolved.stat.size,
|
|
16842
|
+
streamed: true,
|
|
16843
|
+
...lineWindow
|
|
16844
|
+
};
|
|
16845
|
+
}
|
|
16846
|
+
async function replaceGbkFileText(input) {
|
|
16847
|
+
if (input.oldString.length === 0) {
|
|
16848
|
+
throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16849
|
+
}
|
|
16850
|
+
const replaceAll = input.replaceAll ?? false;
|
|
16851
|
+
const resolved = await resolveReadableGbkFile(input);
|
|
16852
|
+
const hasScopedRange = input.startLine !== void 0 || input.endLine !== void 0 || input.startAnchor !== void 0 || input.endAnchor !== void 0;
|
|
16853
|
+
if (resolved.stat.size >= STREAMING_FILE_SIZE_THRESHOLD_BYTES && !hasScopedRange) {
|
|
16854
|
+
return await replaceLargeGbkFileText({
|
|
16855
|
+
...resolved,
|
|
16856
|
+
oldString: input.oldString,
|
|
16857
|
+
newString: input.newString,
|
|
16858
|
+
replaceAll
|
|
16859
|
+
});
|
|
16860
|
+
}
|
|
16861
|
+
const current = await readWholeGbkTextFile(resolved);
|
|
16862
|
+
const scope = resolveEditScope(current.content, input);
|
|
16863
|
+
const occurrencesBefore = countOccurrences(scope.selectedText, input.oldString);
|
|
16864
|
+
if (!replaceAll && occurrencesBefore === 0) {
|
|
16865
|
+
const loose = tryLooseBlockReplace(scope.selectedText, input.oldString, input.newString);
|
|
16866
|
+
if (loose !== null) {
|
|
16867
|
+
const outputText2 = `${current.content.slice(0, scope.rangeStart)}${loose.content}${current.content.slice(scope.rangeEnd)}`;
|
|
16868
|
+
const buffer2 = import_iconv_lite.default.encode(outputText2, current.encoding);
|
|
16869
|
+
await fs2.writeFile(current.filePath, buffer2);
|
|
16870
|
+
return {
|
|
16871
|
+
filePath: current.filePath,
|
|
16872
|
+
encoding: current.encoding,
|
|
16873
|
+
replacements: 1,
|
|
16874
|
+
occurrencesBefore: loose.occurrencesBefore,
|
|
16875
|
+
bytesRead: current.bytesRead,
|
|
16876
|
+
bytesWritten: buffer2.byteLength
|
|
16877
|
+
};
|
|
16878
|
+
}
|
|
16879
|
+
}
|
|
16880
|
+
if (replaceAll) {
|
|
16881
|
+
if (occurrencesBefore === 0) {
|
|
16882
|
+
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
|
|
16883
|
+
}
|
|
16884
|
+
} else if (occurrencesBefore === 0) {
|
|
16885
|
+
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
|
|
16886
|
+
} else if (occurrencesBefore > 1) {
|
|
16887
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
16888
|
+
}
|
|
16889
|
+
const replaced = replaceAll ? scope.selectedText.split(input.oldString).join(input.newString) : scope.selectedText.replace(input.oldString, input.newString);
|
|
16890
|
+
const outputText = `${current.content.slice(0, scope.rangeStart)}${replaced}${current.content.slice(scope.rangeEnd)}`;
|
|
16891
|
+
const buffer = import_iconv_lite.default.encode(outputText, current.encoding);
|
|
16892
|
+
await fs2.writeFile(current.filePath, buffer);
|
|
16893
|
+
return {
|
|
16894
|
+
filePath: current.filePath,
|
|
16895
|
+
encoding: current.encoding,
|
|
16896
|
+
replacements: replaceAll ? occurrencesBefore : 1,
|
|
16897
|
+
occurrencesBefore,
|
|
16898
|
+
bytesRead: current.bytesRead,
|
|
16899
|
+
bytesWritten: buffer.byteLength
|
|
16900
|
+
};
|
|
16901
|
+
}
|
|
16398
16902
|
async function writeGbkFile(input) {
|
|
16399
16903
|
const encoding = input.encoding ?? "gbk";
|
|
16400
16904
|
const createDirectories = input.createDirectories ?? true;
|
|
@@ -16461,43 +16965,16 @@ var gbk_edit_default = tool({
|
|
|
16461
16965
|
oldString: tool.schema.string().describe("Text to replace"),
|
|
16462
16966
|
newString: tool.schema.string().describe("Replacement text"),
|
|
16463
16967
|
replaceAll: tool.schema.boolean().optional().describe("Replace all occurrences"),
|
|
16968
|
+
startLine: tool.schema.number().int().positive().optional().describe("Restrict edit to 1-based start line"),
|
|
16969
|
+
endLine: tool.schema.number().int().positive().optional().describe("Restrict edit to 1-based end line"),
|
|
16970
|
+
startAnchor: tool.schema.string().optional().describe("Restrict edit to content after this anchor"),
|
|
16971
|
+
endAnchor: tool.schema.string().optional().describe("Restrict edit to content before this anchor"),
|
|
16464
16972
|
encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
|
|
16465
16973
|
allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
|
|
16466
16974
|
},
|
|
16467
16975
|
async execute(args, context) {
|
|
16468
|
-
|
|
16469
|
-
|
|
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);
|
|
16976
|
+
const result = await replaceGbkFileText({ ...args, context });
|
|
16977
|
+
return JSON.stringify(result, null, 2);
|
|
16501
16978
|
}
|
|
16502
16979
|
});
|
|
16503
16980
|
|
|
@@ -16508,6 +16985,7 @@ var gbk_read_default = tool({
|
|
|
16508
16985
|
filePath: tool.schema.string().describe("Target file path"),
|
|
16509
16986
|
offset: tool.schema.number().int().positive().optional().describe("1-based start line"),
|
|
16510
16987
|
limit: tool.schema.number().int().positive().optional().describe("Number of lines to read"),
|
|
16988
|
+
tail: tool.schema.boolean().optional().describe("Read last N lines instead of offset-based window"),
|
|
16511
16989
|
encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
|
|
16512
16990
|
allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
|
|
16513
16991
|
},
|