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
- Verify the bearer token is still valid (re-sign-in) and retry.
- Confirm the model id is a streaming id from `listModels()` and that its `api_resource_id` exists in the server's streaming upstream config.
- curl `/api/v1/audio/voices/streaming?model=<apiResourceId>` with auth to see the gateway's upstream error.
- 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
- Only list streaming voices for ids returned by the streaming `listModels()`.
- Verify the server's `api_resource_id` config matches the routed model prefix.
- Gate the call behind `getStreamingTtsAvailable()`.
- Degrade to an empty catalog rather than blocking TTS setup.
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
- audio voices upstream ${res.status}: ${await res.text().catc
- streaming models upstream ${res.status}: ${await res.text().
- audio models upstream ${res.status}: ${await res.text().catc
- streaming voices upstream returned malformed body
- audio voices upstream returned malformed body
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/b32b49e27a0cf3a4.
Report an issue: GitHub.