fix: recover ACP parser after bracket logs

This commit is contained in:
mehmet turac 2026-05-31 10:34:48 +03:00
parent 9250ffb745
commit 112b8bf182
No known key found for this signature in database
GPG key ID: 20D5F9AEE1833B0F
2 changed files with 43 additions and 1 deletions

View file

@ -289,6 +289,7 @@ function currentModelFromSessionResult(result: JsonObject): string | null {
export function createJsonLineStream(onMessage: (message: unknown, rawLine: string) => void) {
let buffer = '';
let pendingJson = '';
let pendingJsonLineCount = 0;
const emit = (candidate: string): boolean => {
try {
@ -301,18 +302,32 @@ export function createJsonLineStream(onMessage: (message: unknown, rawLine: stri
const startPendingJson = (line: string) => {
pendingJson = line;
pendingJsonLineCount = 1;
};
const handleLine = (line: string) => {
const trimmed = line.trim();
if (!trimmed) return;
if (pendingJson) {
if ((trimmed.startsWith('{') || trimmed.startsWith('[')) && emit(trimmed)) {
pendingJson = '';
pendingJsonLineCount = 0;
return;
}
const nextCandidate = `${pendingJson}\n${trimmed}`;
if (emit(nextCandidate)) {
pendingJson = '';
pendingJsonLineCount = 0;
return;
}
pendingJson = nextCandidate.length <= 128_000 ? nextCandidate : '';
pendingJsonLineCount += 1;
if (nextCandidate.length <= 128_000 && pendingJsonLineCount <= 256) {
pendingJson = nextCandidate;
return;
}
pendingJson = '';
pendingJsonLineCount = 0;
handleLine(trimmed);
return;
}
if (emit(trimmed)) return;

View file

@ -408,6 +408,33 @@ test('attachAcpSession accepts pretty-printed ACP startup responses', () => {
assert.equal(events.some((entry) => entry.event === 'error'), false);
});
test('attachAcpSession recovers when bracket-prefixed logs precede JSON frames', () => {
const child = new FakeAcpChild();
const writes: string[] = [];
const events: Array<{ event: string; payload: unknown }> = [];
child.stdin.on('data', (chunk) => writes.push(String(chunk)));
attachAcpSession({
child: child as never,
prompt: 'hello',
cwd: '/tmp/od-project',
model: null,
mcpServers: [],
send: (event, payload) => events.push({ event, payload }),
});
child.stdout.write('[vela] starting OpenCode bridge\n');
child.stdout.write(`${JSON.stringify({ id: 1, result: {} })}\n`);
child.stdout.write('{not json but looks like an object log\n');
child.stdout.write(`${JSON.stringify({ id: 2, result: { sessionId: 'session-1' } })}\n`);
const methods = parseRpcWrites(writes)
.map((entry) => entry.method)
.filter(Boolean);
assert.deepEqual(methods, ['initialize', 'session/new', 'session/prompt']);
assert.equal(events.some((entry) => entry.event === 'error'), false);
});
function parseRpcWrites(writes: string[]): Array<Record<string, unknown>> {
return writes
.join('')