write.ts 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import z from "zod"
  2. import * as path from "path"
  3. import { Tool } from "./tool"
  4. import { LSP } from "../lsp"
  5. import { createTwoFilesPatch } from "diff"
  6. import DESCRIPTION from "./write.txt"
  7. import { Bus } from "../bus"
  8. import { File } from "../file"
  9. import { FileWatcher } from "../file/watcher"
  10. import { FileTime } from "../file/time"
  11. import { Filesystem } from "../util/filesystem"
  12. import { Instance } from "../project/instance"
  13. import { trimDiff } from "./edit"
  14. import { assertExternalDirectory } from "./external-directory"
  15. const MAX_DIAGNOSTICS_PER_FILE = 20
  16. const MAX_PROJECT_DIAGNOSTICS_FILES = 5
  17. export const WriteTool = Tool.define("write", {
  18. description: DESCRIPTION,
  19. parameters: z.object({
  20. content: z.string().describe("The content to write to the file"),
  21. filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
  22. }),
  23. async execute(params, ctx) {
  24. const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
  25. await assertExternalDirectory(ctx, filepath)
  26. const file = Bun.file(filepath)
  27. const exists = await file.exists()
  28. const contentOld = exists ? await file.text() : ""
  29. if (exists) await FileTime.assert(ctx.sessionID, filepath)
  30. const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, params.content))
  31. await ctx.ask({
  32. permission: "edit",
  33. patterns: [path.relative(Instance.worktree, filepath)],
  34. always: ["*"],
  35. metadata: {
  36. filepath,
  37. diff,
  38. },
  39. })
  40. await Bun.write(filepath, params.content)
  41. await Bus.publish(File.Event.Edited, {
  42. file: filepath,
  43. })
  44. await Bus.publish(FileWatcher.Event.Updated, {
  45. file: filepath,
  46. event: exists ? "change" : "add",
  47. })
  48. FileTime.read(ctx.sessionID, filepath)
  49. let output = "Wrote file successfully."
  50. await LSP.touchFile(filepath, true)
  51. const diagnostics = await LSP.diagnostics()
  52. const normalizedFilepath = Filesystem.normalizePath(filepath)
  53. let projectDiagnosticsCount = 0
  54. for (const [file, issues] of Object.entries(diagnostics)) {
  55. const errors = issues.filter((item) => item.severity === 1)
  56. if (errors.length === 0) continue
  57. const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
  58. const suffix =
  59. errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
  60. if (file === normalizedFilepath) {
  61. output += `\n\nLSP errors detected in this file, please fix:\n<diagnostics file="${filepath}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
  62. continue
  63. }
  64. if (projectDiagnosticsCount >= MAX_PROJECT_DIAGNOSTICS_FILES) continue
  65. projectDiagnosticsCount++
  66. output += `\n\nLSP errors detected in other files:\n<diagnostics file="${file}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
  67. }
  68. return {
  69. title: path.relative(Instance.worktree, filepath),
  70. metadata: {
  71. diagnostics,
  72. filepath,
  73. exists: exists,
  74. },
  75. output,
  76. }
  77. },
  78. })