Repository navigation
fix(logs): recover concatenated JSON objects in the Provider Event Stream viewer - #13115
Merged
diegosouzapw merged 2 commits intoSep 16, 2026
Conversation
…ream viewer
Gemini's own stream-chunk capture concatenates multiple complete JSON
objects back-to-back with no separator between them ("}{"), unlike the
SSE blank-line-per-event framing every other provider's capture uses here.
A single `data:` payload can therefore hold several complete top-level
JSON values, not one -- JSON.parse correctly rejected the whole thing
("Unexpected non-whitespace character after JSON") and the *entire*
payload fell back to plain text in the "Provider Event Stream" viewer,
even though every individual object inside it was perfectly well-formed.
Observed live: replayed real stored streamChunks.provider artifacts from
production call logs -- 161 of 663 recent streaming logs hit this, all
gemini, some with as many as 11 concatenated objects in a single capture.
Fix: when a `data:` payload fails to parse as one JSON value,
splitConcatenatedJsonValues() scans bracket/string depth (properly
handling escaped quotes) to recover each complete top-level JSON value in
sequence. Each recovered value renders as its own collapsible node instead
of the whole thing collapsing to unreadable raw text. Genuinely incomplete
or non-JSON content (a real mid-stream truncation, SSE comments,
keep-alives) still falls back to plain text exactly as before -- the
splitter only accepts a payload if it can fully account for every byte as
complete top-level JSON values, and returns null (no change in behavior)
otherwise.
Regression tests cover the splitter directly (multiple objects, escaped
braces/quotes inside strings, a single object correctly returning null so
it isn't double-handled, non-JSON text, and a genuinely truncated value
correctly still failing) plus parseStreamIntoSegments end-to-end using the
exact shape a real 10-chunk Gemini capture produces, and confirm no
regression for genuine non-JSON content (comments/keep-alives). Confirmed
the new test file fails against the pre-fix code (export doesn't exist
yet) and passes after.
Evidence:
- node --test tests/unit/request-logger-stream-concatenated-json.test.ts: 8/8 pass
- node --test tests/unit/request-logger-detail-copy-all.test.ts: 4/4 pass (unaffected)
- eslint: clean
- tsc --noEmit: no errors in touched files
- Live-verified: replayed 8 real production call-log artifacts (the
original incident plus 7 more found via a live scan) through the fixed
parser -- all now recover 100% as JSON segments with zero text fallback,
versus partial/total text fallback before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ewer
Default stays the parsed JsonView tree (this PR's own concatenated-JSON
recovery included); a new toggle button next to autoscroll/copy lets an
operator flip to the exact byte-for-byte captured stream -- timestamp
markers and all -- for the cases where the rendered tree hides or
misrepresents what actually came over the wire. Persisted per browser
via localStorage, same pattern as the existing autoscroll toggle.
New regression test pins the default: renders with no prior opt-in and
asserts the raw-view toggle is present (labeled off) while the literal
raw `data: {...}` line does NOT appear as one contiguous substring --
only the parsed JsonView tree does.
Evidence:
- node --test tests/unit/request-log-detail-stream.test.ts: 6/6 pass
- node --test tests/unit/request-logger-stream-concatenated-json.test.ts: 8/8 pass (unaffected)
- eslint: clean on both touched source/test files
- scripts/i18n/check_translations.py: no missing-key hit for the two new
`rawViewOn`/`rawViewOff` keys
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vk2cBRQdErwRtPea8VuhG6
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 13, 2026
…N objects in the Provider Event Stream viewer) into dev/omniroute-dev-combined
Collaborator
|
Excellent debugging trail — replaying real production stream artifacts to quantify the impact |
diegosouzapw
merged commit Sep 16, 2026
ac32cea
into
cheaper-inference:release/v3.8.51
9 of 16 checks passed
diegosouzapw
added a commit
that referenced
this pull request
Sep 16, 2026
Lands the combined-board reconciliation of today's 22-PR wave as one follow-up: i18n fill for #13115's two new keys (65 locales, real translation in vi) and the file-size rebaseline for cursor.ts (#13627) and chatHelpers.ts (#13879). check-new-key-coverage PASS; only the three pre-existing file-size reds remain on the tip.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…ream viewer (cheaper-inference#13115) Merged. Concatenated JSON objects in the Provider Event Stream are a real wire shape, not a corruption — recovering them in the viewer instead of dropping the batch makes the tool trustworthy when it matters. Validated as a combined board first (this PR merged with the 21 siblings of the same wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-counts, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 176 passing / 0 failing focused node:test cases across the 25 test files the wave touches and the dashboard test under Vitest (2/0). Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run. Thank you.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…per-inference#13904) Lands the combined-board reconciliation of today's 22-PR wave as one follow-up: i18n fill for cheaper-inference#13115's two new keys (65 locales, real translation in vi) and the file-size rebaseline for cursor.ts (cheaper-inference#13627) and chatHelpers.ts (cheaper-inference#13879). check-new-key-coverage PASS; only the three pre-existing file-size reds remain on the tip.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Problem This Solves
The "Provider Event Stream" viewer on a call log's detail page sometimes
showed raw, unreadable text instead of the expected collapsible JSON tree
-- but only for Gemini, and only intermittently.
Root Cause
Gemini's own stream-chunk capture concatenates multiple complete JSON
objects back-to-back with no separator between them (
}{), unlike theSSE blank-line-per-event framing every other provider's capture uses here.
A single
data:payload can therefore hold several complete top-levelJSON values, not one --
JSON.parsecorrectly rejects the whole thing:...and the entire payload fell back to plain text, even though every
individual object inside it was perfectly well-formed.
I confirmed this by replaying real stored
streamChunks.providerartifacts from production call logs: 161 of 663 recent streaming logs
hit this, all
gemini, some with as many as 11 concatenated objectsin a single capture.
Fix
When a
data:payload fails to parse as one JSON value,splitConcatenatedJsonValues()scans bracket/string depth (properlyhandling escaped quotes) to recover each complete top-level JSON value in
sequence. Each recovered value renders as its own collapsible node instead
of the whole thing collapsing to unreadable raw text.
Genuinely incomplete or non-JSON content (a real mid-stream truncation,
SSE comments, keep-alives) still falls back to plain text exactly as
before -- the splitter only accepts a payload if it can fully account for
every byte as complete top-level JSON values, and returns
null(nochange in behavior) otherwise.
Evidence
eslint: cleantsc --noEmit: no errors in touched filesLive-verified: replayed 8 real production call-log artifacts (the
original incident plus 7 more found via a live scan) through the fixed
parser -- all now recover 100% as JSON segments with zero text fallback,
versus partial/total text fallback before.