Files
locode/src/tools/writeFile.ts
T
kim 506599020c feat: 15 upgrades — system prompt, multiline input, /undo, thinking tokens, and more
Major upgrades (15 items):

1. System prompt overhaul: tool usage guide, local model guidance, error recovery,
   safety guidelines, read/mutating tool categorization (12 tests)
2. Multiline input: Shift+Enter for newlines, bracketed paste support,
   Enter submits single-line, Ctrl+Enter forces submit on multiline
3. /undo command: removes last user turn + assistant/tool messages,
   preserves filesystem changes, shows count (7 tests)
4. Thinking/reasoning token support: capture `reasoning_content` from
   DeepSeek/QwQ-style models, render as dimmed collapsible block
5. @ mention improvements: fuzzy path matching (character-order matching),
   5-minute cache TTL for file list refresh
6. readFile binary guard: 10MB size limit, extension-based binary detection,
   null-byte heuristic, CRLF line-ending fix
7. writeFile atomic write: temp file + rename pattern, explicit ENOENT check
8. bash string accumulation: O(n²) → O(n) with array chunks
9. estimateTokens regex speedup: per-character loop → regex bulk counting
10. handleCompletedMessage: `any` → `Record<string, unknown>`
11. Session restore: persist `allowedTools` in SessionRecord, restore on resume
12. Context window parallel detection: Promise.allSettled for Ollama + LM Studio
13. Streaming text accumulation: O(n²) → O(n) with text chunks array
14. /dashboard & context bar already implemented (no changes needed)
15. Cost estimation already implemented (no changes needed)

Tests: 322 passing across 42 files, typecheck clean, build 289.56 KB
2026-08-21 17:07:49 +09:00

54 lines
1.9 KiB
TypeScript

import { createPatch } from "diff";
import { randomUUID } from "node:crypto";
import { mkdir, readFile as fsReadFile, rename, unlink, writeFile as fsWriteFile } from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { resolveWithinCwd } from "./pathGuard.js";
import type { ToolDef } from "./types.js";
const schema = z.object({
path: z.string().describe("File path to write, relative to the working directory or absolute."),
content: z.string().describe("Full file content to write."),
});
async function readExisting(resolved: string): Promise<string | null> {
try {
return await fsReadFile(resolved, "utf-8");
} catch (err: any) {
if (err?.code === "ENOENT") return null;
throw err;
}
}
export const writeFileTool: ToolDef<z.infer<typeof schema>> = {
name: "write_file",
description: "Create or overwrite a file with the given content. Use for new files or full rewrites. For small changes to an existing file, prefer edit_file instead.",
schema,
mutating: true,
preview: async ({ path: filePath, content }, ctx) => {
let resolved: string;
try {
resolved = resolveWithinCwd(ctx.cwd, filePath);
} catch (err) {
return (err as Error).message;
}
const existing = await readExisting(resolved);
if (existing === null) {
return `Create new file ${resolved} (${content.length} chars)`;
}
return createPatch(resolved, existing, content, "", "");
},
handler: async ({ path: filePath, content }, ctx) => {
const resolved = resolveWithinCwd(ctx.cwd, filePath);
await mkdir(path.dirname(resolved), { recursive: true });
const tmpPath = resolved + ".tmp-" + randomUUID();
try {
await fsWriteFile(tmpPath, content, "utf-8");
await rename(tmpPath, resolved);
} catch (err) {
try { await unlink(tmpPath); } catch {}
throw err;
}
return { path: resolved, bytesWritten: Buffer.byteLength(content, "utf-8") };
},
};