fix(commandcode): replay raw byte chunks to preserve all NDJSON lines

This commit is contained in:
Christian Gennari
2026-09-26 11:53:44 +07:00
committed by decolua
parent 5d2cfbf3c5
commit c2148179c0
2 changed files with 33 additions and 25 deletions

View File

@@ -141,7 +141,7 @@ export async function inspectAndWrapCommandCodeResponse(originalResponse, model)
const reader = originalResponse.body.getReader(); const reader = originalResponse.body.getReader();
const decoder = new TextDecoder(); const decoder = new TextDecoder();
let buffer = ""; let buffer = "";
const bufferedLines = []; const rawChunks = [];
let detectedError = null; let detectedError = null;
try { try {
@@ -155,16 +155,15 @@ export async function inspectAndWrapCommandCodeResponse(originalResponse, model)
const parsed = JSON.parse(jsonStr); const parsed = JSON.parse(jsonStr);
if (parsed?.type === "error") { if (parsed?.type === "error") {
detectedError = parsed; detectedError = parsed;
} else {
bufferedLines.push(trimmed);
} }
} catch { } catch {
bufferedLines.push(trimmed); /* ignore */
} }
} }
break; break;
} }
rawChunks.push(value);
buffer += decoder.decode(value, { stream: true }); buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n"); const lines = buffer.split("\n");
buffer = lines.pop() || ""; buffer = lines.pop() || "";
@@ -175,7 +174,6 @@ export async function inspectAndWrapCommandCodeResponse(originalResponse, model)
if (!trimmed) continue; if (!trimmed) continue;
const jsonStr = trimmed.startsWith("data:") ? trimmed.slice(5).trim() : trimmed; const jsonStr = trimmed.startsWith("data:") ? trimmed.slice(5).trim() : trimmed;
if (!jsonStr || jsonStr === "[DONE]") { if (!jsonStr || jsonStr === "[DONE]") {
bufferedLines.push(trimmed);
stopLoop = true; stopLoop = true;
break; break;
} }
@@ -184,7 +182,6 @@ export async function inspectAndWrapCommandCodeResponse(originalResponse, model)
try { try {
event = JSON.parse(jsonStr); event = JSON.parse(jsonStr);
} catch { } catch {
bufferedLines.push(trimmed);
continue; continue;
} }
@@ -194,8 +191,6 @@ export async function inspectAndWrapCommandCodeResponse(originalResponse, model)
break; break;
} }
bufferedLines.push(trimmed);
if ( if (
event?.type === "text-delta" || event?.type === "text-delta" ||
event?.type === "reasoning-delta" || event?.type === "reasoning-delta" ||
@@ -238,29 +233,18 @@ export async function inspectAndWrapCommandCodeResponse(originalResponse, model)
); );
} }
const combinedStream = createReplayedStream(bufferedLines, buffer, reader); const combinedStream = createRawReplayedStream(rawChunks, reader);
return wrapNdjsonAsOpenAISse(combinedStream, model, originalResponse); return wrapNdjsonAsOpenAISse(combinedStream, model, originalResponse);
} }
function createReplayedStream(bufferedLines, remainingBuffer, reader) { function createRawReplayedStream(rawChunks, reader) {
const encoder = new TextEncoder(); let chunkIndex = 0;
let replayed = false;
return new ReadableStream({ return new ReadableStream({
async pull(controller) { async pull(controller) {
if (!replayed) { if (chunkIndex < rawChunks.length) {
replayed = true; controller.enqueue(rawChunks[chunkIndex++]);
let prefix = bufferedLines.join("\n"); return;
if (prefix && remainingBuffer) {
prefix += "\n" + remainingBuffer;
} else if (remainingBuffer) {
prefix = remainingBuffer;
} else if (prefix) {
prefix += "\n";
}
if (prefix) {
controller.enqueue(encoder.encode(prefix));
}
} }
try { try {

View File

@@ -133,6 +133,30 @@ describe("inspectAndWrapCommandCodeResponse", () => {
expect(text).toContain("data: [DONE]"); expect(text).toContain("data: [DONE]");
}); });
it("preserves all lines in a multi-line packet when inspecting tool-input-start", async () => {
const packet = [
JSON.stringify({ type: "start" }),
JSON.stringify({ type: "start-step" }),
JSON.stringify({ type: "tool-input-start", id: "call_1", toolName: "terminal" }),
JSON.stringify({ type: "tool-input-delta", id: "call_1", delta: '{"command": "ls"}' }),
JSON.stringify({ type: "finish-step", finishReason: "tool-calls" }),
JSON.stringify({ type: "finish", finishReason: "tool-calls" }),
].join("\n") + "\n";
const ndjsonBody = createNdjsonStream([packet]);
const fakeResponse = new Response(ndjsonBody, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
const result = await inspectAndWrapCommandCodeResponse(fakeResponse, "cmc/deepseek/deepseek-v4.1-flash");
expect(result.ok).toBe(true);
const text = await result.text();
expect(text).toContain('"name":"terminal"');
expect(text).toContain('"arguments":"{\\"command\\": \\"ls\\"}"');
});
it("retries when initial stream yields an error and succeeds on second attempt", async () => { it("retries when initial stream yields an error and succeeds on second attempt", async () => {
let callCount = 0; let callCount = 0;
const executor = new CommandCodeExecutor(); const executor = new CommandCodeExecutor();