fix(google): bound TTS success JSON response reads#96984
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 27, 2026, 9:57 AM ET / 13:57 UTC. Summary PR surface: Source +49, Tests +55. Total +104 across 3 files. Reproducibility: yes. Current main and the latest release still show the raw Review metrics: 1 noteworthy metric.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrow shared-reader fix once exact-head checks and normal merge gates are green, preserving the maintainer-accepted 16 MiB provider JSON boundary. Do we have a high-confidence way to reproduce the issue? Yes. Current main and the latest release still show the raw Is this the best way to solve the issue? Yes. This is the best fix shape because it changes the only Google TTS success JSON parse site to the existing SDK source-of-truth reader rather than adding a Google-local parser or cap. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against a7bfc06f4597. Label changesLabel justifications:
Evidence reviewedPR surface: Source +49, Tests +55. Total +104 across 3 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Maintainer review and merge prep done. Evidence:
Maintainer decision accepted: Google TTS success JSON over the shared 16 MiB provider cap now fails early. Normal request construction, auth headers, model/voice normalization, retry count, PCM extraction, WAV wrapping, and Opus transcoding are unchanged. Known proof gap: no credential-backed live Google TTS call was run; this PR changes the response-body reader boundary, which is covered by the provider test and PR loopback proof. |
9ce6c78 to
9890e08
Compare
|
Refresh after mainline sync: #96984 was rebased and revalidated at exact head Additional evidence:
|
|
Merged via squash.
|
Origin / follow-up
Follow-up to #96970 and the same bounded-reader fan-out strategy from #96505. #96970 applied the shared provider JSON cap to ClickClack REST success responses; this PR applies the same source-of-truth reader to Google Gemini TTS success JSON responses.
Summary
readProviderJsonResponse.postJsonRequest, realfetch, and realreadProviderJsonResponsefor both a normal small response and an oversized success body.Maintainer-ready fields
response.json()instead of OpenClaw's shared bounded provider JSON reader.synthesizeGoogleTtsPcmOnce()called rawawait res.json()afterpostJsonRequestreturned a successful Response.readProviderJsonResponserestores the response-size invariant before downstream audio payload extraction and retry handling.postJsonRequest, realfetch, and realreadProviderJsonResponse. The proof covers a normal small Google TTS-style JSON envelope and an oversized success JSON stream that closes before the full 18 MiB response is sent.pnpm check:test-types, then ran the loopback proof withpnpm --dir /media/vdb/code/ai/aispace/openclaw-worktrees/pr-96984 exec tsx /media/vdb/code/ai/aispace/openclaw-pr-96984-evidence/google-tts-loopback-proof.ts.small_status=200,small_pcm_bytes=4, andoversized_error=Google TTS response: JSON response exceeds 16777216 bytes.openclaw/plugin-sdk/provider-http; this change moves Google TTS success JSON parsing onto that shared contract before downstream typed response projection.extensions/google/speech-provider.test.ts.Responsewith aReadableStreambody larger than 16 MiB is returned to the Google TTS success path; the reader rejects at the cap and cancels the stream on both retry attempts.synthesizeGoogleTtsPcmOnce()covers the only Google TTS success JSON path without touching provider configuration, request generation, audio extraction, or retry policy.merge-risk: 🚨 compatibility,behavior-change: oversized-provider-response, andauth-provider.response.json()behavior, GREEN verification covers target behavior and test typechecking, and loopback proof covers the requested HTTP boundary.What Problem This Solves
Google TTS already uses the shared provider HTTP request path, but its successful Gemini response body was still parsed with raw native JSON parsing. This left the success response path outside the bounded-reader guardrail used for provider JSON responses elsewhere.
Root Cause
The Google TTS implementation bounded provider errors through the shared HTTP stack but parsed successful JSON locally with
await res.json(). That split the response-body contract: unsuccessful responses were normalized through shared helpers, while successful TTS JSON bypassed the shared size cap before audio payload extraction.Why it matters / User impact
A too-large Gemini TTS success response can otherwise be buffered by native JSON parsing before OpenClaw has a chance to reject it. The patch makes the failure deterministic and closes the body stream once the shared cap is exceeded.
What did NOT change
Architecture / source-of-truth check
OpenClaw already owns provider response-size enforcement in
openclaw/plugin-sdk/provider-http. Google TTS now uses that shared reader for successful Gemini JSON responses instead of maintaining a local native JSON parse path.Target test file
extensions/google/speech-provider.test.tsScenario locked in
The regression returns a real
Responsewith aReadableStreambody of16 * 1024 * 1024 + 1bytes to the Google TTS success path. Thejson()method is a sentinel: the test fails on the old code when the sentinel is called, and passes after the code reads the stream through the bounded reader.Evidence
RED observation against the raw
response.json()implementation:GREEN target test:
Test typecheck:
Loopback HTTP proof
The follow-up proof uses a local loopback HTTP server and sends requests through real
postJsonRequestwith realfetch, then parses the returnedResponsethrough realreadProviderJsonResponse.The small path returns a Google TTS-style JSON envelope with inline PCM audio data. The oversized path streams an 18 MiB success response and records where the server sees the client close the connection.
Command:
Observed output:
This directly covers the requested loopback proof: normal small response parsing succeeds, the oversized success body fails with the bounded-reader error, and the server observes early close before the full 18 MiB response is sent.
Real behavior proof
The unit regression exercises the actual Google speech provider code path after
postJsonRequestreturns a successfulResponse. The follow-up loopback proof additionally exercises the real HTTP request boundary throughpostJsonRequest, realfetch, andreadProviderJsonResponse.Review findings addressed
postJsonRequest, realfetch, and realreadProviderJsonResponse.postJsonRequestunit boundary.small_status=200,small_pcm_bytes=4,oversized_error=Google TTS response: JSON response exceeds 16777216 bytes, andoversized_closed_before_full_response=true.External environment disclosure
No external Google account, API credential, or hosted Google TTS synthesis call was used for this follow-up. The completed verification uses the reviewer-requested loopback option and exercises the provider HTTP request path locally through real
postJsonRequest, realfetch, and realreadProviderJsonResponse.Verification
Regression Test Plan
extensions/google/speech-provider.test.ts.Merge risk
auth-provider, because the changed production file is the Google provider implementation.extensions/google/speech-provider.tsoverlap; fix(speech): bound TTS response reads #96874 covers Volcengine/Xiaomi TTS, fix(vydra): bound control response reads #96875 covers Vydra speech, and fix(feishu,browser,msteams,azure-speech,bedrock-mantle,googlechat,huggingface,perplexity): bound JSON response reads #96620 covers GoogleChat/Azure Speech rather than Google Gemini TTS.Risk labels considered
merge-risk: 🚨 compatibilitybehavior-change: oversized-provider-responseauth-providerdetected and explained aboveRisk explanation
The only intended behavior change is that Google TTS success JSON bodies over the shared provider cap now fail early with a bounded-reader error. Existing valid responses continue into the same
extractGoogleSpeechPcmpath.Why acceptable
The patch aligns Google TTS with OpenClaw's shared provider response-body boundary and keeps the change at the only success JSON parse point. The proof covers both the oversized boundary and a normal small response over loopback HTTP, and existing speech-provider tests continue to pass.
Maintainer-ready confidence
High for contributor-side readiness. The root cause is localized, the diff is small, related open PR overlap was checked, fresh CI is green on commit
9ce6c78521924393bfc72560f5a1a3cb9f6dbf9a, and loopback HTTP proof now covers the requested real boundary. The remaining merge decision is maintainer acceptance of the 16 MiB compatibility boundary.Patch quality notes
readProviderJsonResponseso existing provider tests can exercise production code that imports the shared reader.Competition / linked PR analysis
This PR is intentionally separate from #96970: #96970 covers ClickClack REST success JSON reads, while this PR covers Google Gemini TTS success JSON reads. Related open PR scan found no competing Google Gemini TTS bounded-reader PR.