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

  1. 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
  2. Check the speech provider settings: an invalid API key or unreachable base URL keeps the catalog (and therefore the open path) from ever succeeding
  3. If it fires on every message, use the warn payload (provider + segment text) and devtools to find why the onBeforeMessageComposed open path fails
  4. 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

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


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/27542fac18a6e3b4. Report an issue: GitHub.