Skip to content

[RFC] Code Server Protocol #800

@ije

Description

@ije

A CSP(Code Server Protocol) for code editors and AI agents.

Motivation

Code Server Protocol (CSP) is a protocol for embedding language intelligence in code editors, AI agents, and cloud runtimes. It exposes the same capabilities as Language Server Protocol (LSP) like completions, diagnostics, go-to-definition, and refactors, over transports that fit modern deployment: in-process modules, Web Workers, HTTP, and WebSocket.

LSP itself is built around a different shape: a long-lived native process talking to a desktop IDE over stdio. That model breaks down for the workloads CSP targets.

Code editors on the web need language services without spawning child processes or shipping platform-specific binaries. A server that runs in a Web Worker, over HTTP, or over WebSocket fits how browser-based editors actually deploy.

AI agents need more than raw file text. To write correct code, an agent must know whether an edit introduced type errors, what symbols exist in a file, and how to apply structured patches the same capabilities LSP already defines. Wiring a general-purpose agent to ad-hoc linters and parsers per language does not scale; exposing CSP diagnostics and edits as agent tools gives agents editor-grade feedback in a portable format.

Cloud and edge runtimes (Bun, Deno, Cloudflare Workers) share the same constraint: no assumption that a language server is a local subprocess. The server must be embeddable, importable as a module, routable through fetch, and message-driven in workers while still serving real language features through pluggable language modules.

Git-backed workspaces add another requirement. Agents and remote editors often operate on repositories stored in object storage, not on a developer's laptop filesystem. Language services need to read and write through that storage layer and stay in sync with commit, push, and pull workflows.

@pierre/csp addresses these gaps with a small Server/Client pair: configure languages and storage once, connect over whichever transport fits the host (in-process, worker, HTTP, WebSocket), and consume the same API from a browser editor, an AI agent's tool loop, or a server-side automation pipeline.

Installation

You can install the package using npm/pnpm/bun:

npm install @pierre/csp

Usage

@pierre/csp consists of two parts:
The server hosts languages and storage, the client is how your app queries diagnostics and applies edits.

  • Server: a CSP server that runs in a Web Worker, over HTTP, or over WebSocket.
  • Client: a CSP client that connects to the server and registers documents to analyze.

The server and client are designed to be used together, but they can also be used separately.

Server

Configure language services, storage, and a transport for your runtime.

import { Server, FileSystemStorage } from "@pierre/csp";
import { languages, registerLanguage } from "@pierre/csp/languages";

const server = new Server({
  storage: new FileSystemStorage("/path/to/project"),
  languages: {
    // built-in languages
    // - html
    // - css
    // - typescript(javascript,jsx,tsx)
    // - json
    ...languages,
    zig: () => import("@pierre/csp/languages/zig"),
    customLanguage: registerLanguage({ ... })
  }
});

// For Hybrid/Bun/Deno/Cloudflare Workers
export default server;

// For Nodejs
import { createServer } from "http";
import { createRequestListener } from "remix/node-fetch-server";
const server = createServer(createRequestListener(server.fetch));
server.listen(3000);

// Web worker
self.addEventListener("message", (e) => {
  server.handle(e.data, (data) => self.postMessage(data));
});

// WebSocket server
const wss = new WebSocketServer({ port: 3000 });
wss.on("connection", (ws) => {
  ws.on("message", (data) => {
    server.handle(data, (data) => ws.send(data));
  });
});

Client

Connect to the server and register documents to analyze.

import { Client, createTextDocument } from "@pierre/csp";

const client = new Client({
  // Hybrid mode, may affect the rendering performance with editor.
  server: import("./server.js"),
  // Web Worker mode.
  server: new Worker(new URL("./server.js", import.meta.url), { type: "module" }),
  // HTTP mode.
  server: new URL("https://example.com/csp"),
  // WebSocket mode.
  server: new WebSocket("wss://example.com/csp-ws"),
})

const document = createTextDocument("index.js", "let foo: number = 'bar';", 'javascript');
await client.writeDocument(document);

AI Agents

Expose diagnostics and structured edits as tools in an agent loop.

// use agent tools to interact with the client
import { tool } from 'ai';
import { z } from 'zod';

const checkTool = tool({
  description: 'Check the CSP diagnostics of the document, you should re-check the document after applying edits',
  inputSchema: z.object({
    uri: z.string().describe('The URI of the document to check'),
  }),
  execute: async ({ uri }) => {
    const ret = await client.check(uri);
    /**
     * @example
     * <csp_summary>
     * Name: index.js
     * Language: javascript
     * Line count: 1
     * Diagnostics:
     * - [error] `bar` is not a number at index.js:1:10
     * </csp_summary>
     */
    return ret.markdown;
  },
});
const editTool = tool({
  description: 'Edit the document',
  inputSchema: z.object({
    uri: z.string().describe('The URI of the document to edit'),
    edits: z.array(z.object({
      range: z.object({
        start: z.object({ line: z.number(), character: z.number() }),
        end: z.object({ line: z.number(), character: z.number() }),
      }),
      newText: z.string().describe('The new text to apply to the document'),
    })),
  }),
  execute: async ({ uri, edits }) => {
    await client.applyEdits(uri, edits);
    return 'Edits applied successfully to ' + uri;
  },
});

Code Editors

Plug the client into @pierre/diffs/editor for in-browser language features.

import { Editor } from "@pierre/diffs/editor";
const editor = new Editor({
  lsp: client.lsp,
});
editor.edit(fileInstance);

Integrating with code[.]storage

Back the server with a code.storage repository so language services and git operations share the same workspace.

import { GitStorage } from '@pierre/storage';
import { Server, CodeStorage } from "@pierre/csp";
import { languages } from "@pierre/csp/languages";

const store = new GitStorage({
  name: 'your-org',
  key: env.privateKey,
});

const repo = await store.createRepo({ id: 'new-workspace' });

const server = new Server({
  storage: new CodeStorage(repo),
  languages: languages,
});

const client = new Client({
  server: server
})

await client.applyEdits("index.js", [])
await client.check("index.js")
await client.git.add("index.js").commit("fix: typo").push()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions