{"version":3,"sources":["../../src/inference/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { ThrowsPromise } from '@livekit/throws-transformer/throws';\nimport { WebSocket } from 'ws';\nimport { APIError, APIStatusError, APITimeoutError } from '../_exceptions.js';\nimport { AudioByteStream } from '../audio.js';\nimport { ConnectionPool } from '../connection_pool.js';\nimport { type LanguageCode, normalizeLanguage } from '../language.js';\nimport { log } from '../log.js';\nimport { createStreamChannel } from '../stream/stream_channel.js';\nimport type { ChunkedStream } from '../tts/index.js';\nimport { SynthesizeStream as BaseSynthesizeStream, TTS as BaseTTS } from '../tts/index.js';\nimport { dropBracketCues, sentenceTokenizer } from '../tts/provider_format.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../types.js';\nimport {\n  Event,\n  Future,\n  Task,\n  cancelAndWait,\n  combineSignals,\n  shortuuid,\n  waitUntilTimeout,\n} from '../utils.js';\nimport { type TimedString, createTimedString } from '../voice/io.js';\nimport {\n  type TtsClientEvent,\n  type TtsServerEvent,\n  ttsClientEventSchema,\n  ttsKnownServerEventSchema,\n  ttsServerEventSchema,\n} from './api_protos.js';\nimport { type AnyString, connectWs, createAccessToken, getDefaultInferenceUrl } from './utils.js';\n\nexport type CartesiaModels =\n  | 'cartesia/sonic-3.5'\n  | 'cartesia/sonic-3'\n  | 'cartesia/sonic-2'\n  | 'cartesia/sonic-turbo'\n  | 'cartesia/sonic'\n  | 'cartesia/sonic-3-latest'\n  | 'cartesia/sonic-latest';\n\nexport type DeepgramTTSModels = 'deepgram/aura' | 'deepgram/aura-2';\n\nexport type InworldModels =\n  | 'inworld/inworld-tts-2'\n  | 'inworld/inworld-tts-1.5-max'\n  | 'inworld/inworld-tts-1.5-mini'\n  | 'inworld/inworld-tts-1.5'\n  | 'inworld/inworld-tts-1-max'\n  | 'inworld/inworld-tts-1';\n\nexport type RimeModels = 'rime/coda' | 'rime/mistv2' | 'rime/mistv3' | 'rime/mist';\n\nexport type XaiTTSModels = 'xai/tts-1';\n\nexport type FishAudioModels =\n  | 'fishaudio'\n  | 'fishaudio/s2.1-pro'\n  | 'fishaudio/s2.1-pro-free'\n  | 'fishaudio/s2-pro';\n\nexport interface CartesiaOptions {\n  emotion?: string;\n  /** Maximum duration of audio in seconds. */\n  duration?: number;\n  /** Speech speed. Default: not specified. */\n  speed?: 'slow' | 'normal' | 'fast' | number;\n  volume?: number;\n  max_buffer_delay_ms?: number;\n  add_timestamps?: boolean;\n  add_phoneme_timestamps?: boolean;\n  use_normalized_timestamps?: boolean;\n}\n\nexport interface DeepgramTTSOptions {\n  /** Default: false. */\n  mip_opt_out?: boolean;\n}\n\nexport interface RimeOptions {\n  /** Maximum number of tokens to generate. */\n  max_tokens?: number;\n  /** Values above 1 slow down audio; values below 1 speed it up. */\n  time_scale_factor?: number;\n  /** Default 1.0, <1 = faster, >1 = slower. */\n  speed_alpha?: number;\n  /** Default false. */\n  pause_between_brackets?: boolean;\n  /** Default false. */\n  phonemize_between_brackets?: boolean;\n  /** Comma-separated speed factors for [bracketed] words. */\n  inline_speed_alpha?: string;\n  /** Default false. */\n  no_text_normalization?: boolean;\n}\n\nexport interface InworldOptions {\n  /** Range >0.5, <=1.5. */\n  speaking_rate?: number;\n  /** Range 0-2. */\n  temperature?: number;\n  /** Controls output variation on inworld-tts-2 only. */\n  delivery_mode?: 'DELIVERY_MODE_UNSPECIFIED' | 'STABLE' | 'BALANCED' | 'CREATIVE';\n  timestamp_type?: 'TIMESTAMP_TYPE_UNSPECIFIED' | 'WORD' | 'CHARACTER';\n  apply_text_normalization?: 'APPLY_TEXT_NORMALIZATION_UNSPECIFIED' | 'ON' | 'OFF';\n  /** @deprecated Backward-compatible alias. Use `apply_text_normalization`. */\n  text_normalization?: 'ON' | 'OFF';\n}\n\nexport interface XaiTTSOptions {\n  /** Output bit rate in bits per second. */\n  bit_rate?: 32000 | 64000 | 96000 | 128000 | 192000;\n  /** Speaking-rate multiplier. Default: 1.0. */\n  speed?: number;\n}\n\n/** See https://docs.fish.audio/api-reference/endpoint/websocket/tts-live */\nexport interface FishAudioOptions {\n  /** Prosody speaking-rate multiplier, 1.0 is natural. */\n  speed?: number;\n  /** Prosody loudness adjustment in dB, 0 is natural. */\n  volume?: number;\n  /** Consistent output loudness; S2-Pro family only. */\n  normalize_loudness?: boolean;\n  /** Range 0-1, higher is more varied/expressive. */\n  temperature?: number;\n  /** Nucleus sampling probability mass, range 0-1. */\n  top_p?: number;\n  /** Normalize numbers/dates/abbreviations before synthesis. */\n  normalize?: boolean;\n  /** Streaming latency mode. */\n  latency?: 'normal' | 'balanced' | 'low';\n  /** Characters buffered before auto-synthesis, range 100-300. */\n  chunk_length?: number;\n  /** Max audio tokens per text chunk, default 1024. */\n  max_new_tokens?: number;\n  /** Min characters before splitting a new chunk, range 0-100. */\n  min_chunk_length?: number;\n  /** Use prior audio as context for consistency. */\n  condition_on_previous_chunks?: boolean;\n  /** Early-stopping threshold for batching, range 0-1. */\n  early_stop_threshold?: number;\n}\n\ntype _TTSModels =\n  | CartesiaModels\n  | DeepgramTTSModels\n  | RimeModels\n  | InworldModels\n  | XaiTTSModels\n  | FishAudioModels;\n\nexport type TTSModels =\n  | CartesiaModels\n  | DeepgramTTSModels\n  | RimeModels\n  | InworldModels\n  | XaiTTSModels\n  | FishAudioModels\n  | AnyString;\n\nexport type ModelWithVoice = `${_TTSModels}:${string}` | TTSModels;\n\nexport type TTSOptions<TModel extends TTSModels> = TModel extends CartesiaModels\n  ? CartesiaOptions\n  : TModel extends DeepgramTTSModels\n    ? DeepgramTTSOptions\n    : TModel extends RimeModels\n      ? RimeOptions\n      : TModel extends InworldModels\n        ? InworldOptions\n        : TModel extends XaiTTSModels\n          ? XaiTTSOptions\n          : TModel extends FishAudioModels\n            ? FishAudioOptions\n            : Record<string, unknown>;\n\n/** Parse a model string into [model, voice]. Voice is undefined if not specified. */\nexport function parseTTSModelString(model: string): [string, string | undefined] {\n  const idx = model.lastIndexOf(':');\n  if (idx !== -1) {\n    return [model.slice(0, idx), model.slice(idx + 1)];\n  }\n  return [model, undefined];\n}\n\n/**\n * Whether the given Inference TTS provider+options combination emits aligned\n * (word- or character-level) timestamps alongside the synthesized audio.\n *\n * Mirrors the Python implementation: support is opt-in through the provider's\n * `extra_kwargs` / `modelOptions` payload — the gateway only emits\n * `output_alignment` events when the provider-specific flag is set.\n */\nexport function hasAlignedTranscript(\n  model: string | undefined,\n  modelOptions: Record<string, unknown> | undefined,\n): boolean {\n  if (!model) return false;\n  const provider = model.split('/')[0];\n  const opts = modelOptions ?? {};\n  if (provider === 'cartesia') {\n    return Boolean(opts.add_timestamps);\n  }\n  if (provider === 'inworld') {\n    return opts.timestamp_type === 'WORD' || opts.timestamp_type === 'CHARACTER';\n  }\n  return false;\n}\n\n/** Inference Fallback Adapter: configuration for a fallback TTS model that runs server-side in LiveKit Inference, providing automatic fallback between providers. */\nexport interface TTSFallbackModel {\n  /** Model name (e.g. \"cartesia/sonic\", \"deepgram/aura-2\", \"rime/coda\"). */\n  model: string;\n  /** Voice to use for the model. */\n  voice: string;\n  /** Extra configuration for the model. */\n  extraKwargs?: Record<string, unknown>;\n}\n\nexport type TTSFallbackModelType = TTSFallbackModel | string;\n\n/** Normalize a single or list of FallbackModelType into TTSFallbackModel[]. */\nexport function normalizeTTSFallback(\n  fallback: TTSFallbackModelType | TTSFallbackModelType[],\n): TTSFallbackModel[] {\n  const makeFallback = (model: TTSFallbackModelType): TTSFallbackModel => {\n    if (typeof model === 'string') {\n      const [name, voice] = parseTTSModelString(model);\n      return { model: name, voice: voice ?? '' };\n    }\n    return model;\n  };\n\n  if (Array.isArray(fallback)) {\n    return fallback.map(makeFallback);\n  }\n  return [makeFallback(fallback)];\n}\n\ntype TTSEncoding = 'pcm_s16le';\n\nconst DEFAULT_ENCODING: TTSEncoding = 'pcm_s16le';\nconst DEFAULT_SAMPLE_RATE = 16000;\nconst NUM_CHANNELS = 1;\nconst DEFAULT_LANGUAGE = 'en';\n\nexport interface InferenceTTSOptions<TModel extends TTSModels> {\n  model?: TModel;\n  voice?: string;\n  language?: LanguageCode;\n  encoding: TTSEncoding;\n  sampleRate: number;\n  baseURL: string;\n  apiKey: string;\n  apiSecret: string;\n  /** Flat provider-specific inference options forwarded as the `extra` payload field. */\n  modelOptions: TTSOptions<TModel>;\n  fallback?: TTSFallbackModel[];\n  connOptions?: APIConnectOptions;\n}\n\n/**\n * Livekit Cloud Inference TTS\n */\nexport class TTS<TModel extends TTSModels> extends BaseTTS {\n  private opts: InferenceTTSOptions<TModel>;\n  private streams: Set<SynthesizeStream<TModel>> = new Set();\n  pool: ConnectionPool<WebSocket>;\n\n  #logger = log();\n  #alignedTranscript: boolean;\n\n  constructor(opts: {\n    model: TModel;\n    voice?: string;\n    language?: string;\n    baseURL?: string;\n    encoding?: TTSEncoding;\n    sampleRate?: number;\n    apiKey?: string;\n    apiSecret?: string;\n    /** Flat provider-specific inference options forwarded as the `extra` payload field. */\n    modelOptions?: TTSOptions<TModel>;\n    fallback?: TTSFallbackModelType | TTSFallbackModelType[];\n    connOptions?: APIConnectOptions;\n  }) {\n    const sampleRate = opts?.sampleRate ?? DEFAULT_SAMPLE_RATE;\n    const initialAligned = hasAlignedTranscript(\n      opts?.model,\n      opts?.modelOptions as Record<string, unknown> | undefined,\n    );\n    super(sampleRate, 1, { streaming: true, alignedTranscript: initialAligned });\n\n    const {\n      model,\n      voice,\n      language = DEFAULT_LANGUAGE,\n      baseURL,\n      encoding = DEFAULT_ENCODING,\n      apiKey,\n      apiSecret,\n      modelOptions = {} as TTSOptions<TModel>,\n      fallback,\n      connOptions,\n    } = opts || {};\n\n    const lkBaseURL = baseURL || getDefaultInferenceUrl();\n    const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;\n    if (!lkApiKey) {\n      throw new Error('apiKey is required: pass apiKey or set LIVEKIT_API_KEY');\n    }\n\n    const lkApiSecret =\n      apiSecret || process.env.LIVEKIT_INFERENCE_API_SECRET || process.env.LIVEKIT_API_SECRET;\n    if (!lkApiSecret) {\n      throw new Error('apiSecret is required: pass apiSecret or set LIVEKIT_API_SECRET');\n    }\n\n    // read voice id from the model if provided: \"provider/model:voice_id\"\n    let nextModel = model;\n    let nextVoice = voice;\n    if (typeof nextModel === 'string') {\n      const idx = nextModel.lastIndexOf(':');\n      if (idx !== -1) {\n        const voiceFromModel = nextModel.slice(idx + 1);\n        if (nextVoice && nextVoice !== voiceFromModel) {\n          this.#logger.warn(\n            '`voice` is provided via both argument and model, using the one from the argument',\n            { voice: nextVoice, model: nextModel },\n          );\n        } else {\n          nextVoice = voiceFromModel;\n        }\n        nextModel = nextModel.slice(0, idx) as TModel;\n      }\n    }\n\n    const normalizedFallback = fallback ? normalizeTTSFallback(fallback) : undefined;\n\n    this.opts = {\n      model: nextModel,\n      voice: nextVoice,\n      language: normalizeLanguage(language),\n      encoding,\n      sampleRate,\n      baseURL: lkBaseURL,\n      apiKey: lkApiKey,\n      apiSecret: lkApiSecret,\n      modelOptions,\n      fallback: normalizedFallback,\n      connOptions: connOptions ?? DEFAULT_API_CONNECT_OPTIONS,\n    };\n\n    this.#alignedTranscript = hasAlignedTranscript(\n      nextModel,\n      modelOptions as Record<string, unknown>,\n    );\n\n    // Initialize connection pool\n    this.pool = new ConnectionPool<WebSocket>({\n      connectCb: (timeout) => this.connectWs(timeout),\n      closeCb: (ws) => this.closeWs(ws),\n      maxSessionDuration: 300_000,\n      markRefreshedOnGet: true,\n      connectTimeout: 10_000, // 10 seconds default\n    });\n  }\n\n  get label() {\n    return 'inference.TTS';\n  }\n\n  /**\n   * Key the shared markup tables off the gateway model's provider.\n   *\n   * `llmInstructions` / `normalize` / `convert` are inherited from the base markup helper,\n   * keyed on this.\n   */\n  protected override markupProviderKey(): string {\n    const model = this.opts?.model ?? '';\n    const provider = model.split('/')[0] ?? '';\n    if (provider === 'inworld' && model.includes('tts-2')) {\n      return 'inworld';\n    } else if (provider === 'inworld') {\n      return ''; // older inworld models don't support markup\n    }\n    return provider;\n  }\n\n  get model(): string {\n    return this.opts.model ?? 'unknown';\n  }\n\n  get provider(): string {\n    return 'livekit';\n  }\n\n  override get capabilities() {\n    return { streaming: true, alignedTranscript: this.#alignedTranscript };\n  }\n\n  static fromModelString(modelString: string): TTS<AnyString> {\n    const [model, voice] = parseTTSModelString(modelString);\n    return new TTS({ model, voice: voice || undefined });\n  }\n\n  updateOptions(\n    opts: Partial<\n      Pick<InferenceTTSOptions<TModel>, 'model' | 'voice' | 'language' | 'modelOptions'>\n    >,\n  ) {\n    const mergedModelOptions =\n      opts.modelOptions !== undefined\n        ? ({ ...this.opts.modelOptions, ...opts.modelOptions } as TTSOptions<TModel>)\n        : this.opts.modelOptions;\n\n    const sessionAffectingChange =\n      opts.model !== undefined ||\n      opts.voice !== undefined ||\n      opts.language !== undefined ||\n      opts.modelOptions !== undefined;\n\n    this.opts = {\n      ...this.opts,\n      ...opts,\n      modelOptions: mergedModelOptions,\n      language: opts.language !== undefined ? normalizeLanguage(opts.language) : this.opts.language,\n    };\n\n    this.#alignedTranscript = hasAlignedTranscript(\n      this.opts.model,\n      this.opts.modelOptions as Record<string, unknown>,\n    );\n\n    // The inference gateway only consumes `model` / `voice` / `language` /\n    // `extra` (modelOptions) when the WebSocket is first opened via\n    // `session.create`. Pooled sockets keep the old session settings and, for\n    // example, would keep reporting `alignedTranscript=true` while the server\n    // never emits `output_alignment`. Invalidate so the next `stream()` opens\n    // a fresh connection with the up-to-date payload.\n    if (sessionAffectingChange) {\n      this.pool.invalidate();\n    }\n\n    for (const stream of this.streams) {\n      stream.updateOptions(this.opts);\n    }\n  }\n\n  synthesize(_: string): ChunkedStream {\n    throw new Error('ChunkedStream is not implemented');\n  }\n\n  stream(options?: { connOptions?: APIConnectOptions }): SynthesizeStream<TModel> {\n    const { connOptions = this.opts.connOptions ?? DEFAULT_API_CONNECT_OPTIONS } = options || {};\n    const stream = new SynthesizeStream(this, { ...this.opts }, connOptions);\n    this.streams.add(stream);\n    return stream;\n  }\n\n  async connectWs(timeout: number): Promise<WebSocket> {\n    let baseURL = this.opts.baseURL;\n    if (baseURL.startsWith('http://') || baseURL.startsWith('https://')) {\n      baseURL = baseURL.replace('http', 'ws');\n    }\n\n    const token = await createAccessToken(this.opts.apiKey, this.opts.apiSecret);\n    const url = `${baseURL}/tts?model=${encodeURIComponent(this.model)}`;\n    const headers = { Authorization: `Bearer ${token}` } as Record<string, string>;\n\n    const params = {\n      type: 'session.create',\n      sample_rate: String(this.opts.sampleRate),\n      encoding: this.opts.encoding,\n      extra: this.opts.modelOptions,\n    } as Record<string, unknown>;\n\n    if (this.opts.voice) (params as Record<string, unknown>).voice = this.opts.voice;\n    if (this.opts.model) (params as Record<string, unknown>).model = this.opts.model;\n    if (this.opts.language) (params as Record<string, unknown>).language = this.opts.language;\n\n    if (this.opts.fallback?.length) {\n      params.fallback = {\n        models: this.opts.fallback.map((m) => ({\n          model: m.model,\n          voice: m.voice,\n          extra: m.extraKwargs ?? {},\n        })),\n      };\n    }\n\n    if (this.opts.connOptions) {\n      params.connection = {\n        timeout: this.opts.connOptions.timeoutMs / 1000,\n        retries: this.opts.connOptions.maxRetry,\n      };\n    }\n\n    this.#logger.debug({ url }, 'inference.TTS creating new websocket connection (pool miss)');\n    const socket = await connectWs(url, headers, timeout);\n    socket.send(JSON.stringify(params));\n    return socket;\n  }\n\n  async closeWs(ws: WebSocket) {\n    await ws.close();\n  }\n\n  prewarm(): void {\n    this.pool.prewarm();\n  }\n\n  async close() {\n    for (const stream of this.streams) {\n      await stream.close();\n    }\n    this.streams.clear();\n    await this.pool.close();\n  }\n}\n\nexport class SynthesizeStream<TModel extends TTSModels> extends BaseSynthesizeStream {\n  private opts: InferenceTTSOptions<TModel>;\n  private tts: TTS<TModel>;\n  /**\n   * Snapshot whether expressive is active now, while the framework holds it fixed for this\n   * synthesis (set synchronously before `stream()`). Reading it lazily in `run` would race\n   * with the next turn/session mutating the shared TTS instance.\n   */\n  private expressive: boolean;\n  /**\n   * Alignment arrives finer-grained than a cue, so `dropBracketCues` parks the tail of an\n   * unclosed span here between messages.\n   */\n  private heldTokens: TimedString[] = [];\n\n  #logger = log();\n\n  constructor(tts: TTS<TModel>, opts: InferenceTTSOptions<TModel>, connOptions: APIConnectOptions) {\n    super(tts, connOptions);\n    this.opts = opts;\n    this.tts = tts;\n    this.expressive = tts.expressive;\n  }\n\n  get label() {\n    return 'inference.SynthesizeStream';\n  }\n\n  updateOptions(\n    opts: Partial<\n      Pick<InferenceTTSOptions<TModel>, 'model' | 'voice' | 'language' | 'modelOptions'>\n    >,\n  ) {\n    const mergedModelOptions =\n      opts.modelOptions !== undefined\n        ? ({ ...this.opts.modelOptions, ...opts.modelOptions } as TTSOptions<TModel>)\n        : this.opts.modelOptions;\n\n    this.opts = {\n      ...this.opts,\n      ...opts,\n      modelOptions: mergedModelOptions,\n      language: opts.language !== undefined ? normalizeLanguage(opts.language) : this.opts.language,\n    };\n  }\n\n  protected async run(): Promise<void> {\n    let closing = false;\n    let lastFrame: AudioFrame | undefined;\n    // Timestamps are delivered in their own WS message; buffer them and attach\n    // to the next audio frame that we forward to the output emitter. This\n    // mirrors the semantics of `output_emitter.push_timed_transcript` on the\n    // Python side.\n    let pendingTimedTranscripts: TimedString[] = [];\n\n    // chunking defaults (cap + expressive batch size) live in provider_format\n    const provider = (this.opts.model ?? '').split('/')[0] ?? '';\n    const sendTokenizerStream = sentenceTokenizer(provider, {\n      expressive: this.expressive,\n    }).stream();\n    const eventChannel = createStreamChannel<TtsServerEvent>();\n    const requestId = shortuuid('tts_request_');\n    const inputSentEvent = new Event();\n\n    // Signal for protocol-driven completion (when 'done' message is received)\n    const completionFuture = new Future<void>();\n\n    const resourceCleanup = async () => {\n      if (closing) return;\n      closing = true;\n      sendTokenizerStream.close();\n      // close() returns a promise; don't leak it\n      await eventChannel.close();\n    };\n\n    const sendClientEvent = async (event: TtsClientEvent, ws: WebSocket, signal: AbortSignal) => {\n      // Don't send events to a closed WebSocket or aborted controller\n      if (signal.aborted || closing) return;\n\n      const validatedEvent = await ttsClientEventSchema.parseAsync(event);\n      if (ws.readyState !== WebSocket.OPEN) {\n        this.#logger.warn('Trying to send client TTS event to a closed WebSocket');\n        return;\n      }\n      ws.send(JSON.stringify(validatedEvent));\n    };\n\n    const sendLastFrame = (segmentId: string, final: boolean) => {\n      if (lastFrame) {\n        this.queue.put({\n          requestId,\n          segmentId,\n          frame: lastFrame,\n          final,\n          timedTranscripts:\n            pendingTimedTranscripts.length > 0 ? pendingTimedTranscripts : undefined,\n        });\n        lastFrame = undefined;\n        pendingTimedTranscripts = [];\n      }\n    };\n\n    const createInputTask = async (signal: AbortSignal) => {\n      for await (const data of this.input) {\n        if (signal.aborted || closing) break;\n        if (data === SynthesizeStream.FLUSH_SENTINEL) {\n          sendTokenizerStream.flush();\n          continue;\n        }\n        // only expressive turns can carry markup; without it the text is a plain\n        // utterance and must reach the provider byte-for-byte\n        sendTokenizerStream.pushText(this.expressive ? this.tts.markup.normalize(data) : data);\n      }\n      // Only call endInput if the stream hasn't been closed by cleanup\n      if (!closing) {\n        sendTokenizerStream.endInput();\n      }\n    };\n\n    const createSentenceStreamTask = async (ws: WebSocket, signal: AbortSignal) => {\n      for await (const ev of sendTokenizerStream) {\n        if (signal.aborted || closing) break;\n\n        // Carry per-utterance generation config so mid-stream voice/model/language\n        // changes ride the gateway's hot path (no reconnect needed on the active\n        // session). The gateway also merges `extra` into the live session.\n        const generationConfig: Record<string, string> = {};\n        if (this.opts.voice) generationConfig.voice = this.opts.voice;\n        if (this.opts.model) generationConfig.model = this.opts.model;\n        if (this.opts.language) generationConfig.language = this.opts.language;\n\n        // re-normalize at sentence level: tags split across input chunks aren't caught by\n        // the per-chunk normalize in the input task\n        const converted = this.expressive\n          ? this.tts.markup.convert(this.tts.markup.normalize(ev.token))\n          : ev.token;\n\n        this.markStarted();\n        await sendClientEvent(\n          {\n            type: 'input_transcript',\n            transcript: converted + ' ',\n            generation_config: generationConfig,\n            extra: (this.opts.modelOptions as Record<string, unknown>) ?? {},\n          },\n          ws,\n          signal,\n        );\n        inputSentEvent.set();\n      }\n\n      await sendClientEvent({ type: 'session.flush' }, ws, signal);\n      // needed in case empty input is sent\n      inputSentEvent.set();\n    };\n\n    // Handles WebSocket message routing and error handling\n    // Completes based on protocol messages, NOT on ws.close()\n    const createWsListenerTask = async (ws: WebSocket, signal: AbortSignal) => {\n      const onMessage = (data: Buffer) => {\n        try {\n          const eventJson = JSON.parse(data.toString()) as Record<string, unknown>;\n          const validatedEvent = ttsServerEventSchema.parse(eventJson);\n          // writer.write returns a promise; avoid unhandled rejections if stream is closed\n          void eventChannel.write(validatedEvent).catch((error) => {\n            this.#logger.debug(\n              { error },\n              'Failed writing TTS event to stream channel (likely closed)',\n            );\n          });\n        } catch (e) {\n          this.#logger.error({ error: e }, 'Error parsing WebSocket message');\n        }\n      };\n\n      const onError = (e: Error) => {\n        this.#logger.error({ error: e }, 'WebSocket error');\n        void resourceCleanup();\n        try {\n          // If the ws is misbehaving, hard-stop it immediately to avoid buffering.\n          ws.terminate?.();\n        } catch {\n          // ignore\n        }\n        // Ensure this ws is not reused\n        this.tts.pool.remove(ws);\n        completionFuture.reject(e);\n      };\n\n      const onClose = () => {\n        // WebSocket closed unexpectedly (not by us)\n        if (!closing) {\n          this.#logger.error('WebSocket closed unexpectedly');\n          void resourceCleanup();\n          // Ensure this ws is not reused\n          this.tts.pool.remove(ws);\n          completionFuture.reject(\n            new APIStatusError({\n              message: 'Gateway connection closed unexpectedly',\n              options: { requestId },\n            }),\n          );\n        }\n      };\n\n      const onAbort = () => {\n        void resourceCleanup();\n        try {\n          // On interruption/abort, close the websocket immediately so the server stops streaming\n          // and the ws library doesn't buffer unread frames in memory.\n          ws.terminate?.();\n        } catch {\n          // ignore\n        }\n        this.tts.pool.remove(ws);\n        inputSentEvent.set();\n        completionFuture.resolve();\n      };\n\n      // Attach listeners\n      ws.on('message', onMessage);\n      ws.on('error', onError);\n      ws.on('close', onClose);\n      signal.addEventListener('abort', onAbort);\n\n      try {\n        // Wait for protocol-driven completion or error\n        await completionFuture.await;\n      } finally {\n        // IMPORTANT: Remove listeners so connection can be reused\n        ws.off('message', onMessage);\n        ws.off('error', onError);\n        ws.off('close', onClose);\n        signal.removeEventListener('abort', onAbort);\n      }\n    };\n\n    const createRecvTask = async (signal: AbortSignal) => {\n      let currentSessionId: string | null = null;\n      const recvTimeoutMs = this.connOptions.timeoutMs;\n\n      const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);\n      const serverEventStream = eventChannel.stream();\n      const reader = serverEventStream.getReader();\n\n      try {\n        await inputSentEvent.wait();\n\n        while (!this.closed && !signal.aborted) {\n          const result = await waitUntilTimeout(\n            reader.read(),\n            recvTimeoutMs,\n            () => new APITimeoutError({ message: 'TTS recv idle timeout' }),\n          );\n\n          if (signal.aborted) return;\n          if (result.done) return;\n\n          const parsedServerEvent = ttsKnownServerEventSchema.safeParse(result.value);\n          if (!parsedServerEvent.success) {\n            continue;\n          }\n\n          const serverEvent = parsedServerEvent.data;\n          const sessionIdFromEvent = (serverEvent as { session_id?: string }).session_id;\n          if (currentSessionId === null && sessionIdFromEvent) {\n            currentSessionId = sessionIdFromEvent;\n          }\n\n          switch (serverEvent.type) {\n            case 'session.created':\n              currentSessionId = serverEvent.session_id;\n              break;\n            case 'output_audio':\n              const base64Data = new Int8Array(Buffer.from(serverEvent.audio, 'base64'));\n              for (const frame of bstream.write(base64Data.buffer)) {\n                sendLastFrame(currentSessionId!, false);\n                lastFrame = frame;\n              }\n              break;\n            case 'output_alignment':\n              let aligned: TimedString[] = [];\n              if (serverEvent.words && serverEvent.words.length > 0) {\n                aligned = serverEvent.words.map((w) =>\n                  createTimedString({\n                    text: provider === 'cartesia' ? `${w.word} ` : w.word,\n                    startTime: w.start,\n                    endTime: w.end,\n                  }),\n                );\n              } else if (serverEvent.chars && serverEvent.chars.length > 0) {\n                aligned = serverEvent.chars.map((c) =>\n                  createTimedString({ text: c.char, startTime: c.start, endTime: c.end }),\n                );\n              }\n              if (aligned.length > 0) {\n                // the provider aligned the *converted* text, so under expressive it carries\n                // native cues that were never spoken\n                pendingTimedTranscripts.push(\n                  ...(this.expressive ? dropBracketCues(aligned, this.heldTokens) : aligned),\n                );\n              }\n              break;\n            case 'done':\n              if (this.heldTokens.length > 0) {\n                // release an unclosed span, cue unresolved\n                pendingTimedTranscripts.push(\n                  ...dropBracketCues([], this.heldTokens, { final: true }),\n                );\n              }\n              for (const frame of bstream.flush()) {\n                sendLastFrame(currentSessionId!, false);\n                lastFrame = frame;\n              }\n              sendLastFrame(currentSessionId!, true);\n              this.queue.put(SynthesizeStream.END_OF_STREAM);\n              await resourceCleanup();\n              completionFuture.resolve();\n              return;\n            case 'session.closed':\n              await resourceCleanup();\n              completionFuture.resolve();\n              return;\n            case 'error':\n              this.#logger.error(\n                { serverEvent },\n                'Received error message from LiveKit TTS WebSocket',\n              );\n              await resourceCleanup();\n              completionFuture.reject(\n                new APIError(`LiveKit TTS returned error: ${serverEvent.message}`),\n              );\n              return;\n          }\n        }\n      } catch (e) {\n        if (e instanceof APITimeoutError) {\n          this.#logger.warn('TTS recv task timed out waiting for server message');\n          await resourceCleanup();\n          completionFuture.reject(e);\n          return;\n        }\n        throw e;\n      } finally {\n        reader.releaseLock();\n        try {\n          await serverEventStream.cancel();\n        } catch (e) {\n          this.#logger.debug('Error cancelling serverEventStream (may already be cancelled):', e);\n        }\n      }\n    };\n\n    try {\n      await this.tts.pool.withConnection(\n        async (ws: WebSocket) => {\n          try {\n            // IMPORTANT: don't cancel the stream's controller on normal completion,\n            // otherwise the pool will remove+close the ws and every run becomes a pool miss.\n            const runController = new AbortController();\n            const onStreamAbort = () => runController.abort(this.abortController.signal.reason);\n            this.abortController.signal.addEventListener('abort', onStreamAbort, { once: true });\n\n            const tasks = [\n              Task.from(\n                async (controller) => {\n                  const combined = combineSignals(runController.signal, controller.signal);\n                  await createInputTask(combined);\n                },\n                undefined,\n                'inference-tts-input',\n              ),\n              Task.from(\n                async (controller) => {\n                  const combined = combineSignals(runController.signal, controller.signal);\n                  await createSentenceStreamTask(ws, combined);\n                },\n                undefined,\n                'inference-tts-sentence',\n              ),\n              Task.from(\n                async (controller) => {\n                  const combined = combineSignals(runController.signal, controller.signal);\n                  await createWsListenerTask(ws, combined);\n                },\n                undefined,\n                'inference-tts-ws-listener',\n              ),\n              Task.from(\n                async (controller) => {\n                  const combined = combineSignals(runController.signal, controller.signal);\n                  await createRecvTask(combined);\n                },\n                undefined,\n                'inference-tts-recv',\n              ),\n            ];\n\n            try {\n              await ThrowsPromise.all(tasks.map((t) => t.result));\n            } finally {\n              // Mirror python finally: unblock recv and cancel all tasks.\n              inputSentEvent.set();\n              await resourceCleanup();\n              await cancelAndWait(tasks, 5000);\n              this.abortController.signal.removeEventListener('abort', onStreamAbort);\n            }\n          } catch (e) {\n            // If aborted, don't throw - let cleanup handle it\n            if (e instanceof Error && e.name === 'AbortError') {\n              return;\n            }\n            throw e;\n          }\n        },\n        {\n          timeout: this.connOptions.timeoutMs,\n        },\n      );\n    } catch (e) {\n      // Handle connection errors\n      if (e instanceof Error && e.name === 'AbortError') {\n        // Abort is expected during normal shutdown\n        return;\n      }\n      throw e;\n    } finally {\n      // Ensure cleanup always runs (and don't leak the promise)\n      await resourceCleanup();\n    }\n  }\n}\n"],"mappings":"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","names":["import_utils","BaseTTS","BaseSynthesizeStream"]}