Skip to content

Commit d49122a

Browse files
committed
improvement(chat): require key-value colon evidence before dropping an unparsable tag body
1 parent 9775580 commit d49122a

2 files changed

Lines changed: 29 additions & 21 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.test.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,8 @@ describe('parseSpecialTags with <question>', () => {
330330
// fails JSON.parse, so the old was-it-ever-JSON test called them prose and
331331
// rendered the whole payload verbatim — the markdown layer then swallowed
332332
// the tag markers and the reader saw a wall of raw JSON. They open `{"` or
333-
// `[{`, which marks them as attempted payloads: droppable, like any other
334-
// broken emission.
333+
// `[{` and carry key-value colons, which marks them as attempted payloads:
334+
// droppable, like any other broken emission.
335335
const cases = [
336336
'Prose before. <question>{"type": "single_select", "prompt": "How should I proceed?", "options": [{"id": "a", "label": "Confirm the id"}}]}</question>',
337337
'Prose before. <question>{"type":"multi_select","prompt":"What should I build now?",options": [{"id":"lib","label":"Pattern library"}]}</question>',
@@ -377,15 +377,17 @@ describe('parseSpecialTags with <question>', () => {
377377
expect(renderedText(parseSpecialTags(raw, false).segments)).toBe(raw)
378378
})
379379

380-
it('treats brace-wrapped quoted prose as an attempted payload, by design', () => {
381-
// The deliberate edge of the opener heuristic, pinned so it stays a
382-
// decision: `{"..."}` reads as a payload the model started and botched (a
383-
// key with no value), not prose — prose the reader was meant to see arrives
384-
// unwrapped or in bare quotes, and both of those stay rendered (see the
385-
// cases above). The array twin parses as JSON, so it was already dropped as
386-
// `wrong-shape` before the opener heuristic existed.
380+
it('renders brace-wrapped quoted prose an opener alone is not an attempt', () => {
381+
// The attempted-payload call takes BOTH kinds of evidence: the `{"` opener
382+
// and a key-value colon outside string literals. `{"the Q4 report"}` has
383+
// the opener but no colon — prose in costume, so it renders; a colon
384+
// inside the quotes changes nothing. The array twin parses as JSON, so it
385+
// was dropped as `wrong-shape` before this heuristic existed and still is —
386+
// that verdict comes from a real parse, not from the opener.
387387
const braceWrapped = 'see <options>{"the Q4 report"}</options> end'
388-
expect(renderedText(parseSpecialTags(braceWrapped, false).segments)).toBe('see end')
388+
expect(renderedText(parseSpecialTags(braceWrapped, false).segments)).toBe(braceWrapped)
389+
const quotedColon = 'see <options>{"ratio: 4:5"}</options> end'
390+
expect(renderedText(parseSpecialTags(quotedColon, false).segments)).toBe(quotedColon)
389391
const arrayWrapped = 'see <options>["some list item"]</options> end'
390392
expect(renderedText(parseSpecialTags(arrayWrapped, false).segments)).toBe('see end')
391393
})

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -609,8 +609,8 @@ const LONGEST_TAG_MARKER = Math.max(...SPECIAL_TAG_NAMES.map((name) => `</${name
609609
*/
610610
function blankJsonStringLiterals(body: string): string {
611611
// With no quote there is no string literal, so the loop below would copy the
612-
// body to itself character by character. Both callers reach here on bodies
613-
// that are usually plain prose, and this runs per opener per streamed chunk.
612+
// body to itself character by character. Callers reach here on bodies that
613+
// are usually plain prose, and this runs per opener per streamed chunk.
614614
if (!body.includes('"')) return body
615615

616616
let out = ''
@@ -984,20 +984,26 @@ type BodyClass =
984984
* JSON payload — the line between `not-parsable` (droppable) and
985985
* `not-a-payload` (must render).
986986
*
987-
* The test is the opener pair, whitespace-tolerant: every payload these tags
988-
* carry is an object of quoted keys or an array of objects/strings, so an
989-
* attempt opens `{"`, `[{`, or `["`. Prose falls outside it by construction —
987+
* Two pieces of evidence, both required, both structural: the opener pair and
988+
* a key-value colon. Every payload these tags carry is built from objects of
989+
* quoted keys, so an attempt opens `{"`, `[{`, or `["` AND carries a `:`
990+
* outside its string literals. Prose fails one or the other by construction —
990991
* `{the Q4 report}` opens `{t`, `{type: "file"}` opens `{t`, `{'type':'file'}`
991-
* opens `{'`, a bare scalar opens with its own first character — so the
992-
* wrapped-prose cases stay rendered while a payload one typo away from valid
993-
* (`{"type":"multi_select",options": …`) is recognized as the broken emission
994-
* it is. Named for the question it answers, not the check it performs: the
995-
* class names assert meaning, and this predicate is what earns the assertion.
992+
* opens `{'`, a bare scalar opens with its own first character, and a
993+
* brace-wrapped quoted phrase (`{"the Q4 report"}`) has no colon outside its
994+
* quotes — so every wrapped-prose shape stays rendered while a payload one
995+
* typo away from valid (`{"type":"multi_select",options": …`) is recognized as
996+
* the broken emission it is. Deleting text is the harm here, so the predicate
997+
* fails toward rendering. Named for the question it answers, not the checks it performs:
998+
* the class names assert meaning, and this predicate is what earns the
999+
* assertion. Blanks on the rare path only, like {@link isParseableJson} — the
1000+
* common cases never reach it.
9961001
*/
9971002
function wasAttemptedPayload(body: string): boolean {
9981003
const opener = /^\s*([{[])\s*(["{])/.exec(body)
9991004
if (!opener) return false
1000-
return opener[1] === '{' ? opener[2] === '"' : true
1005+
if (opener[1] === '{' && opener[2] !== '"') return false
1006+
return blankJsonStringLiterals(body).includes(':')
10011007
}
10021008

10031009
/**

0 commit comments

Comments
 (0)