fix(gateway): keep overflow stream chunks editable - #45938
Conversation
|
Verified clean — gateway stream consumer overflow refactor + chunk-boundary fix. Reviewed the full diff and test additions. Key observations:
No issues found. The |
85739f6 to
79b8e7a
Compare
|
Rebased this onto current Verification:
|
teknium1
left a comment
There was a problem hiding this comment.
Thanks for identifying a real current-main streaming issue: gateway/stream_consumer.py:672-701 sends every initial-overflow chunk and clears _accumulated, so it leaves no editable trailing preview.
Problems
gateway/stream_consumer.py:538in this PR replacesadapter.truncate_message()with newline-only direct slicing. That bypasses the documented splitter contract ingateway/platforms/base.py:5521-5648, including word boundaries, balanced/reopened code fences, and chunk indicators. It also bypasses Yuanbao's table/fence-aware override atgateway/platforms/yuanbao.py:4982-5008.
Suggested changes
- Preserve the adapter splitter contract while implementing the rolling editable tail; do not duplicate a reduced splitter in the stream consumer.
- Add a first-send rolling-edit regression using an overflowed fenced block. Existing coverage explicitly protects balanced fences at
tests/gateway/test_platform_base.py:1505-1535and UTF-16 fence handling attests/gateway/test_platform_base.py:1718-1728.
Automated hermes-sweeper review.
| reply_to = self._message_id or self._initial_reply_to_id | ||
| for chunk in chunks: | ||
| reply_to = self._initial_reply_to_id | ||
| while _len_fn(self._accumulated) > _safe_limit: |
There was a problem hiding this comment.
This loop replaces adapter.truncate_message() with newline-only slicing. That loses the shared splitter's word-boundary, balanced/reopened code-fence, and chunk-indicator behavior (gateway/platforms/base.py:5521-5648) and bypasses adapter overrides such as Yuanbao's table-aware splitter. Please retain that contract while keeping the editable tail.
79b8e7a to
e0ed971
Compare
|
Rebased onto the current upstream The stream overflow path now preserves the platform adapter contract instead of directly slicing oversized text. The rolling editable tail remains intact, including adapter-specific fence/table handling. Verification on this head:
The PR is ready for review of this head. |
Widen #48476's fence guarantees to the two splitters that still emitted fence-broken chunks: * GatewayStreamConsumer._split_text_chunks (fallback final send): close the orphaned ``` at each chunk boundary and reopen it — with the original language tag — on the next chunk, mirroring BasePlatformAdapter.truncate_message's contract. Headroom is reserved so balanced chunks stay within the platform limit. * Slack block_kit._split_text (3000-char section chunking): same close/reopen balancing for mrkdwn section text carrying fences. With these, every chunk boundary — non-streaming send (truncate_message), streaming overflow (_truncate_for_stream via adapter.truncate_message per #45938), fallback final (_split_text_chunks), final-send balance (ensure_closed_code_fences), and Block Kit section splits — delivers fence-balanced chunks. Regression tests probe each path with fenced fixtures, assert per-chunk balance, limit compliance, language-tag reopening, and prose passthrough.
Widen #48476's fence guarantees to the two splitters that still emitted fence-broken chunks: * GatewayStreamConsumer._split_text_chunks (fallback final send): close the orphaned ``` at each chunk boundary and reopen it — with the original language tag — on the next chunk, mirroring BasePlatformAdapter.truncate_message's contract. Headroom is reserved so balanced chunks stay within the platform limit. * Slack block_kit._split_text (3000-char section chunking): same close/reopen balancing for mrkdwn section text carrying fences. With these, every chunk boundary — non-streaming send (truncate_message), streaming overflow (_truncate_for_stream via adapter.truncate_message per #45938), fallback final (_split_text_chunks), final-send balance (ensure_closed_code_fences), and Block Kit section splits — delivers fence-balanced chunks. Regression tests probe each path with fenced fixtures, assert per-chunk balance, limit compliance, language-tag reopening, and prose passthrough.
Widen #48476's fence guarantees to the two splitters that still emitted fence-broken chunks: * GatewayStreamConsumer._split_text_chunks (fallback final send): close the orphaned ``` at each chunk boundary and reopen it — with the original language tag — on the next chunk, mirroring BasePlatformAdapter.truncate_message's contract. Headroom is reserved so balanced chunks stay within the platform limit. * Slack block_kit._split_text (3000-char section chunking): same close/reopen balancing for mrkdwn section text carrying fences. With these, every chunk boundary — non-streaming send (truncate_message), streaming overflow (_truncate_for_stream via adapter.truncate_message per #45938), fallback final (_split_text_chunks), final-send balance (ensure_closed_code_fences), and Block Kit section splits — delivers fence-balanced chunks. Regression tests probe each path with fenced fixtures, assert per-chunk balance, limit compliance, language-tag reopening, and prose passthrough.
|
Merged via #70191 — your commit was cherry-picked/reapplied onto current main with your authorship preserved in git history: your editable overflow stream chunks fix was cherry-picked. Thanks for the contribution! |
Widen NousResearch#48476's fence guarantees to the two splitters that still emitted fence-broken chunks: * GatewayStreamConsumer._split_text_chunks (fallback final send): close the orphaned ``` at each chunk boundary and reopen it — with the original language tag — on the next chunk, mirroring BasePlatformAdapter.truncate_message's contract. Headroom is reserved so balanced chunks stay within the platform limit. * Slack block_kit._split_text (3000-char section chunking): same close/reopen balancing for mrkdwn section text carrying fences. With these, every chunk boundary — non-streaming send (truncate_message), streaming overflow (_truncate_for_stream via adapter.truncate_message per NousResearch#45938), fallback final (_split_text_chunks), final-send balance (ensure_closed_code_fences), and Block Kit section splits — delivers fence-balanced chunks. Regression tests probe each path with fenced fixtures, assert per-chunk balance, limit compliance, language-tag reopening, and prose passthrough.
What does this PR do?
Keeps the newest overflow chunk editable while gateway responses stream past a platform message limit.
Previously, when a streamed response overflowed before an editable message existed, the consumer split the entire accumulated response through
truncate_message()and posted every chunk as an immutable standalone message. That meant the second Slack chunk could not keep updating while more tokens arrived. This PR seals only the overflowing head chunks, leaves the trailing chunk as the active preview, and lets later deltas edit that newest chunk in place.It also fixes the same split helper fallback to use the computed codepoint budget when adapters count length in non-codepoint units (for example Telegram UTF-16 units), so overflow chunks do not accidentally exceed the adapter's real limit.
Related Issue
No issue filed; reported from live Slack gateway behavior.
Type of Change
Changes Made
gateway/stream_consumer.py_accumulatedso_send_or_edit()creates it as the active editable chunk._custom_unit_to_cp(...)'s returned codepoint budget for split fallbacks, instead of mixing adapter length units with Python slice indexes.tests/gateway/test_stream_consumer.pyHow to Test
python -m pytest tests/gateway/test_stream_consumer.py::TestInitialOverflowRollingEdit::test_initial_overflow_keeps_last_chunk_as_edit_target -qAssertionError: the second overflow chunk should be edited...python -m pytest tests/gateway/test_stream_consumer.py::TestInitialOverflowRollingEdit::test_initial_overflow_keeps_last_chunk_as_edit_target -q1 passedpython -m pytest tests/gateway/test_stream_consumer.py -q94 passedscripts/run_tests.sh tests/gateway/test_stream_consumer.py tests/gateway/test_stream_consumer_fresh_final.py tests/gateway/test_stream_consumer_thread_routing.py tests/gateway/test_update_streaming.py -- -q150 tests passed, 0 failedpython scripts/check-windows-footguns.py gateway/stream_consumer.py✓ No Windows footguns found (1 file(s) scanned).python -m py_compile gateway/stream_consumer.py tests/gateway/test_stream_consumer.pyscripts/run_tests.sh -j 8 -- -q/tmpvs/private/tmpmacOS path assertions, and a timing-sensitive delegate heartbeat assertion. The targeted gateway suites above passed.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — N/A, no user-facing docs changedcli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/AFor New Skills
N/A — no new skill.
Screenshots / Logs
Not applicable; behavior is covered by the gateway stream consumer regression tests above.