2025-12-27 17:56:40 +08:00
|
|
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
2025-12-05 22:51:46 +09:00
|
|
|
import {
|
|
|
|
|
DEFAULT_MAX_DIAGNOSTICS,
|
|
|
|
|
} from "./constants"
|
2025-12-04 21:54:02 +09:00
|
|
|
import {
|
|
|
|
|
withLspClient,
|
|
|
|
|
formatDiagnostic,
|
|
|
|
|
filterDiagnosticsBySeverity,
|
2025-12-04 22:40:05 +09:00
|
|
|
formatPrepareRenameResult,
|
|
|
|
|
applyWorkspaceEdit,
|
|
|
|
|
formatApplyResult,
|
2025-12-04 21:54:02 +09:00
|
|
|
} from "./utils"
|
2025-12-04 22:40:05 +09:00
|
|
|
import type {
|
|
|
|
|
Diagnostic,
|
|
|
|
|
PrepareRenameResult,
|
|
|
|
|
PrepareRenameDefaultBehavior,
|
|
|
|
|
WorkspaceEdit,
|
|
|
|
|
} from "./types"
|
|
|
|
|
|
2026-01-16 02:17:00 +09:00
|
|
|
// 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-04 21:54:02 +09:00
|
|
|
|
2025-12-27 17:56:40 +08:00
|
|
|
export const lsp_diagnostics: ToolDefinition = tool({
|
2025-12-17 00:16:21 +09:00
|
|
|
description: "Get errors, warnings, hints from language server BEFORE running build.",
|
2025-12-04 21:54:02 +09:00
|
|
|
args: {
|
2025-12-17 00:16:21 +09:00
|
|
|
filePath: tool.schema.string(),
|
2025-12-04 21:54:02 +09:00
|
|
|
severity: tool.schema
|
|
|
|
|
.enum(["error", "warning", "information", "hint", "all"])
|
|
|
|
|
.optional()
|
|
|
|
|
.describe("Filter by severity level"),
|
|
|
|
|
},
|
2025-12-04 22:40:05 +09:00
|
|
|
execute: async (args, context) => {
|
2025-12-04 21:54:02 +09:00
|
|
|
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)
|
|
|
|
|
|
2025-12-04 22:40:05 +09:00
|
|
|
if (diagnostics.length === 0) {
|
|
|
|
|
const output = "No diagnostics found"
|
|
|
|
|
return output
|
|
|
|
|
}
|
2025-12-04 21:54:02 +09:00
|
|
|
|
2025-12-05 22:51:46 +09:00
|
|
|
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")
|
2025-12-04 22:40:05 +09:00
|
|
|
return output
|
2025-12-04 21:54:02 +09:00
|
|
|
} catch (e) {
|
2025-12-04 22:40:05 +09:00
|
|
|
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
|
|
|
|
|
return output
|
2025-12-04 21:54:02 +09:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2025-12-27 17:56:40 +08:00
|
|
|
export const lsp_prepare_rename: ToolDefinition = tool({
|
2025-12-17 00:16:21 +09:00
|
|
|
description: "Check if rename is valid. Use BEFORE lsp_rename.",
|
2025-12-04 22:40:05 +09:00
|
|
|
args: {
|
2025-12-17 00:16:21 +09:00
|
|
|
filePath: tool.schema.string(),
|
|
|
|
|
line: tool.schema.number().min(1).describe("1-based"),
|
|
|
|
|
character: tool.schema.number().min(0).describe("0-based"),
|
2025-12-04 22:40:05 +09:00
|
|
|
},
|
|
|
|
|
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({
|
2025-12-17 00:16:21 +09:00
|
|
|
description: "Rename symbol across entire workspace. APPLIES changes to all files.",
|
2025-12-04 22:40:05 +09:00
|
|
|
args: {
|
2025-12-17 00:16:21 +09:00
|
|
|
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"),
|
2025-12-04 22:40:05 +09:00
|
|
|
},
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|