nc://lsp

v0.1.0Pinned
Backnodecode-ai
pomterrecells: thirty-three cells, ported from oh-my-pi -- providers oh-my-pi carries (can1357/oh-my-pi at c9383c1, MIT, credited in each folder's NOTICE), each as a cell of its own: Alibaba's Coding and Token Plans, Amazon Bedrock, Anthropic's Pro/Max sign-in, Apple's on-device Foundation Models (macOS), Azure OpenAI, ClinePass, Cloudflare AI Gateway, Cursor, Devin, Factory Droid, GitHub Copilot, GitLab Duo and its agent, Antigravity, the Gemini CLI's Cloud Code Assist, Vertex AI, Kilo, Kimi Code, local ONNX and MLX workers, Muse Code, Ollama Cloud, ChatGPT's Codex sign-in and its device-code variant, OpenRouter, Snowflake Cortex, xAI's SuperGrok sign-in, Xiaomi MiMo and Z.AI's Coding Plan; and four that are not chat providers: language servers (nodecode-lsp), Perplexity search, keyless web search engines (nodecode-web-provider) and TypeSafe judgments. Each stands alone: it loads, starts, stops and tests by itself, depends only on nodecode, and shares no code with another; one whose wire none of the four lanes speaks registers a lane of its own. Written against the cell contract (src/CELLS.md) at nodecode fb4bdabc1. Their tests ran green against that tree with no network (~/nodecode-cells/tools/test-cell.sh, 663 tests over the 33, 0 failed).914a3fe· 8 Oct1 commit
test2 filescell.lisp4.9 KBclient.lisp29.9 KBrpc.lisp15.2 KBservers.json12.7 KBservers.lisp14.8 KBtext.lisp12.6 KBverbs.lisp32.9 KB

nodecode-lsp

A Nodecode cell that puts language servers behind the model's eval. With it, an eval that writes a file through edit or write-file ends with what the servers say about that file:

wrote src/a.py: 5 lines, 64 characters
LSP diagnostics (1 error):
src/a.py:5:11 [error] [Pyright] Type "int" is not assignable to declared type "str" (reportAssignmentType)

and the model gets a few verbs in the lsp: package:

(lsp:diagnostics "src/a.rs")                     ; every diagnostic, or OK; a list of paths for several
(lsp:definition "src/a.rs" "parse" :line 40)     ; where it is defined, with the line
(lsp:references "src/a.rs" "parse")              ; every use, the declaration included
(lsp:hover "src/a.rs" "parse")                   ; its type and docs
(lsp:symbols "src/a.rs")                         ; the file's outline; :query "Parser" searches the project
(lsp:rename "src/a.rs" "parse" "parse_all")      ; renames across files and writes them; :apply nil previews
(lsp:status)  (lsp:restart)                      ; what runs, and starting over
(lsp:request "rust-analyzer" "rust-analyzer/expandMacro" :params p :path "src/a.rs")

A symbol is found in the file by name: its first mention, "parse#2" the second, :line N on that line. (help :lsp) is the model's manual.

Ported from oh-my-pi's lsp module: its 55 server definitions (servers.json, omp's defaults.json), its server selection and root markers, its diagnostics wait and output format. See NOTICE.

How it runs

  • A server starts the first time a file it serves is written or asked about, from the project's own bins (node_modules/.bin, .venv/bin, ...) or PATH, in the project root its markers name: the outermost directory holding one inside the session's repository, else the nearest. Every session working there shares it. It stops after idle_minutes unused, or when the cell stops.
  • A file gets every server whose file types name it, type checkers before linters. Diagnostics after a write come from all of them; the verbs ask the first.
  • The diagnostics after a write wait at most wait_ms (3 s) for the servers. A server that has not answered by then says lsp: NAME still checking FILE; (lsp:diagnostics "FILE") for the result. At most 50 diagnostics and 4,000 characters, errors and warnings only, the first line of each message. A clean file adds nothing.
  • A verb waits at most 8 s, inside the eval's ten-second yield window; a project still loading answers still indexing; try again.
  • A server's stderr goes to ~/.nodecode/lsp/NAME.log. A server that fails to start says why, with the last lines it printed, and is tried again 30 s later.

Nothing here edits the core: the cell hooks write-file-text, the one atomic writer under edit and write-file, and the :tool point.

Install

Copy this folder into ~/.nodecode/cells/nodecode-lsp/ and restart Nodecode, or run (restart-cells). Install the language servers you want the usual way (npm i -g pyright, rustup component add rust-analyzer, ...); the cell starts only what it finds.

Configure

Optional. The section is on unless it says "enabled": false:

{
  "lsp": {
    "diagnostics_on_write": true,   // append diagnostics to an eval that wrote files
    "wait_ms": 3000,                // how long that waits for them
    "idle_minutes": 15,             // stop a server unused this long
    "servers": {
      // override one of the 55 by name: any member, the rest kept
      "pyright": { "command": "/opt/pyright/bin/pyright-langserver" },
      "ruff": { "disabled": true },
      // or add one
      "my-ls": { "command": "my-ls", "args": ["--stdio"],
                 "file_types": [".xyz"], "root_markers": [".git"],
                 "settings": {}, "init_options": {} }
    }
  }
}

config.example.jsonc has the whole section.

Test

From this repository's root:

tools/test-cell.sh nodecode-lsp

The tests drive test/fake-server.py, a scripted language server, through python3; nothing touches the network.

Gaps

What omp's module does that v1 does not:

  • Format on write, code actions, rename_file. omp formats a written file through the server, offers quick fixes and import organizing, and renames a file with the server updating its importers. A rename here edits text only: one that would also create, rename or delete a file is refused.
  • Late diagnostics. omp waits 500 ms inline and delivers a slow server's diagnostics into the next turn. Here the write waits wait_ms and a slow server says "still checking"; the model asks again with lsp:diagnostics.
  • The biome and swiftlint clients, and file: "*" checkers. omp drives those two through their CLIs rather than LSP, and can check every file a glob names; the cell skips both servers and checks named files.
  • The diagnostics dedup ledger. omp suppresses a diagnostic it already showed for an unchanged file; here every write reports what stands.
  • The mux daemon and lspmux. Not needed: the gateway is one long-lived process, so a server is already shared by every session in its root.

MIT licensed.

Add nc://lsp

  1. 1

    Install Nodecode

    Once per machine.

    $ curl -fsSL https://nodecode.ai/install.sh | sh
  2. 2

    Add the cell

    Pinned at 914a3fe.

    $ nodecode add nc://lsp

    Or tell the agent add the lsp cell.