fix(coding-agent): use strict JSONL framing fixes #1911

This commit is contained in:
Mario Zechner
2026-03-07 14:20:44 +01:00
parent 841c95ac9c
commit e3adaf1bd9
7 changed files with 188 additions and 27 deletions

View File

@@ -24,6 +24,17 @@ Common options:
All commands support an optional `id` field for request/response correlation. If provided, the corresponding response will include the same `id`.
### Framing
RPC mode uses strict JSONL semantics with LF (`\n`) as the only record delimiter.
This matters for clients:
- Split records on `\n` only
- Accept optional `\r\n` input by stripping a trailing `\r`
- Do not use generic line readers that treat Unicode separators as newlines
In particular, Node `readline` is not protocol-compliant for RPC mode because it also splits on `U+2028` and `U+2029`, which are valid inside JSON strings.
## Commands
### Prompting
@@ -1292,13 +1303,39 @@ For a complete example of handling the extension UI protocol, see [`examples/rpc
```javascript
const { spawn } = require("child_process");
const readline = require("readline");
const { StringDecoder } = require("string_decoder");
const agent = spawn("pi", ["--mode", "rpc", "--no-session"]);
readline.createInterface({ input: agent.stdout }).on("line", (line) => {
function attachJsonlReader(stream, onLine) {
const decoder = new StringDecoder("utf8");
let buffer = "";
stream.on("data", (chunk) => {
buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
while (true) {
const newlineIndex = buffer.indexOf("\n");
if (newlineIndex === -1) break;
let line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
if (line.endsWith("\r")) line = line.slice(0, -1);
onLine(line);
}
});
stream.on("end", () => {
buffer += decoder.end();
if (buffer.length > 0) {
onLine(buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer);
}
});
}
attachJsonlReader(agent.stdout, (line) => {
const event = JSON.parse(line);
if (event.type === "message_update") {
const { assistantMessageEvent } = event;
if (assistantMessageEvent.type === "text_delta") {