@@ -440,12 +440,13 @@ export function parseTextTagBody(body: string): string | null {
440440/**
441441 * Whether `body` is syntactically valid JSON, regardless of its shape.
442442 *
443- * Separates "the agent formed a payload that failed its shape guard" from "this
444- * was never JSON" — the line that decides whether a failed body may be dropped
445- * or must be shown (see {@link classifyBody}). Costs a second parse of a body
446- * that already failed one, which is the rare path; the common cases never reach
447- * it, since a valid payload returns earlier and prose is rejected by the cheaper
448- * viability rule before this runs.
443+ * Separates "the agent formed a well-formed payload that failed its shape
444+ * guard" (`wrong-shape`) from "this body will not parse"; for the latter,
445+ * {@link wasAttemptedPayload} then decides whether it may be dropped or must be
446+ * shown (see {@link classifyBody}). Costs a second parse of a body that already
447+ * failed one, which is the rare path; the common cases never reach it, since a
448+ * valid payload returns earlier and prose is rejected by the cheaper viability
449+ * rule before this runs.
449450 */
450451function isParseableJson ( body : string ) : boolean {
451452 try {
@@ -770,27 +771,29 @@ type TagResolution =
770771 | { outcome : 'segment' ; segment : ContentSegment ; resumeAt : number }
771772 /** Provably not a tag; render the span verbatim and resume after it. */
772773 | { outcome : 'literal' ; resumeAt : number }
773- /** A well-formed payload that failed its shape guard — dropped deliberately. */
774+ /** A payload the agent attempted and botched — dropped deliberately. */
774775 | { outcome : 'discard' ; resumeAt : number }
775776 /** Still streaming and a close remains plausible; suppress the remainder. */
776777 | { outcome : 'pending' }
777778
778779/**
779- * Why a failed body was never an attempted payload — so the markers were literal
780- * text and the span must be shown rather than swallowed. `null` means the body
781- * really was a payload that failed its shape guard.
780+ * Mechanical evidence about a failed body — named for what was OBSERVED, never
781+ * for what it means. The semantic conclusion (attempted payload vs prose) is
782+ * drawn in {@link classifyBody}, which refines `not-viable-json` through
783+ * {@link wasAttemptedPayload}. `null` means the body parsed as JSON and simply
784+ * failed its shape guard.
782785 *
783- * The two reasons resume differently , which is why they are distinguished
784- * rather than collapsed into a boolean (see {@link resumeForClass}).
786+ * The two reasons lead to different resumes , which is why they are
787+ * distinguished rather than collapsed into a boolean (see {@link resumeForClass}).
785788 */
786789type LiteralTextVerdict =
787790 /**
788791 * The body carries a tag marker at `markerOffset` (an index into the body), so
789792 * the close we matched belongs to a different opener.
790793 */
791794 | { reason : 'foreign-markers' ; markerOffset : number }
792- /** The tag wrapped prose that was never JSON to begin with . */
793- | { reason : 'never-a-payload ' }
795+ /** The body is not a viable JSON prefix (first char or bracket depth) . */
796+ | { reason : 'not-viable-json ' }
794797
795798function literalTextReason (
796799 tagName : ( typeof SPECIAL_TAG_NAMES ) [ number ] ,
@@ -805,7 +808,7 @@ function literalTextReason(
805808 const scannable = isJsonBodied ? blankJsonStringLiterals ( body ) : body
806809 const marker = TAG_SHAPED_MARKER . exec ( scannable )
807810 if ( marker ) return { reason : 'foreign-markers' , markerOffset : marker . index }
808- if ( isJsonBodied && ! isViableJsonPrefixOf ( scannable ) ) return { reason : 'never-a-payload ' }
811+ if ( isJsonBodied && ! isViableJsonPrefixOf ( scannable ) ) return { reason : 'not-viable-json ' }
809812 return null
810813}
811814
@@ -906,10 +909,42 @@ type BodyClass =
906909 | { kind : 'prose-nested-marker' }
907910 /** Only a prefix was read, and it settled nothing. Says nothing about the rest. */
908911 | { kind : 'unexamined' }
909- /** Not a payload at all — never JSON, or JSON that will not parse. */
910- | { kind : 'never-json' }
911- /** Parsed as JSON, then failed its shape guard. The only droppable class. */
912- | { kind : 'broken-payload' }
912+ /**
913+ * Never an attempted payload — prose, prose-in-braces, a bare scalar, an
914+ * unquoted-key slip. The model's own words: showing them is mandatory,
915+ * dropping them deletes text the reader was meant to see.
916+ */
917+ | { kind : 'not-a-payload' }
918+ /**
919+ * An attempted payload that will not parse — opens like JSON (see
920+ * {@link wasAttemptedPayload}) but carries a syntax error. Droppable: the
921+ * reader was never meant to see the JSON, and rendering it raw is the
922+ * failure this parser exists to prevent.
923+ */
924+ | { kind : 'not-parsable' }
925+ /** Parsed as JSON, then failed its shape guard. Droppable, like `not-parsable`. */
926+ | { kind : 'wrong-shape' }
927+
928+ /**
929+ * Whether a body that will not parse was nonetheless an ATTEMPT at this tag's
930+ * JSON payload — the line between `not-parsable` (droppable) and
931+ * `not-a-payload` (must render).
932+ *
933+ * The test is the opener pair, whitespace-tolerant: every payload these tags
934+ * carry is an object of quoted keys or an array of objects/strings, so an
935+ * attempt opens `{"`, `[{`, or `["`. Prose falls outside it by construction —
936+ * `{the Q4 report}` opens `{t`, `{type: "file"}` opens `{t`, `{'type':'file'}`
937+ * opens `{'`, a bare scalar opens with its own first character — so the
938+ * wrapped-prose cases stay rendered while a payload one typo away from valid
939+ * (`{"type":"multi_select",options": …`) is recognized as the broken emission
940+ * it is. Named for the question it answers, not the check it performs: the
941+ * class names assert meaning, and this predicate is what earns the assertion.
942+ */
943+ function wasAttemptedPayload ( body : string ) : boolean {
944+ const opener = / ^ \s * ( [ { [ ] ) \s * ( [ " { ] ) / . exec ( body )
945+ if ( ! opener ) return false
946+ return opener [ 1 ] === '{' ? opener [ 2 ] === '"' : true
947+ }
913948
914949/**
915950 * Classify a complete body. Pure: no positions, no outcome, no resume.
@@ -943,29 +978,32 @@ function classifyBody(tagName: (typeof SPECIAL_TAG_NAMES)[number], body: string)
943978 }
944979 if ( inspected . truncated ) return { kind : 'unexamined' }
945980
946- // Dropping text is only defensible for a payload the agent actually FORMED.
947- // `{the Q4 report}` is prose in braces and `{type: "file"}` is an ordinary
948- // model slip; bracket depth cannot tell either from a real payload, only a
949- // parse can. Both routes to that answer are funnelled through one place so the
950- // rescan below cannot be added to one and forgotten on the other.
951- const neverJson =
952- verdict ?. reason === 'never-a-payload' || ( isJsonBodied && ! isParseableJson ( body ) )
981+ // Dropping text is only defensible for a payload the agent actually
982+ // ATTEMPTED. A parse settles the well-formed case (`wrong-shape`); for a body
983+ // that will not parse, the opener decides via wasAttemptedPayload below.
984+ // Bracket depth can tell neither prose-in-braces nor a typo'd payload from a
985+ // real one, so both routes to "unparseable" are funnelled through one place
986+ // and the rescan below cannot be added to one and forgotten on the other.
987+ const unparseable =
988+ verdict ?. reason === 'not-viable-json' || ( isJsonBodied && ! isParseableJson ( body ) )
953989
954- if ( neverJson ) {
990+ if ( unparseable ) {
955991 // literalTextReason blanked this body's quoted regions on the assumption it
956- // was JSON. It never was, so that assumption is void — and a body with an
957- // odd number of `"` blanks the WRONG regions, which can hide a real marker
958- // and turn what should be `nested-marker` into `never-json`. The difference
959- // is not academic: `never-json` resumes past the close, flattening a genuine
960- // tag inside the span, so a card already on screen un-renders when the close
961- // finally arrives. With the JSON premise gone, the raw text is the honest
962- // evidence, and a marker in it means the close we matched belongs elsewhere.
992+ // was valid JSON. It is not, so that assumption is void — and a body with
993+ // an odd number of `"` blanks the WRONG regions, which can hide a real
994+ // marker and misread a mispaired span as this tag's own body. The
995+ // difference is not academic: both classes below resume past the close,
996+ // flattening or discarding a genuine tag inside the span, so a card already
997+ // on screen un-renders when the close finally arrives. With the JSON
998+ // premise gone, the raw text is the honest evidence, and a marker in it
999+ // means the close we matched belongs elsewhere. Only after both marker
1000+ // scans come up empty may the opener test decide the remaining two classes.
9631001 const rawMarker = TAG_SHAPED_MARKER . exec ( inspected . text )
9641002 if ( rawMarker ) return { kind : 'nested-marker' , offsetInBody : rawMarker . index }
965- return { kind : 'never-json ' }
1003+ return wasAttemptedPayload ( body ) ? { kind : 'not-parsable' } : { kind : 'not-a-payload ' }
9661004 }
9671005
968- return { kind : 'broken-payload ' }
1006+ return { kind : 'wrong-shape ' }
9691007}
9701008
9711009/**
@@ -978,8 +1016,9 @@ function classifyBody(tagName: (typeof SPECIAL_TAG_NAMES)[number], body: string)
9781016function resumeForClass ( cls : BodyClass , bodyStart : number , pastClose : number ) : number {
9791017 switch ( cls . kind ) {
9801018 case 'payload' :
981- case 'broken-payload' :
982- case 'never-json' :
1019+ case 'wrong-shape' :
1020+ case 'not-parsable' :
1021+ case 'not-a-payload' :
9831022 // The whole span was read and accounted for; continue after it.
9841023 return pastClose
9851024 case 'nested-marker' :
@@ -1021,14 +1060,14 @@ function resolveMatchedPair(
10211060 switch ( cls . kind ) {
10221061 case 'payload' :
10231062 return { outcome : 'segment' , segment : cls . segment , resumeAt }
1024- case 'broken-payload ' :
1025- // Well-formed but the wrong shape — a broken emission. Showing the reader
1026- // raw JSON is worse than showing nothing.
1063+ case 'wrong-shape ' :
1064+ case 'not-parsable' :
1065+ // Showing the reader raw JSON is worse than showing nothing.
10271066 return { outcome : 'discard' , resumeAt }
10281067 case 'nested-marker' :
10291068 case 'prose-nested-marker' :
10301069 case 'unexamined' :
1031- case 'never-json ' :
1070+ case 'not-a-payload ' :
10321071 return { outcome : 'literal' , resumeAt }
10331072 }
10341073}
0 commit comments