Files
cleveragents-core/docs/api/lsp.md

253 lines
9.3 KiB
Markdown

# `cleveragents.lsp` — Language Server Protocol Integration
The `cleveragents.lsp` package wires Language Server Protocol (LSP) features
into CleverAgents. It provides:
- Pydantic models describing servers, transports, and bindings.
- A thread-safe registry for language server configurations.
- Runtime orchestration (start, health-check, restart, stop) with automatic
reference counting.
- A synchronous protocol client and lifecycle manager around stdio transports.
- A tool adapter that exposes LSP capabilities to the generic tool system.
- A multi-layer language discovery engine that selects servers for files.
All exports listed below are available directly from `cleveragents.lsp`.
---
## Quick start
```python
from cleveragents.lsp import (
LanguageDiscovery,
LspCapability,
LspRegistry,
LspRuntime,
LspServerConfig,
LspToolAdapter,
)
registry = LspRegistry()
registry.register(
LspServerConfig(
name="local/pyright",
description="Pyright language server",
command="pyright-langserver",
languages=["python"],
capabilities=[
LspCapability.DIAGNOSTICS,
LspCapability.COMPLETIONS,
LspCapability.HOVER,
],
)
)
runtime = LspRuntime(registry=registry)
runtime.start_server("local/pyright", workspace_path="/workspace")
adapter = LspToolAdapter(runtime)
pyright_tools = adapter.generate_tool_specs(registry.get_or_raise("local/pyright"))
print([spec["name"] for spec in pyright_tools])
# ['local/pyright/diagnostics', 'local/pyright/completions', 'local/pyright/hover']
language = LanguageDiscovery().detect_file_language("/workspace/app/models.py")
print(language) # 'python'
```
---
## Models
### `LspTransport`
`StrEnum` describing how the runtime communicates with a server:
- `STDIO` (default) — spawn the server and communicate via stdin/stdout JSON-RPC.
- `TCP` — connect to a server listening on a TCP socket.
### `LspCapability`
Enum of LSP features that can be surfaced as ToolSpecs. Members map directly to
protocol operations:
| Capability | Description |
|------------|-------------|
| `DIAGNOSTICS` | Fetch diagnostics, warnings, and errors for a file. |
| `HOVER` | Retrieve hover information (type hints, docs) at a position. |
| `COMPLETIONS` | Request completion candidates at a position. |
| `DEFINITIONS` | Go to definition for a symbol. |
| `REFERENCES` | Find references to a symbol. |
| `RENAME` | Request a rename operation across the workspace. |
| `CODE_ACTIONS` | Obtain code actions for a range. |
| `FORMATTING` | Format a document or selection. |
| `SIGNATURE_HELP` | Fetch call signature help. |
| `DOCUMENT_SYMBOLS` | List symbols defined in a document. |
| `WORKSPACE_SYMBOLS` | Search for symbols across the workspace. |
> **Note:** The runtime currently provides concrete handlers for diagnostics,
> completions, hover, and definitions. Other capabilities raise
> `LspNotAvailableError` until their protocol flows are implemented.
### `LspServerConfig`
Pydantic model describing a language server.
| Field | Type | Description |
|-------|------|-------------|
| `name` | `str` | Namespaced identifier (`namespace/name`); validated to contain a slash. |
| `description` | `str` | Optional human-readable description. |
| `languages` | `list[str]` | Lowercased language identifiers served by the process. |
| `command` | `str` | Executable used to start the server. |
| `args` | `list[str]` | Extra CLI arguments. |
| `transport` | `LspTransport` | Communication mode (`STDIO` by default). |
| `env` | `dict[str, str]` | Environment overrides for the process. |
| `capabilities` | `list[LspCapability]` | Enabled protocol features. |
| `initialization` | `dict[str, Any]` | `initialize` request `initializationOptions`. |
| `workspace_settings` | `dict[str, Any]` | Settings pushed after initialization. |
Helper properties:
- `namespace` — portion before the last `/`.
- `short_name` — portion after the last `/`.
- `to_dict()` — JSON-compatible serialisation respecting model validators.
### `LspBinding`
Binds a server to an actor graph node. Used by the compiler/runtime to know
which servers to activate. Validates that `lsp_server_name` is namespaced and
supports optional per-node language filters.
---
## Registry and discovery
### `LspRegistry`
Thread-safe registry for `LspServerConfig` objects. Public methods acquire a
re-entrant lock, making concurrent registration and lookup safe.
- `register(config)` — add a new server; rejects duplicates.
- `get(name)` — return a config or `None` when missing.
- `get_or_raise(name)` — raise `LspServerNotFoundError` if absent.
- `list_servers(namespace=None, language=None)` — filter by namespace prefix or
supported language, sorted by name.
- `remove(name)` — unregister a server; returns `True` if removed.
### `LanguageDiscovery`
Four-layer language detection engine used by the runtime and compiler. Layers
are consulted in priority order:
1. File extension (`.py`, `.tsx`, …) via a built-in lookup table.
2. Shebang inspection for ambiguous or extension-less files.
3. Optional UKO (Universal Knowledge Ontology) classifications supplied at
construction.
4. Explicit project-level language declarations passed to the constructor.
Key methods:
- `detect_file_language(path)` — returns a language ID (falls back to
`"plaintext"`). Results are cached per path.
- `detect_directory_languages(path)` — walk a tree and return sorted unique
languages (ignoring `plaintext`).
- `invalidate(path)` / `invalidate_all()` — manage the detection cache.
- `get_servers_for_language(language, configs)` — helper to match registered
servers to a detected language.
---
## Runtime orchestration
### `LspLifecycleManager`
Manages running server processes with reference counting. Guarantees that the
underlying transport is started only once per server/workspace combination and
shutdown occurs when the last reference is released. Provides:
- `start_server(config, workspace_path)` — start or reuse a server, returning an
`LspClient`.
- `stop_server(name)` — release a reference; raises `LspServerNotFoundError` if
the server was never acquired.
- `health_check(name)` — return `True` if the process is alive.
- `restart_server(name)` — restart a crashed server while preserving reference
counts.
- `stop_all()` / `list_running()` — maintenance helpers for graceful shutdown
and observability.
### `LspClient`
Synchronous JSON-RPC client layered on top of `StdioTransport`. Exposes typed
methods for the protocol flows currently needed by CleverAgents:
- `initialize(workspace_path, initialization_options=None)` and `shutdown()` for
lifecycle management.
- Document operations: `did_open`, `did_close`, `get_diagnostics`,
`get_completions`, `get_hover`, `get_definitions`.
- Accessors `is_initialized` and `server_capabilities` for telemetry or testing.
Internally the client keeps a bounded queue of notifications, correlates
responses by ID, and raises `LspError` on timeouts or protocol failures.
### `LspRuntime`
High-level facade combining the registry and lifecycle manager. It performs
input validation, logs significant events, and exposes convenience methods:
- `start_server(name, workspace_path)` / `stop_server(name)` — manage process
lifecycle.
- `get_diagnostics`, `get_completions`, `get_hover`, `get_definitions` — delegate
to the underlying `LspClient`, reading file contents and translating between
filesystem paths and LSP URIs.
- `activate_bindings(bindings, workspace_path)` and `deactivate_bindings(bindings)`
start/stop all servers referenced by `LspBinding` objects produced during actor
compilation.
- `stop_all()` — forward to `LspLifecycleManager.stop_all()`.
If a server crashes, `LspRuntime` attempts to restart it automatically before
servicing the next request.
### `StdioTransport`
Process wrapper that launches language servers via subprocess, reads/writes
JSON-RPC messages, and exposes `is_alive`, `start()`, `stop()`, `send_message()`,
`read_message()`. It is the default transport used by the lifecycle manager and
client.
---
## Tool adapter
### `LspToolAdapter`
Generates tool specifications from `LspServerConfig.capabilities` so the
standard tool runtime can invoke language-server functionality.
```python
from cleveragents.lsp import LspToolAdapter
adapter = LspToolAdapter(runtime)
for spec in adapter.generate_tool_specs(config):
tool_registry.register(spec)
```
- When constructed with an `LspRuntime`, capability handlers delegate to runtime
methods (diagnostics, completions, hover, definitions today). Missing runtime
capabilities raise `LspNotAvailableError` with a structured payload.
- Without a runtime (local/offline mode), generated handlers always raise
`LspNotAvailableError`, signalling that remote execution is required.
- Each spec includes a minimal JSON Schema input contract tailored to the
capability (file-only, position-based, rename, or query-based).
---
## Error types
The package exports structured exceptions for callers to handle:
- `LspError` — generic protocol or transport failure.
- `LspNotAvailableError` — capability invoked without an available runtime.
- `LspServerNotFoundError` — registry or lifecycle lookup of an unknown server.
Catch these errors in higher layers to provide user-facing remediation (e.g.
retry prompts, error banners in the TUI, or fallback tool behaviour).