Skip to content

Commit 8063e0b

Browse files
authored
refactor(tool): convert lsp tool internals to Effect (#21806)
1 parent 157c5d7 commit 8063e0b

2 files changed

Lines changed: 64 additions & 70 deletions

File tree

packages/opencode/src/tool/lsp.ts

Lines changed: 62 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import z from "zod"
2+
import { Effect } from "effect"
23
import { Tool } from "./tool"
34
import path from "path"
45
import { LSP } from "../lsp"
56
import DESCRIPTION from "./lsp.txt"
67
import { Instance } from "../project/instance"
78
import { pathToFileURL } from "url"
8-
import { assertExternalDirectory } from "./external-directory"
9-
import { Filesystem } from "../util/filesystem"
9+
import { assertExternalDirectoryEffect } from "./external-directory"
10+
import { AppFileSystem } from "../filesystem"
1011

1112
const operations = [
1213
"goToDefinition",
@@ -20,78 +21,70 @@ const operations = [
2021
"outgoingCalls",
2122
] as const
2223

23-
export const LspTool = Tool.define("lsp", {
24-
description: DESCRIPTION,
25-
parameters: z.object({
26-
operation: z.enum(operations).describe("The LSP operation to perform"),
27-
filePath: z.string().describe("The absolute or relative path to the file"),
28-
line: z.number().int().min(1).describe("The line number (1-based, as shown in editors)"),
29-
character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"),
30-
}),
31-
execute: async (args, ctx) => {
32-
const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
33-
await assertExternalDirectory(ctx, file)
34-
35-
await ctx.ask({
36-
permission: "lsp",
37-
patterns: ["*"],
38-
always: ["*"],
39-
metadata: {},
40-
})
41-
const uri = pathToFileURL(file).href
42-
const position = {
43-
file,
44-
line: args.line - 1,
45-
character: args.character - 1,
46-
}
24+
export const LspTool = Tool.defineEffect(
25+
"lsp",
26+
Effect.gen(function* () {
27+
const lsp = yield* LSP.Service
28+
const fs = yield* AppFileSystem.Service
4729

48-
const relPath = path.relative(Instance.worktree, file)
49-
const title = `${args.operation} ${relPath}:${args.line}:${args.character}`
30+
return {
31+
description: DESCRIPTION,
32+
parameters: z.object({
33+
operation: z.enum(operations).describe("The LSP operation to perform"),
34+
filePath: z.string().describe("The absolute or relative path to the file"),
35+
line: z.number().int().min(1).describe("The line number (1-based, as shown in editors)"),
36+
character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"),
37+
}),
38+
execute: (args: { operation: (typeof operations)[number]; filePath: string; line: number; character: number }, ctx: Tool.Context) =>
39+
Effect.gen(function* () {
40+
const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
41+
yield* assertExternalDirectoryEffect(ctx, file)
42+
yield* Effect.promise(() =>
43+
ctx.ask({ permission: "lsp", patterns: ["*"], always: ["*"], metadata: {} }),
44+
)
5045

51-
const exists = await Filesystem.exists(file)
52-
if (!exists) {
53-
throw new Error(`File not found: ${file}`)
54-
}
46+
const uri = pathToFileURL(file).href
47+
const position = { file, line: args.line - 1, character: args.character - 1 }
48+
const relPath = path.relative(Instance.worktree, file)
49+
const title = `${args.operation} ${relPath}:${args.line}:${args.character}`
5550

56-
const available = await LSP.hasClients(file)
57-
if (!available) {
58-
throw new Error("No LSP server available for this file type.")
59-
}
51+
const exists = yield* fs.existsSafe(file)
52+
if (!exists) throw new Error(`File not found: ${file}`)
6053

61-
await LSP.touchFile(file, true)
54+
const available = yield* lsp.hasClients(file)
55+
if (!available) throw new Error("No LSP server available for this file type.")
6256

63-
const result: unknown[] = await (async () => {
64-
switch (args.operation) {
65-
case "goToDefinition":
66-
return LSP.definition(position)
67-
case "findReferences":
68-
return LSP.references(position)
69-
case "hover":
70-
return LSP.hover(position)
71-
case "documentSymbol":
72-
return LSP.documentSymbol(uri)
73-
case "workspaceSymbol":
74-
return LSP.workspaceSymbol("")
75-
case "goToImplementation":
76-
return LSP.implementation(position)
77-
case "prepareCallHierarchy":
78-
return LSP.prepareCallHierarchy(position)
79-
case "incomingCalls":
80-
return LSP.incomingCalls(position)
81-
case "outgoingCalls":
82-
return LSP.outgoingCalls(position)
83-
}
84-
})()
57+
yield* lsp.touchFile(file, true)
8558

86-
const output = (() => {
87-
if (result.length === 0) return `No results found for ${args.operation}`
88-
return JSON.stringify(result, null, 2)
89-
})()
59+
const result: unknown[] = yield* (() => {
60+
switch (args.operation) {
61+
case "goToDefinition":
62+
return lsp.definition(position)
63+
case "findReferences":
64+
return lsp.references(position)
65+
case "hover":
66+
return lsp.hover(position)
67+
case "documentSymbol":
68+
return lsp.documentSymbol(uri)
69+
case "workspaceSymbol":
70+
return lsp.workspaceSymbol("")
71+
case "goToImplementation":
72+
return lsp.implementation(position)
73+
case "prepareCallHierarchy":
74+
return lsp.prepareCallHierarchy(position)
75+
case "incomingCalls":
76+
return lsp.incomingCalls(position)
77+
case "outgoingCalls":
78+
return lsp.outgoingCalls(position)
79+
}
80+
})()
9081

91-
return {
92-
title,
93-
metadata: { result },
94-
output,
82+
return {
83+
title,
84+
metadata: { result },
85+
output: result.length === 0 ? `No results found for ${args.operation}` : JSON.stringify(result, null, 2),
86+
}
87+
}).pipe(Effect.runPromise),
9588
}
96-
},
97-
})
89+
}),
90+
)

packages/opencode/src/tool/registry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export namespace ToolRegistry {
9292
const read = yield* ReadTool
9393
const question = yield* QuestionTool
9494
const todo = yield* TodoWriteTool
95+
const lsptool = yield* LspTool
9596

9697
const state = yield* InstanceState.make<State>(
9798
Effect.fn("ToolRegistry.state")(function* (ctx) {
@@ -164,7 +165,7 @@ export namespace ToolRegistry {
164165
skill: Tool.init(SkillTool),
165166
patch: Tool.init(ApplyPatchTool),
166167
question: Tool.init(question),
167-
lsp: Tool.init(LspTool),
168+
lsp: Tool.init(lsptool),
168169
plan: Tool.init(PlanExitTool),
169170
})
170171

0 commit comments

Comments
 (0)