write.ts 3.7 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 DESCRIPTION from "./write.txt"
  6. import { Bus } from "../bus"
  7. import { File } from "../file"
  8. import { FileTime } from "../file/time"
  9. import { Filesystem } from "../util/filesystem"
  10. import { Instance } from "../project/instance"
  11. import { Agent } from "../agent/agent"
  12. import { PermissionNext } from "@/permission/next"
  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. /* TODO
  25. if (!Filesystem.contains(Instance.directory, filepath)) {
  26. const parentDir = path.dirname(filepath)
  27. if (agent.permission.external_directory === "ask") {
  28. await Permission.ask({
  29. type: "external_directory",
  30. pattern: [parentDir, path.join(parentDir, "*")],
  31. sessionID: ctx.sessionID,
  32. messageID: ctx.messageID,
  33. callID: ctx.callID,
  34. title: `Write file outside working directory: ${filepath}`,
  35. metadata: {
  36. filepath,
  37. parentDir,
  38. },
  39. })
  40. } else if (agent.permission.external_directory === "deny") {
  41. throw new Permission.RejectedError(
  42. ctx.sessionID,
  43. "external_directory",
  44. ctx.callID,
  45. {
  46. filepath: filepath,
  47. parentDir,
  48. },
  49. `File ${filepath} is not in the current working directory`,
  50. )
  51. }
  52. }
  53. */
  54. const file = Bun.file(filepath)
  55. const exists = await file.exists()
  56. if (exists) await FileTime.assert(ctx.sessionID, filepath)
  57. await PermissionNext.ask({
  58. callID: ctx.callID,
  59. permission: "edit",
  60. message: `Create new file ${path.relative(Instance.directory, filepath)}`,
  61. patterns: [path.relative(Instance.worktree, filepath)],
  62. always: ["*"],
  63. sessionID: ctx.sessionID,
  64. metadata: {},
  65. ruleset: agent.permission,
  66. })
  67. await Bun.write(filepath, params.content)
  68. await Bus.publish(File.Event.Edited, {
  69. file: filepath,
  70. })
  71. FileTime.read(ctx.sessionID, filepath)
  72. let output = ""
  73. await LSP.touchFile(filepath, true)
  74. const diagnostics = await LSP.diagnostics()
  75. const normalizedFilepath = Filesystem.normalizePath(filepath)
  76. let projectDiagnosticsCount = 0
  77. for (const [file, issues] of Object.entries(diagnostics)) {
  78. const errors = issues.filter((item) => item.severity === 1)
  79. if (errors.length === 0) continue
  80. const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
  81. const suffix =
  82. errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
  83. if (file === normalizedFilepath) {
  84. output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
  85. continue
  86. }
  87. if (projectDiagnosticsCount >= MAX_PROJECT_DIAGNOSTICS_FILES) continue
  88. projectDiagnosticsCount++
  89. output += `\n<project_diagnostics>\n${file}\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</project_diagnostics>\n`
  90. }
  91. return {
  92. title: path.relative(Instance.worktree, filepath),
  93. metadata: {
  94. diagnostics,
  95. filepath,
  96. exists: exists,
  97. },
  98. output,
  99. }
  100. },
  101. })