Skip to content

feat(rich_output): block-level and inline markdown rendering for LLM responses (PR2) - #4575

Closed
KUSH42 wants to merge 8 commits into
NousResearch:mainfrom
KUSH42:feat/rich-output-renderer
Closed

feat(rich_output): block-level and inline markdown rendering for LLM responses (PR2)#4575
KUSH42 wants to merge 8 commits into
NousResearch:mainfrom
KUSH42:feat/rich-output-renderer

Conversation

@KUSH42

@KUSH42 KUSH42 commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

⚠️ Stacked on `feat/tool-output-highlighting` (#4471) — merge that first. All commits above the tip of that branch are new here.

Adds a full markdown → ANSI rendering engine applied to every non-code line of LLM responses, covering block-level structure and all common inline spans.

Changes

`apply_block_line` — per-line block markdown → ANSI:

  • ATX headings `# H1` through `###### H6` with distinct ANSI weights
  • Horizontal rules `---` / `***` / `___`
  • Blockquotes `>` with `▌` gutter
  • Unordered list items with bullet symbols by indent depth
  • Ordered list items with dim numeral styling
  • Reference link definitions suppressed

`apply_inline_markdown` — inline spans → ANSI:

  • `bold italic` / `bold italic`
  • `bold` / `bold`
  • `italic` / `italic` (spaces allowed inside underscore spans)
  • ``code`` — bright-white highlight
  • `strikethrough`
  • `text` links — underline + URL preserved
  • `alt` images → `[img: alt]`
  • ``, `` HTML tags

`format_response` extended pipeline:

  1. Fenced code blocks → ANSI-highlighted + line numbers (PR1)
  2. Per non-ANSI line: `apply_block_line` then `apply_inline_markdown`

ANSI corruption fix — `_MD_LINK_RE` uses `(?<!\x1b)` lookbehind: prevents the inline-image reset code `\033[0m` emitted mid-string from being consumed as a link bracket, which would orphan the ESC byte and cause the subsequent ANSI sequence to print as literal text (e.g. `[38;2;…m` visible in terminal output).

Tests

212 passing (`tests/test_display.py` + `tests/test_rich_output.py`).

KUSH42 added 8 commits April 1, 2026 19:52
Introduces agent/rich_output.py — a self-contained Rich/Pygments rendering
toolkit with no project-specific imports.

Public API:
- LanguageDetector: extension map + content-pattern heuristics
- SyntaxHighlighter: Pygments → Rich markup → ANSI string
- FilePathFormatter: per-filetype icons, compact relative paths
- DiffRenderer: unified diff → Rich Text with line numbers
- clean_command_output: strip venv/stacktrace noise from command output

DiffRenderer replaces _render_inline_unified_diff in display.py:
- Intra-line character-level highlighting via SequenceMatcher (threshold 0.5)
- Per-run del/add pairing to avoid cross-hunk false matches
- Summary header: ● filename.py   Added N lines, removed M lines
- Console width from shutil.get_terminal_size, not hardcoded

Tests: 51 passing in tests/test_rich_output.py
Pygments emits Error tokens for content its markdown lexer cannot
tokenize (emoji in headings, unknown syntax, etc.). Mapping Error to
"bold red on red" matched the diff-deletion colour, causing spurious
red backgrounds on unrelated text. Changed to "bold red" (text colour
only), consistent with Generic.Error.
Wires the SyntaxHighlighter/LanguageDetector from rich_output.py into tool
result display and LLM response rendering.

execute_code preview:
- Highlighted Python block printed after successful execution
- Gated on _result_succeeded — nothing shown after a failed run
- Cute-msg drops the inline snippet when highlight is active (no duplication)

read_file preview:
- ┊ 📄 filename.py header + syntax-highlighted content
- Language from extension only; unknown types skipped silently

terminal preview:
- Verb-based language detection from the command
- _FILE_EXEC_COMMANDS blocklist (node, python3, bash, …) prevents runtime
  stdout from being mistaken for source code

LLM response rendering:
- format_response() highlights fenced code blocks in complete responses
- StreamingCodeBlockHighlighter state machine for streaming: buffers fenced
  blocks, flushes highlighted on closing fence, plain text passes through
  immediately with response text colour preserved

Plumbing:
- Verbosity gate: all previews suppressed when tool_progress_mode == "off"
- display.code_highlight config key + /code-highlight toggle
- set_code_highlight_active() keeps display.py decoupled from CLI state
- Module-level _rich_detector singleton (no per-call instantiation)

Tests: 135 passing (tests/test_display.py + tests/test_rich_output.py)
format_response wrapped highlighted code in ``` delimiters under the
theory that "the Panel still looks like a code block". In practice the
ANSI-highlighted block reads cleanly without them, and keeping the
fences caused raw backtick lines to appear in the rendered response.
_number_code_lines() prepends dim right-justified line numbers with a │
separator to every line of a highlighted code block:

  1 │ import requests
  2 │ import time
  ...

Called from both format_response._highlight (batch path) and
StreamingCodeBlockHighlighter._flush_block (streaming path) so line
numbers appear consistently in all LLM response code blocks.
…endering bugs

_PygmentsToRich.format() previously escaped brackets by replacing:
  [ → \[  (correct: literal "[" in Rich markup)
  ] → \]  (WRONG: Rich has no \] escape; renders as literal \])

This caused two bugs:
1. Haskell type signatures like [Integer] rendered as [Integer\]
2. A trailing \ token (e.g. Haskell lambda \a) before a close tag
   like [/bold yellow] formed the Rich escape \[ — consuming the
   closing tag and leaking the tag text into the output.

Fix: use rich.markup.escape() which doubles \ → \\ and escapes
[ → \[ while leaving ] alone (] requires no escaping in Rich markup).
Apply to ALL tokens (styled and unstyled) and to all fallback paths.

Also add _highlight_inline_code() and format_response() for
inline code span styling in LLM response text.
…responses

Adds apply_block_line (headings, hr, blockquotes, lists, ref-link
suppression) and extends apply_inline_markdown (images, links, HTML
inline tags).  format_response gains a pass-2 that applies both
renderers to every non-highlighted line.  The streaming path chains
the same pair in _emit_stream_text and _flush_stream.

Bug fixes included:
- _code_highlight_active was gating apply_block_line/apply_inline_markdown
  in the streaming path; removed the guard (_RICH_RESPONSE is the correct
  gate; _code_highlight_active controls only tool-output highlighting)
- _MD_ITALIC_UNDER_RE rejected phrases with spaces; changed [^_\s\n]+ to
  [^_\n]+ (word-boundary lookbehind prevents snake_case false positives)
- _MD_REF_LINK_RE had a $ anchor that blocked titled reference-link
  definitions from being suppressed; removed $
- Blockquote inline spans reset to terminal default; added reset_suffix
  to the apply_inline_markdown call in the blockquote branch
- format_response splitlines(keepends=True) fed \n into capture groups,
  silently dropping block elements; switched to splitlines() + manual join
- CommonMark backslash escapes (\] → ]) were passed through literally;
  added step-7 re.sub pass in apply_inline_markdown
- Pygments plain-text lexer emits lines with no ANSI codes; pass-2 guard
  "\x1b" in l then fell through and applied markdown to code-fence content
  (### was stripped, ** rendered, etc.); _highlight now prepends \x1b[0m
  to bare lines so the guard is always satisfied
…e correctness

- Anchor code-fence regex to line start ((?m)^) so fences prefixed with
  > are not incorrectly consumed as code block openers; previously caused
  blockquote lines to be syntax-highlighted and the \x1b guard to skip
  apply_block_line, rendering raw > instead of the ▌ gutter
- Remove fence delimiter preservation from format_response; add \033[0m
  fallback for plain-text lexer output so pass 2 always skips code content
- Update _highlight group numbers to match new 3-group regex (backticks,
  lang, code)
- Keep URL visible in link rendering ([text](url) → underlined text (url))
  so users can copy and ctrl+click
@KUSH42

KUSH42 commented Apr 3, 2026

Copy link
Copy Markdown
Contributor Author

Closing as duplicate — all changes from this PR are included in #4504 (feat/markdown-block-rendering), which extends this work with complete inline HTML tag support, nested style restoration, terminal-width-aware diff rendering, setext headings, and 211 passing tests.

@KUSH42 KUSH42 closed this Apr 3, 2026
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.

1 participant