write.ts 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import z from "zod/v4"
  2. import * as path from "path"
  3. import { Tool } from "./tool"
  4. import { LSP } from "../lsp"
  5. import { Permission } from "../permission"
  6. import DESCRIPTION from "./write.txt"
  7. import { Bus } from "../bus"
  8. import { File } from "../file"
  9. import { FileTime } from "../file/time"
  10. import { Filesystem } from "../util/filesystem"
  11. import { Instance } from "../project/instance"
  12. import { Agent } from "../agent/agent"
  13. import { createTwoFilesPatch } from "diff"
  14. import { trimDiff } from "./edit"
  15. export const WriteTool = Tool.define("write", {
  16. description: DESCRIPTION,
  17. parameters: z.object({
  18. filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
  19. content: z.string().describe("The content to write to the file"),
  20. }),
  21. async execute(params, ctx) {
  22. const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
  23. if (!Filesystem.contains(Instance.directory, filepath)) {
  24. throw new Error(`File ${filepath} is not in the current working directory`)
  25. }
  26. const file = Bun.file(filepath)
  27. const exists = await file.exists()
  28. if (exists) await FileTime.assert(ctx.sessionID, filepath)
  29. let oldContent = ""
  30. let diff = ""
  31. if (exists) {
  32. oldContent = await file.text()
  33. }
  34. const agent = await Agent.get(ctx.agent)
  35. if (agent.permission.edit === "ask")
  36. await Permission.ask({
  37. type: "write",
  38. sessionID: ctx.sessionID,
  39. messageID: ctx.messageID,
  40. callID: ctx.callID,
  41. title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
  42. metadata: {
  43. filePath: filepath,
  44. content: params.content,
  45. exists,
  46. },
  47. })
  48. await Bun.write(filepath, params.content)
  49. await Bus.publish(File.Event.Edited, {
  50. file: filepath,
  51. })
  52. FileTime.read(ctx.sessionID, filepath)
  53. // Generate diff for the write operation
  54. diff = trimDiff(createTwoFilesPatch(filepath, filepath, oldContent, params.content))
  55. let output = ""
  56. await LSP.touchFile(filepath, true)
  57. const diagnostics = await LSP.diagnostics()
  58. for (const [file, issues] of Object.entries(diagnostics)) {
  59. if (issues.length === 0) continue
  60. if (file === filepath) {
  61. output += `\nThis file has errors, please fix\n<file_diagnostics>\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</file_diagnostics>\n`
  62. continue
  63. }
  64. output += `\n<project_diagnostics>\n${file}\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</project_diagnostics>\n`
  65. }
  66. return {
  67. title: path.relative(Instance.worktree, filepath),
  68. metadata: {
  69. diagnostics,
  70. filepath,
  71. exists: exists,
  72. },
  73. output,
  74. }
  75. },
  76. })