moeru-ai/airi · error · TypeError

Audio stream or file is required for streaming transcription

Error message

Audio stream or file is required for streaming transcription.

What it means

A synchronous TypeError thrown by resolveAudioStream when none of options.inputAudioStream, options.inputStream, or options.file is provided to streamTranscription(). The adapter needs exactly one audio source to POST to the transcription endpoint; with no source it cannot form a request body. It fails fast before any network call.

Source

Thrown at packages/stage-ui/src/libs/providers/stream-transcription/index.ts:48

  inputAudioStream?: ReadableStream<AudioChunk>
  inputStream?: ReadableStream<AudioChunk>
}

function createDeferred<T>() {
  let resolve!: (value: T | PromiseLike<T>) => void
  let reject!: (reason?: unknown) => void
  const promise = new Promise<T>((res, rej) => {
    resolve = res
    reject = rej
  })

  return { promise, resolve, reject }
}

function resolveAudioStream(options: StreamTranscriptionOptions): ReadableStream<AudioChunk> {
  const stream = options.inputAudioStream ?? options.inputStream ?? options.file?.stream()
  if (!stream)
    throw new TypeError('Audio stream or file is required for streaming transcription.')

  return stream as ReadableStream<AudioChunk>
}

function parseSSELine(line: string): AIRIStreamTranscriptionDelta | undefined {
  if (!line || !line.startsWith('data:'))
    return undefined

  const content = line.slice('data:'.length)
  const data = content.startsWith(' ') ? content.slice(1) : content
  if (!data)
    return undefined

  return JSON.parse(data) as AIRIStreamTranscriptionDelta
}

function createSSETransformer() {
  const decoder = new TextDecoder()

View on GitHub (pinned to 27111382b4)

Solutions

  1. Ensure exactly one of inputAudioStream, inputStream, or file is set before calling streamTranscription; pick the most specific source for your caller.
  2. If using a Blob/File, pass it via options.file and confirm it is non-null.
  3. Add a caller-side guard that skips transcription when no audio source is available rather than relying on the throw.
  4. In tests, provide a Blob fixture (e.g. new Blob([bytes], { type: 'audio/wav' })) as options.file.

Example fix

// before
const result = streamTranscription({ baseURL, headers })

// after: pass an explicit source and guard at the call site
if (!micStream && !audioFile)
  return
const result = streamTranscription({
  baseURL,
  headers,
  inputAudioStream: micStream ?? undefined,
  file: audioFile ?? undefined,
})
Defensive patterns

Strategy: validation

Validate before calling

import type { StreamTranscriptionOptions } from '../stream-transcription'

function hasAudioSource(options: StreamTranscriptionOptions): boolean {
  return !!(options.inputAudioStream ?? options.inputStream ?? options.file?.stream())
}

// before calling
if (!hasAudioSource(options)) {
  // skip transcription or throw a caller-side error with more context
  return
}
const result = streamTranscription(options)

Type guard

function isReadableStreamLike(value: unknown): value is ReadableStream {
  return typeof value === 'object' && value !== null
    && typeof (value as ReadableStream).getReader === 'function'
}

function resolveAudioSource(options: StreamTranscriptionOptions): ReadableStream | undefined {
  if (options.inputAudioStream && isReadableStreamLike(options.inputAudioStream))
    return options.inputAudioStream
  if (options.inputStream && isReadableStreamLike(options.inputStream))
    return options.inputStream
  if (options.file && typeof options.file.stream === 'function')
    return options.file.stream()
  return undefined
}

Prevention

When it happens

Trigger: Calling streamTranscription({}) or streamTranscription({ baseURL, headers }) with no audio source; passing options.file as a value whose .stream() is undefined; a code path that constructs options dynamically and forgets to populate one of the three fields.

Common situations: Refactor that renamed inputAudioStream/inputStream and forgot to update a caller; a Hearing module integration where the mic stream is conditionally created and the falsy branch still calls streamTranscription; tests that call the adapter without a fixture file.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/568da2339001521f. Report an issue: GitHub.