refactor: remove legacy tools format, use permission only
BREAKING: Requires OpenCode 1.1.1+ - Remove supportsNewPermissionSystem/usesLegacyToolsSystem checks - Simplify permission-compat.ts to permission format only - Unify explore/librarian deny lists: write, edit, task, sisyphus_task, call_omo_agent - Add sisyphus_task to oracle deny list - Update agent-tool-restrictions.ts with correct per-agent restrictions - Clean config-handler.ts conditional version checks - Update tests for simplified API
This commit is contained in:
@@ -509,6 +509,37 @@ export class LSPClient {
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
}
|
||||
|
||||
async definition(filePath: string, line: number, character: number): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/definition", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
position: { line: line - 1, character },
|
||||
})
|
||||
}
|
||||
|
||||
async references(filePath: string, line: number, character: number, includeDeclaration = true): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/references", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
position: { line: line - 1, character },
|
||||
context: { includeDeclaration },
|
||||
})
|
||||
}
|
||||
|
||||
async documentSymbols(filePath: string): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/documentSymbol", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
})
|
||||
}
|
||||
|
||||
async workspaceSymbols(query: string): Promise<unknown> {
|
||||
return this.send("workspace/symbol", { query })
|
||||
}
|
||||
|
||||
async diagnostics(filePath: string): Promise<{ items: Diagnostic[] }> {
|
||||
const absPath = resolve(filePath)
|
||||
const uri = pathToFileURL(absPath).href
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
import type { LSPServerConfig } from "./types"
|
||||
|
||||
export const SYMBOL_KIND_MAP: Record<number, string> = {
|
||||
1: "File",
|
||||
2: "Module",
|
||||
3: "Namespace",
|
||||
4: "Package",
|
||||
5: "Class",
|
||||
6: "Method",
|
||||
7: "Property",
|
||||
8: "Field",
|
||||
9: "Constructor",
|
||||
10: "Enum",
|
||||
11: "Interface",
|
||||
12: "Function",
|
||||
13: "Variable",
|
||||
14: "Constant",
|
||||
15: "String",
|
||||
16: "Number",
|
||||
17: "Boolean",
|
||||
18: "Array",
|
||||
19: "Object",
|
||||
20: "Key",
|
||||
21: "Null",
|
||||
22: "EnumMember",
|
||||
23: "Struct",
|
||||
24: "Event",
|
||||
25: "Operator",
|
||||
26: "TypeParameter",
|
||||
}
|
||||
|
||||
export const SEVERITY_MAP: Record<number, string> = {
|
||||
1: "error",
|
||||
2: "warning",
|
||||
@@ -7,6 +36,8 @@ export const SEVERITY_MAP: Record<number, string> = {
|
||||
4: "hint",
|
||||
}
|
||||
|
||||
export const DEFAULT_MAX_REFERENCES = 200
|
||||
export const DEFAULT_MAX_SYMBOLS = 200
|
||||
export const DEFAULT_MAX_DIAGNOSTICS = 200
|
||||
|
||||
export const LSP_INSTALL_HINTS: Record<string, string> = {
|
||||
|
||||
@@ -4,4 +4,4 @@ export * from "./config"
|
||||
export * from "./client"
|
||||
export * from "./utils"
|
||||
// NOTE: lsp_servers removed - duplicates OpenCode's built-in LspServers
|
||||
export { lsp_diagnostics, lsp_prepare_rename, lsp_rename } from "./tools"
|
||||
export { lsp_goto_definition, lsp_find_references, lsp_symbols, lsp_diagnostics, lsp_prepare_rename, lsp_rename } from "./tools"
|
||||
|
||||
+148
-2
@@ -1,9 +1,14 @@
|
||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
||||
import {
|
||||
DEFAULT_MAX_REFERENCES,
|
||||
DEFAULT_MAX_SYMBOLS,
|
||||
DEFAULT_MAX_DIAGNOSTICS,
|
||||
} from "./constants"
|
||||
import {
|
||||
withLspClient,
|
||||
formatLocation,
|
||||
formatDocumentSymbol,
|
||||
formatSymbolInfo,
|
||||
formatDiagnostic,
|
||||
filterDiagnosticsBySeverity,
|
||||
formatPrepareRenameResult,
|
||||
@@ -11,14 +16,155 @@ import {
|
||||
formatApplyResult,
|
||||
} from "./utils"
|
||||
import type {
|
||||
Location,
|
||||
LocationLink,
|
||||
DocumentSymbol,
|
||||
SymbolInfo,
|
||||
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)
|
||||
export const lsp_goto_definition: ToolDefinition = tool({
|
||||
description: "Jump to symbol definition. Find WHERE something is defined.",
|
||||
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.definition(args.filePath, args.line, args.character)) as
|
||||
| Location
|
||||
| Location[]
|
||||
| LocationLink[]
|
||||
| null
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
const output = "No definition found"
|
||||
return output
|
||||
}
|
||||
|
||||
const locations = Array.isArray(result) ? result : [result]
|
||||
if (locations.length === 0) {
|
||||
const output = "No definition found"
|
||||
return output
|
||||
}
|
||||
|
||||
const output = locations.map(formatLocation).join("\n")
|
||||
return output
|
||||
} catch (e) {
|
||||
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
return output
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const lsp_find_references: ToolDefinition = tool({
|
||||
description: "Find ALL usages/references of a symbol across the entire workspace.",
|
||||
args: {
|
||||
filePath: tool.schema.string(),
|
||||
line: tool.schema.number().min(1).describe("1-based"),
|
||||
character: tool.schema.number().min(0).describe("0-based"),
|
||||
includeDeclaration: tool.schema.boolean().optional().describe("Include the declaration itself"),
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
try {
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.references(args.filePath, args.line, args.character, args.includeDeclaration ?? true)) as
|
||||
| Location[]
|
||||
| null
|
||||
})
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
const output = "No references found"
|
||||
return output
|
||||
}
|
||||
|
||||
const total = result.length
|
||||
const truncated = total > DEFAULT_MAX_REFERENCES
|
||||
const limited = truncated ? result.slice(0, DEFAULT_MAX_REFERENCES) : result
|
||||
const lines = limited.map(formatLocation)
|
||||
if (truncated) {
|
||||
lines.unshift(`Found ${total} references (showing first ${DEFAULT_MAX_REFERENCES}):`)
|
||||
}
|
||||
const output = lines.join("\n")
|
||||
return output
|
||||
} catch (e) {
|
||||
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
return output
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const lsp_symbols: ToolDefinition = tool({
|
||||
description: "Get symbols from file (document) or search across workspace. Use scope='document' for file outline, scope='workspace' for project-wide symbol search.",
|
||||
args: {
|
||||
filePath: tool.schema.string().describe("File path for LSP context"),
|
||||
scope: tool.schema.enum(["document", "workspace"]).default("document").describe("'document' for file symbols, 'workspace' for project-wide search"),
|
||||
query: tool.schema.string().optional().describe("Symbol name to search (required for workspace scope)"),
|
||||
limit: tool.schema.number().optional().describe("Max results (default 50)"),
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
try {
|
||||
const scope = args.scope ?? "document"
|
||||
|
||||
if (scope === "workspace") {
|
||||
if (!args.query) {
|
||||
return "Error: 'query' is required for workspace scope"
|
||||
}
|
||||
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.workspaceSymbols(args.query!)) as SymbolInfo[] | null
|
||||
})
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
return "No symbols found"
|
||||
}
|
||||
|
||||
const total = result.length
|
||||
const limit = Math.min(args.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS)
|
||||
const truncated = total > limit
|
||||
const limited = result.slice(0, limit)
|
||||
const lines = limited.map(formatSymbolInfo)
|
||||
if (truncated) {
|
||||
lines.unshift(`Found ${total} symbols (showing first ${limit}):`)
|
||||
}
|
||||
return lines.join("\n")
|
||||
} else {
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.documentSymbols(args.filePath)) as DocumentSymbol[] | SymbolInfo[] | null
|
||||
})
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
return "No symbols found"
|
||||
}
|
||||
|
||||
const total = result.length
|
||||
const limit = Math.min(args.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS)
|
||||
const truncated = total > limit
|
||||
const limited = truncated ? result.slice(0, limit) : result
|
||||
|
||||
const lines: string[] = []
|
||||
if (truncated) {
|
||||
lines.push(`Found ${total} symbols (showing first ${limit}):`)
|
||||
}
|
||||
|
||||
if ("range" in limited[0]) {
|
||||
lines.push(...(limited as DocumentSymbol[]).map((s) => formatDocumentSymbol(s)))
|
||||
} else {
|
||||
lines.push(...(limited as SymbolInfo[]).map(formatSymbolInfo))
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
} catch (e) {
|
||||
return `Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const lsp_diagnostics: ToolDefinition = tool({
|
||||
description: "Get errors, warnings, hints from language server BEFORE running build.",
|
||||
|
||||
@@ -17,6 +17,33 @@ export interface Range {
|
||||
end: Position
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
uri: string
|
||||
range: Range
|
||||
}
|
||||
|
||||
export interface LocationLink {
|
||||
targetUri: string
|
||||
targetRange: Range
|
||||
targetSelectionRange: Range
|
||||
originSelectionRange?: Range
|
||||
}
|
||||
|
||||
export interface SymbolInfo {
|
||||
name: string
|
||||
kind: number
|
||||
location: Location
|
||||
containerName?: string
|
||||
}
|
||||
|
||||
export interface DocumentSymbol {
|
||||
name: string
|
||||
kind: number
|
||||
range: Range
|
||||
selectionRange: Range
|
||||
children?: DocumentSymbol[]
|
||||
}
|
||||
|
||||
export interface Diagnostic {
|
||||
range: Range
|
||||
severity?: number
|
||||
|
||||
+45
-1
@@ -3,8 +3,12 @@ import { fileURLToPath } from "node:url"
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs"
|
||||
import { LSPClient, lspManager } from "./client"
|
||||
import { findServerForExtension } from "./config"
|
||||
import { SEVERITY_MAP } from "./constants"
|
||||
import { SYMBOL_KIND_MAP, SEVERITY_MAP } from "./constants"
|
||||
import type {
|
||||
Location,
|
||||
LocationLink,
|
||||
DocumentSymbol,
|
||||
SymbolInfo,
|
||||
Diagnostic,
|
||||
PrepareRenameResult,
|
||||
PrepareRenameDefaultBehavior,
|
||||
@@ -106,11 +110,51 @@ export async function withLspClient<T>(filePath: string, fn: (client: LSPClient)
|
||||
}
|
||||
}
|
||||
|
||||
export function formatLocation(loc: Location | LocationLink): string {
|
||||
if ("targetUri" in loc) {
|
||||
const uri = uriToPath(loc.targetUri)
|
||||
const line = loc.targetRange.start.line + 1
|
||||
const char = loc.targetRange.start.character
|
||||
return `${uri}:${line}:${char}`
|
||||
}
|
||||
|
||||
const uri = uriToPath(loc.uri)
|
||||
const line = loc.range.start.line + 1
|
||||
const char = loc.range.start.character
|
||||
return `${uri}:${line}:${char}`
|
||||
}
|
||||
|
||||
export function formatSymbolKind(kind: number): string {
|
||||
return SYMBOL_KIND_MAP[kind] || `Unknown(${kind})`
|
||||
}
|
||||
|
||||
export function formatSeverity(severity: number | undefined): string {
|
||||
if (!severity) return "unknown"
|
||||
return SEVERITY_MAP[severity] || `unknown(${severity})`
|
||||
}
|
||||
|
||||
export function formatDocumentSymbol(symbol: DocumentSymbol, indent = 0): string {
|
||||
const prefix = " ".repeat(indent)
|
||||
const kind = formatSymbolKind(symbol.kind)
|
||||
const line = symbol.range.start.line + 1
|
||||
let result = `${prefix}${symbol.name} (${kind}) - line ${line}`
|
||||
|
||||
if (symbol.children && symbol.children.length > 0) {
|
||||
for (const child of symbol.children) {
|
||||
result += "\n" + formatDocumentSymbol(child, indent + 1)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function formatSymbolInfo(symbol: SymbolInfo): string {
|
||||
const kind = formatSymbolKind(symbol.kind)
|
||||
const loc = formatLocation(symbol.location)
|
||||
const container = symbol.containerName ? ` (in ${symbol.containerName})` : ""
|
||||
return `${symbol.name} (${kind})${container} - ${loc}`
|
||||
}
|
||||
|
||||
export function formatDiagnostic(diag: Diagnostic): string {
|
||||
const severity = formatSeverity(diag.severity)
|
||||
const line = diag.range.start.line + 1
|
||||
|
||||
Reference in New Issue
Block a user