Files
cleveragents-core/robot/helper_lsp_functional_runtime.py
hamza.khyari 4ff075e0da
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 18s
CI / quality (pull_request) Successful in 32s
CI / typecheck (pull_request) Successful in 56s
CI / security (pull_request) Successful in 57s
CI / lint (pull_request) Successful in 3m19s
CI / integration_tests (pull_request) Successful in 3m44s
CI / unit_tests (pull_request) Successful in 6m39s
CI / docker (pull_request) Successful in 1m9s
CI / e2e_tests (pull_request) Successful in 9m38s
CI / coverage (pull_request) Successful in 11m19s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Failing after 1h13m38s
feat(lsp): implement functional LSP runtime
Replace local-mode stubs with real LSP protocol support:

- StdioTransport: subprocess management with JSON-RPC framing
- LspClient: LSP protocol (initialize/shutdown/diagnostics/completions)
- LspLifecycleManager: reference-counted instances, health checks, crash restart
- LspRuntime: registry-based server lookup, auto-restart on crash
- LspToolAdapter: runtime-delegating handlers with local-mode fallback
- LanguageDiscovery: 4-layer detection (extension, shebang, UKO, project)
- activate_bindings/deactivate_bindings: actor compiler LSP binding wiring

Tests: 27 Behave scenarios, 6 Robot integration tests, 250 existing pass.

ISSUES CLOSED: #826
2026-03-24 13:01:12 +00:00

145 lines
4.2 KiB
Python

"""Robot Framework helper for LSP functional runtime integration tests."""
from __future__ import annotations
import importlib
import sys
from pathlib import Path
# Ensure local source tree is importable
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
import cleveragents # noqa: E402
importlib.reload(cleveragents)
from cleveragents.lsp.discovery import LanguageDiscovery # noqa: E402
from cleveragents.lsp.errors import LspServerNotFoundError # noqa: E402
from cleveragents.lsp.lifecycle import LspLifecycleManager # noqa: E402
from cleveragents.lsp.models import LspCapability, LspServerConfig # noqa: E402
from cleveragents.lsp.runtime import LspRuntime # noqa: E402
from cleveragents.lsp.tool_adapter import LspToolAdapter # noqa: E402
from cleveragents.lsp.transport import StdioTransport # noqa: E402
def _run_transport_lifecycle() -> None:
"""Test transport create/alive check."""
t = StdioTransport(command="cat")
assert not t.is_alive
t.start()
assert t.is_alive
t.stop()
assert not t.is_alive
print("transport-lifecycle-ok")
def _run_transport_empty_command() -> None:
"""Test transport rejects empty command."""
try:
StdioTransport(command="")
print("FAIL: expected ValueError")
sys.exit(1)
except ValueError:
pass
print("transport-empty-command-ok")
def _run_lifecycle_manager_empty() -> None:
"""Test lifecycle manager starts empty."""
mgr = LspLifecycleManager()
assert len(mgr.list_running()) == 0
assert not mgr.health_check("local/test")
try:
mgr.stop_server("local/test")
print("FAIL: expected LspServerNotFoundError")
sys.exit(1)
except LspServerNotFoundError:
pass
print("lifecycle-manager-empty-ok")
def _run_runtime_validation() -> None:
"""Test runtime input validation."""
rt = LspRuntime()
errors = 0
try:
rt.start_server("", "/tmp")
except ValueError:
errors += 1
try:
rt.start_server("local/test", "")
except ValueError:
errors += 1
try:
rt.start_server("local/missing", "/tmp")
except LspServerNotFoundError:
errors += 1
assert errors == 3, f"Expected 3 validation errors, got {errors}"
print("runtime-validation-ok")
def _run_discovery() -> None:
"""Test language discovery."""
d = LanguageDiscovery()
assert d.detect_file_language("test.py") == "python"
assert d.detect_file_language("app.ts") == "typescript"
assert d.detect_file_language("main.rs") == "rust"
assert d.detect_file_language("unknown.xyz") == "plaintext"
assert d.detect_file_language("Dockerfile") == "dockerfile"
d2 = LanguageDiscovery(project_languages=["go"])
assert d2.detect_file_language("no_ext") == "go"
print("discovery-ok")
def _run_tool_adapter_modes() -> None:
"""Test tool adapter local vs runtime mode."""
config = LspServerConfig(
name="local/test",
command="echo",
languages=["python"],
capabilities=[LspCapability.DIAGNOSTICS, LspCapability.COMPLETIONS],
)
# Local mode
adapter_local = LspToolAdapter()
specs_local = adapter_local.generate_tool_specs(config)
assert len(specs_local) == 2
assert specs_local[0]["name"] == "local/test/diagnostics"
# Runtime mode
adapter_rt = LspToolAdapter(runtime=LspRuntime())
specs_rt = adapter_rt.generate_tool_specs(config)
assert len(specs_rt) == 2
assert specs_rt[0]["name"] == "local/test/diagnostics"
print("tool-adapter-modes-ok")
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "all"
dispatch = {
"transport-lifecycle": _run_transport_lifecycle,
"transport-empty-command": _run_transport_empty_command,
"lifecycle-manager-empty": _run_lifecycle_manager_empty,
"runtime-validation": _run_runtime_validation,
"discovery": _run_discovery,
"tool-adapter-modes": _run_tool_adapter_modes,
}
if cmd == "all":
for fn in dispatch.values():
fn()
elif cmd in dispatch:
dispatch[cmd]()
else:
print(f"Unknown command: {cmd}")
sys.exit(1)