From 9c8fb899794c70c3ffe508a1eb3bf0941d7dbb23 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 9 Sep 2026 09:15:52 -0400 Subject: [PATCH] fix(core): preserve inline code boundary backticks (#47410) --- packages/core/src/tool/html-markdown.ts | 17 ++++++--- packages/core/test/tool-webfetch.test.ts | 47 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/packages/core/src/tool/html-markdown.ts b/packages/core/src/tool/html-markdown.ts index 557b9c7fa2e..65766ab5165 100644 --- a/packages/core/src/tool/html-markdown.ts +++ b/packages/core/src/tool/html-markdown.ts @@ -217,13 +217,20 @@ export function convertHTMLToMarkdown(html: string) { } if (code.inline) { const fence = "`".repeat(Math.max(1, backticks + 1)) - const padding = /^ | $/.test(code.text) && !/^ +$/.test(code.text) ? " " : "" flushSpace() prefixQuote() - const wrapper = encoder.encode(`${fence}${padding}${padding}${fence}`).byteLength - appendRaw( - `${fence}${padding}${sliceBytes(code.text, Math.max(0, CONTENT_BYTES - outputBytes - wrapper))}${padding}${fence}`, - ) + const available = Math.max(0, CONTENT_BYTES - outputBytes - fence.length * 2) + let payload = sliceBytes(code.text, available) + while (payload) { + const padding = /^[ `]|[ `]$/.test(payload) && !/^ +$/.test(payload) ? " " : "" + const bytes = encoder.encode(payload).byteLength + if (bytes + padding.length * 2 <= available) { + appendRaw(`${fence}${padding}${payload}${padding}${fence}`) + return + } + // Padding costs at most two bytes, so at most two whole-code-point trims are needed. + payload = sliceBytes(payload, bytes - 1) + } return } if (activeCell) { diff --git a/packages/core/test/tool-webfetch.test.ts b/packages/core/test/tool-webfetch.test.ts index 1f1ec9aee9b..bcec31a6735 100644 --- a/packages/core/test/tool-webfetch.test.ts +++ b/packages/core/test/tool-webfetch.test.ts @@ -90,6 +90,53 @@ describe("WebFetchTool helpers", () => { ) }) + test.each([ + ["`x`", "`` `x` ``"], + ["`x", "`` `x ``"], + ["x`", "`` x` ``"], + ["`", "`` ` ``"], + ["``", "``` `` ```"], + ["``x`", "``` ``x` ```"], + ["say(`x`)", "``say(`x`)``"], + ["a``b`c", "```a``b`c```"], + ["x", "`x`"], + [" x ", "` x `"], + [" x", "` x `"], + ["x ", "` x `"], + [" ", "` `"], + [" ` ", "`` ` ``"], + ])("preserves inline code boundaries for %j", (content, expected) => { + expect(WebFetchTool.convertHTMLToMarkdown(`

Use ${content}.

`)).toBe(`Use ${expected}.`) + }) + + test.each([ + ["discarded trailing backtick after ASCII", "x`", 7, "``x``"], + ["discarded trailing backtick after Unicode", "😀`", 10, "``😀``"], + ["discarded trailing backtick with spare room", "x`", 9, "``x``"], + ["retained trailing backtick", "x`", 10, "`` x` ``"], + ["new trailing backtick from an internal run", "x`y", 8, "``x``"], + ["leading backtick without padding room", "`x", 8, ""], + ["leading backtick alone fits", "`x", 9, "`` ` ``"], + ["leading backtick with payload fits", "`x", 10, "`` `x ``"], + ["all backticks truncated", "``", 11, "``` ` ```"], + ["all backticks fit", "``", 12, "``` `` ```"], + ["mixed internal runs truncated", "a``b`c", 11, "```a```"], + ["Unicode code point cannot fit", "😀`", 9, ""], + ["ordinary payload cannot fit", "x", 3, ""], + ["spaces cannot fit", " ", 4, ""], + ["space-only prefix fits", " ", 5, "` `"], + ["truncated prefix becomes space-only", " x", 6, "` `"], + ["discarded trailing space", "x ", 5, "`x`"], + ] as const)("fits inline code to its emitted boundaries: %s", (_name, content, spare, expected) => { + const prefix = "x".repeat(WebFetchTool.MAX_RESPONSE_BYTES - 64 * 1024 - spare) + const html = `

${prefix}${content}

` + expect(Buffer.byteLength(html)).toBeLessThanOrEqual(WebFetchTool.MAX_RESPONSE_BYTES) + const output = WebFetchTool.convertHTMLToMarkdown(html) + expect(output.slice(0, prefix.length)).toBe(prefix) + expect(output.slice(prefix.length)).toBe(expected) + expect(Buffer.byteLength(output)).toBeLessThanOrEqual(WebFetchTool.MAX_RESPONSE_BYTES) + }) + test("keeps nested ordered and unordered lists structurally readable", () => { const html = `
  1. alpha
    • nested item
  2. beta first

    beta second

` expect(WebFetchTool.convertHTMLToMarkdown(html)).toBe(