Skip to content

Commit f549f50

Browse files
foxy1402Copilot
andcommitted
Fix IDE streaming: use SSE comments instead of empty delta
- Replace initial empty delta with SSE comment (': keep-alive') - Some IDEs (Zed) interpret empty delta as end of stream and disconnect - SSE comments keep connection alive without signaling message end - Only include 'role: assistant' in first content chunk (per OpenAI spec) - Subsequent chunks contain only content delta Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3e7240f commit f549f50

1 file changed

Lines changed: 18 additions & 15 deletions

File tree

src/routes/chat.js

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,9 @@ router.post('/', (req, res) => {
8383
req.socket.setTimeout(0);
8484
req.socket.setKeepAlive(true);
8585

86-
// Send initial empty delta immediately to establish SSE stream (required for IDE tools)
87-
const initialChunk = {
88-
id,
89-
object: 'chat.completion.chunk',
90-
created: Math.floor(Date.now() / 1000),
91-
model,
92-
choices: [{
93-
index: 0,
94-
delta: { role: 'assistant', content: '' },
95-
finish_reason: null
96-
}]
97-
};
98-
res.write(`data: ${JSON.stringify(initialChunk)}\n\n`);
86+
// Send SSE comment immediately to keep connection alive (IDE compatibility)
87+
// Don't send an empty delta - some IDEs interpret that as end of stream
88+
res.write(': keep-alive\n\n');
9989

10090
// Explicitly flush the response buffer
10191
if (typeof res.flush === 'function') res.flush();
@@ -109,7 +99,6 @@ router.post('/', (req, res) => {
10999
flags,
110100
timeoutMs: QODER_TIMEOUT_MS,
111101
onChunk: (data) => {
112-
hasReceivedData = true;
113102
const content = extractTextContent(data.message);
114103
const toolCalls = extractToolCalls(data.message?.content);
115104
const finishReason = data.message?.stop_reason || null;
@@ -120,7 +109,21 @@ router.post('/', (req, res) => {
120109
res.write(`data: ${JSON.stringify(buildToolCallStreamChunk(data, model, id))}\n\n`);
121110
lastFinishReason = 'tool_calls';
122111
} else if (content) {
123-
res.write(`data: ${JSON.stringify(buildStreamChunk(content, model, id))}\n\n`);
112+
// First chunk should include role, subsequent chunks should not
113+
const delta = hasReceivedData
114+
? { content }
115+
: { role: 'assistant', content };
116+
117+
const chunk = {
118+
id,
119+
object: 'chat.completion.chunk',
120+
created: Math.floor(Date.now() / 1000),
121+
model,
122+
choices: [{ index: 0, delta, finish_reason: null }]
123+
};
124+
125+
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
126+
hasReceivedData = true;
124127
}
125128
},
126129
onDone: (code, stderr) => {

0 commit comments

Comments
 (0)