moeru-ai/airi · warning · Error

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

Error message

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

What it means

Thrown by the official streaming speech provider's `listModels()` when `GET ${SERVER_URL}/api/v1/audio/models/streaming` replies non-2xx. This probe also drives `streamingTtsAvailable` — the flag is reset to `false` before the fetch so a failed probe hides the provider instead of leaving a stale 'available'. Typical statuses: 401 (no/expired bearer token) or 5xx (server-side `STREAMING_TTS_UPSTREAM` not configured/unreachable).

Source

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

    return provider
  },
  validationRequiredWhen: () => false,
  extraMethods: {
    listModels: async (): Promise<ModelInfo[]> => {
      // Streaming TTS catalog is operator-controlled via configKV
      // (`UNSPEECH_UPSTREAM.streaming`). Wire shape uses `<backend>/<api_resource_id>`
      // (see `unspeech/docs/wire-protocols/audio-speech-stream-v1.md`); the
      // server returns whatever the operator put there, no client-side
      // defaults. `default` (when set) seeds initial model selection via
      // {@link getDefaultStreamingModel}.
      // Reset the operator-driven signals up front so a failed/aborted probe
      // leaves the provider hidden rather than stuck on a stale "available".
      streamingTtsAvailable.value = false
      defaultStreamingModelId = null

      const res = await globalThis.fetch(`${SERVER_URL}/api/v1/audio/models/streaming`, { headers: authHeaders() })
      if (!res.ok)
        throw new Error(`streaming models upstream ${res.status}: ${await res.text().catch(() => '')}`.slice(0, 256))

      const data = await res.json() as { available?: boolean, models: { id: string, name?: string, description?: string }[], default?: string | null }
      if (!Array.isArray(data.models))
        throw new Error('streaming models upstream missing models[]')

      streamingTtsAvailable.value = data.available === true
      defaultStreamingModelId = typeof data.default === 'string' && data.default.length > 0 ? data.default : null

      return data.models.map(m => ({
        id: m.id,
        name: m.name ?? m.id,
        provider: OFFICIAL_SPEECH_STREAMING_PROVIDER_ID,
        description: m.description,
      }))
    },
    listVoices: async (_config, _provider, model): Promise<VoiceInfo[]> => {
      // Streaming voices live behind a dedicated endpoint
      // (`/audio/voices/streaming`) because they come from the

View on GitHub (pinned to 677329427f)

Solutions

  1. Ensure the user is authenticated before the probe runs (the UI should hide streaming until signed in).
  2. curl `/api/v1/audio/models/streaming` with a bearer token to see the real status and body.
  3. On the server, set `STREAMING_TTS_UPSTREAM` with a valid backend, or expect this probe to fail and keep the streaming provider hidden (that is by design).
  4. Update both server and client so the route and the `available`/`models[]` contract match.

Example fix

// before
const models = await streamingProvider.extraMethods.listModels()
// after — treat a failed probe as 'streaming unavailable' instead of an error
let models: ModelInfo[] = []
try {
  models = await streamingProvider.extraMethods.listModels()
}
catch (error) {
  console.warn('streaming TTS unavailable', error)
}
Defensive patterns

Strategy: try-catch

Validate before calling

const token = getAuthToken()
if (!token)
  return { models: [], available: false } // skip the probe entirely

Try / catch

try {
  models = await streamingProvider.extraMethods.listModels()
}
catch (error) {
  console.warn('streaming TTS probe failed — keeping provider hidden', error)
  models = []
}

Prevention

When it happens

Trigger: Probing streaming models while signed out or with an expired JWT; server revision without the `/models/streaming` route (404); `STREAMING_TTS_UPSTREAM` unset or its upstream down, surfacing as 502/503 from the gateway.

Common situations: Fresh sign-in flow where the probe races token refresh; self-hosted deployment that never configured streaming TTS but runs a new client that probes it; partial outage of the volcengine/other streaming upstream behind the gateway.

Related errors


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