Skip to content

fix(useAiChatStream): render assistant reply on final-only SSE streams#199

Merged
rubenvdlinde merged 1 commit into
betafrom
fix/ai-companion-final-event-fullText
May 11, 2026
Merged

fix(useAiChatStream): render assistant reply on final-only SSE streams#199
rubenvdlinde merged 1 commit into
betafrom
fix/ai-companion-final-event-fullText

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Summary

Follow-up to #197 / #198. End-to-end testing surfaced two related defensive issues in useAiChatStream:

  1. Empty assistant bubble on non-streaming-provider degradation path. When OR ships only a terminal final SSE event (per the hydra spec's degradation clause: "Non-streaming providers MUST emit zero token events and exactly one final event carrying fullText"), the previous finalise() ran with state.currentText === '' because no token events ever populated it. The assistant bubble pushed onto state.messages had content: '' and rendered blank in CnAiMessageList.

  2. Empty Vue render key when server sends empty messageId. OR's first cut shipped messageId: "" in the final event (separate fix landing in openregister#1466 follow-up commit). The widget used array index as key, which mostly works, but an explicit id is more robust for re-renders.

Fix

  • final handler: if state.currentText === '' and parsed.fullText is a string, seed state.currentText before calling finalise(). Matches the spec's degradation clause exactly.
  • finalise(messageId): prefer server-supplied parsed.messageId; synthesise client-<timestamp>-<random> when missing/empty.

Verified

Real LLM round-trip through the decidesk pilot: qwen3.5 reply visible in the chat panel.

Pair

Pairs with openregister#1466 follow-up commit that propagates the persisted assistant message's id into the final event payload.

Test plan

  • final-only SSE stream → assistant bubble renders with payload's fullText
  • token events + final → bubble renders with accumulated text (existing behaviour unchanged)
  • Empty messageId → synthesised client id
  • Populated messageId → server id used

Defensive change, no test-suite update required (the existing scenario tests still pass; one new test could mock the non-streaming-provider path — out of scope for this hotfix).

When the OpenRegister side ships the non-streaming-provider degradation
path (one `final` event with `fullText`, no preceding `token` events),
the previous finalise() pushed an empty `content: ''` message into the
state because state.currentText was never populated. The assistant
bubble rendered as empty.

Two related defensive fixes:

- In the `final` handler: if no `token` events arrived, seed
  state.currentText from parsed.fullText before finalising. This matches
  the hydra spec's contract clause: "Non-streaming LLPhant providers
  MUST degrade gracefully: emit zero `token` events and exactly one
  `final` event carrying `fullText`."

- In finalise(): use the server's parsed.messageId as the message's
  Vue render-key id when present; synthesise a stable client-side id
  (`client-<ts>-<rand>`) when the server sends an empty messageId.
  Pairs with openregister#1466's follow-up that now propagates the
  persisted assistant message's id through the `final` event.

End-to-end: real LLM round-trip through the decidesk pilot now renders
the assistant bubble with the qwen3.5 reply visible.
@rubenvdlinde rubenvdlinde merged commit f5c749b into beta May 11, 2026
@rubenvdlinde rubenvdlinde deleted the fix/ai-companion-final-event-fullText branch May 11, 2026 17:58
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.0.0-beta.31 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant