moeru-ai/airi · error · Error

streaming-tts: not authenticated

Error message

streaming-tts: not authenticated

What it means

Thrown synchronously at the top of streamingSynthesize when no auth token is available. The function needs a Bearer JWT to open the WebSocket at /api/v1/audio/speech/ws; it first checks options.token then falls back to getAuthToken(). If both are falsy it throws before constructing the ws URL, preventing an unauthenticated connection attempt.

Source

Thrown at packages/stage-ui/src/libs/speech/streaming-session.ts:91

 *
 * Use when:
 * - The stage speech pipeline's per-segment `tts()` callback wants to use
 *   the streaming gateway instead of HTTP `/audio/speech`.
 *
 * Expects:
 * - The user is authenticated; `getAuthToken()` returns a JWT or one is
 *   passed in `options.token`.
 * - The server has `STREAMING_TTS_UPSTREAM` configured.
 *
 * Returns:
 * - `{ audio, sentences, byteLength }` once `session.finished` arrives.
 * - Rejects with the upstream `error.message` on a server error event.
 * - Rejects with the abort reason on signal abort.
 */
export async function streamingSynthesize(options: StreamingTtsSessionOptions): Promise<StreamingTtsSessionResult> {
  const token = options.token ?? getAuthToken()
  if (!token)
    throw new Error('streaming-tts: not authenticated')

  const baseUrl = options.serverUrl ?? SERVER_URL
  const wsUrl = toWebSocketUrl(baseUrl, '/api/v1/audio/speech/ws', token, {
    ttsTrigger: options.ttsTrigger ?? 'manual',
    ttsSource: options.ttsSource ?? 'manual_preview',
    ttsVoiceType: options.ttsVoiceType ?? 'unknown',
  })

  const audioChunks: ArrayBuffer[] = []
  const sentences: StreamingTtsSessionResult['sentences'] = []
  let totalBytes = 0

  return new Promise<StreamingTtsSessionResult>((resolve, reject) => {
    const ws = new WebSocket(wsUrl)
    ws.binaryType = 'arraybuffer'

    let settled = false
    function settle(action: () => void) {

View on GitHub (pinned to 27111382b4)

Solutions

  1. Ensure the user is authenticated before invoking streamingSynthesize; gate the call on a non-empty auth token.
  2. If calling during startup, await auth hydration before initializing the speech pipeline.
  3. Pass an explicit options.token when orchestrating server-side or with a freshly refreshed token.
  4. Catch this error and redirect the user to login rather than retrying blindly.

Example fix

// before
const { audio } = await streamingSynthesize({ model, voice, input })

// after: guard on auth state
const token = getAuthToken()
if (!token) {
  // prompt login / skip TTS
  return
}
const { audio } = await streamingSynthesize({ model, voice, input, token })
Defensive patterns

Strategy: validation

Validate before calling

import { getAuthToken } from '../auth'

function canStreamTts(tokenOverride?: string): boolean {
  return !!(tokenOverride ?? getAuthToken())
}

// before calling
if (!canStreamTts(options.token)) {
  // prompt login or skip
  return
}
await streamingSynthesize(options)

Type guard

function hasStreamingTtsCredentials(options: StreamingTtsSessionOptions): boolean {
  return !!(options.token ?? getAuthToken())
}

Try / catch

try {
  const { audio } = await streamingSynthesize(options)
}
catch (error) {
  if (String(error).includes('not authenticated')) {
    // redirect to login / refresh token
  }
  throw error
}

Prevention

When it happens

Trigger: Calling streamingSynthesize({ model, voice, input }) while the user is not logged in (getAuthToken() returns null/empty) and options.token is omitted; the auth store has not been hydrated yet on app startup; the session expired and the token was cleared.

Common situations: TTS invoked during the brief window before auth bootstrap completes; logged-out user triggering streaming TTS from settings test or chat auto-TTS; token refresh failed silently leaving getAuthToken empty; misordered app init where speech pipeline starts before auth.

Understand the failure class

Related errors


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