moeru-ai/airi · error · Error

streaming voices upstream returned malformed body

Error message

streaming voices upstream returned malformed body

What it means

Thrown by the official streaming speech provider's `listVoices()` when `/api/v1/audio/voices/streaming` answered 200 but `data.voices` is not an array. Before the check the handler stores `data.recommended` into the per-provider recommendation map, so a malformed body still updates recommendations and then fails the catalog mapping. Same wire family as unspeech `ListVoicesResponse`.

Source

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

      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 : {}

      if (!Array.isArray(data.voices))
        throw new Error('streaming voices upstream returned malformed body')

      return data.voices.map((v) => {
        const rawGender = typeof v.labels?.gender === 'string' ? (v.labels.gender as string) : undefined
        return {
          id: v.id,
          name: v.name,
          provider: OFFICIAL_SPEECH_STREAMING_PROVIDER_ID,
          description: v.description || undefined,
          gender: rawGender?.toLowerCase() || undefined,
          previewURL: v.preview_audio_url || undefined,
          languages: Array.isArray(v.languages) ? v.languages : [],
        }
      })
    },
  },
})

export const providerOfficialTranscription = defineProvider({

View on GitHub (pinned to 677329427f)

Solutions

  1. Inspect the raw body with curl and compare it to the expected `{ voices, recommended }` shape.
  2. Redeploy server and client from the same revision.
  3. Have the server always emit `voices: []` when the catalog is empty rather than omitting the field.
  4. Catch this in the caller and show an empty voice list instead of breaking settings.

Example fix

// before
if (!Array.isArray(data.voices))
  throw new Error('streaming voices upstream returned malformed body')
// after — tolerate an absent catalog while keeping recommendations
if (!Array.isArray(data.voices)) {
  console.warn('streaming voices payload lacked voices[]', data)
  return []
}
Defensive patterns

Strategy: type-guard

Validate before calling

const data: unknown = await res.json()
if (!isStreamingVoicesPayload(data))
  console.warn('streaming voices payload malformed', data)

Type guard

function isStreamingVoicesPayload(data: unknown): data is { voices: Array<{ id: string, name: string }>, recommended?: Record<string, string> } {
  if (typeof data !== 'object' || data === null)
    return false
  return Array.isArray((data as { voices?: unknown }).voices)
}

Try / catch

return isStreamingVoicesPayload(data)
  ? data.voices.map(toVoiceInfo)
  : []

Prevention

When it happens

Trigger: 200 response with an error envelope or `null`; version skew where the streaming voices route returns a different shape than `{ voices: [...], recommended?: {...} }`; an edge returning 200 HTML that `res.json()` happens not to choke on (rare) or a JSON body without the key.

Common situations: Rolling deploy mixing old server and new client; streaming voice endpoint still experimental on the server and returning an empty object when the upstream has no voice catalog; proxy rewriting failures to 200.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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