fix(chat): surface empty upstream model completions instead of silence
From the PR description
Summary
When an upstream provider ends a chat stream cleanly but produces zero content (observed intermittently via OpenRouter), the client currently renders nothing - the composer just sits there looking hung. This PR emits an explicit, safe-to-display error event instead:
The model returned an empty response. Try again, or pick a different model.
What changed
routes/chat.ts: afterrunLLMStreamresolves, iffullTextis empty/whitespace and no events were produced, emit the retry-guidance error event followed by[DONE].- Turns that produced tool artifacts (documents, ask-inputs flows, etc.) keep their existing completion path - the guard only fires when nothing visible was generated.
Why
An empty 200-complete stream is indistinguishable from a hang to the user, and the underlying trigger is provider-side (retrying or switching models works). Surfacing it turns a dead-end into actionable guidance.
Testing performed
- New integration test in
chat.routes.test.ts: emptyrunLLMStreamresult → SSE contains theerrorevent withsafe_to_display: trueand terminates with[DONE]. - Full backend suite on Node 22: 726 passed / 25 skipped.
Our analysis
Surface empty chat-stream failures — read the full analysis →
Think the analysis missed something the PR description covers?
Capture this PR into my fork
Download a Markdown prompt that tells Claude how to port every
commit in this PR into your working tree. Run it via
claude -p < capture-pull-379.md from
inside the repo you want the changes in.