feat: write_file 全面增强 — base64写二进制/mode(overwrite+append)/覆盖提示/10MB/返回行数+文件大小
This commit is contained in:
@@ -154,33 +154,77 @@ function extToMime(ext: string): string {
|
|||||||
return map[ext] || 'application/octet-stream';
|
return map[ext] || 'application/octet-stream';
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleWriteFile(params: { path: string; content: string; encoding?: string }): Promise<ToolResult> {
|
export async function handleWriteFile(params: { path: string; content: string; encoding?: string; mode?: string }): Promise<ToolResult> {
|
||||||
try {
|
try {
|
||||||
const filePath = resolvePath(params.path);
|
const filePath = resolvePath(params.path);
|
||||||
const allowed = checkPathAllowed(filePath, 'write');
|
const allowed = checkPathAllowed(filePath, 'write');
|
||||||
if (!allowed.ok) return { success: false, error: allowed.reason };
|
if (!allowed.ok) return { success: false, error: allowed.reason };
|
||||||
|
|
||||||
if (params.content.length > 5 * 1024 * 1024) {
|
const writeMode = params.mode || 'overwrite';
|
||||||
return { success: false, error: '内容过大,最大支持 5MB' };
|
const encoding = (params.encoding as BufferEncoding) || 'utf-8';
|
||||||
|
|
||||||
|
// ── base64 解码:支持写入二进制文件 ──
|
||||||
|
let writeBuffer: Buffer;
|
||||||
|
let contentLength: number;
|
||||||
|
if (encoding === 'base64') {
|
||||||
|
writeBuffer = Buffer.from(params.content, 'base64');
|
||||||
|
contentLength = writeBuffer.length;
|
||||||
|
} else {
|
||||||
|
writeBuffer = Buffer.from(params.content, encoding);
|
||||||
|
contentLength = Buffer.byteLength(params.content, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
let created = false;
|
const MAX_SIZE = writeMode === 'append' ? 5 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||||
|
if (contentLength > MAX_SIZE) {
|
||||||
|
return { success: false, error: `内容过大 (${(contentLength / 1024 / 1024).toFixed(1)}MB),最大支持 ${MAX_SIZE / 1024 / 1024}MB` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 检查目标文件状态 ──
|
||||||
|
let existed = false;
|
||||||
|
let prevSize = 0;
|
||||||
|
let prevLines = 0;
|
||||||
try {
|
try {
|
||||||
await fs.stat(filePath);
|
const prevStat = await fs.stat(filePath);
|
||||||
|
existed = true;
|
||||||
|
prevSize = prevStat.size;
|
||||||
|
if (writeMode === 'overwrite' && !params.encoding?.startsWith('base64')) {
|
||||||
|
const prevContent = await fs.readFile(filePath, 'utf-8');
|
||||||
|
prevLines = prevContent.split('\n').length;
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
created = true;
|
// 文件不存在,正常
|
||||||
}
|
}
|
||||||
|
|
||||||
const dir = path.dirname(filePath);
|
const dir = path.dirname(filePath);
|
||||||
await fs.mkdir(dir, { recursive: true });
|
await fs.mkdir(dir, { recursive: true });
|
||||||
|
|
||||||
await fs.writeFile(filePath, params.content, (params.encoding as BufferEncoding) || 'utf-8');
|
if (writeMode === 'append') {
|
||||||
|
await fs.appendFile(filePath, writeBuffer);
|
||||||
|
} else {
|
||||||
|
await fs.writeFile(filePath, writeBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 读回统计 ──
|
||||||
|
const stat = await fs.stat(filePath);
|
||||||
|
const lines = encoding === 'base64' ? 0 : (await fs.readFile(filePath, 'utf-8')).split('\n').length;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
bytesWritten: Buffer.byteLength(params.content, (params.encoding as BufferEncoding) || 'utf-8'),
|
bytesWritten: contentLength,
|
||||||
created
|
totalSize: stat.size,
|
||||||
|
lines,
|
||||||
|
mode: writeMode,
|
||||||
|
created: !existed,
|
||||||
|
...(existed && writeMode === 'overwrite' && {
|
||||||
|
overwrote: true,
|
||||||
|
prevSize,
|
||||||
|
...(prevLines > 0 && { prevLines }),
|
||||||
|
}),
|
||||||
|
...(writeMode === 'append' && {
|
||||||
|
appended: true,
|
||||||
|
prevSize,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { success: false, error: (err as Error).message };
|
return { success: false, error: (err as Error).message };
|
||||||
|
|||||||
@@ -33,13 +33,15 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
|
|||||||
type: 'function',
|
type: 'function',
|
||||||
function: {
|
function: {
|
||||||
name: 'write_file',
|
name: 'write_file',
|
||||||
description: 'Write content to a local file. Creates parent directories automatically. Overwrites existing files. Max 5MB content.',
|
description: 'Write content to a local file. Creates parent directories automatically. Supports text (utf-8/latin1) and binary (base64) modes. Use mode="append" to add to existing file instead of overwriting. Max 10MB (overwrite) or 5MB (append). Returns file stats including overwrite info.',
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['path', 'content'],
|
required: ['path', 'content'],
|
||||||
properties: {
|
properties: {
|
||||||
path: { type: 'string', description: 'The file path to write to.' },
|
path: { type: 'string', description: 'The file path to write to. Absolute or relative to workspace.' },
|
||||||
content: { type: 'string', description: 'The content to write.' }
|
content: { type: 'string', description: 'The content to write. For binary files, pass base64-encoded string with encoding="base64".' },
|
||||||
|
encoding: { type: 'string', enum: ['utf-8', 'latin1', 'base64'], description: 'File encoding. Use "base64" to write binary content (images, PDFs, etc). Default: utf-8.' },
|
||||||
|
mode: { type: 'string', enum: ['overwrite', 'append'], description: 'Write mode. overwrite = replace file, append = add to end. Default: overwrite.' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user