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
fda2a18
fix(copilot): render unclosed special tags as text on completed messages
j15z Jul 25, 2026
3645d61
improvement(copilot): show text as soon as an unclosed tag cannot res…
j15z Jul 25, 2026
336ff5c
fix(copilot): ignore tag syntax quoted inside a JSON tag body
j15z Jul 25, 2026
9c46ea8
fix(copilot): keep prose a mispaired tag would swallow, and bail on d…
j15z Jul 25, 2026
b9c88ec
fix(copilot): keep prose a tag wrapped instead of a JSON payload
j15z Jul 25, 2026
30f2e68
refactor(copilot): resolve each special tag through four named outcomes
j15z Jul 25, 2026
bc15e45
test(copilot): pin the no-closing-tag case as lossless
j15z Jul 25, 2026
3335d70
fix(copilot): stop the literal path flashing payloads and rescanning …
j15z Jul 25, 2026
156fbc0
refactor(copilot): drop the JSON-prefix wrapper the split orphaned
j15z Jul 27, 2026
e71501a
refactor(copilot): drop the nested-marker scan the JSON rule already …
j15z Jul 27, 2026
39d7606
fix(copilot): stop the rescan deleting quoted text, and bound its cost
j15z Jul 27, 2026
d2fd114
test(copilot): assert what a reader sees, and pin the unclosed-thinki…
j15z Jul 27, 2026
0abfbc1
docs(copilot): name the scan window's blind spot instead of glossing it
j15z Jul 27, 2026
d6db73d
fix(copilot): make the indexOf cache correct for any call order
j15z Jul 28, 2026
737aada
fix(copilot): resume at the window edge, not past the close, on a tru…
j15z Jul 28, 2026
cc7ebda
fix(copilot): only discard a body that actually parsed as JSON
j15z Jul 28, 2026
2f9b60a
fix(copilot): apply the nesting rule to a prose body on the matched-p…
j15z Jul 28, 2026
3d429e3
test(copilot): assert parser invariants over generated messages
j15z Jul 28, 2026
fc46478
fix(copilot): strip a stray null byte from the test file
j15z Jul 28, 2026
d92c8b9
refactor(copilot): split resolveTagAt into budget, classification, an…
j15z Jul 28, 2026
82ac4f8
fix(copilot): stop backtick stripping from unbalancing a whole message
j15z Jul 28, 2026
66a9500
fix(copilot): apply the same backtick guard to the balanced unwrap
j15z Jul 28, 2026
bad05fe
fix(copilot): pair backticks the way markdown does, and bound the scan
j15z Jul 28, 2026
ced378a
fix(copilot): address the confirmed review findings
j15z Jul 28, 2026
2f57cf6
refactor(copilot): quality pass over the parser and its tests
j15z Jul 28, 2026
f599991
docs(copilot): reattach an orphaned docstring and cut comment noise
j15z Jul 28, 2026
7dc9148
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