Skip to content

fix(cartesia): surface rejected /tts/bytes responses as APIStatusError - #2443

Open
brodie-avoca wants to merge 4 commits into
livekit:mainfrom
brodie-avoca:fix/cartesia-bytes-status-errors
Open

fix(cartesia): surface rejected /tts/bytes responses as APIStatusError#2443
brodie-avoca wants to merge 4 commits into
livekit:mainfrom
brodie-avoca:fix/cartesia-bytes-status-errors

Conversation

@brodie-avoca

@brodie-avoca brodie-avoca commented Sep 8, 2026

Copy link
Copy Markdown

Stacked on #2442. This branch is cut from that PR's branch, so the diff below currently shows both PRs' changes. Only the commits after a4a4f56 are new here: the APIStatusError handling, its two tests, and the base-class rejection fix. The first commit, the two http/https import lines, the startBytesServer harness, the max_buffer_delay_ms test and .changeset/cartesia-bytes-max-buffer-delay.md all belong to #2442. Once #2442 merges this diff collapses to roughly +110/-5. Review #2442 first.

Description

ChunkedStream.run() never looks at res.statusCode. On a rejected response the error body is written into the AudioByteStream, is too short to fill a frame, and the stream completes successfully with zero audio. The caller only sees "no audio was received", never the reason, and a 401 or 400 is indistinguishable from an empty result.

Supersedes #1660, which stalled on two review points: the test needed a self-signed certificate, and (per the Devin review) every status was wrapped in APIConnectionError, so a 401 would be retried. This PR rejects with APIStatusError so retryability follows the status code, and tests against a plain HTTP server (protocol selection landed in #2442).

Parity: the Python plugin calls resp.raise_for_status() on /tts/bytes and maps the failure to APIStatusError (tts.py#L380, tts.py#L395-L398).

Changes Made

  • On a non-2xx response, buffer the body and reject with APIStatusError. The message carries only the status; Cartesia's response text goes in error.body per REVIEW.md, so it can be redacted from logs. 5xx and 429 stay retryable; other 4xx do not.
  • tts.ChunkedStream (base class) now awaits its background task the same way SynthesizeStream already does, so a terminal failure no longer surfaces as an unhandled promise rejection. The failure is already reported through the TTS error event. This removes the need for the unhandledRejection suppression in the Cartesia test.
  • Only resolve on request close when no response was received, so a late request close cannot beat the response handler's rejection.
  • Rethrow APIError instances as-is in the catch instead of re-wrapping them as retryable connection errors, matching the websocket path.
  • Tests: a 400 with a JSON body surfaces as APIStatusError with status 400, makes exactly one request under default connect options, and yields no audio; a 503 followed by 200 retries and yields audio.

Pre-Review Checklist

  • Build passes: All builds (lint, typecheck, tests) pass locally
  • AI-generated code reviewed: Removed unnecessary comments and ensured code quality
  • Changes explained: All changes are properly documented and justified above
  • Scope appropriate: All changes relate to the PR title
  • Video demo: n/a, error-path change covered by tests

Testing

  • Automated tests added/updated
  • All tests pass
  • restaurant_agent.ts / realtime_agent.ts: n/a, not a major change
pnpm build
pnpm vitest run plugins/cartesia/src/tts.test.ts   # 13 passed, 1 skipped (credential-gated)
pnpm exec eslint plugins/cartesia/src/tts.ts plugins/cartesia/src/tts.test.ts
pnpm exec prettier --check plugins/cartesia/src/tts.ts plugins/cartesia/src/tts.test.ts

Additional Notes

Neuphonic and Resemble build their non-streaming request the same way and also never check the status. Left out of this PR to keep it to Cartesia; happy to follow up.

Co-authored-by: Jason Lernerman jason.lernerman@livekit.io

@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b293f4e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 38 packages
Name Type
@livekit/agents-plugin-cartesia Patch
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@CLAassistant

CLAassistant commented Sep 8, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

devin-ai-integration[bot]

This comment was marked as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants