Skip to content

feat: block-level and inline markdown rendering for LLM responses - #4501

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

feat: block-level and inline markdown rendering for LLM responses#4501
KUSH42 wants to merge 6 commits into
NousResearch:mainfrom
KUSH42:feat/rich-output-renderer

Conversation

@KUSH42

@KUSH42 KUSH42 commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

⚠️ Stacked — merge in order:

  1. feat: Rich-based rendering engine with intra-line diff highlighting (PR1) #4470 feat/rich-diff-renderer
  2. feat: syntax highlighting for tool outputs and LLM responses (PR2) #4471 feat/tool-output-highlighting
  3. this PR

Summary

  • Adds apply_block_line(line) — single-line-detectable block elements rendered to ANSI
  • Extends apply_inline_markdown with images, links, and HTML inline tags
  • Chains apply_block_line before apply_inline_markdown in both format_response and the streaming path
  • Fixes format_response code-fence regex and blockquote rendering bug

What's rendered

Element Input Output
h1 # Foo bold bright-white, # stripped
h2 ## Foo bold white
h3 ### Foo bold
h4–h6 #### Foo bold dim
Horizontal rule --- / *** / ___ dim line across terminal
Blockquote > text dim gutter + dim text
Nested blockquote >> text collapses to single
Unordered list - item at depth 0, at 1, at 2, · at 3
Ordered list 1. item unchanged (already readable)
Reference link def [ref]: https://… suppressed (metadata, not prose)
Image ![alt](url) [img: alt] dim placeholder
Link [text](url) underlined text (url) — URL kept for copy/ctrl+click
<em> <em>text</em> italic, tags stripped
<strong> <strong>text</strong> bold, tags stripped

Bug fixes included

  • Blockquote renderingformat_response code-fence regex was unanchored and matched ``` mid-line (inside > ```python), causing blockquote lines to be syntax-highlighted; the \x1b guard then skipped apply_block_line, rendering raw > instead of . Fixed by anchoring with (?m)^.
  • format_response fence delimiter leakage — fence lines were preserved in output and leaked into the Panel display. Fixed by consuming fences entirely; added \033[0m fallback for plain-text lexer lines so pass 2 always skips code content.
  • format_response newline losssplitlines(keepends=True) fed \n-bearing strings into apply_block_line; matched block elements were returned without \n and concatenated with the following line. Fixed by splitlines() + manual "\n".join.
  • Blockquote reset_suffix — inline spans inside blockquotes reset to terminal default instead of restoring the dim gutter style. Fixed by passing reset_suffix=_BLOCKQUOTE_ANSI.
  • _MD_REF_LINK_RE titled forms$ anchor prevented suppression of titled reference-link definitions. Removed $.

Known interim regression

--- immediately after a paragraph renders as an hr instead of a setext h2. Corrected in PR4 (StreamingBlockBuffer; see docs/spec-markdown-stateful-blocks.md).

Non-goals (deferred to PR4)

  • Setext headings, tables, multi-line blockquote continuation

KUSH42 added 5 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.
…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 2, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #4504.

@KUSH42 KUSH42 closed this Apr 2, 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