docs: add LSP API reference, update module index, and add changelog entries for 2026-04-05 merged PRs #3282
+40
-1681
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@ classes, functions, and exceptions with signatures and usage examples.
|
||||
| [`cleveragents.skills`](skills.md) | Skill framework — schema, protocol, registry, discovery, and inline executor |
|
||||
| [`cleveragents.tool`](tool.md) | Tool runtime, lifecycle, registry, router, and container executor |
|
||||
| [`cleveragents.mcp`](mcp.md) | Model Context Protocol (MCP) adapter, client, registry, and sandbox |
|
||||
| [`cleveragents.lsp`](lsp.md) | Language Server Protocol (LSP) lifecycle manager, client, runtime, tool adapter, and registry |
|
||||
| [`cleveragents.resource`](resource.md) | Resource schema, handlers, and type-inheritance system |
|
||||
| [`cleveragents.config`](config.md) | Application settings, logging, metrics, and security scanning |
|
||||
| [`cleveragents.tui`](tui.md) | Interactive Terminal UI — app, persona system, input routing, slash commands, session export/import |
|
||||
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
# `cleveragents.lsp` — Language Server Protocol Integration
|
||||
|
||||
The `lsp` package manages Language Server Protocol (LSP) server processes and
|
||||
provides code intelligence tools (diagnostics, completions, hover, definitions,
|
||||
symbols) to actors.
|
||||
|
||||
See [ADR-027](../adr/ADR-027-language-server-protocol.md) for design rationale.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
from cleveragents.lsp import (
|
||||
LspRegistry,
|
||||
LspRuntime,
|
||||
LspServer,
|
||||
LspToolAdapter,
|
||||
LspServerConfig,
|
||||
LspCapability,
|
||||
LspClient,
|
||||
LspLifecycleManager,
|
||||
LspTransport,
|
||||
StdioTransport,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Models
|
||||
|
||||
**Module:** `cleveragents.lsp.models`
|
||||
|
||||
### `LspServerConfig`
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class LspServerConfig:
|
||||
name: str # namespaced server name, e.g. "lsp/pyright"
|
||||
command: str # executable to launch
|
||||
args: list[str] = field(default_factory=list)
|
||||
env: dict[str, str] = field(default_factory=dict)
|
||||
description: str = "" # max 1000 chars
|
||||
transport: LspTransport = LspTransport.STDIO
|
||||
initialization: dict[str, Any] = field(default_factory=dict)
|
||||
workspace_settings: dict[str, Any] = field(default_factory=dict)
|
||||
```
|
||||
|
||||
Configuration for a single LSP server instance. `transport` defaults to
|
||||
`LspTransport.STDIO`; TCP transport is reserved for future use.
|
||||
`initialization` is forwarded as `initializationOptions` in the LSP
|
||||
`initialize` handshake; `workspace_settings` is sent via
|
||||
`workspace/didChangeConfiguration`.
|
||||
|
||||
### `LspTransport`
|
||||
|
||||
```python
|
||||
class LspTransport(str, Enum):
|
||||
STDIO = "stdio"
|
||||
TCP = "tcp" # reserved — not yet implemented
|
||||
```
|
||||
|
||||
### `LspCapability`
|
||||
|
||||
```python
|
||||
class LspCapability(str, Enum):
|
||||
DIAGNOSTICS = "diagnostics"
|
||||
COMPLETIONS = "completions"
|
||||
HOVER = "hover"
|
||||
DEFINITIONS = "definitions"
|
||||
SIGNATURE_HELP = "signature_help"
|
||||
DOCUMENT_SYMBOLS = "document_symbols"
|
||||
WORKSPACE_SYMBOLS = "workspace_symbols"
|
||||
FORMATTING = "formatting"
|
||||
RENAME = "rename"
|
||||
REFERENCES = "references"
|
||||
CODE_ACTIONS = "code_actions"
|
||||
```
|
||||
|
||||
All 11 capabilities are advertised during `initialize()`. Tool adapter
|
||||
dispatches each capability to the corresponding `LspRuntime` method.
|
||||
|
||||
### `LspBinding`
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class LspBinding:
|
||||
server: str # server name
|
||||
capabilities: list[LspCapability]
|
||||
```
|
||||
|
||||
Per-node LSP binding declared in actor YAML. Controls which server each
|
||||
actor node uses and which capabilities it exposes.
|
||||
|
||||
---
|
||||
|
||||
## `LspLifecycleManager`
|
||||
|
||||
**Module:** `cleveragents.lsp.lifecycle`
|
||||
|
||||
Thread-safe manager for running LSP server instances with reference counting.
|
||||
Multiple actors can share one server; the process is only stopped when the
|
||||
last reference is released.
|
||||
|
||||
```python
|
||||
from cleveragents.lsp import LspLifecycleManager, LspServerConfig
|
||||
|
||||
manager = LspLifecycleManager()
|
||||
```
|
||||
|
||||
### `start_server(config, workspace_path) -> LspClient`
|
||||
|
||||
Start or acquire a reference to an LSP server.
|
||||
|
||||
If a server with the same name is already running for the same workspace, its
|
||||
reference count is incremented and the existing client is returned. Uses a
|
||||
**3-phase lock pattern** to avoid holding the internal lock during blocking I/O
|
||||
(server startup can take up to 60 seconds):
|
||||
|
||||
- **Phase 1** (short lock): check for an existing live server.
|
||||
- **Phase 2** (no lock): spawn the process and perform the LSP handshake.
|
||||
- **Phase 3** (short lock): commit the new server into shared state, handling
|
||||
the race where another thread started the same server concurrently.
|
||||
|
||||
```python
|
||||
client = manager.start_server(config, workspace_path="/workspace/myproject")
|
||||
```
|
||||
|
||||
**Raises:** `LspError` if the server process cannot be started.
|
||||
|
||||
### `stop_server(name) -> None`
|
||||
|
||||
Release a reference. When the count reaches zero the server is shut down
|
||||
gracefully (`shutdown` + `exit` LSP messages, then process termination).
|
||||
|
||||
**Raises:** `LspServerNotFoundError` if no server with *name* is running.
|
||||
|
||||
### `restart_server(name) -> LspClient`
|
||||
|
||||
Restart a crashed or unresponsive server. Uses the same 3-phase lock pattern
|
||||
as `start_server` — the lock is **released before** blocking I/O so other
|
||||
threads are never blocked during the restart. The old entry is removed from
|
||||
shared state in Phase 1 so concurrent callers see the server as absent during
|
||||
the restart window.
|
||||
|
||||
> **Note (v3.7.0+, fixed PR #3165):** Prior to this fix, `restart_server` held
|
||||
> the internal lock across the entire blocking I/O sequence, causing a deadlock
|
||||
> when another thread called `start_server` or `stop_server` concurrently. The
|
||||
> fix restructures the method to match the 3-phase pattern already used by
|
||||
> `start_server`.
|
||||
|
||||
```python
|
||||
new_client = manager.restart_server("lsp/pyright")
|
||||
```
|
||||
|
||||
**Raises:** `LspServerNotFoundError` if the server was never started.
|
||||
|
||||
### `get_client(name) -> LspClient`
|
||||
|
||||
Return the `LspClient` for a running server without changing the reference
|
||||
count.
|
||||
|
||||
**Raises:** `LspServerNotFoundError` if no server with *name* is running.
|
||||
|
||||
### `health_check(name) -> bool`
|
||||
|
||||
Return `True` if the server process is alive.
|
||||
|
||||
### `stop_all() -> None`
|
||||
|
||||
Shut down all running servers. Called during application cleanup.
|
||||
|
||||
### `list_running() -> list[dict[str, Any]]`
|
||||
|
||||
Return status info for all running servers:
|
||||
|
||||
```python
|
||||
[
|
||||
{
|
||||
"name": "lsp/pyright",
|
||||
"workspace": "/workspace/myproject",
|
||||
"alive": True,
|
||||
"ref_count": 2,
|
||||
"initialized": True,
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `LspClient`
|
||||
|
||||
**Module:** `cleveragents.lsp.client`
|
||||
|
||||
Low-level LSP protocol client. Wraps a `StdioTransport` and implements the
|
||||
JSON-RPC request/notification lifecycle.
|
||||
|
||||
```python
|
||||
from cleveragents.lsp import LspClient, StdioTransport
|
||||
|
||||
transport = StdioTransport(command="pyright-langserver", args=["--stdio"])
|
||||
transport.start()
|
||||
client = LspClient(transport, server_name="lsp/pyright")
|
||||
client.initialize(workspace_path="/workspace/myproject")
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `initialize(workspace_path)` | Perform the LSP `initialize` / `initialized` handshake |
|
||||
| `shutdown()` | Send `shutdown` + `exit` and close the transport |
|
||||
| `get_diagnostics(file_path)` | Return `list[dict]` of LSP diagnostic objects |
|
||||
| `get_completions(file_path, line, character)` | Return completion items |
|
||||
| `get_hover(file_path, line, character)` | Return hover result dict |
|
||||
| `get_definitions(file_path, line, character)` | Return location(s) |
|
||||
| `get_signature_help(file_path, line, character)` | Return signature help |
|
||||
| `get_document_symbols(file_path)` | Return document symbol list |
|
||||
| `get_workspace_symbols(query)` | Return workspace symbol list |
|
||||
| `is_initialized` | `bool` — `True` after a successful handshake |
|
||||
|
||||
All position arguments use **1-based** line/column numbers; the client
|
||||
converts to 0-based internally before sending LSP requests.
|
||||
|
||||
---
|
||||
|
||||
## `LspRuntime`
|
||||
|
||||
**Module:** `cleveragents.lsp.runtime`
|
||||
|
||||
High-level runtime used by `LspToolAdapter`. Wraps `LspLifecycleManager`
|
||||
and adds input validation, file reading, language detection, and coordinate
|
||||
conversion.
|
||||
|
||||
```python
|
||||
from cleveragents.lsp import LspRuntime
|
||||
|
||||
runtime = LspRuntime(lifecycle_manager=manager)
|
||||
diagnostics = runtime.get_diagnostics("lsp/pyright", "/workspace/myproject/main.py")
|
||||
```
|
||||
|
||||
When no runtime is supplied to `LspToolAdapter`, tool handlers fall back to
|
||||
raising `LspNotAvailableError`.
|
||||
|
||||
---
|
||||
|
||||
## `LspToolAdapter`
|
||||
|
||||
**Module:** `cleveragents.lsp.tool_adapter`
|
||||
|
||||
Registers LSP capabilities as tools in `ToolRegistry`.
|
||||
|
||||
```python
|
||||
from cleveragents.lsp import LspToolAdapter
|
||||
|
||||
adapter = LspToolAdapter(runtime=runtime)
|
||||
adapter.register_tools(tool_registry, bindings=[binding])
|
||||
```
|
||||
|
||||
Each `LspCapability` maps to a tool with a JSON Schema spec:
|
||||
|
||||
| Capability | Tool name | Key parameters |
|
||||
|------------|-----------|----------------|
|
||||
| `DIAGNOSTICS` | `lsp_diagnostics` | `file_path` |
|
||||
| `COMPLETIONS` | `lsp_completions` | `file_path`, `line`, `character` |
|
||||
| `HOVER` | `lsp_hover` | `file_path`, `line`, `character` |
|
||||
| `DEFINITIONS` | `lsp_definitions` | `file_path`, `line`, `character` |
|
||||
| `SIGNATURE_HELP` | `lsp_signature_help` | `file_path`, `line`, `character` |
|
||||
| `DOCUMENT_SYMBOLS` | `lsp_document_symbols` | `file_path` |
|
||||
| `WORKSPACE_SYMBOLS` | `lsp_workspace_symbols` | `query` |
|
||||
| `FORMATTING` | `lsp_formatting` | `file_path` |
|
||||
| `RENAME` | `lsp_rename` | `file_path`, `line`, `character`, `new_name` |
|
||||
| `REFERENCES` | `lsp_references` | `file_path`, `line`, `character` |
|
||||
| `CODE_ACTIONS` | `lsp_code_actions` | `file_path`, `line`, `character` |
|
||||
|
||||
---
|
||||
|
||||
## `LspRegistry`
|
||||
|
||||
**Module:** `cleveragents.lsp.registry`
|
||||
|
||||
Stores `LspServerConfig` objects indexed by name. Used by the DI container
|
||||
to resolve server configs at runtime.
|
||||
|
||||
```python
|
||||
from cleveragents.lsp import LspRegistry
|
||||
|
||||
registry = LspRegistry()
|
||||
registry.register(config)
|
||||
server_config = registry.get("lsp/pyright")
|
||||
all_configs = registry.list_all()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `LspServer`
|
||||
|
||||
**Module:** `cleveragents.lsp.server`
|
||||
|
||||
Domain model representing a registered LSP server entry (name, config, status).
|
||||
Distinct from the internal `_ManagedServer` lifecycle state holder.
|
||||
|
||||
---
|
||||
|
||||
## `LanguageDiscovery`
|
||||
|
||||
**Module:** `cleveragents.lsp.discovery`
|
||||
|
||||
4-layer language detection for a given file path:
|
||||
|
||||
1. File extension mapping
|
||||
2. Shebang line parsing
|
||||
3. UKO ontology classification
|
||||
4. Project config heuristics
|
||||
|
||||
```python
|
||||
from cleveragents.lsp import LanguageDiscovery
|
||||
|
||||
discovery = LanguageDiscovery()
|
||||
language = discovery.detect("/workspace/myproject/main.py") # -> "python"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `StdioTransport`
|
||||
|
||||
**Module:** `cleveragents.lsp.transport`
|
||||
|
||||
Manages the LSP server subprocess over stdin/stdout JSON-RPC.
|
||||
|
||||
```python
|
||||
from cleveragents.lsp import StdioTransport
|
||||
|
||||
transport = StdioTransport(
|
||||
command="pyright-langserver",
|
||||
args=["--stdio"],
|
||||
env={},
|
||||
cwd="/workspace/myproject",
|
||||
)
|
||||
transport.start()
|
||||
# ... use transport ...
|
||||
transport.stop()
|
||||
```
|
||||
|
||||
| Property/Method | Description |
|
||||
|-----------------|-------------|
|
||||
| `is_alive` | `bool` — `True` if the subprocess is running |
|
||||
| `start()` | Spawn the subprocess |
|
||||
| `stop()` | Terminate the subprocess gracefully |
|
||||
|
||||
---
|
||||
|
||||
## Error Types
|
||||
|
||||
**Module:** `cleveragents.lsp.errors`
|
||||
|
||||
| Exception | Description |
|
||||
|-----------|-------------|
|
||||
| `LspError` | Base LSP exception |
|
||||
| `LspNotAvailableError` | LSP runtime not configured for this actor node |
|
||||
| `LspServerNotFoundError` | No server with the given name is running |
|
||||
|
||||
---
|
||||
|
||||
## Actor YAML Integration
|
||||
|
||||
LSP bindings are declared per-node in actor YAML:
|
||||
|
||||
```yaml
|
||||
name: local/python-dev
|
||||
entry_node: analyst
|
||||
nodes:
|
||||
analyst:
|
||||
model: gpt-4o
|
||||
tool_sources: [builtin]
|
||||
lsp_bindings:
|
||||
- server: lsp/pyright
|
||||
capabilities: [diagnostics, completions, hover, definitions]
|
||||
```
|
||||
|
||||
The `ActorCompiler` resolves bindings at compile time and wires the
|
||||
`LspToolAdapter` into the node's tool registry.
|
||||
Reference in New Issue
Block a user