124 lines
4.0 KiB
JavaScript
124 lines
4.0 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { structuredPatch } = require('diff');
|
|
const { getContainerContext, resolveContainerPath, execContainerAction } = require('./container_bridge');
|
|
|
|
function resolvePath(workspace, p) {
|
|
if (path.isAbsolute(p)) return p;
|
|
return path.join(workspace, p);
|
|
}
|
|
|
|
function diffSummary(oldText, newText) {
|
|
const patch = structuredPatch('old', 'new', oldText, newText, '', '', { context: 1 });
|
|
let added = 0;
|
|
let removed = 0;
|
|
const hunks = patch.hunks.map((hunk) => {
|
|
let oldLine = hunk.oldStart;
|
|
let newLine = hunk.newStart;
|
|
const lines = hunk.lines.map((line) => {
|
|
const prefix = line[0];
|
|
const content = line.slice(1);
|
|
let lineNum = null;
|
|
if (prefix === ' ') {
|
|
lineNum = oldLine;
|
|
oldLine += 1;
|
|
newLine += 1;
|
|
} else if (prefix === '-') {
|
|
lineNum = oldLine;
|
|
removed += 1;
|
|
oldLine += 1;
|
|
} else if (prefix === '+') {
|
|
lineNum = newLine;
|
|
added += 1;
|
|
newLine += 1;
|
|
}
|
|
return { prefix, lineNum, content };
|
|
});
|
|
return lines;
|
|
});
|
|
return { added, removed, hunks };
|
|
}
|
|
|
|
function editFileTool(workspace, args) {
|
|
const target = resolvePath(workspace, args.file_path);
|
|
const oldString = args.old_string ?? '';
|
|
const newString = args.new_string ?? '';
|
|
const ctx = getContainerContext();
|
|
if (ctx) {
|
|
const resolved = resolveContainerPath(workspace, args.file_path || '.', ctx);
|
|
if (resolved.error) return { success: false, error: resolved.error };
|
|
const rel = resolved.relativePath;
|
|
if (!oldString) {
|
|
const writeResp = execContainerAction(ctx, 'write_file', {
|
|
path: rel,
|
|
content: newString || '',
|
|
mode: 'w',
|
|
});
|
|
if (!writeResp.success) return { success: false, error: writeResp.error || '写入失败' };
|
|
return {
|
|
success: true,
|
|
path: resolved.containerPath,
|
|
replacements: newString ? 1 : 0,
|
|
};
|
|
}
|
|
|
|
const readResp = execContainerAction(ctx, 'read_file', { path: rel });
|
|
if (!readResp.success) return { success: false, error: readResp.error || '读取失败' };
|
|
const original = readResp.content || '';
|
|
if (!original.includes(oldString)) {
|
|
return { success: false, error: 'old_string 未匹配到内容' };
|
|
}
|
|
const updated = original.split(oldString).join(newString);
|
|
const replacements = original.split(oldString).length - 1;
|
|
const writeResp = execContainerAction(ctx, 'write_file', {
|
|
path: rel,
|
|
content: updated,
|
|
mode: 'w',
|
|
});
|
|
if (!writeResp.success) return { success: false, error: writeResp.error || '写入失败' };
|
|
const diff = diffSummary(original, updated);
|
|
return {
|
|
success: true,
|
|
path: resolved.containerPath,
|
|
replacements,
|
|
diff,
|
|
};
|
|
}
|
|
try {
|
|
let creating = false;
|
|
if (!fs.existsSync(target)) {
|
|
creating = true;
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.writeFileSync(target, '', 'utf8');
|
|
}
|
|
const stat = fs.statSync(target);
|
|
if (!stat.isFile()) {
|
|
return { success: false, error: '目标不是文件' };
|
|
}
|
|
const original = fs.readFileSync(target, 'utf8');
|
|
if (!creating && oldString === '') {
|
|
return { success: false, error: 'old_string 不能为空,请从 read_file 内容中精确复制' };
|
|
}
|
|
if (!creating && !original.includes(oldString)) {
|
|
return { success: false, error: 'old_string 未匹配到内容' };
|
|
}
|
|
const updated = creating ? newString : original.split(oldString).join(newString);
|
|
let replacements = creating ? 0 : original.split(oldString).length - 1;
|
|
if (creating && newString) replacements = 1;
|
|
fs.writeFileSync(target, updated, 'utf8');
|
|
const diff = diffSummary(original, updated);
|
|
return {
|
|
success: true,
|
|
path: target,
|
|
replacements,
|
|
diff,
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message || String(err) };
|
|
}
|
|
}
|
|
|
|
module.exports = { editFileTool, resolvePath };
|