1import { mkdir, writeFile } from "node:fs/promises";
2import { dirname, join, resolve, sep } from "node:path";
3import { ToolInputError } from "../util/errors.js";
4
5export interface WorkspaceContent {
6 filePath: string;
7 content: string;
8 bytes: number;
9}
10
11function ensureContained(workspacePath: string, targetPath: string): void {
12 const resolved = resolve(workspacePath, targetPath);
13 const root = workspacePath.endsWith(sep) ? workspacePath : `${workspacePath}${sep}`;
14
15 if (resolved !== workspacePath && !resolved.startsWith(root)) {
16 throw new ToolInputError(`Path escapes workspace: ${targetPath}`);
17 }
18}
19
20export async function writeWorkspaceFile(
21 workspacePath: string,
22 relativePath: string,
23 content: string,
24): Promise<WorkspaceContent> {
25 const filePath = join(workspacePath, relativePath);
26 ensureContained(workspacePath, filePath);
27 await mkdir(dirname(filePath), { recursive: true });
28 await writeFile(filePath, content, "utf8");
29
30 return {
31 filePath,
32 content,
33 bytes: Buffer.byteLength(content, "utf8"),
34 };
35}