fix(agents): preserve UTF-16 boundaries in block chunks#104441
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 11, 2026, 8:24 AM ET / 12:24 UTC. Summary PR surface: Source +7, Tests +32. Total +39 across 2 files. Reproducibility: yes. Current main's forced fallback can be exercised directly with an astral character straddling Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Next step before merge
Security Review detailsBest possible solution: Land the canonical forced-index normalization and its public-class regressions after ordinary exact-head maintainer review. Do we have a high-confidence way to reproduce the issue? Yes. Current main's forced fallback can be exercised directly with an astral character straddling Is this the best way to solve the issue? Yes. Correcting the single canonical forced break index is narrower and safer than repairing malformed emitted chunks or buffered remainders downstream. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against a4ee15a851df. Label changesLabel justifications:
Evidence reviewedPR surface: Source +7, Tests +32. Total +39 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
Land-ready maintainer proof for exact head
Known proof gaps: downstream channel rendering was not separately exercised; the defect terminates at this shared production chunker’s emit callback and buffered state, both covered directly. |
|
Merged via squash.
|
Summary
EmbeddedBlockChunkermust split atmaxCharsWhat Problem This Solves
When no natural Markdown break existed, the chunker returned the raw UTF-16 code-unit
maxCharsindex. If that index fell between an astral character's high and low surrogates, the emitted chunk ended with an isolated high surrogate and the buffered remainder began with an isolated low surrogate.User Impact
Streamed block chunks no longer contain malformed UTF-16 when a forced size boundary lands inside emoji or another astral character.
Origin / follow-up
sliceUtf16Safeutility.Competition / linked PR analysis
A fresh exact-class search found #94216 and #94247 touching the same chunker files, but both address paragraph-separator preservation. Their diffs append or coordinate
\n\naround paragraph breaks and do not align forcedmaxCharsindices to UTF-16 boundaries. #68986 does not change the chunker source or target test.No open PR covers the surrogate-boundary root cause, and the author's open PR scan found no overlap.
Merge risk
maxCharsbisects a surrogate pair. For the otherwise impossiblemaxChars=1case with an astral first character, the complete two-unit code point is emitted so the chunker retains forward progress.src/agents/embedded-agent-block-chunker.test.tsmaxCharsdirectly as a UTF-16 code-unit slice index and propagated that same unsafe index into chunk emission and remainder state.flushOnParagraph, and callback APIs remain unchanged.#pickBreakIndexis the source of truth for forced split positions; downstream emit consumers are not patched to repair malformed strings.bufferedTextacross both forced branches and the minimum-budget progress case.Real behavior proof
BlockReplyChunkingplus streamed text → ingestionEmbeddedBlockChunker.append→ normalization fence parsing and#pickBreakIndex→ runtime objectBreakResultand internal buffer → emitted chunk and buffered-remainder effect. Concretely, appended assistant text reaches the forcedmaxCharsbranch, receives one shared safe index, passes through#emitBreakResult, and is exposed through the production emit callback andbufferedText.sliceUtf16Safethrough the existing shared utility export; provider-specific constraints are not applicable.EmbeddedBlockChunkerexecution for plain, fenced, one-unit, and natural-paragraph fixtures, pluspnpm test src/agents/embedded-agent-block-chunker.test.ts.Regression Test Plan
pnpm test src/agents/embedded-agent-block-chunker.test.tsRisk / Compatibility
No API, schema, configuration, or dependency changes. Typical forced chunks can be one code unit shorter only when the previous boundary split a surrogate pair. A configured one-unit budget can emit a two-unit astral code point because emitting either half would be malformed and emitting nothing would stall.
Out of scope
No changes to paragraph-separator reconstruction, downstream coalescing, channel formatting, provider output, or delivery transport. The two adjacent open PRs remain responsible for their separate paragraph-boundary behavior.
Root Cause
The forced fallback treated a UTF-16 code-unit budget as a universally safe string boundary. The final
source.slicethen materialized that unsafe index into both sides of the split, so the boundary must be corrected at#pickBreakIndexbefore fence and emission logic consume it.