opencode-gbk-tools 0.1.28 → 0.1.30
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 +5 -3
- package/dist/plugin/index.js +423 -158
- package/dist/plugins/opencode-gbk-tools.js +422 -157
- package/dist/release-manifest.json +2 -2
- package/package.json +1 -1
package/dist/plugin/index.js
CHANGED
|
@@ -3816,6 +3816,9 @@ var require_lib = __commonJS({
|
|
|
3816
3816
|
}
|
|
3817
3817
|
});
|
|
3818
3818
|
|
|
3819
|
+
// src/plugin/index.ts
|
|
3820
|
+
import path5 from "path";
|
|
3821
|
+
|
|
3819
3822
|
// src/lib/session-pressure.ts
|
|
3820
3823
|
var AUTO_SUMMARIZE_PRESSURE_RATIO = 0.85;
|
|
3821
3824
|
var AUTO_SUMMARIZE_COOLDOWN_MS = 6e4;
|
|
@@ -3910,6 +3913,200 @@ function estimateSessionTokens(messages) {
|
|
|
3910
3913
|
return Math.ceil(totalChars / 4);
|
|
3911
3914
|
}
|
|
3912
3915
|
|
|
3916
|
+
// src/lib/encoding-memory.ts
|
|
3917
|
+
import fs from "fs/promises";
|
|
3918
|
+
import os from "os";
|
|
3919
|
+
import path from "path";
|
|
3920
|
+
var ENCODING_MEMORY_VERSION = 1;
|
|
3921
|
+
var ENCODING_MEMORY_FILE_NAME = "encoding-memory.json";
|
|
3922
|
+
var CONFIG_DIR_ENV = "OPENCODE_GBK_TOOLS_CONFIG_DIR";
|
|
3923
|
+
var memoryCache = null;
|
|
3924
|
+
var loadingPromise = null;
|
|
3925
|
+
function resolveConfigDirectory() {
|
|
3926
|
+
const override = process.env[CONFIG_DIR_ENV];
|
|
3927
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
3928
|
+
return path.resolve(override);
|
|
3929
|
+
}
|
|
3930
|
+
if (process.platform === "win32") {
|
|
3931
|
+
return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "opencode-gbk-tools");
|
|
3932
|
+
}
|
|
3933
|
+
if (process.platform === "darwin") {
|
|
3934
|
+
return path.join(os.homedir(), "Library", "Application Support", "opencode-gbk-tools");
|
|
3935
|
+
}
|
|
3936
|
+
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "opencode-gbk-tools");
|
|
3937
|
+
}
|
|
3938
|
+
function normalizeFilePath(filePath) {
|
|
3939
|
+
const resolved = path.normalize(path.resolve(filePath));
|
|
3940
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
3941
|
+
}
|
|
3942
|
+
function toStoredFilePath(filePath) {
|
|
3943
|
+
return path.normalize(path.resolve(filePath));
|
|
3944
|
+
}
|
|
3945
|
+
function isRememberedEntry(value) {
|
|
3946
|
+
if (!value || typeof value !== "object") {
|
|
3947
|
+
return false;
|
|
3948
|
+
}
|
|
3949
|
+
const entry = value;
|
|
3950
|
+
return typeof entry.filePath === "string" && isRememberedGbkEncoding(entry.encoding) && typeof entry.mtimeMs === "number" && Number.isFinite(entry.mtimeMs) && typeof entry.size === "number" && Number.isFinite(entry.size) && typeof entry.lastConfirmedAt === "number" && Number.isFinite(entry.lastConfirmedAt);
|
|
3951
|
+
}
|
|
3952
|
+
async function readEncodingMemoryMap() {
|
|
3953
|
+
if (memoryCache) {
|
|
3954
|
+
return memoryCache;
|
|
3955
|
+
}
|
|
3956
|
+
if (loadingPromise) {
|
|
3957
|
+
return loadingPromise;
|
|
3958
|
+
}
|
|
3959
|
+
loadingPromise = (async () => {
|
|
3960
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
3961
|
+
try {
|
|
3962
|
+
const raw = await fs.readFile(getEncodingMemoryFilePath(), "utf8");
|
|
3963
|
+
const parsed = JSON.parse(raw);
|
|
3964
|
+
const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
3965
|
+
for (const entry of entries) {
|
|
3966
|
+
if (isRememberedEntry(entry)) {
|
|
3967
|
+
map2.set(normalizeFilePath(entry.filePath), entry);
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
} catch (error45) {
|
|
3971
|
+
if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
|
|
3972
|
+
throw error45;
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
memoryCache = map2;
|
|
3976
|
+
loadingPromise = null;
|
|
3977
|
+
return map2;
|
|
3978
|
+
})();
|
|
3979
|
+
return loadingPromise;
|
|
3980
|
+
}
|
|
3981
|
+
async function persistEncodingMemoryMap(map2) {
|
|
3982
|
+
const entries = [...map2.values()].sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
3983
|
+
const payload = {
|
|
3984
|
+
version: ENCODING_MEMORY_VERSION,
|
|
3985
|
+
entries
|
|
3986
|
+
};
|
|
3987
|
+
const filePath = getEncodingMemoryFilePath();
|
|
3988
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
3989
|
+
await fs.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
3990
|
+
}
|
|
3991
|
+
async function statRegularFile(filePath) {
|
|
3992
|
+
try {
|
|
3993
|
+
const stat = await fs.stat(filePath);
|
|
3994
|
+
return stat.isFile() ? stat : null;
|
|
3995
|
+
} catch (error45) {
|
|
3996
|
+
if (error45 instanceof Error && "code" in error45 && error45.code === "ENOENT") {
|
|
3997
|
+
return null;
|
|
3998
|
+
}
|
|
3999
|
+
throw error45;
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
function isRememberedGbkEncoding(encoding) {
|
|
4003
|
+
return encoding === "gbk" || encoding === "gb18030";
|
|
4004
|
+
}
|
|
4005
|
+
function getEncodingMemoryFilePath() {
|
|
4006
|
+
return path.join(resolveConfigDirectory(), ENCODING_MEMORY_FILE_NAME);
|
|
4007
|
+
}
|
|
4008
|
+
async function rememberGbkEncoding(filePath, encoding) {
|
|
4009
|
+
const stat = await statRegularFile(filePath);
|
|
4010
|
+
if (!stat) {
|
|
4011
|
+
return null;
|
|
4012
|
+
}
|
|
4013
|
+
const map2 = await readEncodingMemoryMap();
|
|
4014
|
+
const entry = {
|
|
4015
|
+
filePath: toStoredFilePath(filePath),
|
|
4016
|
+
encoding,
|
|
4017
|
+
mtimeMs: stat.mtimeMs,
|
|
4018
|
+
size: Number(stat.size),
|
|
4019
|
+
lastConfirmedAt: Date.now()
|
|
4020
|
+
};
|
|
4021
|
+
map2.set(normalizeFilePath(filePath), entry);
|
|
4022
|
+
await persistEncodingMemoryMap(map2);
|
|
4023
|
+
return entry;
|
|
4024
|
+
}
|
|
4025
|
+
async function forgetRememberedEncoding(filePath) {
|
|
4026
|
+
const map2 = await readEncodingMemoryMap();
|
|
4027
|
+
const deleted = map2.delete(normalizeFilePath(filePath));
|
|
4028
|
+
if (deleted) {
|
|
4029
|
+
await persistEncodingMemoryMap(map2);
|
|
4030
|
+
}
|
|
4031
|
+
return deleted;
|
|
4032
|
+
}
|
|
4033
|
+
async function getRememberedGbkEncoding(filePath) {
|
|
4034
|
+
const map2 = await readEncodingMemoryMap();
|
|
4035
|
+
const key = normalizeFilePath(filePath);
|
|
4036
|
+
const entry = map2.get(key);
|
|
4037
|
+
if (!entry) {
|
|
4038
|
+
return null;
|
|
4039
|
+
}
|
|
4040
|
+
const stat = await statRegularFile(filePath);
|
|
4041
|
+
if (!stat || stat.mtimeMs !== entry.mtimeMs || Number(stat.size) !== entry.size) {
|
|
4042
|
+
map2.delete(key);
|
|
4043
|
+
await persistEncodingMemoryMap(map2);
|
|
4044
|
+
return null;
|
|
4045
|
+
}
|
|
4046
|
+
return entry;
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
// src/lib/path-sandbox.ts
|
|
4050
|
+
import fs2 from "fs/promises";
|
|
4051
|
+
import path2 from "path";
|
|
4052
|
+
|
|
4053
|
+
// src/lib/errors.ts
|
|
4054
|
+
var GbkToolError = class extends Error {
|
|
4055
|
+
code;
|
|
4056
|
+
cause;
|
|
4057
|
+
constructor(code, message, cause) {
|
|
4058
|
+
super(message);
|
|
4059
|
+
this.name = "GbkToolError";
|
|
4060
|
+
this.code = code;
|
|
4061
|
+
this.cause = cause;
|
|
4062
|
+
}
|
|
4063
|
+
};
|
|
4064
|
+
function createGbkError(code, message, cause) {
|
|
4065
|
+
return new GbkToolError(code, message, cause);
|
|
4066
|
+
}
|
|
4067
|
+
var createTextError = createGbkError;
|
|
4068
|
+
|
|
4069
|
+
// src/lib/path-sandbox.ts
|
|
4070
|
+
function resolveBaseDirectory(context) {
|
|
4071
|
+
const value = context.directory ?? process.cwd();
|
|
4072
|
+
return path2.isAbsolute(value) ? value : path2.resolve(process.cwd(), value);
|
|
4073
|
+
}
|
|
4074
|
+
function resolveWorkspaceRoot(context) {
|
|
4075
|
+
const value = context.worktree ?? context.directory ?? process.cwd();
|
|
4076
|
+
return path2.isAbsolute(value) ? value : path2.resolve(process.cwd(), value);
|
|
4077
|
+
}
|
|
4078
|
+
function resolveCandidatePath(filePath, context) {
|
|
4079
|
+
return path2.resolve(resolveBaseDirectory(context), filePath);
|
|
4080
|
+
}
|
|
4081
|
+
async function resolveExistingAnchor(filePath) {
|
|
4082
|
+
let current = filePath;
|
|
4083
|
+
while (true) {
|
|
4084
|
+
try {
|
|
4085
|
+
return await fs2.realpath(current);
|
|
4086
|
+
} catch {
|
|
4087
|
+
const parent = path2.dirname(current);
|
|
4088
|
+
if (parent === current) {
|
|
4089
|
+
throw createGbkError("GBK_IO_ERROR", `\u65E0\u6CD5\u89E3\u6790\u8DEF\u5F84\u951A\u70B9: ${filePath}`);
|
|
4090
|
+
}
|
|
4091
|
+
current = parent;
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
async function assertPathAllowed(filePath, context, allowExternal = false) {
|
|
4096
|
+
const candidatePath = resolveCandidatePath(filePath, context);
|
|
4097
|
+
const workspaceRoot = resolveWorkspaceRoot(context);
|
|
4098
|
+
if (!allowExternal) {
|
|
4099
|
+
const realWorkspaceRoot = await fs2.realpath(workspaceRoot);
|
|
4100
|
+
const realCandidateAnchor = await resolveExistingAnchor(candidatePath);
|
|
4101
|
+
const relative = path2.relative(realWorkspaceRoot, realCandidateAnchor);
|
|
4102
|
+
if (relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative)) {
|
|
4103
|
+
return { candidatePath, workspaceRoot };
|
|
4104
|
+
}
|
|
4105
|
+
throw createGbkError("GBK_PATH_OUTSIDE_ROOT", `\u76EE\u6807\u8DEF\u5F84\u8D85\u51FA\u5DE5\u4F5C\u76EE\u5F55\u8303\u56F4: ${candidatePath}`);
|
|
4106
|
+
}
|
|
4107
|
+
return { candidatePath, workspaceRoot };
|
|
4108
|
+
}
|
|
4109
|
+
|
|
3913
4110
|
// node_modules/zod/v4/classic/external.js
|
|
3914
4111
|
var external_exports = {};
|
|
3915
4112
|
__export(external_exports, {
|
|
@@ -4641,10 +4838,10 @@ function mergeDefs(...defs) {
|
|
|
4641
4838
|
function cloneDef(schema) {
|
|
4642
4839
|
return mergeDefs(schema._zod.def);
|
|
4643
4840
|
}
|
|
4644
|
-
function getElementAtPath(obj,
|
|
4645
|
-
if (!
|
|
4841
|
+
function getElementAtPath(obj, path6) {
|
|
4842
|
+
if (!path6)
|
|
4646
4843
|
return obj;
|
|
4647
|
-
return
|
|
4844
|
+
return path6.reduce((acc, key) => acc?.[key], obj);
|
|
4648
4845
|
}
|
|
4649
4846
|
function promiseAllObject(promisesObj) {
|
|
4650
4847
|
const keys = Object.keys(promisesObj);
|
|
@@ -5005,11 +5202,11 @@ function aborted(x, startIndex = 0) {
|
|
|
5005
5202
|
}
|
|
5006
5203
|
return false;
|
|
5007
5204
|
}
|
|
5008
|
-
function prefixIssues(
|
|
5205
|
+
function prefixIssues(path6, issues) {
|
|
5009
5206
|
return issues.map((iss) => {
|
|
5010
5207
|
var _a;
|
|
5011
5208
|
(_a = iss).path ?? (_a.path = []);
|
|
5012
|
-
iss.path.unshift(
|
|
5209
|
+
iss.path.unshift(path6);
|
|
5013
5210
|
return iss;
|
|
5014
5211
|
});
|
|
5015
5212
|
}
|
|
@@ -5177,7 +5374,7 @@ function treeifyError(error45, _mapper) {
|
|
|
5177
5374
|
return issue2.message;
|
|
5178
5375
|
};
|
|
5179
5376
|
const result = { errors: [] };
|
|
5180
|
-
const processError = (error46,
|
|
5377
|
+
const processError = (error46, path6 = []) => {
|
|
5181
5378
|
var _a, _b;
|
|
5182
5379
|
for (const issue2 of error46.issues) {
|
|
5183
5380
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -5187,7 +5384,7 @@ function treeifyError(error45, _mapper) {
|
|
|
5187
5384
|
} else if (issue2.code === "invalid_element") {
|
|
5188
5385
|
processError({ issues: issue2.issues }, issue2.path);
|
|
5189
5386
|
} else {
|
|
5190
|
-
const fullpath = [...
|
|
5387
|
+
const fullpath = [...path6, ...issue2.path];
|
|
5191
5388
|
if (fullpath.length === 0) {
|
|
5192
5389
|
result.errors.push(mapper(issue2));
|
|
5193
5390
|
continue;
|
|
@@ -5219,8 +5416,8 @@ function treeifyError(error45, _mapper) {
|
|
|
5219
5416
|
}
|
|
5220
5417
|
function toDotPath(_path) {
|
|
5221
5418
|
const segs = [];
|
|
5222
|
-
const
|
|
5223
|
-
for (const seg of
|
|
5419
|
+
const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
5420
|
+
for (const seg of path6) {
|
|
5224
5421
|
if (typeof seg === "number")
|
|
5225
5422
|
segs.push(`[${seg}]`);
|
|
5226
5423
|
else if (typeof seg === "symbol")
|
|
@@ -16462,69 +16659,8 @@ function truncateToolOutput(content, sessionID, fallback = FALLBACK_MAX_OUTPUT_C
|
|
|
16462
16659
|
var import_iconv_lite = __toESM(require_lib(), 1);
|
|
16463
16660
|
import crypto from "crypto";
|
|
16464
16661
|
import { createReadStream } from "fs";
|
|
16465
|
-
import
|
|
16466
|
-
import
|
|
16467
|
-
|
|
16468
|
-
// src/lib/errors.ts
|
|
16469
|
-
var GbkToolError = class extends Error {
|
|
16470
|
-
code;
|
|
16471
|
-
cause;
|
|
16472
|
-
constructor(code, message, cause) {
|
|
16473
|
-
super(message);
|
|
16474
|
-
this.name = "GbkToolError";
|
|
16475
|
-
this.code = code;
|
|
16476
|
-
this.cause = cause;
|
|
16477
|
-
}
|
|
16478
|
-
};
|
|
16479
|
-
function createGbkError(code, message, cause) {
|
|
16480
|
-
return new GbkToolError(code, message, cause);
|
|
16481
|
-
}
|
|
16482
|
-
var createTextError = createGbkError;
|
|
16483
|
-
|
|
16484
|
-
// src/lib/path-sandbox.ts
|
|
16485
|
-
import fs from "fs/promises";
|
|
16486
|
-
import path from "path";
|
|
16487
|
-
function resolveBaseDirectory(context) {
|
|
16488
|
-
const value = context.directory ?? process.cwd();
|
|
16489
|
-
return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
|
|
16490
|
-
}
|
|
16491
|
-
function resolveWorkspaceRoot(context) {
|
|
16492
|
-
const value = context.worktree ?? context.directory ?? process.cwd();
|
|
16493
|
-
return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
|
|
16494
|
-
}
|
|
16495
|
-
function resolveCandidatePath(filePath, context) {
|
|
16496
|
-
return path.resolve(resolveBaseDirectory(context), filePath);
|
|
16497
|
-
}
|
|
16498
|
-
async function resolveExistingAnchor(filePath) {
|
|
16499
|
-
let current = filePath;
|
|
16500
|
-
while (true) {
|
|
16501
|
-
try {
|
|
16502
|
-
return await fs.realpath(current);
|
|
16503
|
-
} catch {
|
|
16504
|
-
const parent = path.dirname(current);
|
|
16505
|
-
if (parent === current) {
|
|
16506
|
-
throw createGbkError("GBK_IO_ERROR", `\u65E0\u6CD5\u89E3\u6790\u8DEF\u5F84\u951A\u70B9: ${filePath}`);
|
|
16507
|
-
}
|
|
16508
|
-
current = parent;
|
|
16509
|
-
}
|
|
16510
|
-
}
|
|
16511
|
-
}
|
|
16512
|
-
async function assertPathAllowed(filePath, context, allowExternal = false) {
|
|
16513
|
-
const candidatePath = resolveCandidatePath(filePath, context);
|
|
16514
|
-
const workspaceRoot = resolveWorkspaceRoot(context);
|
|
16515
|
-
if (!allowExternal) {
|
|
16516
|
-
const realWorkspaceRoot = await fs.realpath(workspaceRoot);
|
|
16517
|
-
const realCandidateAnchor = await resolveExistingAnchor(candidatePath);
|
|
16518
|
-
const relative = path.relative(realWorkspaceRoot, realCandidateAnchor);
|
|
16519
|
-
if (relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
16520
|
-
return { candidatePath, workspaceRoot };
|
|
16521
|
-
}
|
|
16522
|
-
throw createGbkError("GBK_PATH_OUTSIDE_ROOT", `\u76EE\u6807\u8DEF\u5F84\u8D85\u51FA\u5DE5\u4F5C\u76EE\u5F55\u8303\u56F4: ${candidatePath}`);
|
|
16523
|
-
}
|
|
16524
|
-
return { candidatePath, workspaceRoot };
|
|
16525
|
-
}
|
|
16526
|
-
|
|
16527
|
-
// src/lib/gbk-file.ts
|
|
16662
|
+
import fs3 from "fs/promises";
|
|
16663
|
+
import path3 from "path";
|
|
16528
16664
|
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16529
16665
|
var STREAM_READ_CHUNK_SIZE_BYTES = 1024 * 1024;
|
|
16530
16666
|
var gbkLineIndexCache = /* @__PURE__ */ new Map();
|
|
@@ -16538,8 +16674,8 @@ function buildGbkLineDiffPreview(filePath, encoding, beforeText, afterText) {
|
|
|
16538
16674
|
const afterLines = normalizeNewlines(afterText).split("\n");
|
|
16539
16675
|
const maxLines = Math.max(beforeLines.length, afterLines.length);
|
|
16540
16676
|
const header = [
|
|
16541
|
-
`${ANSI_DIM}--- ${
|
|
16542
|
-
`${ANSI_DIM}+++ ${
|
|
16677
|
+
`${ANSI_DIM}--- ${path3.basename(filePath)} (${encoding})${ANSI_RESET}`,
|
|
16678
|
+
`${ANSI_DIM}+++ ${path3.basename(filePath)} (${encoding})${ANSI_RESET}`
|
|
16543
16679
|
];
|
|
16544
16680
|
const body = [];
|
|
16545
16681
|
for (let index = 0; index < maxLines; index += 1) {
|
|
@@ -17033,7 +17169,7 @@ async function resolveReadableGbkFile(input) {
|
|
|
17033
17169
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
17034
17170
|
let stat;
|
|
17035
17171
|
try {
|
|
17036
|
-
stat = await
|
|
17172
|
+
stat = await fs3.stat(candidatePath);
|
|
17037
17173
|
} catch (error45) {
|
|
17038
17174
|
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
|
|
17039
17175
|
}
|
|
@@ -17048,7 +17184,7 @@ async function resolveReadableGbkFile(input) {
|
|
|
17048
17184
|
}
|
|
17049
17185
|
async function readWholeGbkTextFile(input) {
|
|
17050
17186
|
try {
|
|
17051
|
-
const buffer = await
|
|
17187
|
+
const buffer = await fs3.readFile(input.filePath);
|
|
17052
17188
|
const content = await readBufferAsText(buffer, input.encoding);
|
|
17053
17189
|
return {
|
|
17054
17190
|
filePath: input.filePath,
|
|
@@ -17192,7 +17328,7 @@ async function appendEncodedText(filePath, encoding, text) {
|
|
|
17192
17328
|
return 0;
|
|
17193
17329
|
}
|
|
17194
17330
|
const buffer = import_iconv_lite.default.encode(text, encoding);
|
|
17195
|
-
await
|
|
17331
|
+
await fs3.appendFile(filePath, buffer);
|
|
17196
17332
|
return buffer.byteLength;
|
|
17197
17333
|
}
|
|
17198
17334
|
async function copyFileByteRangeToHandle(sourcePath, handle, start, endExclusive) {
|
|
@@ -17271,11 +17407,11 @@ async function replaceLargeGbkFileTextInLineRange(input) {
|
|
|
17271
17407
|
input.replaceAll,
|
|
17272
17408
|
lineIndex.newlineStyle
|
|
17273
17409
|
);
|
|
17274
|
-
const tempPath =
|
|
17275
|
-
|
|
17276
|
-
`${
|
|
17410
|
+
const tempPath = path3.join(
|
|
17411
|
+
path3.dirname(input.filePath),
|
|
17412
|
+
`${path3.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17277
17413
|
);
|
|
17278
|
-
const handle = await
|
|
17414
|
+
const handle = await fs3.open(tempPath, "w");
|
|
17279
17415
|
let bytesWritten = 0;
|
|
17280
17416
|
try {
|
|
17281
17417
|
bytesWritten += await copyFileByteRangeToHandle(input.filePath, handle, 0, rangeStart);
|
|
@@ -17283,8 +17419,8 @@ async function replaceLargeGbkFileTextInLineRange(input) {
|
|
|
17283
17419
|
bytesWritten += await copyFileByteRangeToHandle(input.filePath, handle, rangeEnd, toSafeNumber(input.stat.size));
|
|
17284
17420
|
await handle.close();
|
|
17285
17421
|
const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
|
|
17286
|
-
await
|
|
17287
|
-
await
|
|
17422
|
+
await fs3.chmod(tempPath, mode);
|
|
17423
|
+
await fs3.rename(tempPath, input.filePath);
|
|
17288
17424
|
return {
|
|
17289
17425
|
mode: "replace",
|
|
17290
17426
|
filePath: input.filePath,
|
|
@@ -17296,18 +17432,18 @@ async function replaceLargeGbkFileTextInLineRange(input) {
|
|
|
17296
17432
|
};
|
|
17297
17433
|
} catch (error45) {
|
|
17298
17434
|
await handle.close().catch(() => void 0);
|
|
17299
|
-
await
|
|
17435
|
+
await fs3.rm(tempPath, { force: true }).catch(() => void 0);
|
|
17300
17436
|
throw error45;
|
|
17301
17437
|
}
|
|
17302
17438
|
}
|
|
17303
17439
|
async function replaceLargeGbkFileByAnchor(input) {
|
|
17304
17440
|
const lineIndex = await getGbkLineIndex(input);
|
|
17305
17441
|
const newlineStyle = lineIndex.newlineStyle;
|
|
17306
|
-
const tempPath =
|
|
17307
|
-
|
|
17308
|
-
`${
|
|
17442
|
+
const tempPath = path3.join(
|
|
17443
|
+
path3.dirname(input.filePath),
|
|
17444
|
+
`${path3.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17309
17445
|
);
|
|
17310
|
-
const handle = await
|
|
17446
|
+
const handle = await fs3.open(tempPath, "w");
|
|
17311
17447
|
const alignedContent = alignTextToNewlineStyle(input.content, newlineStyle);
|
|
17312
17448
|
const anchorVariants = buildFlexibleSearchVariants(input.anchor, newlineStyle);
|
|
17313
17449
|
const maxAnchorLength = anchorVariants.reduce((maxLength, candidate) => Math.max(maxLength, candidate.length), input.anchor.length);
|
|
@@ -17420,8 +17556,8 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17420
17556
|
await finalizeInserted();
|
|
17421
17557
|
await handle.close();
|
|
17422
17558
|
const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
|
|
17423
|
-
await
|
|
17424
|
-
await
|
|
17559
|
+
await fs3.chmod(tempPath, mode);
|
|
17560
|
+
await fs3.rename(tempPath, input.filePath);
|
|
17425
17561
|
invalidateGbkLineIndex(input.filePath);
|
|
17426
17562
|
return {
|
|
17427
17563
|
mode: input.mode,
|
|
@@ -17437,18 +17573,18 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17437
17573
|
};
|
|
17438
17574
|
} catch (error45) {
|
|
17439
17575
|
await handle.close().catch(() => void 0);
|
|
17440
|
-
await
|
|
17576
|
+
await fs3.rm(tempPath, { force: true }).catch(() => void 0);
|
|
17441
17577
|
throw error45;
|
|
17442
17578
|
}
|
|
17443
17579
|
}
|
|
17444
17580
|
async function replaceLargeGbkFileText(input) {
|
|
17445
17581
|
const lineIndex = await getGbkLineIndex(input);
|
|
17446
17582
|
const newlineStyle = lineIndex.newlineStyle;
|
|
17447
|
-
const tempPath =
|
|
17448
|
-
|
|
17449
|
-
`${
|
|
17583
|
+
const tempPath = path3.join(
|
|
17584
|
+
path3.dirname(input.filePath),
|
|
17585
|
+
`${path3.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17450
17586
|
);
|
|
17451
|
-
const handle = await
|
|
17587
|
+
const handle = await fs3.open(tempPath, "w");
|
|
17452
17588
|
const carryLength = Math.max(input.oldString.length - 1, 0);
|
|
17453
17589
|
const alignedNewString = alignTextToNewlineStyle(input.newString, newlineStyle);
|
|
17454
17590
|
let carry = "";
|
|
@@ -17507,8 +17643,8 @@ async function replaceLargeGbkFileText(input) {
|
|
|
17507
17643
|
}
|
|
17508
17644
|
await handle.close();
|
|
17509
17645
|
const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
|
|
17510
|
-
await
|
|
17511
|
-
await
|
|
17646
|
+
await fs3.chmod(tempPath, mode);
|
|
17647
|
+
await fs3.rename(tempPath, input.filePath);
|
|
17512
17648
|
invalidateGbkLineIndex(input.filePath);
|
|
17513
17649
|
return {
|
|
17514
17650
|
filePath: input.filePath,
|
|
@@ -17520,7 +17656,7 @@ async function replaceLargeGbkFileText(input) {
|
|
|
17520
17656
|
};
|
|
17521
17657
|
} catch (error45) {
|
|
17522
17658
|
await handle.close().catch(() => void 0);
|
|
17523
|
-
await
|
|
17659
|
+
await fs3.rm(tempPath, { force: true }).catch(() => void 0);
|
|
17524
17660
|
throw error45;
|
|
17525
17661
|
}
|
|
17526
17662
|
}
|
|
@@ -17648,7 +17784,7 @@ async function replaceGbkFileText(input) {
|
|
|
17648
17784
|
};
|
|
17649
17785
|
}
|
|
17650
17786
|
const buffer2 = import_iconv_lite.default.encode(insertResult.outputText, current2.encoding);
|
|
17651
|
-
await
|
|
17787
|
+
await fs3.writeFile(current2.filePath, buffer2);
|
|
17652
17788
|
return {
|
|
17653
17789
|
mode,
|
|
17654
17790
|
filePath: current2.filePath,
|
|
@@ -17708,7 +17844,7 @@ async function replaceGbkFileText(input) {
|
|
|
17708
17844
|
if (loose !== null) {
|
|
17709
17845
|
const outputText2 = `${current.content.slice(0, scope.rangeStart)}${loose.content}${current.content.slice(scope.rangeEnd)}`;
|
|
17710
17846
|
const buffer2 = import_iconv_lite.default.encode(outputText2, current.encoding);
|
|
17711
|
-
await
|
|
17847
|
+
await fs3.writeFile(current.filePath, buffer2);
|
|
17712
17848
|
invalidateGbkLineIndex(current.filePath);
|
|
17713
17849
|
return {
|
|
17714
17850
|
mode: "replace",
|
|
@@ -17736,7 +17872,7 @@ async function replaceGbkFileText(input) {
|
|
|
17736
17872
|
const replaced = replaceAll ? scope.selectedText.split(effectiveOldString).join(alignedNewString) : scope.selectedText.replace(effectiveOldString, alignedNewString);
|
|
17737
17873
|
const outputText = `${current.content.slice(0, scope.rangeStart)}${replaced}${current.content.slice(scope.rangeEnd)}`;
|
|
17738
17874
|
const buffer = import_iconv_lite.default.encode(outputText, current.encoding);
|
|
17739
|
-
await
|
|
17875
|
+
await fs3.writeFile(current.filePath, buffer);
|
|
17740
17876
|
invalidateGbkLineIndex(current.filePath);
|
|
17741
17877
|
return {
|
|
17742
17878
|
mode: "replace",
|
|
@@ -17821,11 +17957,11 @@ async function writeGbkFile(input) {
|
|
|
17821
17957
|
const overwrite = input.overwrite ?? false;
|
|
17822
17958
|
const append = input.append ?? false;
|
|
17823
17959
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
17824
|
-
const parent =
|
|
17960
|
+
const parent = path3.dirname(candidatePath);
|
|
17825
17961
|
assertEncodingSupported(encoding);
|
|
17826
17962
|
if (append) {
|
|
17827
17963
|
try {
|
|
17828
|
-
const parentStat = await
|
|
17964
|
+
const parentStat = await fs3.stat(parent);
|
|
17829
17965
|
if (!parentStat.isDirectory()) {
|
|
17830
17966
|
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17831
17967
|
}
|
|
@@ -17834,14 +17970,14 @@ async function writeGbkFile(input) {
|
|
|
17834
17970
|
if (!createDirectories) {
|
|
17835
17971
|
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17836
17972
|
}
|
|
17837
|
-
await
|
|
17973
|
+
await fs3.mkdir(parent, { recursive: true });
|
|
17838
17974
|
} else if (error45 instanceof Error && "code" in error45) {
|
|
17839
17975
|
throw error45;
|
|
17840
17976
|
}
|
|
17841
17977
|
}
|
|
17842
17978
|
let existed = false;
|
|
17843
17979
|
try {
|
|
17844
|
-
await
|
|
17980
|
+
await fs3.stat(candidatePath);
|
|
17845
17981
|
existed = true;
|
|
17846
17982
|
} catch (error45) {
|
|
17847
17983
|
if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
|
|
@@ -17860,7 +17996,7 @@ async function writeGbkFile(input) {
|
|
|
17860
17996
|
};
|
|
17861
17997
|
}
|
|
17862
17998
|
try {
|
|
17863
|
-
const stat = await
|
|
17999
|
+
const stat = await fs3.stat(candidatePath);
|
|
17864
18000
|
if (stat.isDirectory()) {
|
|
17865
18001
|
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
17866
18002
|
}
|
|
@@ -17877,7 +18013,7 @@ async function writeGbkFile(input) {
|
|
|
17877
18013
|
}
|
|
17878
18014
|
}
|
|
17879
18015
|
try {
|
|
17880
|
-
const parentStat = await
|
|
18016
|
+
const parentStat = await fs3.stat(parent);
|
|
17881
18017
|
if (!parentStat.isDirectory()) {
|
|
17882
18018
|
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17883
18019
|
}
|
|
@@ -17886,15 +18022,15 @@ async function writeGbkFile(input) {
|
|
|
17886
18022
|
if (!createDirectories) {
|
|
17887
18023
|
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17888
18024
|
}
|
|
17889
|
-
await
|
|
18025
|
+
await fs3.mkdir(parent, { recursive: true });
|
|
17890
18026
|
} else if (error45 instanceof Error && "code" in error45) {
|
|
17891
18027
|
throw error45;
|
|
17892
18028
|
}
|
|
17893
18029
|
}
|
|
17894
18030
|
try {
|
|
17895
|
-
const existed = await
|
|
18031
|
+
const existed = await fs3.stat(candidatePath).then(() => true).catch(() => false);
|
|
17896
18032
|
const buffer = import_iconv_lite.default.encode(input.content, encoding);
|
|
17897
|
-
await
|
|
18033
|
+
await fs3.writeFile(candidatePath, buffer);
|
|
17898
18034
|
invalidateGbkLineIndex(candidatePath);
|
|
17899
18035
|
return {
|
|
17900
18036
|
filePath: candidatePath,
|
|
@@ -18110,39 +18246,12 @@ var gbk_write_default = tool({
|
|
|
18110
18246
|
}
|
|
18111
18247
|
});
|
|
18112
18248
|
|
|
18113
|
-
// src/plugin/text-guidance.ts
|
|
18114
|
-
var TEXT_TOOL_SYSTEM_MARKER = "[opencode-gbk-tools:text-rules]";
|
|
18115
|
-
var TEXT_TOOL_SYSTEM_PROMPT = [
|
|
18116
|
-
TEXT_TOOL_SYSTEM_MARKER,
|
|
18117
|
-
"\u6587\u672C\u6587\u4EF6\u5904\u7406\u89C4\u5219\uFF1A",
|
|
18118
|
-
"- \u5904\u7406\u6587\u672C\u6587\u4EF6\u65F6\uFF0C\u9ED8\u8BA4\u4F18\u5148\u4F7F\u7528 gbk_read\u3001gbk_write\u3001gbk_edit\u3001gbk_search\u3002",
|
|
18119
|
-
"- gbk_* \u9002\u5408\u5F53\u524D\u4ED3\u5E93\u7684\u81EA\u52A8\u7F16\u7801\u6587\u672C\u4E0E GBK/GB18030 \u6587\u4EF6\uFF1B\u5373\u4F7F\u4E0D\u662F\u660E\u786E\u7684 GBK \u6587\u4EF6\uFF0C\u4E5F\u4F18\u5148\u8D70\u8FD9\u7EC4\u5DE5\u5177\u3002",
|
|
18120
|
-
"- \u65B0\u5EFA .txt \u6587\u4EF6\u5728 encoding=auto \u4E0B\u9ED8\u8BA4\u4F7F\u7528 GBK\uFF1B\u5176\u4ED6\u65B0\u6587\u4EF6\u8BF7\u663E\u5F0F\u6307\u5B9A encoding\u3002",
|
|
18121
|
-
"- \u5982\u679C\u610F\u56FE\u662F\u2018\u5728\u67D0\u6807\u7B7E\u524D\u540E\u63D2\u5165\u5185\u5BB9\u2019\uFF0C\u4F18\u5148\u4F7F\u7528 mode=insertAfter \u6216 mode=insertBefore\uFF0C\u5E76\u4F20 anchor/content\u3002",
|
|
18122
|
-
"- \u53EA\u6709\u5728\u660E\u786E\u505A\u7CBE\u786E\u66FF\u6362\u65F6\uFF0C\u624D\u4F7F\u7528 oldString/newString\u3002",
|
|
18123
|
-
"- anchor\u3001startAnchor\u3001endAnchor\u3001oldString \u82E5\u76F4\u63A5\u590D\u5236\u81EA\u8BFB\u53D6\u7ED3\u679C\uFF0C\u53EF\u4FDD\u7559 LF \u6362\u884C\uFF1B\u5DE5\u5177\u4F1A\u5C3D\u91CF\u6309\u6587\u4EF6\u6362\u884C\u98CE\u683C\u81EA\u52A8\u5BF9\u9F50\u3002",
|
|
18124
|
-
'- \u82E5\u8BFB\u53D6\u7ED3\u679C\u5E26\u6709 "N: " \u884C\u53F7\u524D\u7F00\uFF0Cgbk_edit \u4F1A\u5C3D\u91CF\u81EA\u52A8\u5265\u79BB\u8FD9\u4E9B\u524D\u7F00\u540E\u518D\u5339\u914D\u3002',
|
|
18125
|
-
"- text_read\u3001text_write\u3001text_edit \u4EC5\u4F5C\u4E3A\u517C\u5BB9\u5DE5\u5177\uFF0C\u4E0D\u518D\u4F5C\u4E3A\u9ED8\u8BA4\u63A8\u8350\u8DEF\u5F84\u3002"
|
|
18126
|
-
].join("\n");
|
|
18127
|
-
function appendTextToolSystemPrompt(system) {
|
|
18128
|
-
if (system.some((item) => item.includes(TEXT_TOOL_SYSTEM_MARKER))) {
|
|
18129
|
-
return;
|
|
18130
|
-
}
|
|
18131
|
-
if (system.length === 0) {
|
|
18132
|
-
system.push(TEXT_TOOL_SYSTEM_PROMPT);
|
|
18133
|
-
return;
|
|
18134
|
-
}
|
|
18135
|
-
system[0] = `${system[0]}
|
|
18136
|
-
|
|
18137
|
-
${TEXT_TOOL_SYSTEM_PROMPT}`;
|
|
18138
|
-
}
|
|
18139
|
-
|
|
18140
18249
|
// src/lib/text-file.ts
|
|
18141
18250
|
var import_iconv_lite2 = __toESM(require_lib(), 1);
|
|
18142
18251
|
import crypto2 from "crypto";
|
|
18143
18252
|
import { createReadStream as createReadStream2 } from "fs";
|
|
18144
|
-
import
|
|
18145
|
-
import
|
|
18253
|
+
import fs4 from "fs/promises";
|
|
18254
|
+
import path4 from "path";
|
|
18146
18255
|
var TEXT_STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
18147
18256
|
var TEXT_DETECTION_SAMPLE_BYTES = 64 * 1024;
|
|
18148
18257
|
var UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
@@ -18178,7 +18287,7 @@ function resolveExplicitTextEncoding(value, fallback) {
|
|
|
18178
18287
|
return requested === "auto" ? fallback : requested;
|
|
18179
18288
|
}
|
|
18180
18289
|
function shouldDefaultNewTextFileToGbk(filePath) {
|
|
18181
|
-
return
|
|
18290
|
+
return path4.extname(filePath).toLowerCase() === ".txt";
|
|
18182
18291
|
}
|
|
18183
18292
|
function getBomPrefix(encoding, hasBom) {
|
|
18184
18293
|
if (!hasBom) {
|
|
@@ -18474,8 +18583,8 @@ function buildLineDiffPreview(filePath, encoding, beforeText, afterText) {
|
|
|
18474
18583
|
const afterLines = normalizeNewlines2(afterText).split("\n");
|
|
18475
18584
|
const maxLines = Math.max(beforeLines.length, afterLines.length);
|
|
18476
18585
|
const header = [
|
|
18477
|
-
`${ANSI_DIM2}--- ${
|
|
18478
|
-
`${ANSI_DIM2}+++ ${
|
|
18586
|
+
`${ANSI_DIM2}--- ${path4.basename(filePath)} (${encoding})${ANSI_RESET2}`,
|
|
18587
|
+
`${ANSI_DIM2}+++ ${path4.basename(filePath)} (${encoding})${ANSI_RESET2}`
|
|
18479
18588
|
];
|
|
18480
18589
|
const body = [];
|
|
18481
18590
|
for (let index = 0; index < maxLines; index += 1) {
|
|
@@ -18591,7 +18700,7 @@ async function resolveReadableTextFile(input) {
|
|
|
18591
18700
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
18592
18701
|
let stat;
|
|
18593
18702
|
try {
|
|
18594
|
-
stat = await
|
|
18703
|
+
stat = await fs4.stat(candidatePath);
|
|
18595
18704
|
} catch (error45) {
|
|
18596
18705
|
throw createTextError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
|
|
18597
18706
|
}
|
|
@@ -18601,7 +18710,7 @@ async function resolveReadableTextFile(input) {
|
|
|
18601
18710
|
return { filePath: candidatePath, stat };
|
|
18602
18711
|
}
|
|
18603
18712
|
async function readDetectionBuffer(filePath, sampleSize = TEXT_DETECTION_SAMPLE_BYTES) {
|
|
18604
|
-
const handle = await
|
|
18713
|
+
const handle = await fs4.open(filePath, "r");
|
|
18605
18714
|
try {
|
|
18606
18715
|
const buffer = Buffer.alloc(sampleSize);
|
|
18607
18716
|
const { bytesRead } = await handle.read(buffer, 0, sampleSize, 0);
|
|
@@ -18764,8 +18873,8 @@ async function visitDecodedTextChunks2(resolved, visitor) {
|
|
|
18764
18873
|
}
|
|
18765
18874
|
}
|
|
18766
18875
|
async function writeLargeTextFile(filePath, encoding, hasBom, producer) {
|
|
18767
|
-
const tempPath =
|
|
18768
|
-
const handle = await
|
|
18876
|
+
const tempPath = path4.join(path4.dirname(filePath), `${path4.basename(filePath)}.opencode-text-${crypto2.randomUUID()}.tmp`);
|
|
18877
|
+
const handle = await fs4.open(tempPath, "w");
|
|
18769
18878
|
try {
|
|
18770
18879
|
if (hasBom) {
|
|
18771
18880
|
const bom = getBomPrefix(encoding, hasBom);
|
|
@@ -18775,11 +18884,11 @@ async function writeLargeTextFile(filePath, encoding, hasBom, producer) {
|
|
|
18775
18884
|
}
|
|
18776
18885
|
const bytesWritten = await producer(handle);
|
|
18777
18886
|
await handle.close();
|
|
18778
|
-
await
|
|
18887
|
+
await fs4.rename(tempPath, filePath);
|
|
18779
18888
|
return bytesWritten + getBomPrefix(encoding, hasBom).length;
|
|
18780
18889
|
} catch (error45) {
|
|
18781
18890
|
await handle.close().catch(() => void 0);
|
|
18782
|
-
await
|
|
18891
|
+
await fs4.rm(tempPath, { force: true }).catch(() => void 0);
|
|
18783
18892
|
throw error45;
|
|
18784
18893
|
}
|
|
18785
18894
|
}
|
|
@@ -18874,7 +18983,7 @@ async function replaceLargeTextFileText(input) {
|
|
|
18874
18983
|
async function readWholeTextFile(input) {
|
|
18875
18984
|
const resolved = await resolveReadableTextFile(input);
|
|
18876
18985
|
try {
|
|
18877
|
-
const buffer = await
|
|
18986
|
+
const buffer = await fs4.readFile(resolved.filePath);
|
|
18878
18987
|
assertLikelyTextBuffer(buffer);
|
|
18879
18988
|
const detected = detectTextEncodingFromBuffer(buffer, input.encoding ?? "auto");
|
|
18880
18989
|
const content = decodeText(buffer, detected.detectedEncoding);
|
|
@@ -19070,7 +19179,7 @@ function ensureLossless(input, encoding, hasBom = encoding === "utf8-bom") {
|
|
|
19070
19179
|
}
|
|
19071
19180
|
async function ensureParentDirectory(parent, createDirectories) {
|
|
19072
19181
|
try {
|
|
19073
|
-
const parentStat = await
|
|
19182
|
+
const parentStat = await fs4.stat(parent);
|
|
19074
19183
|
if (!parentStat.isDirectory()) {
|
|
19075
19184
|
throw createTextError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
19076
19185
|
}
|
|
@@ -19079,12 +19188,26 @@ async function ensureParentDirectory(parent, createDirectories) {
|
|
|
19079
19188
|
if (!createDirectories) {
|
|
19080
19189
|
throw createTextError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
19081
19190
|
}
|
|
19082
|
-
await
|
|
19191
|
+
await fs4.mkdir(parent, { recursive: true });
|
|
19083
19192
|
return;
|
|
19084
19193
|
}
|
|
19085
19194
|
throw error45;
|
|
19086
19195
|
}
|
|
19087
19196
|
}
|
|
19197
|
+
async function detectTextFileEncoding(input) {
|
|
19198
|
+
const detected = await detectReadableTextFile(input);
|
|
19199
|
+
const fileSize = typeof detected.stat.size === "bigint" ? Number(detected.stat.size) : detected.stat.size;
|
|
19200
|
+
return {
|
|
19201
|
+
filePath: detected.filePath,
|
|
19202
|
+
encoding: detected.encoding,
|
|
19203
|
+
requestedEncoding: detected.requestedEncoding,
|
|
19204
|
+
detectedEncoding: detected.detectedEncoding,
|
|
19205
|
+
confidence: detected.confidence,
|
|
19206
|
+
hasBom: detected.hasBom,
|
|
19207
|
+
bytesRead: Math.min(fileSize, TEXT_DETECTION_SAMPLE_BYTES),
|
|
19208
|
+
newlineStyle: "unknown"
|
|
19209
|
+
};
|
|
19210
|
+
}
|
|
19088
19211
|
async function readTextFile(input) {
|
|
19089
19212
|
const offset = normalizeOptionalPositiveInteger(input.offset, "offset") ?? 1;
|
|
19090
19213
|
const limit = normalizeOptionalPositiveInteger(input.limit, "limit") ?? 2e3;
|
|
@@ -19142,7 +19265,7 @@ async function writeTextFile(input) {
|
|
|
19142
19265
|
const overwrite = input.overwrite ?? false;
|
|
19143
19266
|
const append = input.append ?? false;
|
|
19144
19267
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
19145
|
-
const parent =
|
|
19268
|
+
const parent = path4.dirname(candidatePath);
|
|
19146
19269
|
await ensureParentDirectory(parent, createDirectories);
|
|
19147
19270
|
let existing = null;
|
|
19148
19271
|
try {
|
|
@@ -19171,7 +19294,7 @@ async function writeTextFile(input) {
|
|
|
19171
19294
|
const outputContent = existing && preserveNewlineStyle ? alignTextToNewlineStyle2(rawContent, existing.newlineStyle) : rawContent;
|
|
19172
19295
|
ensureLossless(outputContent, targetEncoding, targetHasBom);
|
|
19173
19296
|
const buffer = encodeText(outputContent, targetEncoding, targetHasBom);
|
|
19174
|
-
await
|
|
19297
|
+
await fs4.writeFile(candidatePath, buffer);
|
|
19175
19298
|
return {
|
|
19176
19299
|
filePath: candidatePath,
|
|
19177
19300
|
encoding: targetEncoding,
|
|
@@ -19235,7 +19358,7 @@ async function replaceTextFileText(input) {
|
|
|
19235
19358
|
const targetHasBom2 = normalizedInput.preserveEncoding === false ? targetEncoding2 === "utf8-bom" || targetEncoding2 === "utf16le" || targetEncoding2 === "utf16be" : loaded2.hasBom;
|
|
19236
19359
|
ensureLossless(insertResult.outputText, targetEncoding2, targetHasBom2);
|
|
19237
19360
|
const buffer2 = encodeText(insertResult.outputText, targetEncoding2, targetHasBom2);
|
|
19238
|
-
await
|
|
19361
|
+
await fs4.writeFile(loaded2.filePath, buffer2);
|
|
19239
19362
|
return {
|
|
19240
19363
|
mode,
|
|
19241
19364
|
filePath: loaded2.filePath,
|
|
@@ -19312,7 +19435,7 @@ async function replaceTextFileText(input) {
|
|
|
19312
19435
|
const outputText2 = `${loaded.content.slice(0, scope.rangeStart)}${loose.content}${loaded.content.slice(scope.rangeEnd)}`;
|
|
19313
19436
|
ensureLossless(outputText2, targetEncoding, targetHasBom);
|
|
19314
19437
|
const buffer2 = encodeText(outputText2, targetEncoding, targetHasBom);
|
|
19315
|
-
await
|
|
19438
|
+
await fs4.writeFile(loaded.filePath, buffer2);
|
|
19316
19439
|
return {
|
|
19317
19440
|
mode: "replace",
|
|
19318
19441
|
filePath: loaded.filePath,
|
|
@@ -19344,7 +19467,7 @@ async function replaceTextFileText(input) {
|
|
|
19344
19467
|
const outputText = `${loaded.content.slice(0, scope.rangeStart)}${replaced}${loaded.content.slice(scope.rangeEnd)}`;
|
|
19345
19468
|
ensureLossless(outputText, targetEncoding, targetHasBom);
|
|
19346
19469
|
const buffer = encodeText(outputText, targetEncoding, targetHasBom);
|
|
19347
|
-
await
|
|
19470
|
+
await fs4.writeFile(loaded.filePath, buffer);
|
|
19348
19471
|
return {
|
|
19349
19472
|
mode: "replace",
|
|
19350
19473
|
filePath: loaded.filePath,
|
|
@@ -19362,6 +19485,35 @@ async function replaceTextFileText(input) {
|
|
|
19362
19485
|
};
|
|
19363
19486
|
}
|
|
19364
19487
|
|
|
19488
|
+
// src/plugin/text-guidance.ts
|
|
19489
|
+
var TEXT_TOOL_SYSTEM_MARKER = "[opencode-gbk-tools:text-rules]";
|
|
19490
|
+
var TEXT_TOOL_SYSTEM_PROMPT = [
|
|
19491
|
+
TEXT_TOOL_SYSTEM_MARKER,
|
|
19492
|
+
"\u6587\u672C\u6587\u4EF6\u5904\u7406\u89C4\u5219\uFF1A",
|
|
19493
|
+
"- \u666E\u901A UTF-8 / UTF-8 BOM / UTF-16 \u6587\u672C\uFF0C\u4F18\u5148\u4F7F\u7528 OpenCode \u5185\u7F6E read\u3001write\u3001edit\u3002",
|
|
19494
|
+
"- \u9047\u5230 GBK / GB18030 \u6587\u4EF6\u3001\u4E2D\u6587\u4E71\u7801\u3001\u975E UTF-8 \u65E7\u6587\u672C\uFF0C\u4F18\u5148\u4F7F\u7528 gbk_read\u3001gbk_write\u3001gbk_edit\u3001gbk_search\uFF0C\u4E0D\u8981\u5148\u5C1D\u8BD5 text_* \u6216\u5185\u7F6E read/write/edit\u3002",
|
|
19495
|
+
"- \u65B0\u5EFA .txt \u6587\u4EF6\u5728 encoding=auto \u4E0B\u9ED8\u8BA4\u4F7F\u7528 GBK\uFF1B\u5176\u4ED6\u65B0\u6587\u4EF6\u8BF7\u663E\u5F0F\u6307\u5B9A encoding\u3002",
|
|
19496
|
+
"- \u5BF9\u73B0\u6709 .txt / .cfg / .ini / .log \u7B49\u65E7\u6587\u672C\u6587\u4EF6\uFF0C\u53EA\u8981\u6000\u7591\u662F\u4E2D\u6587\u672C\u5730\u7F16\u7801\uFF0C\u4F18\u5148\u5148\u7528 gbk_read \u5224\u65AD\uFF0C\u4E0D\u8981\u5148 edit \u518D\u56E0\u4E3A\u5339\u914D\u5931\u8D25\u6216\u6587\u4EF6\u65F6\u95F4\u6233\u53D8\u5316\u800C\u56DE\u9000\u3002",
|
|
19497
|
+
"- \u5DF2\u786E\u8BA4\u6216\u9996\u6B21\u68C0\u6D4B\u5230\u662F GBK/GB18030 \u7684\u6587\u4EF6\u4F1A\u88AB\u63D2\u4EF6\u6301\u4E45\u8BB0\u5FC6\uFF1B\u518D\u6B21\u64CD\u4F5C\u540C\u4E00\u8DEF\u5F84\u65F6\uFF0C\u4F1A\u76F4\u63A5\u62E6\u622A\u5185\u7F6E read/write/edit \u548C text_*\uFF0C\u5E76\u8981\u6C42\u6539\u7528 gbk_*\u3002",
|
|
19498
|
+
"- \u5982\u679C\u610F\u56FE\u662F\u2018\u5728\u67D0\u6807\u7B7E\u524D\u540E\u63D2\u5165\u5185\u5BB9\u2019\uFF0C\u4F18\u5148\u4F7F\u7528 mode=insertAfter \u6216 mode=insertBefore\uFF0C\u5E76\u4F20 anchor/content\u3002",
|
|
19499
|
+
"- \u53EA\u6709\u5728\u660E\u786E\u505A\u7CBE\u786E\u66FF\u6362\u65F6\uFF0C\u624D\u4F7F\u7528 oldString/newString\u3002",
|
|
19500
|
+
"- anchor\u3001startAnchor\u3001endAnchor\u3001oldString \u82E5\u76F4\u63A5\u590D\u5236\u81EA\u8BFB\u53D6\u7ED3\u679C\uFF0C\u53EF\u4FDD\u7559 LF \u6362\u884C\uFF1Bgbk_edit / text_edit \u4F1A\u5C3D\u91CF\u6309\u6587\u4EF6\u6362\u884C\u98CE\u683C\u81EA\u52A8\u5BF9\u9F50\u3002",
|
|
19501
|
+
'- \u82E5\u8BFB\u53D6\u7ED3\u679C\u5E26\u6709 "N: " \u884C\u53F7\u524D\u7F00\uFF0Cgbk_edit / text_edit \u4F1A\u5C3D\u91CF\u81EA\u52A8\u5265\u79BB\u8FD9\u4E9B\u524D\u7F00\u540E\u518D\u5339\u914D\u3002',
|
|
19502
|
+
"- gbk-engine \u662F\u5F3A\u5236 GBK \u4E13\u5C5E\u6A21\u5F0F\uFF1A\u53EA\u5141\u8BB8 gbk_*\uFF0C\u4E0D\u8D70\u5185\u7F6E\u8BFB\u5199\u7F16\u8F91\u5DE5\u5177\u3002"
|
|
19503
|
+
].join("\n");
|
|
19504
|
+
function appendTextToolSystemPrompt(system) {
|
|
19505
|
+
if (system.some((item) => item.includes(TEXT_TOOL_SYSTEM_MARKER))) {
|
|
19506
|
+
return;
|
|
19507
|
+
}
|
|
19508
|
+
if (system.length === 0) {
|
|
19509
|
+
system.push(TEXT_TOOL_SYSTEM_PROMPT);
|
|
19510
|
+
return;
|
|
19511
|
+
}
|
|
19512
|
+
system[0] = `${system[0]}
|
|
19513
|
+
|
|
19514
|
+
${TEXT_TOOL_SYSTEM_PROMPT}`;
|
|
19515
|
+
}
|
|
19516
|
+
|
|
19365
19517
|
// src/tools/text_edit.ts
|
|
19366
19518
|
var text_edit_default = tool({
|
|
19367
19519
|
description: `Edit text files with automatic encoding detection and preservation.
|
|
@@ -19505,6 +19657,58 @@ var MANAGED_TOOL_IDS = /* @__PURE__ */ new Set([
|
|
|
19505
19657
|
"text_write",
|
|
19506
19658
|
"text_edit"
|
|
19507
19659
|
]);
|
|
19660
|
+
var BUILTIN_TEXT_TOOL_IDS = /* @__PURE__ */ new Set(["read", "write", "edit"]);
|
|
19661
|
+
var TEXT_TOOL_IDS = /* @__PURE__ */ new Set(["text_read", "text_write", "text_edit"]);
|
|
19662
|
+
var ROUTED_TEXT_TOOL_IDS = /* @__PURE__ */ new Set([...BUILTIN_TEXT_TOOL_IDS, ...TEXT_TOOL_IDS]);
|
|
19663
|
+
function getToolFilePath(args) {
|
|
19664
|
+
if (!args || typeof args !== "object") {
|
|
19665
|
+
return null;
|
|
19666
|
+
}
|
|
19667
|
+
const filePath = args.filePath;
|
|
19668
|
+
return typeof filePath === "string" ? filePath : null;
|
|
19669
|
+
}
|
|
19670
|
+
function getToolAllowExternal(args) {
|
|
19671
|
+
if (!args || typeof args !== "object") {
|
|
19672
|
+
return false;
|
|
19673
|
+
}
|
|
19674
|
+
return args.allowExternal === true;
|
|
19675
|
+
}
|
|
19676
|
+
function normalizeSessionFilePath(filePath, directory, worktree) {
|
|
19677
|
+
const resolved = path5.normalize(resolveCandidatePath(filePath, { directory, worktree }));
|
|
19678
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
19679
|
+
}
|
|
19680
|
+
function buildGbkRoutingMessage(filePath, encoding) {
|
|
19681
|
+
return `\u6587\u4EF6\u68C0\u6D4B\u4E3A ${encoding.toUpperCase()} \u7F16\u7801\uFF0C\u8BF7\u76F4\u63A5\u6539\u7528 gbk_read\u3001gbk_write\u3001gbk_edit \u6216 gbk_search\uFF0C\u4E0D\u8981\u5148\u4F7F\u7528\u5185\u7F6E read/write/edit \u6216 text_*\uFF1A${filePath}`;
|
|
19682
|
+
}
|
|
19683
|
+
function buildTextEditSessionRoutingMessage(filePath) {
|
|
19684
|
+
return `\u5F53\u524D\u4F1A\u8BDD\u5DF2\u5BF9\u8BE5\u6587\u4EF6\u4F7F\u7528 text_edit\uFF0C\u8BF7\u7EE7\u7EED\u4F7F\u7528 text_read\u3001text_write\u3001text_edit \u6216\u76F4\u63A5\u6539\u7528 gbk_*\uFF1B\u4E0D\u8981\u518D\u5207\u56DE\u5185\u7F6E read/write/edit\uFF0C\u4EE5\u514D\u89E6\u53D1\u6587\u4EF6\u65B0\u9C9C\u5EA6\u68C0\u67E5\u51B2\u7A81\uFF1A${filePath}`;
|
|
19685
|
+
}
|
|
19686
|
+
async function detectExistingGbkEncoding(filePath, allowExternal, directory, worktree) {
|
|
19687
|
+
try {
|
|
19688
|
+
const detected = await detectTextFileEncoding({
|
|
19689
|
+
filePath,
|
|
19690
|
+
encoding: "auto",
|
|
19691
|
+
allowExternal,
|
|
19692
|
+
context: { directory, worktree }
|
|
19693
|
+
});
|
|
19694
|
+
return isRememberedGbkEncoding(detected.encoding) ? detected.encoding : null;
|
|
19695
|
+
} catch {
|
|
19696
|
+
return null;
|
|
19697
|
+
}
|
|
19698
|
+
}
|
|
19699
|
+
async function maybePersistRememberedEncoding(metadata) {
|
|
19700
|
+
const filePath = typeof metadata.filePath === "string" ? metadata.filePath : null;
|
|
19701
|
+
const encoding = metadata.encoding;
|
|
19702
|
+
if (!filePath) {
|
|
19703
|
+
return;
|
|
19704
|
+
}
|
|
19705
|
+
if (isRememberedGbkEncoding(encoding)) {
|
|
19706
|
+
await rememberGbkEncoding(filePath, encoding);
|
|
19707
|
+
metadata.rememberedEncoding = encoding;
|
|
19708
|
+
return;
|
|
19709
|
+
}
|
|
19710
|
+
await forgetRememberedEncoding(filePath);
|
|
19711
|
+
}
|
|
19508
19712
|
function truncateMetadataPreview(value, sessionID) {
|
|
19509
19713
|
const previewMaxChars = Math.max(800, Math.min(2e3, Math.floor(getMaxOutputChars(sessionID) / 2)));
|
|
19510
19714
|
if (value.length <= previewMaxChars) return value;
|
|
@@ -19555,7 +19759,25 @@ async function maybeAutoSummarizeSession(client, directory, input) {
|
|
|
19555
19759
|
markAutoSummarizeFinished(input.sessionID, false);
|
|
19556
19760
|
}
|
|
19557
19761
|
}
|
|
19558
|
-
function createOpencodeGbkHooks(client, directory) {
|
|
19762
|
+
function createOpencodeGbkHooks(client, directory, worktree) {
|
|
19763
|
+
const sessionTextEditedFiles = /* @__PURE__ */ new Map();
|
|
19764
|
+
function rememberSessionTextEditFile(sessionID, normalizedFilePath) {
|
|
19765
|
+
if (!sessionID) {
|
|
19766
|
+
return;
|
|
19767
|
+
}
|
|
19768
|
+
let files = sessionTextEditedFiles.get(sessionID);
|
|
19769
|
+
if (!files) {
|
|
19770
|
+
files = /* @__PURE__ */ new Set();
|
|
19771
|
+
sessionTextEditedFiles.set(sessionID, files);
|
|
19772
|
+
}
|
|
19773
|
+
files.add(normalizedFilePath);
|
|
19774
|
+
}
|
|
19775
|
+
function hasSessionTextEditFile(sessionID, normalizedFilePath) {
|
|
19776
|
+
if (!sessionID) {
|
|
19777
|
+
return false;
|
|
19778
|
+
}
|
|
19779
|
+
return sessionTextEditedFiles.get(sessionID)?.has(normalizedFilePath) ?? false;
|
|
19780
|
+
}
|
|
19559
19781
|
return {
|
|
19560
19782
|
tool: {
|
|
19561
19783
|
gbk_read: gbk_read_default,
|
|
@@ -19581,6 +19803,38 @@ function createOpencodeGbkHooks(client, directory) {
|
|
|
19581
19803
|
await maybeAutoSummarizeSession(client, directory, input);
|
|
19582
19804
|
}
|
|
19583
19805
|
},
|
|
19806
|
+
async "tool.execute.before"(input, output) {
|
|
19807
|
+
const filePath = getToolFilePath(output.args);
|
|
19808
|
+
if (!filePath) {
|
|
19809
|
+
return;
|
|
19810
|
+
}
|
|
19811
|
+
if (!ROUTED_TEXT_TOOL_IDS.has(input.tool)) {
|
|
19812
|
+
return;
|
|
19813
|
+
}
|
|
19814
|
+
const normalizedFilePath = normalizeSessionFilePath(filePath, directory, worktree);
|
|
19815
|
+
if (BUILTIN_TEXT_TOOL_IDS.has(input.tool) && hasSessionTextEditFile(input.sessionID, normalizedFilePath)) {
|
|
19816
|
+
throw new Error(buildTextEditSessionRoutingMessage(filePath));
|
|
19817
|
+
}
|
|
19818
|
+
const resolvedFilePath = resolveCandidatePath(filePath, { directory, worktree });
|
|
19819
|
+
const remembered = await getRememberedGbkEncoding(resolvedFilePath);
|
|
19820
|
+
if (remembered) {
|
|
19821
|
+
throw new Error(buildGbkRoutingMessage(filePath, remembered.encoding));
|
|
19822
|
+
}
|
|
19823
|
+
const detectedEncoding = await detectExistingGbkEncoding(
|
|
19824
|
+
resolvedFilePath,
|
|
19825
|
+
getToolAllowExternal(output.args),
|
|
19826
|
+
directory,
|
|
19827
|
+
worktree
|
|
19828
|
+
);
|
|
19829
|
+
if (!detectedEncoding) {
|
|
19830
|
+
return;
|
|
19831
|
+
}
|
|
19832
|
+
try {
|
|
19833
|
+
await rememberGbkEncoding(resolvedFilePath, detectedEncoding);
|
|
19834
|
+
} catch {
|
|
19835
|
+
}
|
|
19836
|
+
throw new Error(buildGbkRoutingMessage(filePath, detectedEncoding));
|
|
19837
|
+
},
|
|
19584
19838
|
async "tool.execute.after"(input, output) {
|
|
19585
19839
|
if (!MANAGED_TOOL_IDS.has(input.tool)) return;
|
|
19586
19840
|
const maxOutputChars = getMaxOutputChars(input.sessionID);
|
|
@@ -19596,6 +19850,17 @@ function createOpencodeGbkHooks(client, directory) {
|
|
|
19596
19850
|
if (typeof metadata.diffPreview === "string") {
|
|
19597
19851
|
metadata.diffPreview = truncateMetadataPreview(metadata.diffPreview, input.sessionID);
|
|
19598
19852
|
}
|
|
19853
|
+
if (input.tool === "text_edit" && typeof metadata.filePath === "string") {
|
|
19854
|
+
rememberSessionTextEditFile(
|
|
19855
|
+
input.sessionID,
|
|
19856
|
+
normalizeSessionFilePath(metadata.filePath, directory, worktree)
|
|
19857
|
+
);
|
|
19858
|
+
}
|
|
19859
|
+
try {
|
|
19860
|
+
await maybePersistRememberedEncoding(metadata);
|
|
19861
|
+
} catch {
|
|
19862
|
+
metadata.encodingMemoryWarning = "\u8BB0\u5FC6\u6587\u4EF6\u7F16\u7801\u5931\u8D25\uFF0C\u5DF2\u5FFD\u7565";
|
|
19863
|
+
}
|
|
19599
19864
|
metadata.maxOutputChars = maxOutputChars;
|
|
19600
19865
|
if (compactionCount > 0) {
|
|
19601
19866
|
metadata.sessionCompactions = compactionCount;
|
|
@@ -19622,7 +19887,7 @@ function createOpencodeGbkHooks(client, directory) {
|
|
|
19622
19887
|
var pluginModule = {
|
|
19623
19888
|
id: "opencode-gbk-tools",
|
|
19624
19889
|
async server(ctx) {
|
|
19625
|
-
return createOpencodeGbkHooks(ctx.client, ctx.directory);
|
|
19890
|
+
return createOpencodeGbkHooks(ctx.client, ctx.directory, ctx.worktree);
|
|
19626
19891
|
}
|
|
19627
19892
|
};
|
|
19628
19893
|
var plugin_default = pluginModule;
|