LSP Engine
The cosh_sdk::lsp module is a compact, tokio-native LSP client stack for
driving language servers from an agent process — no editor UI involved.
Layering
Four layers, each testable in isolation:
| Layer | Module | Responsibility |
|---|---|---|
| Framing | jsonrpc |
Content-Length framing + message classification |
| Transport | transport |
I/O tasks, request routing, timeouts, backpressure |
| Client | client |
Handshake, document sync, server→client dispatch |
| Manager | manager |
Discovery, lazy spawning, fan-out, lifecycle |
A fifth concern — diagnostics aggregation — lives in
diagnostics.rs and sits between the manager’s event
stream and the tools.
Design principles
-
Behavioral port, not vendor. The engine re-expresses battle-tested designs from Zed (
crates/lsp), Helix (helix-lsp) and opencode (TS) using our own architecture on tokio +lsp-types. No GPL code, no gpui, no coupled crates. -
Disk is the source of truth. No editor buffer: documents are opened from disk via
touch_file, versions are internal counters, and stale content never reaches a server. The documents lock is held across the wire notifications, serializing every toucher of one path — adidChangecan never overtake thedidOpenit depends on, and two concurrent first touches cannot both senddidOpen. The LRU slot is claimed before any wire send so concurrent observers see the pending open; if the send fails (session dying) the slot is rolled back for a clean retry. -
Every failure degrades cleanly. Timeouts, stream death, protocol violations and missing binaries all surface as typed errors — nothing panics, nothing hangs. The transport re-checks the terminal exit watch while holding the pending map lock before issuing a request, so a request against a dead session fails fast with
NotRunninginstead of waiting out its deadline on a response that will never come. -
Backpressure over unbounded buffering. Both the inbound (128 msgs) and outbound (256 frames) queues are bounded; when full, OS pipes apply pressure to the server instead of growing our heap.
-
Canonical keys. Project roots returned by the manager are canonicalized (with Windows verbatim-prefix stripping) so the same directory reached through a symlink or a non-canonical workspace root produces one
ClientKey— never two server processes for one project.
Key types
Transport Handle to one running connection (I/O tasks).
LanguageServer Transport + handshake + doc sync + dispatch loop.
Manager Workspace-level engine: discovery, lazy spawn, fan-out.
DiagnosticsEngine Push+pull store with settle-wait and LLM formatting.
Testing
All layers are tested over in-memory duplex pipes using the fake server
harness in test_support.rs — no real processes needed for CI.
See also
- Diagnostics — push/pull hybrid store
- Tools reference — model-facing API