agent-Specialization/easyagent/src/tools/edit_file.js

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 };