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
|
@@ -16240,6 +16240,8 @@ tool.schema = external_exports;
|
|
|
16240
16240
|
|
|
16241
16241
|
// src/lib/gbk-file.ts
|
|
16242
16242
|
var import_iconv_lite = __toESM(require_lib(), 1);
|
|
16243
|
+
import crypto from "crypto";
|
|
16244
|
+
import { createReadStream } from "fs";
|
|
16243
16245
|
import fs2 from "fs/promises";
|
|
16244
16246
|
import path2 from "path";
|
|
16245
16247
|
|
|
@@ -16302,6 +16304,7 @@ async function assertPathAllowed(filePath, context, allowExternal = false) {
|
|
|
16302
16304
|
}
|
|
16303
16305
|
|
|
16304
16306
|
// src/lib/gbk-file.ts
|
|
16307
|
+
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16305
16308
|
function assertEncodingSupported(encoding) {
|
|
16306
16309
|
if (encoding !== "gbk" && encoding !== "gb18030") {
|
|
16307
16310
|
throw createGbkError("GBK_INVALID_ENCODING", `\u4E0D\u652F\u6301\u7684\u7F16\u7801: ${encoding}`);
|
|
@@ -16340,15 +16343,56 @@ function splitLinesWithNumbers(text, offset = 1, limit = 2e3) {
|
|
|
16340
16343
|
content
|
|
16341
16344
|
};
|
|
16342
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
|
+
}
|
|
16343
16388
|
async function readBufferAsText(buffer, encoding) {
|
|
16344
16389
|
assertEncodingSupported(encoding);
|
|
16345
16390
|
assertNotBinary(buffer);
|
|
16346
16391
|
return import_iconv_lite.default.decode(buffer, encoding);
|
|
16347
16392
|
}
|
|
16348
|
-
async function
|
|
16393
|
+
async function resolveReadableGbkFile(input) {
|
|
16349
16394
|
const encoding = input.encoding ?? "gbk";
|
|
16350
|
-
|
|
16351
|
-
const limit = input.limit ?? 2e3;
|
|
16395
|
+
assertEncodingSupported(encoding);
|
|
16352
16396
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
16353
16397
|
let stat;
|
|
16354
16398
|
try {
|
|
@@ -16359,21 +16403,197 @@ async function readGbkFile(input) {
|
|
|
16359
16403
|
if (stat.isDirectory()) {
|
|
16360
16404
|
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
16361
16405
|
}
|
|
16406
|
+
return {
|
|
16407
|
+
filePath: candidatePath,
|
|
16408
|
+
encoding,
|
|
16409
|
+
stat
|
|
16410
|
+
};
|
|
16411
|
+
}
|
|
16412
|
+
async function readWholeGbkTextFile(input) {
|
|
16362
16413
|
try {
|
|
16363
|
-
const buffer = await fs2.readFile(
|
|
16364
|
-
const
|
|
16414
|
+
const buffer = await fs2.readFile(input.filePath);
|
|
16415
|
+
const content = await readBufferAsText(buffer, input.encoding);
|
|
16365
16416
|
return {
|
|
16366
|
-
filePath:
|
|
16367
|
-
encoding,
|
|
16368
|
-
|
|
16417
|
+
filePath: input.filePath,
|
|
16418
|
+
encoding: input.encoding,
|
|
16419
|
+
content,
|
|
16420
|
+
bytesRead: buffer.byteLength
|
|
16369
16421
|
};
|
|
16370
16422
|
} catch (error45) {
|
|
16371
16423
|
if (error45 instanceof Error && "code" in error45) {
|
|
16372
16424
|
throw error45;
|
|
16373
16425
|
}
|
|
16374
|
-
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${
|
|
16426
|
+
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
|
|
16375
16427
|
}
|
|
16376
16428
|
}
|
|
16429
|
+
async function visitDecodedTextChunks(input, visitor) {
|
|
16430
|
+
const decoder = import_iconv_lite.default.getDecoder(input.encoding);
|
|
16431
|
+
const stream = createReadStream(input.filePath);
|
|
16432
|
+
try {
|
|
16433
|
+
for await (const chunk of stream) {
|
|
16434
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
16435
|
+
assertNotBinary(buffer);
|
|
16436
|
+
const text = decoder.write(buffer);
|
|
16437
|
+
if (text.length > 0) {
|
|
16438
|
+
await visitor(text);
|
|
16439
|
+
}
|
|
16440
|
+
}
|
|
16441
|
+
const trailingText = decoder.end();
|
|
16442
|
+
if (trailingText && trailingText.length > 0) {
|
|
16443
|
+
await visitor(trailingText);
|
|
16444
|
+
}
|
|
16445
|
+
} catch (error45) {
|
|
16446
|
+
if (error45 instanceof Error && "code" in error45) {
|
|
16447
|
+
throw error45;
|
|
16448
|
+
}
|
|
16449
|
+
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
|
|
16450
|
+
} finally {
|
|
16451
|
+
stream.destroy();
|
|
16452
|
+
}
|
|
16453
|
+
}
|
|
16454
|
+
function createLineCollector(offset, limit) {
|
|
16455
|
+
const lines = [];
|
|
16456
|
+
let pending = "";
|
|
16457
|
+
let totalLines = 0;
|
|
16458
|
+
let crlfCount = 0;
|
|
16459
|
+
let lfCount = 0;
|
|
16460
|
+
const emitLine = (line) => {
|
|
16461
|
+
totalLines += 1;
|
|
16462
|
+
if (totalLines >= offset && lines.length < limit) {
|
|
16463
|
+
lines.push(`${totalLines}: ${line}`);
|
|
16464
|
+
}
|
|
16465
|
+
};
|
|
16466
|
+
return {
|
|
16467
|
+
push(text) {
|
|
16468
|
+
if (text.length === 0) {
|
|
16469
|
+
return;
|
|
16470
|
+
}
|
|
16471
|
+
const combined = pending + text;
|
|
16472
|
+
let start = 0;
|
|
16473
|
+
while (true) {
|
|
16474
|
+
const newlineIndex = combined.indexOf("\n", start);
|
|
16475
|
+
if (newlineIndex === -1) {
|
|
16476
|
+
break;
|
|
16477
|
+
}
|
|
16478
|
+
let line = combined.slice(start, newlineIndex);
|
|
16479
|
+
if (line.endsWith("\r")) {
|
|
16480
|
+
line = line.slice(0, -1);
|
|
16481
|
+
crlfCount += 1;
|
|
16482
|
+
} else {
|
|
16483
|
+
lfCount += 1;
|
|
16484
|
+
}
|
|
16485
|
+
emitLine(line);
|
|
16486
|
+
start = newlineIndex + 1;
|
|
16487
|
+
}
|
|
16488
|
+
pending = combined.slice(start);
|
|
16489
|
+
},
|
|
16490
|
+
finish() {
|
|
16491
|
+
emitLine(pending);
|
|
16492
|
+
const endLine = lines.length === 0 ? totalLines : offset + lines.length - 1;
|
|
16493
|
+
return {
|
|
16494
|
+
startLine: offset,
|
|
16495
|
+
endLine,
|
|
16496
|
+
totalLines,
|
|
16497
|
+
content: lines.join("\n"),
|
|
16498
|
+
tail: false,
|
|
16499
|
+
truncated: endLine < totalLines,
|
|
16500
|
+
newlineStyle: finalizeNewlineStyle(crlfCount, lfCount)
|
|
16501
|
+
};
|
|
16502
|
+
}
|
|
16503
|
+
};
|
|
16504
|
+
}
|
|
16505
|
+
function createTailCollector(limit) {
|
|
16506
|
+
assertPositiveInteger(limit, "limit");
|
|
16507
|
+
const lines = [];
|
|
16508
|
+
let pending = "";
|
|
16509
|
+
let totalLines = 0;
|
|
16510
|
+
let crlfCount = 0;
|
|
16511
|
+
let lfCount = 0;
|
|
16512
|
+
const emitLine = (line) => {
|
|
16513
|
+
totalLines += 1;
|
|
16514
|
+
lines.push(line);
|
|
16515
|
+
if (lines.length > limit) {
|
|
16516
|
+
lines.shift();
|
|
16517
|
+
}
|
|
16518
|
+
};
|
|
16519
|
+
return {
|
|
16520
|
+
push(text) {
|
|
16521
|
+
if (text.length === 0) {
|
|
16522
|
+
return;
|
|
16523
|
+
}
|
|
16524
|
+
const combined = pending + text;
|
|
16525
|
+
let start = 0;
|
|
16526
|
+
while (true) {
|
|
16527
|
+
const newlineIndex = combined.indexOf("\n", start);
|
|
16528
|
+
if (newlineIndex === -1) {
|
|
16529
|
+
break;
|
|
16530
|
+
}
|
|
16531
|
+
let line = combined.slice(start, newlineIndex);
|
|
16532
|
+
if (line.endsWith("\r")) {
|
|
16533
|
+
line = line.slice(0, -1);
|
|
16534
|
+
crlfCount += 1;
|
|
16535
|
+
} else {
|
|
16536
|
+
lfCount += 1;
|
|
16537
|
+
}
|
|
16538
|
+
emitLine(line);
|
|
16539
|
+
start = newlineIndex + 1;
|
|
16540
|
+
}
|
|
16541
|
+
pending = combined.slice(start);
|
|
16542
|
+
},
|
|
16543
|
+
finish() {
|
|
16544
|
+
emitLine(pending);
|
|
16545
|
+
const startLine = Math.max(1, totalLines - lines.length + 1);
|
|
16546
|
+
return {
|
|
16547
|
+
startLine,
|
|
16548
|
+
endLine: totalLines,
|
|
16549
|
+
totalLines,
|
|
16550
|
+
content: lines.map((line, index) => `${startLine + index}: ${line}`).join("\n"),
|
|
16551
|
+
truncated: startLine > 1,
|
|
16552
|
+
tail: true,
|
|
16553
|
+
newlineStyle: finalizeNewlineStyle(crlfCount, lfCount)
|
|
16554
|
+
};
|
|
16555
|
+
}
|
|
16556
|
+
};
|
|
16557
|
+
}
|
|
16558
|
+
async function readGbkFile(input) {
|
|
16559
|
+
const offset = input.offset ?? 1;
|
|
16560
|
+
const limit = input.limit ?? 2e3;
|
|
16561
|
+
const tail = input.tail ?? false;
|
|
16562
|
+
const resolved = await resolveReadableGbkFile(input);
|
|
16563
|
+
if (resolved.stat.size < STREAMING_FILE_SIZE_THRESHOLD_BYTES) {
|
|
16564
|
+
const textFile = await readWholeGbkTextFile(resolved);
|
|
16565
|
+
const lineWindow2 = tail ? collectTailLines(textFile.content, limit) : {
|
|
16566
|
+
...splitLinesWithNumbers(textFile.content, offset, limit),
|
|
16567
|
+
tail: false,
|
|
16568
|
+
truncated: false,
|
|
16569
|
+
newlineStyle: detectNewlineStyle(textFile.content)
|
|
16570
|
+
};
|
|
16571
|
+
if (!tail) {
|
|
16572
|
+
lineWindow2.truncated = lineWindow2.endLine < lineWindow2.totalLines;
|
|
16573
|
+
}
|
|
16574
|
+
return {
|
|
16575
|
+
filePath: textFile.filePath,
|
|
16576
|
+
encoding: textFile.encoding,
|
|
16577
|
+
bytesRead: textFile.bytesRead,
|
|
16578
|
+
fileSize: textFile.bytesRead,
|
|
16579
|
+
streamed: false,
|
|
16580
|
+
...lineWindow2
|
|
16581
|
+
};
|
|
16582
|
+
}
|
|
16583
|
+
const collector = tail ? createTailCollector(limit) : createLineCollector(offset, limit);
|
|
16584
|
+
await visitDecodedTextChunks(resolved, (text) => {
|
|
16585
|
+
collector.push(text);
|
|
16586
|
+
});
|
|
16587
|
+
const lineWindow = collector.finish();
|
|
16588
|
+
return {
|
|
16589
|
+
filePath: resolved.filePath,
|
|
16590
|
+
encoding: resolved.encoding,
|
|
16591
|
+
bytesRead: resolved.stat.size,
|
|
16592
|
+
fileSize: resolved.stat.size,
|
|
16593
|
+
streamed: true,
|
|
16594
|
+
...lineWindow
|
|
16595
|
+
};
|
|
16596
|
+
}
|
|
16377
16597
|
|
|
16378
16598
|
// src/tools/gbk_read.ts
|
|
16379
16599
|
var gbk_read_default = tool({
|
|
@@ -16382,6 +16602,7 @@ var gbk_read_default = tool({
|
|
|
16382
16602
|
filePath: tool.schema.string().describe("Target file path"),
|
|
16383
16603
|
offset: tool.schema.number().int().positive().optional().describe("1-based start line"),
|
|
16384
16604
|
limit: tool.schema.number().int().positive().optional().describe("Number of lines to read"),
|
|
16605
|
+
tail: tool.schema.boolean().optional().describe("Read last N lines instead of offset-based window"),
|
|
16385
16606
|
encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
|
|
16386
16607
|
allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
|
|
16387
16608
|
},
|
|
@@ -16240,6 +16240,8 @@ tool.schema = external_exports;
|
|
|
16240
16240
|
|
|
16241
16241
|
// src/lib/gbk-file.ts
|
|
16242
16242
|
var import_iconv_lite = __toESM(require_lib(), 1);
|
|
16243
|
+
import crypto from "crypto";
|
|
16244
|
+
import { createReadStream } from "fs";
|
|
16243
16245
|
import fs2 from "fs/promises";
|
|
16244
16246
|
import path2 from "path";
|
|
16245
16247
|
|
|
@@ -16302,6 +16304,7 @@ async function assertPathAllowed(filePath, context, allowExternal = false) {
|
|
|
16302
16304
|
}
|
|
16303
16305
|
|
|
16304
16306
|
// src/lib/gbk-file.ts
|
|
16307
|
+
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16305
16308
|
function assertEncodingSupported(encoding) {
|
|
16306
16309
|
if (encoding !== "gbk" && encoding !== "gb18030") {
|
|
16307
16310
|
throw createGbkError("GBK_INVALID_ENCODING", `\u4E0D\u652F\u6301\u7684\u7F16\u7801: ${encoding}`);
|