From 04f2446337a10b8ece8474c33a42ad77e4ce71c7 Mon Sep 17 00:00:00 2001 From: thzxx Date: Wed, 10 Jun 2026 14:15:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20write=5Ffile=20=E5=85=A8=E9=9D=A2?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=20=E2=80=94=20base64=E5=86=99=E4=BA=8C?= =?UTF-8?q?=E8=BF=9B=E5=88=B6/mode(overwrite+append)/=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E6=8F=90=E7=A4=BA/10MB/=E8=BF=94=E5=9B=9E=E8=A1=8C=E6=95=B0+?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/tool-handlers-fs.ts | 62 ++++++++++++++++++++++---- src/renderer/services/tool-registry.ts | 8 ++-- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/main/tool-handlers-fs.ts b/src/main/tool-handlers-fs.ts index 7f8d950..b779a31 100644 --- a/src/main/tool-handlers-fs.ts +++ b/src/main/tool-handlers-fs.ts @@ -154,33 +154,77 @@ function extToMime(ext: string): string { return map[ext] || 'application/octet-stream'; } -export async function handleWriteFile(params: { path: string; content: string; encoding?: string }): Promise { +export async function handleWriteFile(params: { path: string; content: string; encoding?: string; mode?: string }): Promise { try { const filePath = resolvePath(params.path); const allowed = checkPathAllowed(filePath, 'write'); if (!allowed.ok) return { success: false, error: allowed.reason }; - if (params.content.length > 5 * 1024 * 1024) { - return { success: false, error: '内容过大,最大支持 5MB' }; + const writeMode = params.mode || 'overwrite'; + 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 { - 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 { - created = true; + // 文件不存在,正常 } const dir = path.dirname(filePath); 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 { success: true, path: filePath, - bytesWritten: Buffer.byteLength(params.content, (params.encoding as BufferEncoding) || 'utf-8'), - created + bytesWritten: contentLength, + totalSize: stat.size, + lines, + mode: writeMode, + created: !existed, + ...(existed && writeMode === 'overwrite' && { + overwrote: true, + prevSize, + ...(prevLines > 0 && { prevLines }), + }), + ...(writeMode === 'append' && { + appended: true, + prevSize, + }), }; } catch (err) { return { success: false, error: (err as Error).message }; diff --git a/src/renderer/services/tool-registry.ts b/src/renderer/services/tool-registry.ts index 9659cca..2b0ef60 100644 --- a/src/renderer/services/tool-registry.ts +++ b/src/renderer/services/tool-registry.ts @@ -33,13 +33,15 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [ type: 'function', function: { 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: { type: 'object', required: ['path', 'content'], properties: { - path: { type: 'string', description: 'The file path to write to.' }, - content: { type: 'string', description: 'The content to write.' } + path: { type: 'string', description: 'The file path to write to. Absolute or relative to workspace.' }, + 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.' } } } }