opencode_offline/packages/opencode/src/tool/edit.ts

113 lines
3.4 KiB
TypeScript
Raw Normal View History

2025-06-01 02:41:00 +08:00
import { z } from "zod"
import * as path from "path"
import { Tool } from "./tool"
import { FileTimes } from "./util/file-times"
import { LSP } from "../lsp"
2025-06-05 06:32:10 +08:00
import { createTwoFilesPatch } from "diff"
2025-06-03 07:51:26 +08:00
import { Permission } from "../permission"
2025-06-05 01:12:13 +08:00
import DESCRIPTION from "./edit.txt"
2025-05-20 07:29:38 +08:00
2025-06-01 05:12:16 +08:00
export const EditTool = Tool.define({
id: "opencode.edit",
2025-05-20 07:29:38 +08:00
description: DESCRIPTION,
parameters: z.object({
filePath: z.string().describe("The absolute path to the file to modify"),
oldString: z.string().describe("The text to replace"),
2025-06-05 01:12:13 +08:00
newString: z
.string()
.describe(
"The text to replace it with (must be different from old_string)",
),
replaceAll: z
.boolean()
.default(false)
.describe("Replace all occurences of old_string (default false)"),
2025-05-20 07:29:38 +08:00
}),
2025-06-03 07:51:26 +08:00
async execute(params, ctx) {
if (!params.filePath) {
2025-06-01 02:41:00 +08:00
throw new Error("filePath is required")
2025-05-20 07:29:38 +08:00
}
2025-06-05 01:12:13 +08:00
const filepath = path.isAbsolute(params.filePath)
? params.filePath
: path.join(process.cwd(), params.filePath)
2025-05-20 07:29:38 +08:00
2025-06-03 07:51:26 +08:00
await Permission.ask({
id: "opencode.edit",
sessionID: ctx.sessionID,
2025-06-05 01:12:13 +08:00
title: "Edit this file: " + filepath,
2025-06-03 07:51:26 +08:00
metadata: {
2025-06-05 01:12:13 +08:00
filePath: filepath,
2025-06-03 07:51:26 +08:00
oldString: params.oldString,
newString: params.newString,
},
})
2025-06-01 06:42:43 +08:00
let contentOld = ""
let contentNew = ""
2025-05-21 10:00:00 +08:00
await (async () => {
if (params.oldString === "") {
2025-06-01 06:42:43 +08:00
contentNew = params.newString
2025-06-05 01:12:13 +08:00
await Bun.write(filepath, params.newString)
2025-06-01 02:41:00 +08:00
return
2025-05-21 10:00:00 +08:00
}
2025-05-20 07:29:38 +08:00
2025-06-05 01:12:13 +08:00
const file = Bun.file(filepath)
if (!(await file.exists())) throw new Error(`File ${filepath} not found`)
2025-06-01 02:41:00 +08:00
const stats = await file.stat()
2025-05-21 10:00:00 +08:00
if (stats.isDirectory())
2025-06-05 01:12:13 +08:00
throw new Error(`Path is a directory, not a file: ${filepath}`)
2025-06-05 01:33:25 +08:00
await FileTimes.assert(ctx.sessionID, filepath)
2025-06-01 06:42:43 +08:00
contentOld = await file.text()
const index = contentOld.indexOf(params.oldString)
2025-05-21 10:00:00 +08:00
if (index === -1)
throw new Error(
`oldString not found in file. Make sure it matches exactly, including whitespace and line breaks`,
2025-06-01 02:41:00 +08:00
)
2025-05-21 10:00:00 +08:00
2025-06-05 01:12:13 +08:00
if (params.replaceAll) {
contentNew = contentOld.replaceAll(params.oldString, params.newString)
}
if (!params.replaceAll) {
const lastIndex = contentOld.lastIndexOf(params.oldString)
if (index !== lastIndex)
throw new Error(
`oldString appears multiple times in the file. Please provide more context to ensure a unique match`,
)
contentNew =
contentOld.substring(0, index) +
params.newString +
contentOld.substring(index + params.oldString.length)
}
2025-05-21 10:00:00 +08:00
2025-06-01 06:42:43 +08:00
await file.write(contentNew)
2025-06-01 02:41:00 +08:00
})()
2025-05-21 10:00:00 +08:00
2025-06-05 01:12:13 +08:00
const diff = createTwoFilesPatch(filepath, filepath, contentOld, contentNew)
2025-06-01 06:42:43 +08:00
2025-06-05 01:12:13 +08:00
FileTimes.read(ctx.sessionID, filepath)
2025-05-21 10:00:00 +08:00
2025-06-01 02:41:00 +08:00
let output = ""
2025-06-05 01:12:13 +08:00
await LSP.file(filepath)
2025-06-01 02:41:00 +08:00
const diagnostics = await LSP.diagnostics()
2025-05-27 05:15:41 +08:00
for (const [file, issues] of Object.entries(diagnostics)) {
2025-06-01 02:41:00 +08:00
if (issues.length === 0) continue
2025-06-05 01:12:13 +08:00
if (file === filepath) {
2025-06-01 02:41:00 +08:00
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</file_diagnostics>\n`
continue
2025-05-21 10:00:00 +08:00
}
2025-06-01 02:41:00 +08:00
output += `\n<project_diagnostics>\n${file}\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</project_diagnostics>\n`
2025-05-20 07:29:38 +08:00
}
2025-05-20 23:11:06 +08:00
return {
2025-05-22 04:35:33 +08:00
metadata: {
diagnostics,
2025-06-04 01:44:46 +08:00
diff,
2025-05-22 04:35:33 +08:00
},
2025-05-21 10:00:00 +08:00
output,
2025-06-01 02:41:00 +08:00
}
2025-05-20 07:29:38 +08:00
},
2025-06-01 02:41:00 +08:00
})