# `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).
