253 lines
9.3 KiB
Markdown
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).
|