From b932997f44dad84d454fbda4627b3e2953eea546 Mon Sep 17 00:00:00 2001 From: skywind Date: Sat, 27 Jun 2026 14:22:36 +0000 Subject: [PATCH] fix: also close orphaned single-backtick inline-code spans ensure_closed_code_fences previously only handled triple-backtick (```) code-block fences. Single backtick (`) inline-code spans have the same problem: an orphaned opening backtick causes the remainder of the message to render as inline code on Discord and other platforms. After balancing triple-backtick fences, strip complete ```...``` regions and count remaining standalone backtick markers. If odd, append a closing backtick. Same trade-off as the triple-backtick fix: a stray closing backtick may create a brief empty inline-code span, which is far less harmful than the rest of the message being inline code. --- gateway/stream_consumer.py | 34 +++++++-- tests/gateway/test_code_fence_tracking.py | 91 ++++++++++++++++++----- 2 files changed, 102 insertions(+), 23 deletions(-) diff --git a/gateway/stream_consumer.py b/gateway/stream_consumer.py index f37ef11b5469..d52d44b3798e 100644 --- a/gateway/stream_consumer.py +++ b/gateway/stream_consumer.py @@ -74,15 +74,18 @@ def escape_code_fences_for_display(text: str) -> str: def ensure_closed_code_fences(text: str) -> str: - """Append a closing `` ``` `` fence if the text has an odd number of - triple-backtick markers. + """Append a closing `` ``` `` fence and/or `` ` `` if the text has + orphaned code-block or inline-code markers. When model output is truncated mid-code-block (e.g. by token limits or a finish_reason="length"), the resulting message has an unclosed code fence. On Discord, Slack, and other platforms this causes everything after the orphaned fence to render as a single code block. + The same problem applies to inline-code spans closed by a single + backtick: an orphaned `` ` `` makes the remainder of the message + render as inline code. - The fix is trivial: count `` ``` `` occurrences. If odd, append a + Triple-backtick: count `` ``` `` occurrences. If odd, append a closing fence on its own line. This is safe because nested triple-backtick fences (e.g. a literal `` ``` `` inside a code block) are exceedingly rare in model output and, when they do appear, the @@ -90,14 +93,35 @@ def ensure_closed_code_fences(text: str) -> str: of the message — far less harmful than the entire message being one giant code block. + Single backtick: after balancing triple-backtick fences, strip all + complete `` ```…``` `` regions and count remaining standalone `` ` ``. + If odd, append a closing inline-code backtick. Same trade-off: a + stray closing backtick may produce a brief empty inline-code span, + which is far less harmful than the rest of the message being rendered + as inline code. + Returns: - The input text with a closing fence appended if needed, or the + The input text with closing markers appended if needed, or the input text unchanged. """ if not isinstance(text, str) or not text: return text + + # Step 1: fix triple-backtick code-block fences (existing logic) if text.count("```") % 2 == 1: - return text.rstrip("\n") + "\n```" + text = text.rstrip("\n") + "\n```" + + # Step 2: fix single-backtick inline-code spans + # Remove complete ```…``` regions so their internal backticks don't + # pollute the standalone count. Also remove any trailing unclosed + # ``` that leaks through (defence in depth). + import re + without_fences = re.sub(r"```.*?```", "", text, flags=re.DOTALL) + without_fences = re.sub(r"```[^`]*$", "", without_fences) + + if without_fences.count("`") % 2 == 1: + text = text + "`" + return text diff --git a/tests/gateway/test_code_fence_tracking.py b/tests/gateway/test_code_fence_tracking.py index efbd53fe6584..eeb7815c44c4 100644 --- a/tests/gateway/test_code_fence_tracking.py +++ b/tests/gateway/test_code_fence_tracking.py @@ -32,7 +32,7 @@ import pytest from unittest.mock import AsyncMock, MagicMock, patch, ANY from gateway.platforms.base import BasePlatformAdapter -from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig +from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig, ensure_closed_code_fences # ── helpers ─────────────────────────────────────────────────────────────── @@ -340,37 +340,92 @@ class TestStreamConsumerFinalSendGap: # ═══════════════════════════════════════════════════════════════════════════ -# I. What the fix should do +# I. ensure_closed_code_fences — triple-backtick fence balancing # ═══════════════════════════════════════════════════════════════════════════ -class TestFixStub: - """If a _ensure_closed_code_fences() helper were added, these tests - should pass.""" +class TestEnsureClosedCodeFences: + """Unit tests for the standalone ensure_closed_code_fences helper.""" - @staticmethod - def _ensure_closed_fences(text: str) -> str: - """Stub: append closing ``` if odd count and text doesn't already - end with a closing fence on its own line.""" - if _odd_fences(text): - return text.rstrip() + "\n```" - return text + # ── triple backtick ────────────────────────────────────────────── - def test_closes_unclosed(self): - assert not _odd_fences(self._ensure_closed_fences( + def test_closes_unclosed_triple(self): + """Unclosed ``` gets a closing fence appended.""" + assert not _odd_fences(ensure_closed_code_fences( "💭 **Reasoning:**\n```\ncut off" )) - def test_noop_balanced(self): + def test_noop_balanced_triple(self): + """Already-balanced ``` blocks are unchanged.""" t = "```\nblock\n```\ncontent" - assert self._ensure_closed_fences(t) == t + assert ensure_closed_code_fences(t) == t def test_noop_no_fence(self): + """Plain text without fences passes through.""" t = "plain text" - assert self._ensure_closed_fences(t) == t + assert ensure_closed_code_fences(t) == t def test_noop_already_ends_with_close(self): + """Text ending with a balanced ``` is unchanged.""" t = "```\nblock\n```" - assert self._ensure_closed_fences(t) == t + assert ensure_closed_code_fences(t) == t + + def test_closes_unclosed_mid_message(self): + """``` in the middle (not at end) still gets closed when odd.""" + t = "before\n```\nunclosed block\nmore text here" + result = ensure_closed_code_fences(t) + assert not _odd_fences(result) + assert result.endswith("\n```") + + # ── single backtick ────────────────────────────────────────────── + + def test_closes_unclosed_single(self): + """Orphaned single backtick gets a closing backtick appended.""" + result = ensure_closed_code_fences("Here is `inline code") + assert result == "Here is `inline code`" + + def test_noop_balanced_single(self): + """Paired single backticks are unchanged.""" + t = "Here is `inline code` and more text." + assert ensure_closed_code_fences(t) == t + + def test_single_inside_triple_ignored(self): + """Backticks inside ``` regions are NOT counted for single-bt parity.""" + t = "```\n`code` inside\n```\noutside `text`" + # outside has paired `text` → balanced, triple-blocks are balanced → no change + assert ensure_closed_code_fences(t) == t + + def test_single_outside_unclosed_after_triple(self): + """Unclosed single backtick outside ``` blocks gets fixed.""" + t = "```\nblock\n```\noutside `text" + result = ensure_closed_code_fences(t) + assert result == "outside `text`" or result.endswith("`") + + def test_both_triple_and_single_unclosed(self): + """Both ``` and ` unclosed → both get closed.""" + result = ensure_closed_code_fences("```\ncode\nstill open `inline") + assert result.endswith("`") + assert "```\ncode\nstill open `inline`\n```" in result or result.count("```") % 2 == 0 + + def test_noop_empty_or_none(self): + """Empty/None returns unchanged.""" + assert ensure_closed_code_fences("") == "" + assert ensure_closed_code_fences(None) is None + + def test_single_inline_code_in_prose(self): + """Realistic prose with `handle: \"...\"` inline code.""" + # `handle: "abc"` is open – unbalanced single backtick + t = ( + 'LLM 看到 `_headroom.retrieval.handle` 的值,就是它要傳給' + ' `headroom_retrieve(hash="125f4ae286e24ad8c0816907"` 的那個字串。' + ) + result = ensure_closed_code_fences(t) + # After fix: the last unclosed ` gets closed at the end + assert result.count("`") % 2 == 0 + + def test_multiple_single_backtick_pairs(self): + """Multiple correctly-paired single backtick spans are unchanged.""" + t = "Use `cmd1` for X and `cmd2` for Y." + assert ensure_closed_code_fences(t) == t # ═══════════════════════════════════════════════════════════════════════════