Where the vendor knowledge lives
One question — where does the knowledge of how to talk to a vendor live? — and a tenfold difference in what the answer costs. Measured against the Rust ports.
| pi | deepseek | nerv | |
|---|---|---|---|
| where it lives | in-process: thirteen protocol modules and a 103-row table | one crate behind a port trait | a separate program, spawned per turn |
| size | ~9,700 production lines | 1,121 lines | ~5,300 production lines, plus 943 of Lua |
| providers shipped | 103 — 88 of them pure data | 1 | 8 |
| wire protocols | 13 native modules | 1 | 8 Lua tables |
| adding a compatible provider | ~17 lines of Rust data, then recompile | not a supported operation | ~10 lines of Lua, no rebuild |
| adding a new protocol | a module, a route kind, a normaliser, recompile | a new crate | a do … end block, no rebuild |
| HTTP | hand-rolled HTTP/1.1, 3,034 lines | a library | a library |
| does it stream | yes | no | yes, end to end, across the pipe |
| credential store | 13,156 lines: four OAuth flows, an AWS chain, an SSO cache, a rotation ring | environment variables, 83 lines | environment variables and one OAuth store |
The same shape, at a tenth of the cost
pi and nerv arrive at structurally similar layers — a catalog of vendors as data, a small set of protocol adapters, one neutral message model. pi pays roughly ten times for it, and most of that is not the process boundary. It is the difference between “a protocol is a Rust module” and “a protocol is a Lua table”.
-- melchior/config/providers.lua — one provider, no rebuild
melchior.provider("deepseek", {
name = "DeepSeek", api = "openai-completions",
base_url = "https://api.deepseek.com",
auth = { kind = "api-key", vars = { "DEEPSEEK_API_KEY" } },
compat = { thinking_format = "deepseek" },
models = { { id = "deepseek-chat" } },
})
The best idea in pi's provider layer
Its table feeds everything else, including predicates that would otherwise be hardcoded lists — “is this provider keyless” is derived from having no auth variables and no auth header, so local runtimes work without being named anywhere — and including the published documentation, rendered from the same table with a golden test on the output. That is cheap and worth having.
What the separate process is for
- One parser for every provider's stream, on a correct argument, rather than four.
- A typed failure class that reaches the loop and changes what the loop does — rather than a regular expression over the vendor's prose.
- Retry and retraction as one end-to-end contract: the screen is told to un-draw what a failed attempt already streamed. Neither of the others has this.
- Three protocol hostings sharing one body, differing in about ten lines. pi's equivalent for one vendor is 1,882.