Files
python-sdk/pkg/hanzo-lsp/tests/mock_lsp_server.py
zeekay ebb04882d6 feat: hanzo-dev Python TUI + LSP + hooks + sandbox + vim + git + multi-auth + full parity
Rename hanzo-repl → hanzo-dev to match @hanzo/dev (Rust/TS).

New packages:
- hanzo-lsp: async LSP client (diagnostics, go-to-def, references, context enrichment)
- hanzo-hooks: pre/post tool use hooks (shell scripts, exit-code deny)
- hanzo-sandbox: container detection, filesystem isolation, Linux unshare

hanzo-dev TUI:
- Vim keybindings (Normal/Insert/Visual/Command modes, 37 tests)
- Git slash commands (/branch /commit /worktree /diff /stash, 13 tests)
- All slash commands at parity: /model /permissions /clear /resume /memory
  /init /export /session /plan /solve /code /auto /fast /remote-control
- Multi-provider auth: Anthropic OAuth, OpenAI device code, Hanzo PKCE, auto-detect

Auth:
- login_with_anthropic() — PKCE via console.hanzo.ai
- login_with_openai() — device code flow via auth.openai.com
- login_auto() — env var detection + saved credential fallback
- OAuthCredentialStore supports provider keys (hanzo/anthropic/openai)

Parity test suite (38 tests): SSE, MCP normalization, permissions, compaction,
token usage, PKCE RFC vector, config, backoff, session roundtrip, hooks.

348 tests total, 0 failures across Python + Rust + Universe E2E.
2026-04-01 10:41:46 -07:00

114 lines
3.4 KiB
Python

"""Mock LSP server for integration tests. Speaks Content-Length framed JSON-RPC."""
import json
import sys
def read_message():
headers = {}
while True:
line = sys.stdin.buffer.readline()
if not line:
return None
if line == b"\r\n":
break
key, value = line.decode("utf-8").split(":", 1)
headers[key.strip().lower()] = value.strip()
length = int(headers["content-length"])
body = sys.stdin.buffer.read(length)
return json.loads(body)
def write_message(payload):
raw = json.dumps(payload).encode("utf-8")
sys.stdout.buffer.write(f"Content-Length: {len(raw)}\r\n\r\n".encode("utf-8"))
sys.stdout.buffer.write(raw)
sys.stdout.buffer.flush()
while True:
message = read_message()
if message is None:
break
method = message.get("method")
if method == "initialize":
write_message({
"jsonrpc": "2.0",
"id": message["id"],
"result": {
"capabilities": {
"definitionProvider": True,
"referencesProvider": True,
"textDocumentSync": 1,
}
},
})
elif method == "initialized":
continue
elif method == "textDocument/didOpen":
document = message["params"]["textDocument"]
write_message({
"jsonrpc": "2.0",
"method": "textDocument/publishDiagnostics",
"params": {
"uri": document["uri"],
"diagnostics": [
{
"range": {
"start": {"line": 0, "character": 0},
"end": {"line": 0, "character": 3},
},
"severity": 1,
"source": "mock-server",
"message": "mock error",
}
],
},
})
elif method == "textDocument/didChange":
continue
elif method == "textDocument/didSave":
continue
elif method == "textDocument/definition":
uri = message["params"]["textDocument"]["uri"]
write_message({
"jsonrpc": "2.0",
"id": message["id"],
"result": [
{
"uri": uri,
"range": {
"start": {"line": 0, "character": 0},
"end": {"line": 0, "character": 3},
},
}
],
})
elif method == "textDocument/references":
uri = message["params"]["textDocument"]["uri"]
write_message({
"jsonrpc": "2.0",
"id": message["id"],
"result": [
{
"uri": uri,
"range": {
"start": {"line": 0, "character": 0},
"end": {"line": 0, "character": 3},
},
},
{
"uri": uri,
"range": {
"start": {"line": 1, "character": 4},
"end": {"line": 1, "character": 7},
},
},
],
})
elif method == "shutdown":
write_message({"jsonrpc": "2.0", "id": message["id"], "result": None})
elif method == "exit":
break