9.3 KiB
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
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
LspNotAvailableErroruntil 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 orNonewhen missing.get_or_raise(name)— raiseLspServerNotFoundErrorif absent.list_servers(namespace=None, language=None)— filter by namespace prefix or supported language, sorted by name.remove(name)— unregister a server; returnsTrueif removed.
LanguageDiscovery
Four-layer language detection engine used by the runtime and compiler. Layers are consulted in priority order:
- File extension (
.py,.tsx, …) via a built-in lookup table. - Shebang inspection for ambiguous or extension-less files.
- Optional UKO (Universal Knowledge Ontology) classifications supplied at construction.
- 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 (ignoringplaintext).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 anLspClient.stop_server(name)— release a reference; raisesLspServerNotFoundErrorif the server was never acquired.health_check(name)— returnTrueif 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)andshutdown()for lifecycle management.- Document operations:
did_open,did_close,get_diagnostics,get_completions,get_hover,get_definitions. - Accessors
is_initializedandserver_capabilitiesfor 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 underlyingLspClient, reading file contents and translating between filesystem paths and LSP URIs.activate_bindings(bindings, workspace_path)anddeactivate_bindings(bindings)— start/stop all servers referenced byLspBindingobjects produced during actor compilation.stop_all()— forward toLspLifecycleManager.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.
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 raiseLspNotAvailableErrorwith 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).