Files
oh-my-opencode/src/tools/lsp/tools.ts
T

116 lines
3.8 KiB
TypeScript
Raw Normal View History

2025-12-27 17:56:40 +08:00
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
import {
DEFAULT_MAX_DIAGNOSTICS,
} from "./constants"
import {
withLspClient,
formatDiagnostic,
filterDiagnosticsBySeverity,
formatPrepareRenameResult,
applyWorkspaceEdit,
formatApplyResult,
} from "./utils"
import type {
Diagnostic,
PrepareRenameResult,
PrepareRenameDefaultBehavior,
WorkspaceEdit,
} from "./types"
// NOTE: lsp_goto_definition, lsp_find_references, lsp_symbols are removed
// as they duplicate OpenCode's built-in LSP tools (LspGotoDefinition, LspFindReferences, LspDocumentSymbols, LspWorkspaceSymbols)
2025-12-27 17:56:40 +08:00
export const lsp_diagnostics: ToolDefinition = tool({
description: "Get errors, warnings, hints from language server BEFORE running build.",
args: {
filePath: tool.schema.string(),
severity: tool.schema
.enum(["error", "warning", "information", "hint", "all"])
.optional()
.describe("Filter by severity level"),
},
execute: async (args, context) => {
try {
const result = await withLspClient(args.filePath, async (client) => {
return (await client.diagnostics(args.filePath)) as { items?: Diagnostic[] } | Diagnostic[] | null
})
let diagnostics: Diagnostic[] = []
if (result) {
if (Array.isArray(result)) {
diagnostics = result
} else if (result.items) {
diagnostics = result.items
}
}
diagnostics = filterDiagnosticsBySeverity(diagnostics, args.severity)
if (diagnostics.length === 0) {
const output = "No diagnostics found"
return output
}
const total = diagnostics.length
const truncated = total > DEFAULT_MAX_DIAGNOSTICS
const limited = truncated ? diagnostics.slice(0, DEFAULT_MAX_DIAGNOSTICS) : diagnostics
const lines = limited.map(formatDiagnostic)
if (truncated) {
lines.unshift(`Found ${total} diagnostics (showing first ${DEFAULT_MAX_DIAGNOSTICS}):`)
}
const output = lines.join("\n")
return output
} catch (e) {
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
return output
}
},
})
2025-12-27 17:56:40 +08:00
export const lsp_prepare_rename: ToolDefinition = tool({
description: "Check if rename is valid. Use BEFORE lsp_rename.",
args: {
filePath: tool.schema.string(),
line: tool.schema.number().min(1).describe("1-based"),
character: tool.schema.number().min(0).describe("0-based"),
},
execute: async (args, context) => {
try {
const result = await withLspClient(args.filePath, async (client) => {
return (await client.prepareRename(args.filePath, args.line, args.character)) as
| PrepareRenameResult
| PrepareRenameDefaultBehavior
| null
})
const output = formatPrepareRenameResult(result)
return output
} catch (e) {
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
return output
}
},
})
2025-12-27 17:56:40 +08:00
export const lsp_rename: ToolDefinition = tool({
description: "Rename symbol across entire workspace. APPLIES changes to all files.",
args: {
filePath: tool.schema.string(),
line: tool.schema.number().min(1).describe("1-based"),
character: tool.schema.number().min(0).describe("0-based"),
newName: tool.schema.string().describe("New symbol name"),
},
execute: async (args, context) => {
try {
const edit = await withLspClient(args.filePath, async (client) => {
return (await client.rename(args.filePath, args.line, args.character, args.newName)) as WorkspaceEdit | null
})
const result = applyWorkspaceEdit(edit)
const output = formatApplyResult(result)
return output
} catch (e) {
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
return output
}
},
})