moeru-ai/airi · error · Error

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

Error message

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

What it means

Thrown by the official speech provider's `listVoices()` when `GET ${SERVER_URL}/api/v1/audio/voices?model=<id|auto>` replies non-2xx. The `model` query param is passed through so the server routes to the right TTS adapter (Azure, cosyvoice, …), defaulting to `auto` when model discovery has not run yet. Auth header comes from `getAuthToken()`.

Source

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

      return data.models.map(m => ({
        id: m.id,
        name: m.name,
        description: m.description,
        provider: OFFICIAL_SPEECH_PROVIDER_ID,
      }))
    },
    listVoices: async (_config, _provider, model): Promise<VoiceInfo[]> => {
      // Voice catalogs are model-scoped on the server side. Pass the active
      // model through so Azure / cosyvoice / future provider voices route to
      // the right adapter. If model discovery has not completed yet, keep the
      // legacy `auto` request as a startup fallback.
      const target = model && model.length > 0 ? model : 'auto'
      const url = new URL(`${SERVER_URL}/api/v1/audio/voices`)
      url.searchParams.set('model', target)
      const res = await globalThis.fetch(url.toString(), { headers: authHeaders() })
      if (!res.ok)
        throw new Error(`audio voices upstream ${res.status}: ${await res.text().catch(() => '')}`.slice(0, 256))

      // Shape aligned with unspeech's types.ListVoicesResponse, plus the
      // `recommended` field our server injects from configKV DEFAULT_TTS_VOICES.
      // https://github.com/moeru-ai/unspeech/blob/main/pkg/backend/types/voices.go
      const data = await res.json() as {
        voices?: {
          id: string
          name: string
          description?: string
          labels?: Record<string, unknown>
          tags?: string[]
          languages?: { code: string, title: string }[]
          compatible_models?: string[]
          preview_audio_url?: string
        }[]
        recommended?: Record<string, string>
      }

View on GitHub (pinned to 677329427f)

Solutions

  1. Retry after re-authenticating if the status is 401/403.
  2. Check that the active model id is one returned by `listModels()` for this provider, not a streaming id or stale id.
  3. Verify with curl `'/api/v1/audio/voices?model=auto'` using a valid bearer token and read the server-side upstream error.
  4. Ensure `UNSPEECH_UPSTREAM` is fully configured on the server (backend ids, credentials, voice catalogs).

Example fix

// before
const voices = await provider.extraMethods.listVoices(config, provider, model)
// after — fall back to the catalog-wide query when the model-scoped one fails
let voices
try {
  voices = await provider.extraMethods.listVoices(config, provider, model)
}
catch {
  voices = await provider.extraMethods.listVoices(config, provider, 'auto')
}
Defensive patterns

Strategy: try-catch

Validate before calling

const token = getAuthToken()
if (!token)
  throw new Error('Authentication required to list official voices')
if (model && !model.includes('/') && model !== 'auto')
  // ensure the model id came from listModels()

Try / catch

try {
  voices = await provider.extraMethods.listVoices(config, provider, model)
}
catch (error) {
  voices = await provider.extraMethods.listVoices(config, provider, 'auto')
    .catch(() => { throw error })
}

Prevention

When it happens

Trigger: Expired/missing JWT (401); requesting voices for a `model` id the server does not know (400/404); the server's per-backend voice adapter failing (502) because `UNSPEECH_UPSTREAM` names a backend whose credentials are missing; older server without the voices route (404).

Common situations: Voice list loads on the speech settings page right after token expiry; selecting a model id that came from a stale provider cache after the server catalog changed; partial server config where the HTTP TTS upstream is set but its voice catalog fetch fails.

Related errors


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