Skip to content

fix: harden thinking display streaming edge cases - #182

Merged
nesquena merged 2 commits into
masterfrom
fix/thinking-display-edge-cases
Apr 9, 2026
Merged

fix: harden thinking display streaming edge cases#182
nesquena merged 2 commits into
masterfrom
fix/thinking-display-edge-cases

Conversation

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Summary

This PR is a standalone follow-up to #181.

It hardens the thinking/reasoning display path in the chat UI by:

  • hiding partial <think> / <|channel>thought prefixes during streaming so users do not briefly see token fragments
  • renaming the local streaming buffer variable for clarity

Reference

Test plan

  • pytest tests/ -x -q (499 passed)

TaraTheStar and others added 2 commits April 8, 2026 18:14
Hide partial <think> tag prefixes during streaming and rename the local display variable for clarity. References #181.
@nesquena-hermes

Copy link
Copy Markdown
Collaborator Author

Agent review — APPROVED ✅

Reviewed by Hermes agent. Full diff, security audit, test suite, QA harness, and browser feature test all completed.


Summary: Hardens the thinking/reasoning display path by suppressing partial <think>/<|channel>thought token fragments during streaming, and adds inline think-tag parsing for stored messages in renderMessages() so DeepSeek-style and Gemma 4-style reasoning tokens surface as collapsible cards without a separate structured content array.

Diff: 2 files, +46/-1

  • static/messages.js +30/-1: new _thinkPairs constant, _streamDisplay() function, updated _scheduleRender to use it
  • static/ui.js +16/-0: think-tag regex parsing inside renderMessages() for <think> and <|channel>thought formats

Test results:

Suite Result
pytest tests/ (499 tests) ✅ 499 passed, 0 failed
qa/ harness (21 tests) ✅ 20 passed, 1 skipped (unrelated)
Browser API sanity (11 checks) ✅ all passed

Security audit: CLEAN

  • No eval/exec/atob/document.write patterns
  • No external URLs introduced
  • innerHTML in _scheduleRender: txt goes through renderMd() (existing safe pipeline); isThinking branch uses a hardcoded literal with no user input — safe
  • thinkingText extracted from regex and rendered via esc(thinkingText) in existing thinkRow.innerHTML — safe
  • No esc()-in-onclick XSS patterns introduced
  • All pre-existing security controls intact: relative_to (6×), Invalid category (1×), inlineMd (7×), SRI hashes (3×)

Logic correctness: _streamDisplay()

All 12 edge cases verified:

Input Expected output Result
<thi (partial tag) '' (hidden)
<think (partial open) '' (hidden)
<think>reasoning... (open, no close) '' + "Thinking…" placeholder
<think>reasoning</think>answer answer
<|channel>thought\n...<channel|>answer answer (Gemma 4)
Normal text pass-through
<b>bold</b> (non-think HTML) pass-through

renderMessages() think-tag parsing: regex anchored to ^, non-greedy, correctly handles multiline reasoning. Unclosed tags don't match. Only the first matching format is used (guarded by if(!thinkingText)). Both DeepSeek <think> and Gemma 4 <|channel>thought formats extracted correctly.

One design-scope note (not a bug): _streamDisplay() only handles a think block at position 0 of the accumulated buffer. A hypothetical model that emits text then a think block mid-stream would render the second block as literal HTML. This is consistent with how these models actually work (reasoning is always emitted first) and renderMd() handles unrecognized tags safely.


Browser feature test: PASS

Tested on the PR branch (fix/thinking-display-edge-cases) with a live server on port 8789:

  1. Stored session rendering — injected a session with two assistant messages:

    • <think>The user asks a simple arithmetic question...</think>\n\nThe answer is **4**.
    • <|channel>thought\nThe user asks about European capitals...\n<channel|>\n\nThe capital of France is **Paris**.

    Both rendered as collapsible gold "💡 Thinking ▸" cards. The think/channel content was stripped from the visible response body. Expanding the first card showed the verbatim reasoning text. ✅

  2. Live streaming — sent a message via the UI against the mock provider (port 8793, no real API key). Full SSE pipeline ran end-to-end; response streamed and rendered correctly. ✅

No console errors observed.


No fixes required. The PR is clean, correct, and well-scoped. Safe to merge.

@nesquena
nesquena merged commit 0d83837 into master Apr 9, 2026
@nesquena
nesquena deleted the fix/thinking-display-edge-cases branch April 9, 2026 01:51
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request Apr 25, 2026
…ge-cases

fix: harden thinking display streaming edge cases
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
…ge-cases

fix: harden thinking display streaming edge cases
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants