b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
95 lines
3.5 KiB
Python
95 lines
3.5 KiB
Python
"""Streaming display force-flush: long partial lines must paint before the
|
|
first newline arrives (TTFT-perception fix, July 2026).
|
|
|
|
Previously ``_emit_stream_text`` only emitted on ``"\\n"``, so a response
|
|
opening with a long paragraph stayed invisible until the model produced a
|
|
newline — seconds of blank box on slow models. Now partial lines are
|
|
force-flushed at terminal width (mirroring the reasoning box's 80-char rule).
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
|
|
|
|
def _strip_ansi(s: str) -> str:
|
|
return re.sub(r"\x1b\[[0-9;]*m", "", s)
|
|
|
|
|
|
@pytest.fixture
|
|
def cli_stub(monkeypatch):
|
|
from cli import HermesCLI
|
|
import cli as climod
|
|
|
|
cli = HermesCLI.__new__(HermesCLI)
|
|
cli.show_reasoning = False
|
|
cli.final_response_markdown = "raw"
|
|
cli.show_timestamps = False
|
|
cli._reset_stream_state()
|
|
|
|
emitted = []
|
|
monkeypatch.setattr(climod, "_cprint", lambda s: emitted.append(s))
|
|
# Deterministic width regardless of the test runner's terminal
|
|
monkeypatch.setattr(climod, "_terminal_width_for_streaming", lambda: 74)
|
|
return cli, emitted
|
|
|
|
|
|
class TestPartialLineForceFlush:
|
|
def test_long_paragraph_paints_before_first_newline(self, cli_stub):
|
|
cli, emitted = cli_stub
|
|
text = (
|
|
"This is a long opening paragraph that would previously sit "
|
|
"invisible in the buffer until the model finally produced a "
|
|
"newline character, which on a slow model could take seconds. "
|
|
) * 3
|
|
for i in range(0, len(text), 12):
|
|
cli._stream_delta(text[i : i + 12])
|
|
# Box header + several wrapped lines painted with NO newline seen yet
|
|
assert len(emitted) > 3
|
|
|
|
def test_no_content_lost_across_wraps(self, cli_stub):
|
|
cli, emitted = cli_stub
|
|
words = [f"word{i}" for i in range(120)]
|
|
text = " ".join(words)
|
|
for i in range(0, len(text), 7):
|
|
cli._stream_delta(text[i : i + 7])
|
|
cli._flush_stream()
|
|
plain = " ".join(_strip_ansi("\n".join(emitted)).split())
|
|
for w in words:
|
|
assert w in plain, f"lost {w} at a wrap boundary"
|
|
|
|
def test_short_partial_stays_buffered(self, cli_stub):
|
|
cli, emitted = cli_stub
|
|
cli._stream_delta("short line, no newline")
|
|
# Under wrap width: the box header may open, but the text itself
|
|
# stays buffered until a newline or the width threshold.
|
|
plain = _strip_ansi("\n".join(emitted))
|
|
assert "short line" not in plain
|
|
assert cli._stream_buf == "short line, no newline"
|
|
|
|
def test_table_rows_not_force_flushed(self, cli_stub):
|
|
cli, emitted = cli_stub
|
|
# A long partial table row must stay buffered for block realignment
|
|
row = "| " + " | ".join(f"cell{i}" for i in range(20)) + " |"
|
|
cli._stream_delta(row) # no newline
|
|
plain = _strip_ansi("\n".join(emitted))
|
|
assert "cell19" not in plain
|
|
|
|
def test_newline_lines_still_emit_normally(self, cli_stub):
|
|
cli, emitted = cli_stub
|
|
cli._stream_delta("line one\nline two\n")
|
|
plain = _strip_ansi("\n".join(emitted))
|
|
assert "line one" in plain
|
|
assert "line two" in plain
|
|
|
|
def test_unbreakable_run_hard_wraps(self, cli_stub):
|
|
cli, emitted = cli_stub
|
|
blob = "x" * 300 # no spaces
|
|
cli._stream_delta(blob)
|
|
cli._flush_stream()
|
|
plain = _strip_ansi("\n".join(emitted))
|
|
assert plain.count("x") == 300
|