@@ -57,13 +57,16 @@ export function shouldStripCodeFences(generationType?: string): boolean {
5757 * a false positive here would corrupt working code, which is far worse than
5858 * leaving a rare unwrapped response for the user to fix.
5959 *
60- * Only the outermost delimiters are removed: everything between the first and
61- * last fence line is kept verbatim, including any fence lines inside it. A
62- * generated body may legitimately contain line-leading backticks (code that
63- * builds a markdown string), and pairing delimiters off would silently discard
64- * the lines between them. The cost is that a model which answers with several
65- * fenced blocks and prose between them keeps that prose — visibly wrong output
66- * the user can re-roll, rather than code quietly missing a chunk.
60+ * Only two lines can ever be removed: the opening fence, and the final line when
61+ * it is also a fence. An interior fence line is always treated as content, because
62+ * a generated body may legitimately contain line-leading backticks (code that
63+ * builds a markdown string) and there is no way to tell that apart from a
64+ * delimiter. Scanning for the *last* fence anywhere would truncate such a body
65+ * whenever the response is cut off before its closing fence.
66+ *
67+ * The cost is that a model which answers with several fenced blocks and prose
68+ * between them keeps that prose — visibly wrong output the user can re-roll,
69+ * rather than code quietly missing a chunk.
6770 *
6871 * Falls back to the original text if stripping would leave nothing.
6972 */
@@ -78,22 +81,16 @@ export function stripCodeFences(text: string): string {
7881 if ( lines [ index ] . trim ( ) !== '' ) return text
7982 }
8083
81- let closingFence = - 1
82- for ( let index = lines . length - 1 ; index > openingFence ; index -- ) {
83- if ( FENCE_LINE . test ( lines [ index ] ) ) {
84- closingFence = index
85- break
86- }
87- }
88-
89- // An unclosed fence (a truncated response) keeps everything after the opener.
90- const inner =
91- closingFence === - 1
92- ? lines . slice ( openingFence + 1 )
93- : lines . slice ( openingFence + 1 , closingFence )
84+ const inner = lines . slice ( openingFence + 1 )
9485
9586 // Trim blank lines only — leading whitespace on a kept line is indentation,
9687 // which is load-bearing in Python.
88+ while ( inner . length > 0 && inner [ inner . length - 1 ] . trim ( ) === '' ) inner . pop ( )
89+
90+ // Only the very last line may close the wrapper. A truncated response simply
91+ // has no closer, and every line after the opener survives.
92+ if ( inner . length > 0 && FENCE_LINE . test ( inner [ inner . length - 1 ] ) ) inner . pop ( )
93+
9794 while ( inner . length > 0 && inner [ 0 ] . trim ( ) === '' ) inner . shift ( )
9895 while ( inner . length > 0 && inner [ inner . length - 1 ] . trim ( ) === '' ) inner . pop ( )
9996
0 commit comments