lsp — Language-Server Eyes for Agents
TL;DR
@piex-dev/lsp lets the agent ask IDE-class questions; more importantly, it auto-surfaces ERRORs after edit without relying on the model to remember to run diagnostics.
Overview
Without LSP, an agent understands code via read, grep, and the model guessing types/references. This breaks at scale: missed call sites after a signature change, type errors only found by running tsc/cargo check, "where is this defined" reduced to guessing paths. @piex-dev/lsp turns LSP into a Pi tool so the model can actively ask a language server before/after editing.
How it works
The most valuable capabilities: diagnostics (errors/warnings), definition/references (blast radius), hover (symbol type), symbols (structure outline), format (consistent style). It matches a server by file suffix in defaults.json, finds the workspace root via root markers, spawns a child process on stdio JSON-RPC, and caches per root+server within a session to avoid cold starts. Diagnostics come from server-pushed publishDiagnostics, cached by URI — so after an edit you can immediately ask "any red lines left?", closing an edit→verify→re-edit loop.
Editor/Agent Language Server
| |
| initialize |
| textDocument/* |
| ---------------------> |
| publishDiagnostics |
| <--------------------- |
Usage
Install
pi install npm:@piex-dev/lsp
Source: extensions/lsp
Prerequisites
The extension is a client, not a server distributor. The language server binary must be locally executable (e.g. typescript-language-server, rust-analyzer, pyright, gopls). defaults.json ships start commands and args for ~50 servers.
Configuration
Defaults live in extensions/lsp/defaults.json; each server has command / fileTypes / rootMarkers / initOptions / settings / isLinter. A single server's command can be overridden via PI_<NAME>_LSP_COMMAND; post-edit diagnostics can be disabled with PI_LSP_DIAGNOSTICS_ON_EDIT=0.
Verify
cd extensions/lsp && npm install && bun test # mock server unit tests
pi -e ./extensions/lsp/src/lsp.ts -p "what is 1+1" --no-session # smoke
Implementation
lsp.ts is the client + router + tools + post-edit diagnostic hook; defaults.json configures ~50 servers. Post-edit diagnostics hook edit/write (incl. hashline): sync to disk → wait for publishDiagnostics → ERRORs only, capped 20 per file, appended to the result.
| action | purpose |
|---|---|
diagnostics | match multiple servers, aggregate |
definition/type_definition/implementation | navigation |
references/hover/symbols/workspace_symbols | read intelligence |
rename | preview by default; apply=true writes |
code_actions | list or apply by index |
format | TextEdit write-back |
status/reload | ops |
Correctness: diagnostic settle (silent N ms after last publish, default 800ms); LSP 3.17 pull diagnostics (only if diagnosticProvider declared); resolveProvider gating; overlapping TextEdit intersection rejected; stderr captured into error messages (cap 16KB); which includes node_modules/.bin, .venv/bin; Windows .bat/.cmd wrapped via cmd.exe /d /s /c.
Design notes
| Project | Mechanism | piex choice |
|---|---|---|
| oh-my-pi lsp | full LSP client: multi-server routing, didChange, full action surface, diagnostic aggregation | Adopted: JSON-RPC client, defaults.json-driven, lazy start/session reuse. Not adopted: Bun runtime, all-actions-at-once |
| OpenCode post-edit diagnostics | tool_result hook on edit/write → wait publishDiagnostics → ERRORs only, per-file cap | Adopted the whole pattern: sync → wait → only ERROR → cap 20. PI_LSP_DIAGNOSTICS_ON_EDIT=0 disables it |
| VS Code LSP | initialize + settings/didChangeConfiguration + workspace/configuration | Borrowed: initOptions/settings dispatch; full-text didChange; which checks .bin/.venv |
| pi-extensions pi-lsp | diagnostic settle, pull diagnostics, stderr capture, gating, overlap detection, cmd.exe wrap, command override | Adopted all protocol details (fused into resident-process architecture); not adopted: spawn-per-call, two-tools-only |
Core trade-off: prioritize the post-edit ERROR loop (à la OpenCode), diagnostics over navigation exposure; linters don't hijack the primary server's navigation role.
Changelog
Roadmap
| Status | Item |
|---|---|
| ✅ | init/settings, didChange, multi-server, post-edit ERROR, rename/code_actions |
| ✅ | diagnostic settle, pull diagnostics, resolveProvider gating, overlap guard, stderr capture |
| ✅ | mock server unit tests |
| Next | project-level .lsp.json override; module split (client/config/edits) |
| Next | indexing/ready state to avoid cold-start false negatives; directory-level batch diagnostics |
| Deferred | lspmux, auto-download LS, completion, TUI, repo-wide CLI diagnostics |
Version history
| Version | Date | Changes |
|---|---|---|
| 0.2.0 | 2026-07-19 | Early version: multi-server routing, didChange, diagnostic aggregation; returned on first push; blindly called codeAction/resolve; server exits gave only exit code, stderr lost |
| 0.3.0 | 2026-07-21 | push settle window + LSP 3.17 pull diagnostics dual-track; call resolveProvider/diagnosticProvider only when declared; stderr captured into timeout/exit errors; .bat/.cmd via cmd.exe wrap; PI_<NAME>_LSP_COMMAND override; overlapping TextEdit detection prevents file corruption |
0.2.0 lesson: servers like intelephense push empty diagnostics first then real ones — returning on first push reported erroneous files as clean; server crashes gave only exit codes, making debugging guesswork. 0.3.0 fills in the protocol details.
Appendix: pi LSP ecosystem capability comparison
Objective capability differences across four projects (omp / OpenCode / pi-extensions pi-lsp / piex lsp).
| Capability | omp | OpenCode | pi-extensions | piex |
|---|---|---|---|---|
| Action surface | 14 actions | experimental, off by default | 2 (diagnostics+fix) | 13 actions |
| Process model | session-reuse | session-reuse | spawn-per-call | session-reuse |
| push settle window | ❌ | ❌ | ✅ | ✅ (800ms) |
| LSP 3.17 pull diagnostics | ❌ | ❌ | ✅ | ✅ |
| Post-edit diagnostics | writethrough | edit injects ERROR | ❌ | tool_result appends ERROR |
| stderr capture | ❌ | ❌ | ✅ | ✅ (cap 16KB) |
| Overlapping TextEdit guard | ❌ | ❌ | ✅ | ✅ (cwd-limited) |
| Windows .bat/.cmd | — | — | ✅ cmd.exe | ✅ |
| rename / format | ✅ | ❌ | ❌ | ✅ (preview default) |
| Unit tests | — | — | ❌ | ✅ (mock server) |
Source Markdown: docs/packages/lsp.md