write.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import z from "zod"
  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. const MAX_DIAGNOSTICS_PER_FILE = 20
  14. const MAX_PROJECT_DIAGNOSTICS_FILES = 5
  15. export const WriteTool = Tool.define("write", {
  16. description: DESCRIPTION,
  17. parameters: z.object({
  18. content: z.string().describe("The content to write to the file"),
  19. filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
  20. }),
  21. async execute(params, ctx) {
  22. const agent = await Agent.get(ctx.agent)
  23. const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
  24. if (!Filesystem.contains(Instance.directory, filepath)) {
  25. const parentDir = path.dirname(filepath)
  26. if (agent.permission.external_directory === "ask") {
  27. await Permission.ask({
  28. type: "external_directory",
  29. pattern: [parentDir, path.join(parentDir, "*")],
  30. sessionID: ctx.sessionID,
  31. messageID: ctx.messageID,
  32. callID: ctx.callID,
  33. title: `Write file outside working directory: ${filepath}`,
  34. metadata: {
  35. filepath,
  36. parentDir,
  37. },
  38. })
  39. } else if (agent.permission.external_directory === "deny") {
  40. throw new Permission.RejectedError(
  41. ctx.sessionID,
  42. "external_directory",
  43. ctx.callID,
  44. {
  45. filepath: filepath,
  46. parentDir,
  47. },
  48. `File ${filepath} is not in the current working directory`,
  49. )
  50. }
  51. }
  52. const file = Bun.file(filepath)
  53. const exists = await file.exists()
  54. if (exists) await FileTime.assert(ctx.sessionID, filepath)
  55. if (agent.permission.edit === "ask")
  56. await Permission.ask({
  57. type: "write",
  58. sessionID: ctx.sessionID,
  59. messageID: ctx.messageID,
  60. callID: ctx.callID,
  61. title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
  62. metadata: {
  63. filePath: filepath,
  64. content: params.content,
  65. exists,
  66. },
  67. })
  68. await Bun.write(filepath, params.content)
  69. await Bus.publish(File.Event.Edited, {
  70. file: filepath,
  71. })
  72. FileTime.read(ctx.sessionID, filepath)
  73. let output = ""
  74. await LSP.touchFile(filepath, true)
  75. const diagnostics = await LSP.diagnostics()
  76. const normalizedFilepath = Filesystem.normalizePath(filepath)
  77. let projectDiagnosticsCount = 0
  78. for (const [file, issues] of Object.entries(diagnostics)) {
  79. const errors = issues.filter((item) => item.severity === 1)
  80. if (errors.length === 0) continue
  81. const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
  82. const suffix =
  83. errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
  84. if (file === normalizedFilepath) {
  85. output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
  86. continue
  87. }
  88. if (projectDiagnosticsCount >= MAX_PROJECT_DIAGNOSTICS_FILES) continue
  89. projectDiagnosticsCount++
  90. output += `\n<project_diagnostics>\n${file}\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</project_diagnostics>\n`
  91. }
  92. return {
  93. title: path.relative(Instance.worktree, filepath),
  94. metadata: {
  95. diagnostics,
  96. filepath,
  97. exists: exists,
  98. },
  99. output,
  100. }
  101. },
  102. })