opencode-gbk-tools 0.1.0 → 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.
@@ -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 readGbkFile(input) {
16393
+ async function resolveReadableGbkFile(input) {
16349
16394
  const encoding = input.encoding ?? "gbk";
16350
- const offset = input.offset ?? 1;
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(candidatePath);
16364
- const text = await readBufferAsText(buffer, encoding);
16414
+ const buffer = await fs2.readFile(input.filePath);
16415
+ const content = await readBufferAsText(buffer, input.encoding);
16365
16416
  return {
16366
- filePath: candidatePath,
16367
- encoding,
16368
- ...splitLinesWithNumbers(text, offset, limit)
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: ${candidatePath}`, error45);
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}`);