Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
287ae8e
fix(copilot): render unclosed special tags as text on completed messages
j15z Jul 25, 2026
08c3d69
improvement(copilot): show text as soon as an unclosed tag cannot res…
j15z Jul 25, 2026
936bf03
fix(copilot): ignore tag syntax quoted inside a JSON tag body
j15z Jul 25, 2026
f1213c6
fix(copilot): keep prose a mispaired tag would swallow, and bail on d…
j15z Jul 25, 2026
e4ba2f6
fix(copilot): keep prose a tag wrapped instead of a JSON payload
j15z Jul 25, 2026
9492de4
refactor(copilot): resolve each special tag through four named outcomes
j15z Jul 25, 2026
ce1a898
test(copilot): pin the no-closing-tag case as lossless
j15z Jul 25, 2026
ea3a181
fix(copilot): stop the literal path flashing payloads and rescanning …
j15z Jul 25, 2026
6945d80
refactor(copilot): drop the JSON-prefix wrapper the split orphaned
j15z Jul 27, 2026
e7adb0e
refactor(copilot): drop the nested-marker scan the JSON rule already …
j15z Jul 27, 2026
c6d6368
fix(copilot): stop the rescan deleting quoted text, and bound its cost
j15z Jul 27, 2026
600425b
test(copilot): assert what a reader sees, and pin the unclosed-thinki…
j15z Jul 27, 2026
c469644
docs(copilot): name the scan window's blind spot instead of glossing it
j15z Jul 27, 2026
6f9ae20
fix(copilot): make the indexOf cache correct for any call order
j15z Jul 28, 2026
09366ee
fix(copilot): resume at the window edge, not past the close, on a tru…
j15z Jul 28, 2026
fc03111
fix(copilot): only discard a body that actually parsed as JSON
j15z Jul 28, 2026
78f66c7
fix(copilot): apply the nesting rule to a prose body on the matched-p…
j15z Jul 28, 2026
018fb41
test(copilot): assert parser invariants over generated messages
j15z Jul 28, 2026
d48670c
fix(copilot): strip a stray null byte from the test file
j15z Jul 28, 2026
75ec1f4
refactor(copilot): split resolveTagAt into budget, classification, an…
j15z Jul 28, 2026
3064101
fix(copilot): stop backtick stripping from unbalancing a whole message
j15z Jul 28, 2026
3484875
fix(copilot): apply the same backtick guard to the balanced unwrap
j15z Jul 28, 2026
be6d47c
fix(copilot): pair backticks the way markdown does, and bound the scan
j15z Jul 28, 2026
3996303
fix(copilot): address the confirmed review findings
j15z Jul 28, 2026
a3ba7a6
refactor(copilot): quality pass over the parser and its tests
j15z Jul 28, 2026
a6f7f7d
docs(copilot): reattach an orphaned docstring and cut comment noise
j15z Jul 28, 2026
86fe480
fix(copilot): sanitize backticks in one pass so a flush code span sur…
j15z Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,98 @@ describe('sanitizeChatDisplayContent', () => {

expect(sanitizeChatDisplayContent(content)).toBe('Read and found the issue.')
})

it('leaves a backticked mention of the tag name alone', () => {
// Unwrapping exists so stray backticks cannot stop a real chip rendering. A
// MENTION is not a tag: there is no payload and no closing marker, just the
// name written in prose. Stripping its opening backtick leaves the closing
// one unpaired, which opens a code span that swallows the rest of the
// message — every later `code` toggles to the wrong state.
const content = 'The `<workspace_resource>` tag needs a real `path` to render.'

expect(sanitizeChatDisplayContent(content)).toBe(content)
})

it('keeps backticks balanced across a message that mentions the tag repeatedly', () => {
const content =
'Use `<workspace_resource>` for files.\n\nThe `<workspace_resource>` chip needs an `id`.'
const backticks = (text: string) => (text.match(/`/g) || []).length

expect(backticks(sanitizeChatDisplayContent(content))).toBe(backticks(content))
})

it('leaves prose that mentions the opener and the closer separately alone', () => {
// The balanced unwrap reads a backticked opener, prose, and a backticked
// closer as ONE wrapped tag, and strips the outer pair. This is the shape a
// message explaining the tag syntax naturally takes.
const content = 'Use `<workspace_resource>` then close with `</workspace_resource>` at the end.'

expect(sanitizeChatDisplayContent(content)).toBe(content)
})

it('leaves a neighbouring code span alone', () => {
// Only a backtick DIRECTLY against the tag is the tag's wrapping. Allowing
// whitespace between let the pattern reach past the tag and take the
// delimiter off an unrelated span, breaking its pair.
const content =
'Open `config.json` <workspace_resource>{"type":"file","path":"a.md","title":"a"}</workspace_resource> then run `bun test`.'

expect(sanitizeChatDisplayContent(content)).toBe(content)
})

it('leaves a code span sitting flush against the tag alone', () => {
// No space between them, so the span's delimiter is directly against the
// tag and a pattern looking for "a backtick beside a tag" cannot tell the
// two apart. A single pass settles it: a span consumes its own delimiters as
// the scan reaches them, and a trailing backtick is only taken as a stray
// when no further backtick follows on the line.
const before =
'Open `config.json`<workspace_resource>{"type":"file","path":"a.md","title":"a"}</workspace_resource> ok'
const after =
'Open <workspace_resource>{"type":"file","path":"a.md","title":"a"}</workspace_resource>`config.json` ok'
const both =
'Open `a`<workspace_resource>{"type":"file","path":"a.md","title":"a"}</workspace_resource>`b` ok'

expect(sanitizeChatDisplayContent(before)).toBe(before)
expect(sanitizeChatDisplayContent(after)).toBe(after)
expect(sanitizeChatDisplayContent(both)).toBe(both)
})

it('does not break the closing fence of a code block containing a tag', () => {
// Whitespace matching used to cross the newline and consume one of the three
// closing backticks, so the block never closed and the rest of the message
// rendered as code.
const content =
'Example:\n```md\n<workspace_resource>{"type":"file","path":"a.md","title":"a"}</workspace_resource>\n```\nDone.'

expect(sanitizeChatDisplayContent(content)).toBe(content)
})

it('stays linear on a message that repeats the tag name without ever closing it', () => {
// A lazy scan allowed to cross an opener restarts from every opener, which
// is quadratic — 154ms for this input before the bound, on the main thread,
// for every streamed chunk. Generous ceiling so the test pins the complexity
// rather than the machine.
const content = 'The <workspace_resource> tag is used here. '.repeat(4000)

const startedAt = performance.now()
sanitizeChatDisplayContent(content)
expect(performance.now() - startedAt).toBeLessThan(50)
})

it('still unwraps a real tag that carries a stray backtick on one side only', () => {
// The case the unpaired strip is actually for: the model backticked the
// opener but not the closer (or vice versa), which would block the chip.
const leading =
'`<workspace_resource>{"type":"file","path":"a.md","title":"a"}</workspace_resource> done'
const trailing =
'<workspace_resource>{"type":"file","path":"a.md","title":"a"}</workspace_resource>` done'

expect(sanitizeChatDisplayContent(leading)).toBe(
'<workspace_resource>{"type":"file","path":"a.md","title":"a"}</workspace_resource> done'
)
expect(sanitizeChatDisplayContent(trailing)).toBe(
'<workspace_resource>{"type":"file","path":"a.md","title":"a"}</workspace_resource> done'
)
})
})
Original file line number Diff line number Diff line change
@@ -1,12 +1,63 @@
const HIDDEN_INLINE_REFERENCE_PATTERN =
/`[^`\n]*(?:internal\/tool-results\/|internal\/blocktips\/|components\/integrations\/[^`\n]*README)[^`\n]*`/g
const WORKSPACE_RESOURCE_CODE_SPAN_PATTERN =
/`([^`\n]*<workspace_resource>[\s\S]*?<\/workspace_resource>[^`\n]*)`/g

/**
* A complete workspace-resource tag: opener, payload, closer.
*
* Two constraints on the payload, both load-bearing:
*
* - **No backtick.** A payload is JSON and carries none, so this is what tells a
* real tag from prose MENTIONING the tag name — a message explaining the
* syntax writes the opener and the closer as two separately backticked spans.
* - **No nested opener**, via the negative lookahead. A cost bound rather than a
* correctness rule: a lazy scan allowed to cross an opener restarts from every
* opener, so a message repeating the tag name is quadratic — on the main
* thread, for every streamed chunk.
*
* Accepted trade: a resource whose title or path itself contains a backtick is
* not matched, so it renders as text rather than a chip. That costs one chip and
* is rare; the failure it replaces corrupts a whole message and is common.
*/
const COMPLETE_TAG_SOURCE =
'<workspace_resource>(?:(?!<workspace_resource>)[^`])*?<\\/workspace_resource>'

/** Non-global so {@link RegExp.test} has no `lastIndex` to carry between calls. */
const COMPLETE_WORKSPACE_RESOURCE_TAG = new RegExp(COMPLETE_TAG_SOURCE)

/**
* One left-to-right pass over the two things that can own a backtick: an inline
* code span, and a tag with a stray backtick pressed against it.
*
* ONE pass is the design. Two separate passes each have to guess which backticks
* belong together, and every previous arrangement of this file got a different
* case wrong — a span two words away, a code fence, then a span sitting flush
* against the tag. Here a span consumes its own delimiters as the scan reaches
* them, so `` `config.json`<tag> `` keeps its pair without a special case.
*
* The trailing backtick is only taken when no further backtick follows on the
* line; otherwise it is not a stray at all but the opener of the next span, and
* `` <tag>`config.json` `` would lose that span's delimiter. A LEADING backtick
* needs no such guard, because a backtick that closes a span is consumed as part
* of that span. Of the two, only the trailing lookahead is pinned by a test —
* swapping the alternatives changes behaviour only for a span that both opens
* flush against a tag and closes elsewhere, which no fixture covers.
*/
const CODE_SPAN_OR_FLANKED_TAG = new RegExp(
`\`[^\`\\n]*\`|\`?(${COMPLETE_TAG_SOURCE})(?:\`(?![^\`\\n]*\`))?`,
'g'
)

export function sanitizeChatDisplayContent(content: string): string {
return content
.replace(WORKSPACE_RESOURCE_CODE_SPAN_PATTERN, '$1')
.replace(CODE_SPAN_OR_FLANKED_TAG, (match, tag?: string) => {
// A tag with stray backticks against it: keep the tag, drop the strays.
if (tag !== undefined) return tag

// A code span. Unwrap it only when it genuinely holds a tag — the parser
// lifts the tag out either way, so leaving the delimiters would strand a
// pair of backticks around a hole. Anything else is someone else's span.
const inner = match.slice(1, -1)
return COMPLETE_WORKSPACE_RESOURCE_TAG.test(inner) ? inner : match
})
.replace(HIDDEN_INLINE_REFERENCE_PATTERN, '')
.replace(/`(\s*<workspace_resource>)/g, '$1')
.replace(/(<\/workspace_resource>\s*)`/g, '$1')
}
Loading
Loading