moeru-ai/airi · error · Error

streaming voices upstream ${res.status}: ${await res.text().

Error message

streaming voices upstream ${res.status}: ${await res.text().catch(() => '')}

What it means

Thrown by the official streaming speech provider's `listVoices()` when `GET ${SERVER_URL}/api/v1/audio/voices/streaming?model=<apiResourceId>` replies non-2xx. Before the fetch the provider strips the backend prefix from the unspeech-routed id (`volcengine/seed-tts-2.0` → `seed-tts-2.0`) because unspeech's voice filter expects the bare `api_resource_id`.

Source

Thrown at packages/stage-ui/src/libs/providers/providers/official/index.ts:305

      // (`/audio/voices/streaming`) because they come from the
      // `UNSPEECH_UPSTREAM.streaming` configKV subtree rather than the HTTP TTS
      // `?model=...` lookup. The server proxies to unspeech's
      // `/api/voices?provider=volcengine`, which ships an embed-time
      // catalogue without requiring credentials.
      //
      // `model` here is the unspeech-routed id (e.g. `volcengine/seed-tts-2.0`).
      // unspeech expects the bare `api_resource_id` for its filter, so we
      // strip the backend prefix before forwarding.
      const apiResourceId = model?.includes('/') ? model.split('/', 2)[1] : model
      const voicesURL = new URL(`${SERVER_URL}/api/v1/audio/voices/streaming`)
      if (apiResourceId)
        voicesURL.searchParams.set('model', apiResourceId)
      const res = await globalThis.fetch(
        voicesURL.toString(),
        { headers: authHeaders() },
      )
      if (!res.ok)
        throw new Error(`streaming voices upstream ${res.status}: ${await res.text().catch(() => '')}`.slice(0, 256))

      const data = await res.json() as {
        voices?: {
          id: string
          name: string
          description?: string
          labels?: Record<string, unknown>
          languages?: { code: string, title: string }[]
          preview_audio_url?: string
        }[]
        recommended?: Record<string, string>
      }

      // Mirror the HTTP provider: stash the server's per-locale recommendations
      // so setupOfficialSpeechAutoPick can seed a curated default voice when
      // the streaming provider becomes active.
      recommendedVoicesByProvider[OFFICIAL_SPEECH_STREAMING_PROVIDER_ID] = (data.recommended && typeof data.recommended === 'object') ? data.recommended : {}

View on GitHub (pinned to 677329427f)

Solutions

  1. Verify the bearer token is still valid (re-sign-in) and retry.
  2. Confirm the model id is a streaming id from `listModels()` and that its `api_resource_id` exists in the server's streaming upstream config.
  3. curl `/api/v1/audio/voices/streaming?model=<apiResourceId>` with auth to see the gateway's upstream error.
  4. Fix or complete `STREAMING_TTS_UPSTREAM` credentials on the server for the failing backend.

Example fix

// before
const voices = await streamingProvider.extraMethods.listVoices(config, provider, model)
// after — probe availability first, then degrade to an empty catalog
const voices = getStreamingTtsAvailable()
  ? await streamingProvider.extraMethods.listVoices(config, provider, model).catch(() => [])
  : []
Defensive patterns

Strategy: try-catch

Validate before calling

const apiResourceId = model?.includes('/') ? model.split('/', 2)[1] : model
if (!apiResourceId)
  throw new Error('A streaming model id is required to list voices')

Try / catch

try {
  voices = await streamingProvider.extraMethods.listVoices(config, provider, model)
}
catch (error) {
  if (errorMessageFrom(error).includes('401'))
    // re-authenticate and retry once
  else
    voices = [] // streaming voice catalog unavailable
}

Prevention

When it happens

Trigger: 401/403 from a missing or expired bearer token; 400/404 when the stripped `api_resource_id` does not match any configured streaming backend; 502 when the streaming upstream (e.g. Volcengine) rejects the server's voice-catalog call due to bad credentials.

Common situations: Voice list requested for a streaming model id that came from a stale cache or a different server config; server-side `STREAMING_TTS_UPSTREAM` cluster/section ids (like Volcengine `api_resource_id`) misconfigured; token expired between model probe and voice fetch.

Related errors


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