diff --git a/src/tools/diff.ts b/src/tools/diff.ts deleted file mode 100644 index b9402385..00000000 --- a/src/tools/diff.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { tool } from "@langchain/core/tools"; -import { z } from "zod"; -import { applyPatch } from "diff"; - -const applyDiffToolSchema = z.object({ - diff: z.string().describe("The diff to apply"), - file_path: z.string().describe("The file path to apply the diff to"), -}); - -export const applyDiffTool = tool( - (input) => { - const { diff, file_path } = input; - const res = applyPatch(file_path, diff); - return `Successfully applied diff to \`${file_path}\``; - }, - { - name: "apply_diff", - description: "Applies a diff to a file.", - schema: applyDiffToolSchema, - }, -); diff --git a/src/tools/write-file.ts b/src/tools/write-file.ts new file mode 100644 index 00000000..aee83361 --- /dev/null +++ b/src/tools/write-file.ts @@ -0,0 +1,52 @@ +import { tool } from "@langchain/core/tools"; +import { z } from "zod"; +import { applyPatch } from "diff"; +import { GraphConfig } from "../types.js"; +import { Sandbox } from "@e2b/code-interpreter"; +import { readFile, writeFile } from "../utils/read-write.js"; + +const writeFileToolSchema = z.object({ + diff: z.string().describe("The diff to apply"), + file_path: z.string().describe("The file path to apply the diff to"), +}); + +export const writeFileTool = tool( + async (input, config: GraphConfig) => { + const { diff, file_path } = input; + const sessionId = config.configurable?.sandbox_session_id; + if (!sessionId) { + return "FAILED TO RUN COMMAND: No sandbox session ID provided"; + } + + const sandbox = await Sandbox.connect(sessionId); + + const { success: readFileSuccess, output: readFileOutput } = await readFile( + sandbox, + file_path, + ); + if (!readFileSuccess) { + return readFileOutput; + } + + const patchedContent = applyPatch(readFileOutput, diff); + + if (patchedContent === false) { + return `FAILED TO APPLY PATCH: The diff could not be applied to file '${file_path}'. This may be due to an invalid diff format or conflicting changes with the file's current content. Original content length: ${readFileOutput.length}, Diff: ${diff.substring(0, 100)}...`; + } + + // TODO: Should we be committing every time we apply a diff? + const { success: writeFileSuccess, output: writeFileOutput } = + await writeFile(sandbox, file_path, patchedContent); + if (!writeFileSuccess) { + return writeFileOutput; + } + + return `Successfully applied diff to \`${file_path}\` and saved changes.`; + }, + { + name: "write_file", + description: + "Writes a file given a file path and diff content. Can be used to create or update files.", + schema: writeFileToolSchema, + }, +); diff --git a/src/utils/read-write.ts b/src/utils/read-write.ts new file mode 100644 index 00000000..8ef3922f --- /dev/null +++ b/src/utils/read-write.ts @@ -0,0 +1,84 @@ +import { Sandbox } from "@e2b/code-interpreter"; + +export async function readFile( + sandbox: Sandbox, + filePath: string, +): Promise<{ + success: boolean; + output: string; +}> { + try { + const readOutput = await sandbox.commands.run(`cat "${filePath}"`); + if (readOutput.exitCode !== 0) { + console.error( + `Error reading file '${filePath}' from sandbox via cat:`, + readOutput, + ); + return { + success: false, + output: `FAILED TO READ FILE from sandbox '${filePath}'. Exit code: ${readOutput.exitCode}.\nStderr: ${readOutput.stderr}.\nStdout: ${readOutput.stdout}`, + }; + } + if (readOutput.stderr) { + console.warn( + `Stderr while reading file '${filePath}' from sandbox via cat: ${readOutput.stderr}`, + ); + } + return { + success: true, + output: readOutput.stdout, + }; + } catch (e: any) { + console.error( + `Exception while trying to read file '${filePath}' from sandbox via cat:`, + e, + ); + return { + success: false, + output: `FAILED TO EXECUTE READ COMMAND for sandbox '${filePath}'. Error: ${(e as Error).message || String(e)}`, + }; + } +} + +export async function writeFile( + sandbox: Sandbox, + filePath: string, + content: string, +): Promise<{ + success: boolean; + output: string; +}> { + try { + const writeCommand = `printf '%s' '${content}' > "${filePath}"`; + const writeOutput = await sandbox.commands.run(writeCommand); + + if (writeOutput.exitCode !== 0) { + console.error( + `Error writing file '${filePath}' to sandbox via printf:`, + writeOutput, + ); + return { + success: false, + output: `FAILED TO WRITE FILE to sandbox '${filePath}'. Exit code: ${writeOutput.exitCode}. Stderr: ${writeOutput.stderr}. Stdout: ${writeOutput.stdout}`, + }; + } + if (writeOutput.stderr) { + console.warn( + `Stderr while writing file '${filePath}' to sandbox via printf: ${writeOutput.stderr}`, + ); + } + return { + success: true, + output: `Successfully wrote file '${filePath}' to sandbox via printf.`, + }; + } catch (e: any) { + console.error( + `Exception while trying to write file '${filePath}' to sandbox via printf:`, + e, + ); + return { + success: false, + output: `FAILED TO EXECUTE WRITE COMMAND for sandbox '${filePath}'. Error: ${(e as Error).message || String(e)}`, + }; + } +}