lsp
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. |
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), 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. - 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. - 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. - 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
- Waits for project load, sends
textDocument/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; collects returnedWorkspaceEdits and server notes. - Preview mode (
apply === false) only formats those edits. - Apply mode runs each returned
WorkspaceEdit, renames the source path on disk, sendstextDocument/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
LspConfig, notgetActiveClients(). - Calls
detectLspmux()and appends status text whenlspmuxis installed.
Output text
Active language servers: ...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>: ....
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
- Aborts are not converted to text:
ToolAbortErroris rethrown.
Notes
statusreports configured/available servers fromLspConfig, not currently active client processes fromgetActiveClients().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.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 warmup (
discoverStartupLspServers(cwd)insdk.ts) is gated onenableLsp && options.hasUI && settings.get("lsp.diagnosticsOnWrite")— print/RPC/ACP/script sessions skip it and letgetOrCreateClient()cold-start servers on demand. Seedocs/sdk.md§ Startup performance. configCacheis per-process and never auto-invalidated; config changes require a fresh process to be observed bygetConfig()callers.