Query language servers for diagnostics, navigation, symbols, renames, code actions, capabilities, and raw requests.
Source
- Entry:
packages/coding-agent/src/lsp/index.ts - Model-facing prompt:
packages/coding-agent/src/prompts/tools/lsp.md - Key collaborators:
packages/coding-agent/src/lsp/client.tsβ client process lifecycle and JSON-RPCpackages/coding-agent/src/lsp/config.tsβ config loading, auto-detect, server selectionpackages/coding-agent/src/lsp/lspmux.tsβ optionallspmuxcommand wrappingpackages/coding-agent/src/lsp/edits.tsβ applyWorkspaceEditand text editspackages/coding-agent/src/lsp/utils.tsβ URI conversion, symbol resolution, formatting, glob expansionpackages/coding-agent/src/lsp/types.tsβ tool schema and protocol typespackages/coding-agent/src/lsp/clients/index.tsβ custom linter client cache/factorypackages/coding-agent/src/lsp/clients/lsp-linter-client.tsβ LSP-backed linter adapterpackages/coding-agent/src/lsp/clients/biome-client.tsβ Biome CLI diagnostics/formatting adapterpackages/coding-agent/src/lsp/clients/swiftlint-client.tsβ SwiftLint CLI diagnostics adapterpackages/coding-agent/src/tools/index.tsβ tool registration andlsp.enabledgatingpackages/coding-agent/src/tools/tool-timeouts.tsβ timeout defaults and clampingpackages/coding-agent/src/lsp/defaults.jsonβ built-in server definitions for auto-detect
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
action | string enum | Yes | One of diagnostics, definition, references, hover, symbols, rename, rename_file, code_actions, type_definition, implementation, status, reload, capabilities, request. |
file | string | No | File path; for diagnostics also a glob; for workspace forms use "*"; for rename_file this is the source path. |
line | number | No | 1-indexed line number for position-based actions. Defaults to 1 on the single-file action path. |
symbol | string | No | Substring used to resolve the column on line. Supports name#N occurrence selectors; N is 1-indexed and defaults to 1. Required when line is given for definition/references/rename against project-aware servers. |
query | string | No | Workspace symbol query, code-action selector/filter, or LSP method name for action=request. |
new_name | string | No | Required for rename and rename_file. |
apply | boolean | No | For rename/rename_file, apply unless explicitly false. For code_actions, list unless explicitly true. |
timeout | number | No | Seconds, clamped by clampTimeout("lsp", ...) to 5..60, default 20. |
payload | string | No | JSON string for action=request; overrides auto-built params. |
Outputs
- Single-shot
AgentToolResult. contentis always one text block:[{ type: "text", text: string }].detailsisLspToolDetails:action,success, optionalserverName, optional originalrequest.- No streaming updates.
- No artifact URIs or background jobs.
- Many validation failures are returned as ordinary text results with
details.success: false; aborts throwToolAbortErrorinstead.
Flow
packages/coding-agent/src/tools/index.tsregisterslsp: LspTool.createIf; session creation also gates it behindsession.enableLsp !== falseandsettings.get("lsp.enabled").LspTool.execute()inpackages/coding-agent/src/lsp/index.tsclampstimeoutwithclampTimeout("lsp", ...), builds anAbortSignal.timeout(...), and combines it with the caller signal.getConfig()loads and cachesLspConfigper cwd, applies idle-timeout config viasetIdleTimeout(), and reuses the cached config on later calls.- Config loading in
packages/coding-agent/src/lsp/config.tsmergesdefaults.jsonwith JSON/YAML overrides from project, project config dirs, user config dirs, plugin roots, and home; if there are no overrides it auto-detects servers from root markers plus executable discovery. - Server routing uses
getServersForFile()/getServerForFile()fromconfig.ts: extension or basename match, then sort primary servers before linters.index.tsfurther filters custom linter clients out of navigation/refactor paths withgetLspServersForFile()/getLspServerForFile(). getOrCreateClient()inclient.tscreates one process percommand:cwd, optionally wraps supported commands withlspmux, spawns the server, starts the background message reader, sendsinitialize, stores server capabilities, then sendsinitialized.- The message reader in
client.tsparses LSP frames, resolves pending requests, cachespublishDiagnostics, tracks$/progresstokens for project-load completion, answersworkspace/configuration, and appliesworkspace/applyEditrequests throughapplyWorkspaceEdit(). - File-scoped actions call
ensureFileOpen()before requests. Column resolution usesresolveSymbolColumn()fromutils.ts: read the target file, pick first non-whitespace whensymbolis omitted, otherwise find the exact or case-insensitive match on the target line and honor#Noccurrence selectors. - Actions dispatch in
LspTool.execute()through dedicated branches: workspace-only branches (status, somediagnostics, workspacesymbols, workspacereload,capabilities,request) run before the single-file switch; all other single-file actions share one client lookup andswitch(action). - Requests go through
sendRequest()inclient.ts, which allocates an incrementing JSON-RPC id, installs abort and timeout handling, sends$/cancelRequeston abort, and rejects on timeout or process exit. - Actions that return edits either preview with
formatWorkspaceEdit()or apply withapplyWorkspaceEdit()fromedits.ts;rename_filealso performs the filesystem rename and then sendsworkspace/didRenameFiles. - Non-abort failures inside the single-file action block are converted to
LSP error: ...; many precondition failures return explicit text without throwing.
Modes / Variants
Routing and workspace scope
file: "*"is only special fordiagnostics,symbols, andreload.statusignoresfile.capabilitieswith omittedfileor"*"inspects all non-custom LSP servers; with a concrete file it scopes to matching non-custom servers.requestwith omittedfileor"*"chooses the first available non-custom LSP server; with a concrete file it chooses that fileβs primary non-linter server.rename_filesendsworkspace/willRenameFilesandworkspace/didRenameFilesto every non-custom LSP server fromgetLspServers(config)whosefileTypesmatch the source, destination, or any enumerated rename pair β not just one file-scoped server.- Diagnostics are the only tool action that queries both normal LSP servers and custom linter clients (
BiomeClient,SwiftLintClient, orLspLinterClient).
diagnostics
Inputs
- Required:
file, unless using workspace mode withfile: "*". - Optional:
timeout.
Execution
file: "*":runWorkspaceDiagnostics()detects project type from root markers and runs one subprocess command: Rustcargo check --message-format=short, TypeScriptnpx tsc --noEmit, Gogo build ./..., Pythonpyright.- Concrete file or glob:
resolveDiagnosticTargets()treats non-globs as one target, otherwise expands aBun.Globup toMAX_GLOB_DIAGNOSTIC_TARGETS. - Per file, every matching server runs: custom clients call
lint(file); real LSP servers optionally wait for project load, capturediagnosticsVersion,refreshFile(), thenwaitForDiagnostics()for freshpublishDiagnostics(settles on the latest publish; exact-version match accepted immediately). - Results are deduplicated by range+message and severity-sorted.
Output text
- Single target with no issues:
OK. - Single target with issues:
<summary>:\n<grouped diagnostics>. - Batch/glob target: one section per file, plus an initial truncation warning when the glob exceeds the file cap.
- Workspace mode:
Workspace diagnostics (<detected description>):\n<command output>.
definition
Inputs
- Required:
file. - Optional:
line,symbol,timeout.
Execution
- Sends
textDocument/definitionwith{ textDocument, position }. - Accepts
Location,Location[],LocationLink, orLocationLink[];normalizeLocationResult()convertsLocationLinktotargetSelectionRange ?? targetRange. - Requires
symbolwhenlineis given on project-aware servers (the first-non-whitespace-column fallback is disabled for this action). - Waits for project load before the request.
Output text
No definition foundorFound N definition(s):followed byfile:line:coland one context line above/below each location.
type_definition
Same as definition, but sends textDocument/typeDefinition and reports type definition(s).
implementation
Same as definition, but sends textDocument/implementation and reports implementation(s).
references
Inputs
- Required:
file. - Optional:
line,symbol,timeout.
Execution
- Sends
textDocument/referenceswithincludeDeclaration: true. - Requires
symbolwhenlineis given on project-aware servers (the first-non-whitespace-column fallback is disabled for this action). - For project-aware servers, retries up to
REFERENCES_RETRY_COUNTtimes when the only hit is the queried declaration; between retries it waits for project load and sleepsREFERENCES_RETRY_DELAY_MS. - First
REFERENCE_CONTEXT_LIMITreferences include surrounding context; the rest are location-only.
Output text
No references foundorFound N reference(s):with contextual entries first, then... M additional reference(s) shown without contextwhen truncated.
hover
Inputs
- Required:
file. - Optional:
line,symbol,timeout.
Execution
- Sends
textDocument/hover. extractHoverText()flattens strings, markup content, marked-string objects, or arrays into plain text.
Output text
No hover informationor the extracted hover text.
symbols
Inputs
- Workspace mode:
file: "*"or omitted file on the early workspace branch, plus requiredquery. - Document mode: required
file. - Optional:
timeout.
Execution
- Workspace mode sends
workspace/symbolto every non-custom LSP server, post-filters matches withfilterWorkspaceSymbols(), deduplicates withdedupeWorkspaceSymbols(), then truncates toWORKSPACE_SYMBOL_LIMIT. - Document mode sends
textDocument/documentSymbolto the primary server. If the first item hasselectionRange, it formats hierarchicalDocumentSymbols; otherwise it formats flatSymbolInformations.
Output text
- Workspace mode:
Found N symbol(s) matching "query":plus formattedname @ file:line:col, with an omission line when over the limit. - Document mode:
Symbols in <file>:plus hierarchical or flat symbol lines.
rename
Inputs
- Required:
file,new_name. - Optional:
line,symbol,apply,timeout.
Execution
- Requires
symbolwhenlineis given on project-aware servers, then waits for project load, sendstextDocument/rename, receives aWorkspaceEdit. apply !== falseapplies edits immediately withapplyWorkspaceEdit().apply === falserenders a preview withformatWorkspaceEdit().
Output text
Rename returned no edits,Applied rename:plus applied change lines, orRename preview:plus summarized edits.
rename_file
Inputs
- Required:
filesource path,new_namedestination path. - Optional:
apply,timeout.
Execution
- Resolves absolute source and destination, rejects identical paths, missing source, existing destination, empty rename set, or directories with more than
MAX_RENAME_PAIRSfiles. enumerateRenamePairs()returns one{oldUri,newUri}pair for a file or walks every regular file in a directory tree.- Sends
workspace/willRenameFileswith{ files: pairs }to every non-custom LSP server whosefileTypesmatch an affected path; collects returnedWorkspaceEdits and server notes. - Preview mode (
apply === false) only formats those edits. - Apply mode coalesces the returned text edits per URI (a project-aware serverβs edits win on overlap; overlapping edits from other servers are discarded with a note), applies each URI once from a single snapshot, creates the destination parent directory and renames the source path on disk, sends
textDocument/didClosefor every renamed open file, deletes thoseopenFilesentries, then sendsworkspace/didRenameFiles.
Output text
- Preview:
Rename preview: <file-count label> β <dest>plus per-server edit summaries and optional server notes. - Apply:
Renamed <file-count label> β <dest>plus applied edit summaries, filesystem rename line, and optional server notes.
code_actions
Inputs
- Required:
file. - Optional:
line,symbol,query,apply,timeout.
Execution
- Reads cached diagnostics for the open URI from
client.diagnosticsand sendstextDocument/codeActionfor a zero-width range at the resolved position. - When
apply !== true,queryis passed ascontext.only: [query]; this is a server-side kind filter. - When
apply === true,querybecomes a required client-side selector: either a zero-based numeric index or a case-insensitive substring of the action title. - Applying a
CodeActionusesapplyCodeAction(): optionallycodeAction/resolve, thenapplyWorkspaceEdit(edit), then optionalworkspace/executeCommand. - Applying a bare
Commandonly runsworkspace/executeCommand.
Output text
- List mode:
N code action(s):plusindex: [kind] titlelines. - Apply mode success:
Applied "title":plusWorkspace edit:and/orExecuted command(s):sections. - Apply mode miss:
No code action matches "query". Available actions:. - Apply mode with no edit/command:
Action "title" has no workspace edit or command to apply.
status
Inputs
- None.
Execution
- Reads configured servers from cached
LspConfigand cross-referencesgetActiveClients()so each server is labelled(configured, not started)or with its live client status. - Calls
detectLspmux()and appends status text whenlspmuxis installed.
Output text
Language servers: <name (configured, not started) | name (<status>)>plus an explanatory note line, orNo language servers configured for this project, optionally followed bylspmux: active (multiplexing enabled)orlspmux: installed but server not running.
reload
Inputs
- Workspace mode:
file: "*"or omittedfile. - Single-file mode: required
file. - Optional:
timeout.
Execution
- Workspace mode reloads every non-custom LSP server.
- Single-file mode reloads the primary server for that file.
reloadServer()triesrust-analyzer/reloadWorkspace, thenworkspace/didChangeConfigurationwith{ settings: {} }; if neither works it kills the process so the next request cold-starts a new client.
Output text
- One line per server:
Reloaded <server>,Restarted <server>, orFailed to reload <server>: ....
capabilities
Inputs
- Optional:
file,timeout.
Execution
- With a concrete
file, inspects matching non-custom servers for that file. - With omitted
fileor"*", inspects every non-custom configured server. - Starts servers as needed and dumps
client.serverCapabilities ?? {}as pretty JSON.
Output text
- Per server:
<server>:followed by indentedcapabilities: { ... }, or<server>: failed to start (...).
request
Inputs
- Required:
querymethod name. - Optional:
file,line,symbol,payload,timeout.
Execution
- Chooses one non-custom server: file-scoped primary server, otherwise the first configured non-custom server.
- Param building precedence:
- If
payloadis present, parse JSON and use it verbatim. - Else if
fileis concrete andlineis present, build{ textDocument: { uri }, position: { line: line - 1, character } }usingresolveSymbolColumn(). - Else if
fileis concrete, build{ textDocument: { uri } }. - Else use
{}.
- If
- Opens the file first when
fileis concrete.
Output text
- Success:
<server> β <method>:\n<formatted result>, where non-string results areJSON.stringify(..., null, 2)and nullish values becomenull. - Failure:
LSP error from <server> on <method>: ...followed byparams: <preview>echoing the request params (truncated to 400 chars).
Side Effects
- Filesystem
- Reads config files, target files, and root markers.
renameandcode_actionsmay edit/create/delete/rename files viaapplyWorkspaceEdit().rename_filealways renames the source path on disk in apply mode.- Server-initiated
workspace/applyEditrequests also mutate files throughapplyWorkspaceEdit().
- Network
- None directly; communication is local stdio JSON-RPC to subprocesses.
- Subprocesses / native bindings
- Spawns language servers with
ptree.spawn(). - Workspace diagnostics spawns
cargo,npx,go, orpyright. BiomeClientandSwiftLintClientspawn CLI tools.- Optional
lspmuxdetection spawnslspmux status; supported servers may be wrapped throughlspmux client.
- Spawns language servers with
- Session state (transcript, memory, jobs, checkpoints, registries)
- Caches config per cwd in
configCache. - Caches LSP clients per
command:cwd, withpendingRequests,diagnostics,openFiles,serverCapabilities, and project-load state. - Caches custom linter clients by
serverName:cwd. - Updates client
lastActivity; optional idle-timeout cleanup is driven bysetIdleTimeout().
- Caches config per cwd in
- Background work / cancellation
- Every request has an abortable timeout signal.
- Aborting an in-flight LSP request sends
$/cancelRequest. - Background message readers persist for each live client until process exit/shutdown.
Limits & Caps
- Tool timeout clamp: default
20, min5, max60seconds βTOOL_TIMEOUTS.lspinpackages/coding-agent/src/tools/tool-timeouts.ts. - LSP request default timeout inside
sendRequest():30_000msβDEFAULT_REQUEST_TIMEOUT_MSinpackages/coding-agent/src/lsp/client.ts. - Warmup initialize timeout default:
5_000msβWARMUP_TIMEOUT_MSinpackages/coding-agent/src/lsp/client.ts. - Project-load wait fallback:
15_000msβPROJECT_LOAD_TIMEOUT_MSinpackages/coding-agent/src/lsp/client.ts. - Idle-client sweep interval when enabled:
60_000msβIDLE_CHECK_INTERVAL_MSinpackages/coding-agent/src/lsp/client.ts. - Diagnostic message output cap: first
50messages βDIAGNOSTIC_MESSAGE_LIMITinpackages/coding-agent/src/lsp/index.ts. - Single-file diagnostics wait:
3_000msβSINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS. - Batch/glob diagnostics wait per file:
400msβBATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS. - Glob diagnostic target cap: first
20matches βMAX_GLOB_DIAGNOSTIC_TARGETS. - Workspace symbol cap: first
200entries βWORKSPACE_SYMBOL_LIMIT. - Reference context cap: first
50references include source context βREFERENCE_CONTEXT_LIMIT. - References retry count:
2retries,250msbackoff βREFERENCES_RETRY_COUNT,REFERENCES_RETRY_DELAY_MS. - Directory rename cap:
1_000file pairs βMAX_RENAME_PAIRS. detectLspmux()state cache TTL:5 * 60 * 1000ms; liveness check timeout:1_000msβSTATE_CACHE_TTL_MS,LIVENESS_TIMEOUT_MSinpackages/coding-agent/src/lsp/lspmux.ts.- Workspace diagnostics output cap: first
50lines from the subprocess.
Errors
- Missing or invalid inputs are usually returned as text with
details.success: false, not thrown:- missing
file/query/new_name - invalid JSON in
payload - no matching server
- invalid
rename_filesource/destination conditions
- missing
resolveSymbolColumn()throws explicit errors for missing files, missing symbols, and out-of-bounds#Nselectors; these surface asLSP error: ...or request-specific error text.sendRequest()rejects on timeout withLSP request <method> timed out after <ms>ms.- Client process exit rejects all pending requests with an exit-code/stderr error assembled in
getOrCreateClient(). - Single-file action failures inside the main
trybecomeLSP error: <message>. requesthas its own error envelope:LSP error from <server> on <method>: <message>.- Some server failures are intentionally softened:
- diagnostics continue when one server fails
rename_filesuppressesworkspace/willRenameFilesβmethod not foundβ errors and records other server errors as notescode_actionsignorescodeAction/resolvefailures and applies unresolved actions when possible
- Caller aborts are not converted to text:
ToolAbortErroris rethrown. A wall-clock tool timeout without a caller abort instead throwsToolError:LSP <action> timed out after <N>s on <server>. ....
Notes
statusreports configured servers fromLspConfigand labels each one viagetActiveClients():(configured, not started)means the binary resolves on PATH but no request has spawned it; a live client reports its status.getLspServerForFile()excludescreateClientadapters and linter-only servers; navigation/refactor actions never target Biome/SwiftLint custom clients.getServersForFile()matches both file extensions and exact basenames fromfileTypes; config can target names likeDockerfileif present.symbolmatching is exact first, then case-insensitive, and falls back to the Nth occurrence on the specified line only; it never scans other lines.- For
definition,references, andrenameagainst project-aware servers, omittingsymbolwhile passinglineis rejected with aToolErrorinstead of silently falling back to the first non-whitespace column. code_actionsusesqueryin two different ways: server-sidecontext.onlyfilter in list mode, client-side title/index selector in apply mode.renameandrename_filedefault to apply. Preview requiresapply: false.requestwithfile: "*"is treated the same as omittedfile: it does not build workspace-specific params.reloaddoes not recreate a client immediately after killing it; the next request triggers reinitialization.workspace/applyEditcan apply edits initiated by the server outside the direct tool action result path.detectLspmux()can be disabled withPI_DISABLE_LSPMUX=1; onlyrust-analyzeris inDEFAULT_SUPPORTED_SERVERS.- Startup LSP discovery (
discoverStartupLspServers(cwd)insdk.ts) runs forenableLsp && options.hasUI; the background warmup additionally requires!settings.get("lsp.lazy").lsp.lazydefaults totrue, so by default discovered servers are surfaced with status"available"(gray dot in the welcome screen) and cold-start throughgetOrCreateClient()on first use (lsp tool call or edit/write on a matching file type). Print/RPC/ACP/script sessions skip discovery and warmup entirely. Seedocs/sdk.mdΒ§ Startup performance. configCacheis per-process and never auto-invalidated; config changes require a fresh process to be observed bygetConfig()callers.