# jsonrpc A JSON-RPC 2.0 connection over a framed byte stream. It holds everything the specification says and nothing about any particular use of it: requests and their answers, notifications, requests arriving the other way, and the error object a failure travels in. ## Why it is its own package It was `lsp`'s, until a second protocol needed it. [`lsp`](../lsp/) speaks to a language server, `acp` speaks to a coding agent, and between them the *only* difference at this level is how one message is marked off from the next — a `Content-Length` header for one, a newline for the other. That difference is the `Framer` interface; everything above it is shared. ## Layers | File | What it is | | --- | --- | | `jsonrpc.go` | The wire types, the error codes, and the `Framer` seam | | `conn.go` | The connection: concurrent calls matched to replies by id, notifications, inbound requests | | `request.go` | An inbound request, and the answer that may come later | `NewConn` takes an `io.ReadWriteCloser`, not a command. That is what lets a whole client be tested against a peer **in the same process**, over `net.Pipe` — real framing, real concurrency, real decoding, no subprocess and no timing to get lucky with. ## Two things that are easy to get wrong **The two directions are separate id spaces.** A peer numbers *its* requests from one, and so do we. An agent really does send `id: 1` while a call of ours carrying the same id is in flight — that is ordinary behaviour, not a broken agent. Replies are matched only against the ids this connection sent; an inbound request is never looked up among them. **An answer may have to come later.** Every question a language server asks can be answered from what the client already knows. An agent asking permission to run a command cannot be: the answer comes from a dialog somebody has to look at, and opening one belongs to the goroutine that draws. So `RequestFunc` is handed a `*Request` and returns nothing — it may reply now, or keep the `Request` and reply several turns of an event loop later, from another goroutine. `Reply` is guarded by a `sync.Once`. A window closing tears down a pending permission that may already have been answered, and two responses carrying one id would desynchronise a peer that matches answers to requests by exactly that id. ## Tests `go test ./jsonrpc/` drives a real connection over `net.Pipe` against a peer written by hand — deliberately *not* built on `Conn`, because a peer sharing the code under test could not catch that code writing a malformed frame. The peer reads continuously into a buffered channel rather than on demand. `net.Pipe` is synchronous, so a write blocks until somebody reads, and an on-demand reader deadlocks against a `Reply` made from the test goroutine. It also never calls `t.Fatalf`: that is not allowed outside the test goroutine, and doing it there hangs the run instead of failing it.