moeru-ai/airi · error
[Speech Pipeline] bidirectional-ws provider reached per-segm
Error message
[Speech Pipeline] bidirectional-ws provider reached per-segment fallback
What it means
Stage.vue's per-segment speech callback refuses to synthesize for bidirectional-ws providers (streaming, e.g. ElevenLabs). The streaming path must open its own websocket at onBeforeMessageComposed and bypass speech-pipeline; reaching this per-segment fallback means that open never happened — most often the voice catalog had not finished loading when the message was sent. The segment's audio is deliberately dropped (returns null) instead of silently re-opening a ws per segment.
Source
Thrown at packages/stage-ui/src/components/scenes/Stage.vue:443
if (speechMuted.value)
return null
if (activeSpeechProvider.value === 'speech-noop')
return null
if (!activeSpeechProvider.value)
return null
// Streaming provider must NEVER reach this per-segment callback. The
// streaming code path opens its own ws at `onBeforeMessageComposed`
// and bypasses speech-pipeline entirely. If we got here while the
// streaming provider is active, the open path failed (most often:
// voice catalog hadn't finished loading when the user sent the
// message). The old fallback would silently re-open a fresh ws per
// segment — exactly the behavior the refactor is meant to delete.
// Codex review MEDIUM #3: refuse loudly instead.
if (resolveSpeechTransport(activeSpeechProvider.value) === 'bidirectional-ws') {
console.warn('[Speech Pipeline] bidirectional-ws provider reached per-segment fallback', {
reason: 'streaming session was not opened at intent start (voice unset?)',
provider: activeSpeechProvider.value,
segment: request.text?.slice(0, 40),
})
return null
}
const provider = await providersStore.getProviderInstance(activeSpeechProvider.value) as SpeechProviderWithExtraOptions<string, UnElevenLabsOptions>
if (!provider) {
console.error('Failed to initialize speech provider')
return null
}
if (!request.text && !request.special)
return null
const providerConfig = providerStore.getProviderConfig(activeSpeechProvider.value)
View on GitHub (pinned to 677329427f)
Solutions
- Wait for the voice catalog to load and a voice to be selected before sending the first message — subsequent messages open the streaming session correctly
- Check the speech provider settings: an invalid API key or unreachable base URL keeps the catalog (and therefore the open path) from ever succeeding
- If it fires on every message, use the warn payload (provider + segment text) and devtools to find why the onBeforeMessageComposed open path fails
- Reload the page as a last resort to reinitialize the speech pipeline
Defensive patterns
Strategy: validation
Validate before calling
// Before sending a message with a streaming speech provider
if (resolveSpeechTransport(provider) === 'bidirectional-ws') {
const voiceReady = voices.value.length > 0 && activeSpeechVoice.value
if (!voiceReady)
await until(() => voices.value.length > 0 && activeSpeechVoice.value).toBeTruthy({ timeout: 5000 })
} Type guard
function streamingSessionReady(provider: string, voiceId: string | undefined): boolean {
return resolveSpeechTransport(provider) !== 'bidirectional-ws' || typeof voiceId === 'string'
} Prevention
- Block or defer message sending until the voice catalog has loaded for streaming providers
- Validate speech provider credentials at settings-save time so catalog fetch cannot fail silently
- Monitor this warn as an invariant violation: streaming providers must never hit the per-segment path
When it happens
Trigger: The active speech provider resolves to 'bidirectional-ws' while no streaming session was opened at intent start: no voice selected or the voice catalog still loading when the user sent the message, or the open path having failed earlier.
Common situations: Sending the first message immediately after app start, before voices finish loading; a provider API key/voice config that makes catalog fetching fail; a network hiccup during session open.
Related errors
- No streaming TTS model selected and server returned no defau
- Streaming model id missing backend prefix: ${requestedModel}
- MiniMax TTS request failed: ${response.status} ${response.st
- OpenRouter audio request failed: ${response.status} ${await
- OpenRouter audio response has no body
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/27542fac18a6e3b4.
Report an issue: GitHub.