POC: MessageMaterializer + partial JSON for structured streaming#2182
POC: MessageMaterializer + partial JSON for structured streaming#2182mattheworiordan wants to merge 2 commits intomainfrom
Conversation
Proof of concept for a convenience layer over message.append that automatically accumulates appended data, handles late-join via getMessage(), and provides toPartialJSON() for rendering incomplete JSON during AI/LLM token streaming. Includes vendored partial-json parser, integration tests (27 passing), esbuild config, and a runnable demo script.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip You can validate your CodeRabbit configuration file in your editor.If your editor has YAML language server, you can enable auto-completion and validation by adding |
Rewrote the vendored partial-json parser to match the upstream (promplate/partial-json-parser-js) approach: delegate string/number parsing to JSON.parse, use closures instead of a class, and share helpers for keyword matching and error throwing. Added 20 new tests matching upstream test cases (Allow flags, edge cases, error paths). Total: 47 tests, all passing. Size reduction (minified): 6,357 → 1,488 bytes (77% smaller). --- Here are the optimization results across 5 parallel iterations: ┌────────────┬────────────────────────────────────────────────────────────────────┬────────────────┬───────────┐ │ Iteration │ Strategy │ Minified bytes │ Reduction │ ├────────────┼────────────────────────────────────────────────────────────────────┼────────────────┼───────────┤ │ Baseline │ Original class-based parser │ 6,357 │ - │ ├────────────┼────────────────────────────────────────────────────────────────────┼────────────────┼───────────┤ │ 1 │ Upstream approach (closures + JSON.parse delegation) │ 2,080 │ 67% │ ├────────────┼────────────────────────────────────────────────────────────────────┼────────────────┼───────────┤ │ 2 │ Merged container parsing + shared error helper │ 1,601 │ 75% │ ├────────────┼────────────────────────────────────────────────────────────────────┼────────────────┼───────────┤ │ 3 (winner) │ Single recursive parse fn + fail()/J aliases + s[i]<'!' whitespace │ 1,488 │ 77% │ ├────────────┼────────────────────────────────────────────────────────────────────┼────────────────┼───────────┤ │ 4 │ Lookup table for keywords + guard helper │ 1,628 │ 74% │ ├────────────┼────────────────────────────────────────────────────────────────────┼────────────────┼───────────┤ │ 5 │ Micro-optimizations (slice, charCodeAt, cached JSON.parse) │ 2,000 │ 69% │ └────────────┴────────────────────────────────────────────────────────────────────┴────────────────┴───────────┘ Key techniques in the winning version: - JSON.parse for string escape handling and number parsing (instead of manual character scanning) - Closures over local i/s/len instead of a class with this.pos/this.str - fail() and J aliases deduplicate throw Error and JSON.parse references - s[i] < '!' for whitespace (all whitespace chars are below ! in ASCII) - lit() helper handles all 6 keyword types (null/true/false/NaN/Infinity) in ~3 lines Source went from 422 lines → 105 lines. Gzipped from 1,347 → 743 bytes.
Summary
Proof of concept exploring two SDK-level conveniences motivated by real customer asks around
message.appendand structured data streaming. This is a POC for discussion, not a proposed API - intended to inform wider AIT thinking about what the SDK should offer.Background and motivation
Structured data streaming (the Expedia ask)
Expedia asked how to handle updates for things that aren't plain text - specifically, structured JSON objects streamed over
message.append. Today we don't have a good answer for this.The pattern that emerged: if you structure your JSON so fixed keys come first and the streaming text content comes last, a partial JSON parser can render a valid object at every step:
This is a viable pattern for any structured streaming use case (AI/LLM responses, document updates, form data) and
toPartialJSON()makes it trivial for developers.Message materialization (everyone's problem)
While exploring the structured streaming question, the other half became obvious: every developer using
message.appendwrites the same boilerplate. Track serials, accumulate data, handle late-join viagetMessage(), manage cache eviction. This has come up repeatedly - see the real-world accumulation discussion in #ait-sdk where the team ran into exactly this building with append.The
MessageMaterializerhandles all of it. Similar to how annotations emit the full summary so users don't apply incremental updates themselves, the materializer emits the full materialized message at each step.Related issues
What the POC demonstrates
Before (what every developer writes today)
After (with MessageMaterializer)
What's included
MessageMaterializergetMessage(), manages cache evictiontoPartialJSON()partial-jsonparsernpx tsx examples/materializer-demo.ts- shows progressive streaming, late-join catch-upCaveats
partial-jsonparser is vendored inline; dependency decisions TBD if this progressesmutableMessagesenabled on the channel namespaceDiscussion
This POC is meant to prompt conversation about:
🤖 Generated with Claude Code