Compare commits

...

3 Commits

Author SHA1 Message Date
HAL9000 7ae95ad229 docs(api): link shared and LSP references
CI / push-validation (pull_request) Successful in 52s
CI / helm (pull_request) Successful in 1m25s
CI / build (pull_request) Successful in 8m2s
CI / lint (pull_request) Successful in 8m46s
CI / security (pull_request) Successful in 9m18s
CI / quality (pull_request) Successful in 9m25s
CI / integration_tests (pull_request) Failing after 9m44s
CI / typecheck (pull_request) Successful in 10m1s
CI / e2e_tests (pull_request) Successful in 13m54s
CI / unit_tests (pull_request) Failing after 16m23s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Successful in 11m26s
2026-04-13 22:29:53 +00:00
HAL9000 d8ee6e7fac docs(api): describe LSP integration 2026-04-13 22:29:35 +00:00
HAL9000 3cd49dda8e docs(api): document shared redaction helpers 2026-04-13 22:29:18 +00:00
3 changed files with 371 additions and 0 deletions
+2
View File
@@ -9,10 +9,12 @@ classes, functions, and exceptions with signatures and usage examples.
| Module | Description |
|--------|-------------|
| [`cleveragents.core`](core.md) | Exception hierarchy, error handling, retry patterns, async cleanup |
| [`cleveragents.shared`](shared.md) | Secret redaction utilities for logs, telemetry, and structured errors |
| [`cleveragents.a2a`](a2a.md) | Agent-to-Agent (A2A) protocol facade, transport, models, and events |
| [`cleveragents.actor`](actor.md) | Actor registry, configuration, loader, and compiler |
| [`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.lsp`](lsp.md) | Language Server Protocol integration — registry, runtime, client, and tool adapter |
| [`cleveragents.mcp`](mcp.md) | Model Context Protocol (MCP) adapter, client, registry, and sandbox |
| [`cleveragents.resource`](resource.md) | Resource schema, handlers, and type-inheritance system |
| [`cleveragents.config`](config.md) | Application settings, logging, metrics, and security scanning |
+252
View File
@@ -0,0 +1,252 @@
# `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).
+117
View File
@@ -0,0 +1,117 @@
# `cleveragents.shared` — Secret Redaction Utilities
The `cleveragents.shared` package exposes cross-cutting helpers that can be
used from any layer without violating architecture boundaries. At the moment
its public surface focuses on a single responsibility: deterministic masking of
secrets before they reach logs, error payloads, or tool output.
All exports live in `cleveragents.shared.redaction` and are re-exported from the
package root for convenience.
---
## Quick start
```python
from cleveragents.shared import redact_dict, register_pattern
# redact nested structures before logging
payload = {
"provider_api_key": "sk-ant-api03-secret-1234567890",
"token_usage": 1280,
"upstream": {
"authorization": "Bearer 0123456789ABCDEFGHIJKLMNOPQRSTUV",
},
}
print(redact_dict(payload))
# {'provider_api_key': '***REDACTED***', 'token_usage': 1280,
# 'upstream': {'authorization': '***REDACTED***'}}
# extend detection with an internal pattern
register_pattern(r"my-secret-[0-9a-f]{16}")
```
---
## Global redaction controls
| Symbol | Description |
|--------|-------------|
| `REDACTED` | Constant string (`***REDACTED***`) used as the replacement token. |
| `get_show_secrets() -> bool` | Returns the global flag that determines whether redaction is bypassed. |
| `set_show_secrets(value: bool) -> None` | Toggles the flag. Accepts only boolean values and is thread-safe. |
When `get_show_secrets()` returns `True`, the redaction helpers shortcut and
return the original data unmodified—useful for debugging in trusted
environments. The flag is protected by a lock so it can be flipped safely from
CLI options or unit tests.
---
## Key helpers
### `is_sensitive_key(name: str) -> bool`
Heuristically classifies dictionary keys as sensitive (e.g. containing
"token", "secret", "password"). False positives such as `token_count`
are explicitly whitelisted in the implementation.
### `redact_value(value: str) -> str`
Scans a string for known secret patterns (OpenAI, Anthropic, Google keys,
`tok_` IDs, bearer tokens, long hex strings) and replaces each match with
`REDACTED`. Additional patterns can be registered at runtime via
`register_pattern()`.
### `redact_dict(data: Mapping[str, Any], *, show_secrets: bool | None = None) -> dict`
Recursively traverses nested dictionaries and lists, redacting sensitive keys
with `REDACTED` and scrubbing string values via `redact_value`. By default it
honours the global `get_show_secrets()` flag; override it per call with
`show_secrets=True` when you explicitly need the original values.
### `mask_database_url(url: str) -> str`
Masks the password segment of SQL connection URLs. SQLite URLs are returned
unchanged; any `scheme://user:password@host` pattern is rewritten as
`scheme://user:***@host`.
### `register_pattern(pattern: str) -> None`
Allows projects or extensions to add custom regular expressions to the
redaction list. Patterns are compiled under a lock so multiple workers can
register safely. Passing an empty pattern raises `ValueError`.
---
## Structlog integration
`secrets_masking_processor(logger, method_name, event_dict)` implements the
structlog processor contract. It walks every value in the event dictionary,
redacting keys detected by `is_sensitive_key` and values using `redact_value`
and `redact_dict`. The processor is designed to be inserted near the top of the
structlog pipeline so that downstream formatters never observe raw secrets.
```python
import structlog
from cleveragents.shared import secrets_masking_processor
structlog.configure(
processors=[
secrets_masking_processor,
structlog.processors.JSONRenderer(),
],
)
```
---
## Thread-safety notes
The module guards both the global `show_secrets` flag and the list of compiled
patterns with locks. You can safely call `set_show_secrets()` or
`register_pattern()` from concurrent tasks without corrupting shared state.
Because all helpers return new dictionaries (never mutating inputs), you may
confidently hand the redacted payloads to logging subsystems or telemetry
pipelines without affecting the original objects used by business logic.