summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-10 10:07:19 -0400
committerGitHub <[email protected]>2026-04-10 10:07:19 -0400
commit8063e0b5c67de4622c3c77867c84734260f220fe (patch)
treed4d4f78ddeb3f582a9a1986de850f537989a1c51
parent157c5d77f80571906ad3b5ac5527fa62b84adf23 (diff)
downloadopencode-8063e0b5c67de4622c3c77867c84734260f220fe.tar.gz
opencode-8063e0b5c67de4622c3c77867c84734260f220fe.zip
refactor(tool): convert lsp tool internals to Effect (#21806)
-rw-r--r--packages/opencode/src/tool/lsp.ts131
-rw-r--r--packages/opencode/src/tool/registry.ts3
2 files changed, 64 insertions, 70 deletions
diff --git a/packages/opencode/src/tool/lsp.ts b/packages/opencode/src/tool/lsp.ts
index 52aef0f9e..f58dc82d4 100644
--- a/packages/opencode/src/tool/lsp.ts
+++ b/packages/opencode/src/tool/lsp.ts
@@ -1,12 +1,13 @@
import z from "zod"
+import { Effect } from "effect"
import { Tool } from "./tool"
import path from "path"
import { LSP } from "../lsp"
import DESCRIPTION from "./lsp.txt"
import { Instance } from "../project/instance"
import { pathToFileURL } from "url"
-import { assertExternalDirectory } from "./external-directory"
-import { Filesystem } from "../util/filesystem"
+import { assertExternalDirectoryEffect } from "./external-directory"
+import { AppFileSystem } from "../filesystem"
const operations = [
"goToDefinition",
@@ -20,78 +21,70 @@ const operations = [
"outgoingCalls",
] as const
-export const LspTool = Tool.define("lsp", {
- description: DESCRIPTION,
- parameters: z.object({
- operation: z.enum(operations).describe("The LSP operation to perform"),
- filePath: z.string().describe("The absolute or relative path to the file"),
- line: z.number().int().min(1).describe("The line number (1-based, as shown in editors)"),
- character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"),
- }),
- execute: async (args, ctx) => {
- const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
- await assertExternalDirectory(ctx, file)
-
- await ctx.ask({
- permission: "lsp",
- patterns: ["*"],
- always: ["*"],
- metadata: {},
- })
- const uri = pathToFileURL(file).href
- const position = {
- file,
- line: args.line - 1,
- character: args.character - 1,
- }
+export const LspTool = Tool.defineEffect(
+ "lsp",
+ Effect.gen(function* () {
+ const lsp = yield* LSP.Service
+ const fs = yield* AppFileSystem.Service
- const relPath = path.relative(Instance.worktree, file)
- const title = `${args.operation} ${relPath}:${args.line}:${args.character}`
+ return {
+ description: DESCRIPTION,
+ parameters: z.object({
+ operation: z.enum(operations).describe("The LSP operation to perform"),
+ filePath: z.string().describe("The absolute or relative path to the file"),
+ line: z.number().int().min(1).describe("The line number (1-based, as shown in editors)"),
+ character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"),
+ }),
+ execute: (args: { operation: (typeof operations)[number]; filePath: string; line: number; character: number }, ctx: Tool.Context) =>
+ Effect.gen(function* () {
+ const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
+ yield* assertExternalDirectoryEffect(ctx, file)
+ yield* Effect.promise(() =>
+ ctx.ask({ permission: "lsp", patterns: ["*"], always: ["*"], metadata: {} }),
+ )
- const exists = await Filesystem.exists(file)
- if (!exists) {
- throw new Error(`File not found: ${file}`)
- }
+ const uri = pathToFileURL(file).href
+ const position = { file, line: args.line - 1, character: args.character - 1 }
+ const relPath = path.relative(Instance.worktree, file)
+ const title = `${args.operation} ${relPath}:${args.line}:${args.character}`
- const available = await LSP.hasClients(file)
- if (!available) {
- throw new Error("No LSP server available for this file type.")
- }
+ const exists = yield* fs.existsSafe(file)
+ if (!exists) throw new Error(`File not found: ${file}`)
- await LSP.touchFile(file, true)
+ const available = yield* lsp.hasClients(file)
+ if (!available) throw new Error("No LSP server available for this file type.")
- const result: unknown[] = await (async () => {
- switch (args.operation) {
- case "goToDefinition":
- return LSP.definition(position)
- case "findReferences":
- return LSP.references(position)
- case "hover":
- return LSP.hover(position)
- case "documentSymbol":
- return LSP.documentSymbol(uri)
- case "workspaceSymbol":
- return LSP.workspaceSymbol("")
- case "goToImplementation":
- return LSP.implementation(position)
- case "prepareCallHierarchy":
- return LSP.prepareCallHierarchy(position)
- case "incomingCalls":
- return LSP.incomingCalls(position)
- case "outgoingCalls":
- return LSP.outgoingCalls(position)
- }
- })()
+ yield* lsp.touchFile(file, true)
- const output = (() => {
- if (result.length === 0) return `No results found for ${args.operation}`
- return JSON.stringify(result, null, 2)
- })()
+ const result: unknown[] = yield* (() => {
+ switch (args.operation) {
+ case "goToDefinition":
+ return lsp.definition(position)
+ case "findReferences":
+ return lsp.references(position)
+ case "hover":
+ return lsp.hover(position)
+ case "documentSymbol":
+ return lsp.documentSymbol(uri)
+ case "workspaceSymbol":
+ return lsp.workspaceSymbol("")
+ case "goToImplementation":
+ return lsp.implementation(position)
+ case "prepareCallHierarchy":
+ return lsp.prepareCallHierarchy(position)
+ case "incomingCalls":
+ return lsp.incomingCalls(position)
+ case "outgoingCalls":
+ return lsp.outgoingCalls(position)
+ }
+ })()
- return {
- title,
- metadata: { result },
- output,
+ return {
+ title,
+ metadata: { result },
+ output: result.length === 0 ? `No results found for ${args.operation}` : JSON.stringify(result, null, 2),
+ }
+ }).pipe(Effect.runPromise),
}
- },
-})
+ }),
+)
diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts
index 9c0771b8d..b653c336e 100644
--- a/packages/opencode/src/tool/registry.ts
+++ b/packages/opencode/src/tool/registry.ts
@@ -92,6 +92,7 @@ export namespace ToolRegistry {
const read = yield* ReadTool
const question = yield* QuestionTool
const todo = yield* TodoWriteTool
+ const lsptool = yield* LspTool
const state = yield* InstanceState.make<State>(
Effect.fn("ToolRegistry.state")(function* (ctx) {
@@ -164,7 +165,7 @@ export namespace ToolRegistry {
skill: Tool.init(SkillTool),
patch: Tool.init(ApplyPatchTool),
question: Tool.init(question),
- lsp: Tool.init(LspTool),
+ lsp: Tool.init(lsptool),
plan: Tool.init(PlanExitTool),
})