fix(gateway): send terminal chunk before [DONE] in OpenAI streaming

OpenAI-compatible clients expect a terminal chunk with an empty delta
and finish_reason:"stop" before the [DONE] sentinel. Without it, some
clients crash while finalizing the stream.

Centralizes stream shutdown in an endStream() helper that ensures the
terminal chunk is sent exactly once before [DONE].

Closes #4298
This commit is contained in:
Ayush Ojha 2026-01-30 00:45:16 -08:00
parent 6af205a13a
commit b6ea87d6ac

View File

@ -252,6 +252,26 @@ export async function handleOpenAiHttpRequest(
let wroteRole = false;
let sawAssistantDelta = false;
let closed = false;
let sentTerminalChunk = false;
/** Send a terminal chunk with finish_reason before [DONE]. */
const endStream = () => {
if (closed) return;
closed = true;
if (!sentTerminalChunk) {
sentTerminalChunk = true;
writeSse(res, {
id: runId,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
});
}
unsubscribe();
writeDone(res);
res.end();
};
const unsubscribe = onAgentEvent((evt) => {
if (evt.runId !== runId) return;
@ -294,17 +314,16 @@ export async function handleOpenAiHttpRequest(
if (evt.stream === "lifecycle") {
const phase = evt.data?.phase;
if (phase === "end" || phase === "error") {
closed = true;
unsubscribe();
writeDone(res);
res.end();
endStream();
}
}
});
req.on("close", () => {
closed = true;
unsubscribe();
if (!closed) {
closed = true;
unsubscribe();
}
});
void (async () => {
@ -363,6 +382,7 @@ export async function handleOpenAiHttpRequest(
}
} catch (err) {
if (closed) return;
sentTerminalChunk = true;
writeSse(res, {
id: runId,
object: "chat.completion.chunk",
@ -382,12 +402,7 @@ export async function handleOpenAiHttpRequest(
data: { phase: "error" },
});
} finally {
if (!closed) {
closed = true;
unsubscribe();
writeDone(res);
res.end();
}
endStream();
}
})();