2026-03-07 13:05:58 -07:00
import { resolve } from "path"
2026-02-08 13:57:26 +09:00
import { tool , type ToolDefinition } from "@opencode-ai/plugin/tool"
import { DEFAULT_MAX_DIAGNOSTICS } from "./constants"
2026-03-07 13:05:58 -07:00
import { aggregateDiagnosticsForDirectory } from "./directory-diagnostics"
2026-03-31 17:33:42 -07:00
import { inferExtensionFromDirectory } from "./infer-extension"
2026-02-08 13:57:26 +09:00
import { filterDiagnosticsBySeverity , formatDiagnostic } from "./lsp-formatters"
2026-03-07 13:05:58 -07:00
import { isDirectoryPath , withLspClient } from "./lsp-client-wrapper"
2026-02-08 13:57:26 +09:00
import type { Diagnostic } from "./types"
export const lsp_diagnostics : ToolDefinition = tool ( {
2026-03-07 13:05:58 -07:00
description :
2026-03-31 17:33:42 -07:00
'Get errors, warnings, hints from language server BEFORE running build. Works for both single files and directories — file extension is auto-detected for directories.' ,
2026-02-08 13:57:26 +09:00
args : {
2026-03-27 12:54:10 +09:00
filePath : tool.schema
. string ( )
. describe ( "File or directory path to check diagnostics for" ) ,
2026-02-08 13:57:26 +09:00
severity : tool.schema
. enum ( [ "error" , "warning" , "information" , "hint" , "all" ] )
. optional ( )
. describe ( "Filter by severity level" ) ,
} ,
2026-02-16 22:12:21 +09:00
execute : async ( args , _context ) = > {
2026-02-08 13:57:26 +09:00
try {
2026-03-31 17:33:42 -07:00
if ( ! args . filePath ) {
throw new Error ( "'filePath' parameter is required." )
2026-03-27 12:54:10 +09:00
}
2026-03-31 17:33:42 -07:00
const absPath = resolve ( args . filePath )
2026-03-07 13:05:58 -07:00
if ( isDirectoryPath ( absPath ) ) {
2026-03-31 17:33:42 -07:00
const extension = inferExtensionFromDirectory ( absPath )
if ( ! extension ) {
2026-03-07 13:05:58 -07:00
throw new Error (
2026-03-31 17:33:42 -07:00
` No supported source files found in directory: ${ absPath } `
2026-03-07 13:05:58 -07:00
)
}
2026-03-31 17:33:42 -07:00
return await aggregateDiagnosticsForDirectory ( absPath , extension , args . severity )
2026-03-07 13:05:58 -07:00
}
2026-03-31 17:33:42 -07:00
const result = await withLspClient ( args . filePath , async ( client ) = > {
return ( await client . diagnostics ( args . filePath ) ) as { items? : Diagnostic [ ] } | Diagnostic [ ] | null
2026-02-08 13:57:26 +09:00
} )
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 ) } `
throw new Error ( output )
}
} ,
} )