Skip to content

Commit b139bc2

Browse files
authored
refactor(tool): convert write tool to Tool.defineEffect (#21901)
1 parent 378b8ca commit b139bc2

3 files changed

Lines changed: 255 additions & 349 deletions

File tree

packages/opencode/src/tool/registry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export namespace ToolRegistry {
110110
const bash = yield* BashTool
111111
const codesearch = yield* CodeSearchTool
112112
const globtool = yield* GlobTool
113+
const writetool = yield* WriteTool
113114

114115
const state = yield* InstanceState.make<State>(
115116
Effect.fn("ToolRegistry.state")(function* (ctx) {
@@ -173,7 +174,7 @@ export namespace ToolRegistry {
173174
glob: Tool.init(globtool),
174175
grep: Tool.init(GrepTool),
175176
edit: Tool.init(EditTool),
176-
write: Tool.init(WriteTool),
177+
write: Tool.init(writetool),
177178
task: Tool.init(task),
178179
fetch: Tool.init(webfetch),
179180
todo: Tool.init(todo),
Lines changed: 76 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import z from "zod"
22
import * as path from "path"
3+
import { Effect } from "effect"
34
import { Tool } from "./tool"
45
import { LSP } from "../lsp"
56
import { createTwoFilesPatch } from "diff"
@@ -9,76 +10,90 @@ import { File } from "../file"
910
import { FileWatcher } from "../file/watcher"
1011
import { Format } from "../format"
1112
import { FileTime } from "../file/time"
12-
import { Filesystem } from "../util/filesystem"
13+
import { AppFileSystem } from "../filesystem"
1314
import { Instance } from "../project/instance"
1415
import { trimDiff } from "./edit"
15-
import { assertExternalDirectory } from "./external-directory"
16+
import { assertExternalDirectoryEffect } from "./external-directory"
1617

1718
const MAX_DIAGNOSTICS_PER_FILE = 20
1819
const MAX_PROJECT_DIAGNOSTICS_FILES = 5
1920

20-
export const WriteTool = Tool.define("write", {
21-
description: DESCRIPTION,
22-
parameters: z.object({
23-
content: z.string().describe("The content to write to the file"),
24-
filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
25-
}),
26-
async execute(params, ctx) {
27-
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
28-
await assertExternalDirectory(ctx, filepath)
21+
export const WriteTool = Tool.defineEffect(
22+
"write",
23+
Effect.gen(function* () {
24+
const lsp = yield* LSP.Service
25+
const fs = yield* AppFileSystem.Service
26+
const filetime = yield* FileTime.Service
2927

30-
const exists = await Filesystem.exists(filepath)
31-
const contentOld = exists ? await Filesystem.readText(filepath) : ""
32-
if (exists) await FileTime.assert(ctx.sessionID, filepath)
28+
return {
29+
description: DESCRIPTION,
30+
parameters: z.object({
31+
content: z.string().describe("The content to write to the file"),
32+
filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
33+
}),
34+
execute: (params: { content: string; filePath: string }, ctx: Tool.Context) =>
35+
Effect.gen(function* () {
36+
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
37+
yield* assertExternalDirectoryEffect(ctx, filepath)
3338

34-
const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, params.content))
35-
await ctx.ask({
36-
permission: "edit",
37-
patterns: [path.relative(Instance.worktree, filepath)],
38-
always: ["*"],
39-
metadata: {
40-
filepath,
41-
diff,
42-
},
43-
})
39+
const exists = yield* fs.existsSafe(filepath)
40+
const contentOld = exists ? yield* fs.readFileString(filepath) : ""
41+
if (exists) yield* filetime.assert(ctx.sessionID, filepath)
4442

45-
await Filesystem.write(filepath, params.content)
46-
await Format.file(filepath)
47-
Bus.publish(File.Event.Edited, { file: filepath })
48-
await Bus.publish(FileWatcher.Event.Updated, {
49-
file: filepath,
50-
event: exists ? "change" : "add",
51-
})
52-
await FileTime.read(ctx.sessionID, filepath)
43+
const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, params.content))
44+
yield* Effect.promise(() =>
45+
ctx.ask({
46+
permission: "edit",
47+
patterns: [path.relative(Instance.worktree, filepath)],
48+
always: ["*"],
49+
metadata: {
50+
filepath,
51+
diff,
52+
},
53+
}),
54+
)
5355

54-
let output = "Wrote file successfully."
55-
await LSP.touchFile(filepath, true)
56-
const diagnostics = await LSP.diagnostics()
57-
const normalizedFilepath = Filesystem.normalizePath(filepath)
58-
let projectDiagnosticsCount = 0
59-
for (const [file, issues] of Object.entries(diagnostics)) {
60-
const errors = issues.filter((item) => item.severity === 1)
61-
if (errors.length === 0) continue
62-
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
63-
const suffix =
64-
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
65-
if (file === normalizedFilepath) {
66-
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>`
67-
continue
68-
}
69-
if (projectDiagnosticsCount >= MAX_PROJECT_DIAGNOSTICS_FILES) continue
70-
projectDiagnosticsCount++
71-
output += `\n\nLSP errors detected in other files:\n<diagnostics file="${file}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
72-
}
56+
yield* fs.writeWithDirs(filepath, params.content)
57+
yield* Effect.promise(() => Format.file(filepath))
58+
Bus.publish(File.Event.Edited, { file: filepath })
59+
yield* Effect.promise(() =>
60+
Bus.publish(FileWatcher.Event.Updated, {
61+
file: filepath,
62+
event: exists ? "change" : "add",
63+
}),
64+
)
65+
yield* filetime.read(ctx.sessionID, filepath)
7366

74-
return {
75-
title: path.relative(Instance.worktree, filepath),
76-
metadata: {
77-
diagnostics,
78-
filepath,
79-
exists: exists,
80-
},
81-
output,
67+
let output = "Wrote file successfully."
68+
yield* lsp.touchFile(filepath, true)
69+
const diagnostics = yield* lsp.diagnostics()
70+
const normalizedFilepath = AppFileSystem.normalizePath(filepath)
71+
let projectDiagnosticsCount = 0
72+
for (const [file, issues] of Object.entries(diagnostics)) {
73+
const errors = issues.filter((item) => item.severity === 1)
74+
if (errors.length === 0) continue
75+
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
76+
const suffix =
77+
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
78+
if (file === normalizedFilepath) {
79+
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>`
80+
continue
81+
}
82+
if (projectDiagnosticsCount >= MAX_PROJECT_DIAGNOSTICS_FILES) continue
83+
projectDiagnosticsCount++
84+
output += `\n\nLSP errors detected in other files:\n<diagnostics file="${file}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
85+
}
86+
87+
return {
88+
title: path.relative(Instance.worktree, filepath),
89+
metadata: {
90+
diagnostics,
91+
filepath,
92+
exists: exists,
93+
},
94+
output,
95+
}
96+
}).pipe(Effect.orDie, Effect.runPromise),
8297
}
83-
},
84-
})
98+
}),
99+
)

0 commit comments

Comments
 (0)