Skip to content

Expose TTS fallback controls and activation events (LKINF-495) - #2468

Open
russellmartin-livekit wants to merge 1 commit into
mainfrom
russellmartin/lkinf-495-expose-tts-fallback-controls-and-notices
Open

Expose TTS fallback controls and activation events (LKINF-495)#2468
russellmartin-livekit wants to merge 1 commit into
mainfrom
russellmartin/lkinf-495-expose-tts-fallback-controls-and-notices

Conversation

@russellmartin-livekit

@russellmartin-livekit russellmartin-livekit commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add disableSystemDefaultFallback and exact opt-out-only session serialization without changing existing fallback payloads
  • expose a typed fallback_activated event with idiomatic sessionId and fallbackType
  • surface regular and system-default fallback activations with provider, model, voice, and sanitized cause
  • forward fallback activation events through FallbackAdapter and StreamAdapter
  • normalize future causes and fallback types to unknown without interrupting audio
  • publish the additive public API as a minor changeset

fallback_activated is declared on the shared JS TTSCallbacks surface because the base TTS emitter is not generic. Scoping it to inference as Python does would require a broader emitter redesign; the adapters forward the event so wrapping an inference TTS does not create a dead listener.

Testing

  • pnpm exec vitest run agents/src/inference/tts.test.ts agents/src/inference/api_protos.test.ts agents/src/inference/tts.type.test.ts agents/src/tts/fallback_adapter.test.ts
  • pnpm --filter @livekit/agents typecheck
  • pnpm --filter @livekit/agents build:types
  • pnpm --filter @livekit/agents api:check
  • pnpm --filter @livekit/agents lint
  • pnpm exec prettier --check agents/src/inference/api_protos.ts agents/src/inference/api_protos.test.ts agents/src/inference/tts.test.ts agents/src/inference/tts.type.test.ts agents/src/tts/tts.ts agents/etc/agents.api.md

Coordinated PRs

LKINF-495

@changeset-bot

changeset-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d133fbc

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 Minor
@livekit/agents-plugin-anam Minor
@livekit/agents-plugin-anthropic Minor
@livekit/agents-plugin-assemblyai Minor
@livekit/agents-plugin-azure Minor
@livekit/agents-plugin-baseten Minor
@livekit/agents-plugin-bey Minor
@livekit/agents-plugin-cartesia Minor
@livekit/agents-plugin-cerebras Minor
@livekit/agents-plugin-deepgram Minor
@livekit/agents-plugin-did Minor
@livekit/agents-plugin-elevenlabs Minor
@livekit/agents-plugin-fishaudio Minor
@livekit/agents-plugin-google Minor
@livekit/agents-plugin-hume Minor
@livekit/agents-plugin-inworld Minor
@livekit/agents-plugin-krisp Minor
@livekit/agents-plugin-lemonslice Minor
@livekit/agents-plugin-liveavatar Minor
@livekit/agents-plugin-livekit Minor
@livekit/agents-plugin-minimax Minor
@livekit/agents-plugin-mistral Minor
@livekit/agents-plugin-mistralai Minor
@livekit/agents-plugin-neuphonic Minor
@livekit/agents-plugin-openai Minor
@livekit/agents-plugin-perplexity Minor
@livekit/agents-plugin-phonic Minor
@livekit/agents-plugin-protoface Minor
@livekit/agents-plugin-resemble Minor
@livekit/agents-plugin-rime Minor
@livekit/agents-plugin-runway Minor
@livekit/agents-plugin-sarvam Minor
@livekit/agents-plugin-silero Minor
@livekit/agents-plugin-soniox Minor
@livekit/agents-plugin-tavus Minor
@livekit/agents-plugins-test Minor
@livekit/agents-plugin-trugen Minor
@livekit/agents-plugin-xai Minor

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

@russellmartin-livekit
russellmartin-livekit force-pushed the russellmartin/lkinf-495-expose-tts-fallback-controls-and-notices branch 3 times, most recently from 203175f to 6578d87 Compare September 10, 2026 13:15
@russellmartin-livekit
russellmartin-livekit marked this pull request as ready for review September 10, 2026 16:10
@russellmartin-livekit
russellmartin-livekit requested a review from a team as a code owner September 10, 2026 16:10

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread agents/src/tts/fallback_adapter.ts Outdated
for (const tts of this.ttsInstances) {
tts.removeAllListeners('metrics_collected');
tts.removeAllListeners('error');
tts.removeAllListeners('system_default_fallback');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Adapter close erases child listeners

When FallbackAdapter.close() runs, removeAllListeners deletes consumer-installed system_default_fallback listeners from every child. Reused children then silently lose fallback notices.

Learn more

The adapter attaches an anonymous forwarding callback to every child. Because it retains no callback reference, shutdown cannot remove only its own listener and instead removes all listeners for the event. EventEmitter listeners belong to their registrants, so closing one wrapper must not alter unrelated subscriptions on a shared or later-reused child.

Example: An application subscribes directly to inferenceTts.on('system_default_fallback', auditNotice), then wraps that instance in a FallbackAdapter. After adapter.close(), reusing inferenceTts no longer invokes auditNotice, although the application never removed it.

Recommended fix: Store each forwarding callback per child, as StreamAdapter does, and call off('system_default_fallback', storedCallback) during close. Apply the same ownership-safe cleanup pattern to the existing metrics and error forwarders instead of using removeAllListeners.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@russellmartin-livekit
russellmartin-livekit force-pushed the russellmartin/lkinf-495-expose-tts-fallback-controls-and-notices branch from 6578d87 to 9a94c2b Compare September 10, 2026 16:54

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Gateway errors bypass PII redaction

A gateway error event can contain provider or customer content. Logging it as serverEvent lacks the required pii marker, preventing reliable redaction.

(Refers to this code)

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread agents/src/inference/tts.ts Outdated

this.#tts.on('metrics_collected', this.#forwardMetrics);
this.#tts.on('error', this.#forwardError);
this.#tts.on('fallback_activated', this.#forwardFallbackActivated);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Non-streaming fallback notices leak listeners

Each non-streaming fallback utterance adds #forwardFallbackActivated permanently. getStreamingInstance creates a new adapter, but its stream never closes it. Listener counts grow without bound and trigger EventEmitter warnings.

Learn more

A StreamAdapter subscribes to its wrapped TTS in its constructor and unsubscribes only in close(). The fallback streaming path creates a fresh adapter for every attempt through getStreamingInstance, then retains only the returned stream. Nothing closes that temporary adapter after success, failure, cancellation, or shutdown, so this added subscription remains on the child TTS.

Example: With one non-streaming child, eleven utterances create eleven permanent fallback_activated listeners on that child. Node then emits a MaxListenersExceededWarning, and later utterances keep increasing the listener array.

Recommended fix: Give the fallback stream explicit ownership of temporary StreamAdapter instances and close each one in the attempt's finally block. Do not close adapters that represent caller-owned streaming TTS instances.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@russellmartin-livekit russellmartin-livekit changed the title Expose TTS system fallback controls and notices (LKINF-495) Expose TTS fallback controls and activation events (LKINF-495) Sep 10, 2026
@russellmartin-livekit
russellmartin-livekit force-pushed the russellmartin/lkinf-495-expose-tts-fallback-controls-and-notices branch 2 times, most recently from 77f73ec to 5afdad3 Compare September 10, 2026 17:48
@russellmartin-livekit
russellmartin-livekit force-pushed the russellmartin/lkinf-495-expose-tts-fallback-controls-and-notices branch from 5afdad3 to d133fbc Compare September 11, 2026 09:55
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.

1 participant