Jelajahi Sumber

refactor(tool): convert lsp tool internals to Effect (#21806)

Kit Langton 5 bulan lalu
induk
melakukan
8063e0b5c6
2 mengubah file dengan 64 tambahan dan 70 penghapusan
  1. 62 69
      packages/opencode/src/tool/lsp.ts
  2. 2 1
      packages/opencode/src/tool/registry.ts

+ 62 - 69
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),
     }
-  },
-})
+  }),
+)

+ 2 - 1
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),
           })