{"version":3,"sources":["../../src/stt/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { type AudioFrame, AudioResampler } from '@livekit/rtc-node';\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport type { ReadableStream } from 'node:stream/web';\nimport { APIConnectionError, APIError } from '../_exceptions.js';\nimport { calculateAudioDurationSeconds } from '../audio.js';\nimport type { LanguageCode } from '../language.js';\nimport { log } from '../log.js';\nimport type { STTMetrics } from '../metrics/base.js';\nimport { DeferredReadableStream } from '../stream/deferred_stream.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS, intervalForRetry } from '../types.js';\nimport type { AudioBuffer } from '../utils.js';\nimport { AsyncIterableQueue, delay, startSoon, toError } from '../utils.js';\nimport type { ConversationItemAddedEvent } from '../voice/events.js';\nimport type { TimedString } from '../voice/index.js';\n\n/** Indicates start/middle/end of speech */\nexport enum SpeechEventType {\n  /**\n   * Indicate the start of speech.\n   * If the STT doesn't support this event, this will be emitted at the same time\n   * as the first INTERIM_TRANSCRIPT.\n   */\n  START_OF_SPEECH = 0,\n  /**\n   * Interim transcript, useful for real-time transcription.\n   */\n  INTERIM_TRANSCRIPT = 1,\n  /**\n   * Final transcript, emitted when the STT is confident enough that a certain\n   * portion of the speech will not change.\n   */\n  FINAL_TRANSCRIPT = 2,\n  /**\n   * Indicate the end of speech, emitted when the user stops speaking.\n   * The first alternative is a combination of all the previous FINAL_TRANSCRIPT events.\n   */\n  END_OF_SPEECH = 3,\n  /** Usage event, emitted periodically to indicate usage metrics. */\n  RECOGNITION_USAGE = 4,\n  /**\n   * Preflight transcript, emitted before final transcript when STT has high confidence\n   * but hasn't fully committed yet. Includes all pre-committed transcripts including\n   * final transcript from the previous STT run.\n   */\n  PREFLIGHT_TRANSCRIPT = 5,\n}\n\n/** SpeechData contains metadata about this {@link SpeechEvent}. */\nexport interface SpeechData {\n  /** Language code of the speech. */\n  language: LanguageCode;\n  /** Transcribed text. */\n  text: string;\n  /** Start time of the speech segment in seconds. */\n  startTime: number;\n  /** End time of the speech segment in seconds. */\n  endTime: number;\n  /** Confidence score of the transcription (0-1). */\n  confidence: number;\n  /** Word-level timing information. */\n  words?: TimedString[];\n  /** Speaker identifier when the provider supports diarization. */\n  speakerId?: string | null;\n  /**\n   * The source languages spoken by the user.\n   *\n   * Populated by STT services that support translation, where `language` holds the\n   * target language and `sourceLanguages` holds the original spoken language(s),\n   * or by multi-language detection services where `language` holds the dominant\n   * language and `sourceLanguages` holds all detected languages sorted by prevalence.\n   *\n   * May contain multiple entries when a single utterance spans multiple source languages.\n   */\n  sourceLanguages?: LanguageCode[];\n  /**\n   * The original transcription segments in the source language(s), when translation is active.\n   * Each entry corresponds to the same-indexed entry in `sourceLanguages`.\n   */\n  sourceTexts?: string[];\n  /**\n   * The target language(s) produced by a translation-capable STT service, one entry per\n   * consecutive same-language run, parallel to `targetTexts`.\n   *\n   * `language` holds the dominant or first target language and `targetLanguages` carries the\n   * fine-grained per-run breakdown. Populated when translation is active.\n   */\n  targetLanguages?: LanguageCode[];\n  /**\n   * The translated transcription segments in the target language(s).\n   * Each entry corresponds to the same-indexed entry in `targetLanguages`.\n   */\n  targetTexts?: string[];\n  /**\n   * Optional plugin-specific metadata (e.g. voice profile, provider diagnostics).\n   *\n   * Plugins may populate this with provider-specific data that doesn't map to standard fields.\n   */\n  metadata?: Record<string, unknown>;\n}\n\nexport interface RecognitionUsage {\n  /** Duration of the audio that was recognized in seconds. */\n  audioDuration: number;\n  /** Input audio tokens (for token-based STT billing). */\n  inputTokens?: number;\n  /** Output text tokens (for token-based STT billing). */\n  outputTokens?: number;\n}\n\n/** SpeechEvent is a packet of speech-to-text data. */\nexport interface SpeechEvent {\n  type: SpeechEventType;\n  alternatives?: [SpeechData, ...SpeechData[]];\n  /** Wall-clock time when speech ended, in milliseconds since the Unix epoch. */\n  speechEndTime?: number;\n  requestId?: string;\n  recognitionUsage?: RecognitionUsage;\n}\n\n/**\n * Describes the capabilities of the STT provider.\n *\n * @remarks\n * At present, the framework only supports providers that have a streaming endpoint.\n */\nexport interface STTCapabilities {\n  streaming: boolean;\n  interimResults: boolean;\n  /**\n   * Whether this STT supports aligned transcripts with word/chunk timestamps.\n   * - 'word': Provider returns word-level timestamps\n   * - 'chunk': Provider returns chunk-level timestamps (e.g., sentence/phrase boundaries)\n   * - false: Provider does not support aligned transcripts\n   */\n  alignedTranscript?: 'word' | 'chunk' | false;\n  /** Whether this STT supports speaker diarization. */\n  diarization?: boolean;\n  /** Whether the STT supports keyterm prompting */\n  keyterms?: boolean;\n  /** Whether the STT can natively consume conversation context (see STT._pushConversationItem) */\n  chatContext?: boolean;\n}\n\nexport interface STTError {\n  type: 'stt_error';\n  timestamp: number;\n  label: string;\n  error: Error;\n  recoverable: boolean;\n}\n\nexport type STTCallbacks = {\n  ['metrics_collected']: (metrics: STTMetrics) => void;\n  ['error']: (error: STTError) => void;\n};\n\n/**\n * An instance of a speech-to-text adapter.\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child STT class, which inherits this class's methods.\n */\nexport abstract class STT extends (EventEmitter as new () => TypedEmitter<STTCallbacks>) {\n  abstract label: string;\n  #capabilities: STTCapabilities;\n  #keytermsUnsupportedWarned = false;\n  #chatContextUnsupportedWarned = false;\n\n  constructor(capabilities: STTCapabilities) {\n    super();\n    this.#capabilities = capabilities;\n  }\n\n  /** Returns this STT's capabilities */\n  get capabilities(): STTCapabilities {\n    return this.#capabilities;\n  }\n\n  protected updateCapabilities(caps: Partial<STTCapabilities>): void {\n    this.#capabilities = { ...this.#capabilities, ...caps };\n  }\n\n  /**\n   * Get the model name/identifier for this STT instance.\n   *\n   * @returns The model name if available, \"unknown\" otherwise.\n   *\n   * @remarks\n   * Plugins should override this property to provide their model information.\n   */\n  get model(): string {\n    return 'unknown';\n  }\n\n  /**\n   * Get the provider name for this STT instance.\n   *\n   * @returns The provider name if available, \"unknown\" otherwise.\n   *\n   * @remarks\n   * Plugins should override this property to provide their provider information.\n   */\n  get provider(): string {\n    return 'unknown';\n  }\n\n  /** Receives an audio buffer and returns transcription in the form of a {@link SpeechEvent} */\n  async recognize(frame: AudioBuffer, abortSignal?: AbortSignal): Promise<SpeechEvent> {\n    const startTime = process.hrtime.bigint();\n    const event = await this._recognize(frame, abortSignal);\n    const durationMs = Number((process.hrtime.bigint() - startTime) / BigInt(1000000));\n    this.emit('metrics_collected', {\n      type: 'stt_metrics',\n      requestId: event.requestId ?? '',\n      timestamp: Date.now(),\n      durationMs,\n      label: this.label,\n      audioDurationMs: Math.round(calculateAudioDurationSeconds(frame) * 1000),\n      streamed: false,\n      metadata: {\n        modelProvider: this.provider,\n        modelName: this.model,\n      },\n    });\n    return event;\n  }\n\n  protected abstract _recognize(\n    frame: AudioBuffer,\n    abortSignal?: AbortSignal,\n  ): Promise<SpeechEvent>;\n\n  /**\n   * Set the framework-managed keyterms (session config + auto-detection).\n   *\n   * Internal hook called by the framework, kept separate from the user's own keyterms\n   * (constructor / `updateOptions`). Plugins that support keyterms override this to\n   * store the session set and apply it merged with the user keyterms.\n   *\n   * @internal\n   */\n  _updateSessionKeyterms(_keyterms: string[]): void {\n    if (!this.#capabilities.keyterms) {\n      if (!this.#keytermsUnsupportedWarned) {\n        this.#keytermsUnsupportedWarned = true;\n        log()\n          .child({ stt: this.label })\n          .warn('keyterms are not supported by this STT, ignoring keyterms update');\n      }\n      return;\n    }\n  }\n\n  /**\n   * Feed a new conversation turn to the STT to bias recognition (context carryover).\n   *\n   * Plugins with native context support set `STTCapabilities.chatContext` and override\n   * this to forward the item to their provider's carryover field.\n   *\n   * @internal\n   */\n  _pushConversationItem(_ev: ConversationItemAddedEvent): void {\n    if (!this.#capabilities.chatContext) {\n      if (!this.#chatContextUnsupportedWarned) {\n        this.#chatContextUnsupportedWarned = true;\n        log()\n          .child({ stt: this.label })\n          .warn('chat context is not supported by this STT, ignoring chat context update');\n      }\n      return;\n    }\n  }\n\n  /**\n   * Returns a {@link SpeechStream} that can be used to push audio frames and receive\n   * transcriptions\n   *\n   * @param options - Optional configuration including connection options\n   */\n  abstract stream(options?: { connOptions?: APIConnectOptions }): SpeechStream;\n\n  async close(): Promise<void> {\n    return;\n  }\n}\n\n/**\n * An instance of a speech-to-text stream, as an asynchronous iterable iterator.\n *\n * @example Looping through frames\n * ```ts\n * for await (const event of stream) {\n *   if (event.type === SpeechEventType.FINAL_TRANSCRIPT) {\n *     console.log(event.alternatives[0].text)\n *   }\n * }\n * ```\n *\n * @remarks\n * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that\n * exports its own child SpeechStream class, which inherits this class's methods.\n */\nexport abstract class SpeechStream implements AsyncIterableIterator<SpeechEvent> {\n  protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n  protected input = new AsyncIterableQueue<AudioFrame | typeof SpeechStream.FLUSH_SENTINEL>();\n  protected output = new AsyncIterableQueue<SpeechEvent>();\n  protected queue = new AsyncIterableQueue<SpeechEvent>();\n  protected neededSampleRate?: number;\n  protected resampler?: AudioResampler;\n  abstract label: string;\n  protected closed = false;\n  #stt: STT;\n  #failed = false;\n  private deferredInputStream: DeferredReadableStream<AudioFrame>;\n  private logger = log();\n  private _connOptions: APIConnectOptions;\n  private _startTimeOffset: number = 0;\n  private _numRetries: number = 0;\n\n  protected abortController = new AbortController();\n\n  constructor(\n    stt: STT,\n    sampleRate?: number,\n    connectionOptions: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,\n  ) {\n    this.#stt = stt;\n    this._connOptions = connectionOptions;\n    this.deferredInputStream = new DeferredReadableStream<AudioFrame>();\n    this.neededSampleRate = sampleRate;\n    this.monitorMetrics();\n    this.pumpInput();\n\n    // this is a hack to immitate asyncio.create_task so that mainTask\n    // is run **after** the constructor has finished. Otherwise we get\n    // runtime error when trying to access class variables in the\n    // `run` method.\n    const runMainTask = async () => {\n      try {\n        await this.mainTask();\n      } catch {\n        // already surfaced via emitError; swallow to avoid unhandled rejection.\n      } finally {\n        this.queue.close();\n      }\n    };\n    startSoon(() => {\n      void runMainTask();\n    });\n  }\n\n  /**\n   * Runs the STT with retry logic. Errors are emitted via {@link STT} error events\n   * and then re-thrown to end the attempt loop; the caller swallows them.\n   *\n   * @throws {APIError} When the STT request fails with a non-retryable error\n   * @throws {APIConnectionError} When all retry attempts are exhausted\n   * @internal Not annotated with Throws<> because this is fire-and-forget via startSoon()\n   */\n  private async mainTask(): Promise<void> {\n    let lastStartTime = Date.now();\n    // `_numRetries` is reset by monitorMetrics() on every FINAL_TRANSCRIPT, so the budget\n    // applies to consecutive failures rather than to the lifetime of the stream. Providers\n    // that recycle their socket on a fixed interval (e.g. Gemini Live's 10-minute session\n    // cap) would otherwise exhaust it and permanently stop recognizing on long sessions.\n    while (this._numRetries <= this._connOptions.maxRetry) {\n      try {\n        // Keep provider-relative transcript timestamps linear across reconnect attempts.\n        this._startTimeOffset += (Date.now() - lastStartTime) / 1000;\n        lastStartTime = Date.now();\n        return await this.run();\n      } catch (error) {\n        // If the stream was intentionally aborted (e.g. session shutdown), exit\n        // silently. Downstream listeners may already be detached by this point,\n        // and emitting an `error` event here would trigger ERR_UNHANDLED_ERROR\n        // in Node's EventEmitter.\n        if (this.abortController.signal.aborted) {\n          return;\n        }\n\n        if (error instanceof APIError) {\n          const retryInterval = intervalForRetry(this._connOptions, this._numRetries);\n\n          if (this._connOptions.maxRetry === 0 || !error.retryable) {\n            this.emitError({ error, recoverable: false });\n            throw error;\n          } else if (this._numRetries === this._connOptions.maxRetry) {\n            this.emitError({ error, recoverable: false });\n            throw new APIConnectionError({\n              message: `failed to recognize speech after ${this._connOptions.maxRetry + 1} attempts`,\n              options: { retryable: false },\n            });\n          } else {\n            // Don't emit error event for recoverable errors during retry loop\n            // to avoid ERR_UNHANDLED_ERROR or premature session termination\n            this.logger.warn(\n              { stt: this.#stt.label, attempt: this._numRetries + 1, error },\n              `failed to recognize speech, retrying in ${retryInterval}ms`,\n            );\n          }\n\n          if (retryInterval > 0) {\n            await delay(retryInterval);\n          }\n\n          this._numRetries += 1;\n        } else {\n          this.emitError({ error: toError(error), recoverable: false });\n          throw error;\n        }\n      }\n    }\n  }\n\n  private emitError({ error, recoverable }: { error: Error; recoverable: boolean }) {\n    if (!recoverable) this.#failed = true;\n    this.#stt.emit('error', {\n      type: 'stt_error',\n      timestamp: Date.now(),\n      label: this.#stt.label,\n      error,\n      recoverable,\n    });\n  }\n\n  protected async pumpInput() {\n    // TODO(AJS-35): Implement STT with webstreams API\n    const inputStream = this.deferredInputStream.stream;\n    const reader = inputStream.getReader();\n\n    try {\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        this.pushFrame(value);\n      }\n    } catch (error) {\n      this.logger.error('Error in STTStream mainTask:', error);\n    } finally {\n      reader.releaseLock();\n    }\n  }\n\n  protected async monitorMetrics() {\n    for await (const event of this.queue) {\n      if (!this.output.closed) {\n        try {\n          this.output.put(event);\n        } catch (e) {\n          if (e instanceof Error && e.message.includes('Queue is closed')) {\n            this.logger.warn(\n              { err: e },\n              'Queue closed during transcript processing (expected during disconnect)',\n            );\n          }\n        }\n      }\n      if (event.type === SpeechEventType.FINAL_TRANSCRIPT) {\n        // reset the retry count after a successful recognition\n        this._numRetries = 0;\n      }\n      if (event.type !== SpeechEventType.RECOGNITION_USAGE) continue;\n      const metrics: STTMetrics = {\n        type: 'stt_metrics',\n        timestamp: Date.now(),\n        requestId: event.requestId!,\n        durationMs: 0,\n        label: this.#stt.label,\n        audioDurationMs: Math.round(event.recognitionUsage!.audioDuration * 1000),\n        inputTokens: event.recognitionUsage!.inputTokens ?? 0,\n        outputTokens: event.recognitionUsage!.outputTokens ?? 0,\n        streamed: true,\n        metadata: {\n          modelProvider: this.#stt.provider,\n          modelName: this.#stt.model,\n        },\n      };\n      this.#stt.emit('metrics_collected', metrics);\n    }\n    if (!this.output.closed) {\n      this.output.close();\n    }\n  }\n\n  protected abstract run(): Promise<void>;\n\n  protected get abortSignal(): AbortSignal {\n    return this.abortController.signal;\n  }\n\n  /** Whether this stream ended with an unrecoverable error. @internal */\n  get _failed(): boolean {\n    return this.#failed;\n  }\n\n  get startTimeOffset(): number {\n    return this._startTimeOffset;\n  }\n\n  set startTimeOffset(value: number) {\n    if (value < 0) {\n      throw new Error('startTimeOffset must be non-negative');\n    }\n    this._startTimeOffset = value;\n  }\n\n  updateInputStream(audioStream: ReadableStream<AudioFrame>) {\n    this.deferredInputStream.setSource(audioStream);\n  }\n\n  detachInputStream() {\n    this.deferredInputStream.detachSource();\n  }\n\n  /** Push an audio frame to the STT */\n  pushFrame(frame: AudioFrame) {\n    if (this.input.closed) {\n      throw new Error('Input is closed');\n    }\n    if (this.closed) {\n      throw new Error('Stream is closed');\n    }\n\n    if (this.neededSampleRate && frame.sampleRate !== this.neededSampleRate) {\n      if (!this.resampler) {\n        this.resampler = new AudioResampler(frame.sampleRate, this.neededSampleRate);\n      }\n    }\n\n    if (frame.samplesPerChannel === 0) {\n      this.input.put(frame);\n      return;\n    }\n\n    if (this.resampler) {\n      const frames = this.resampler.push(frame);\n      for (const frame of frames) {\n        this.input.put(frame);\n      }\n    } else {\n      this.input.put(frame);\n    }\n  }\n\n  /** Flush the STT, causing it to process all pending text */\n  flush() {\n    if (this.input.closed) {\n      throw new Error('Input is closed');\n    }\n    if (this.closed) {\n      throw new Error('Stream is closed');\n    }\n    this.input.put(SpeechStream.FLUSH_SENTINEL);\n  }\n\n  /** Mark the input as ended and forbid additional pushes */\n  endInput() {\n    if (this.input.closed) {\n      throw new Error('Input is closed');\n    }\n    if (this.closed) {\n      throw new Error('Stream is closed');\n    }\n    this.input.close();\n  }\n\n  next(): Promise<IteratorResult<SpeechEvent>> {\n    return this.output.next();\n  }\n\n  /** Close both the input and output of the STT stream */\n  close() {\n    if (!this.input.closed) this.input.close();\n    if (!this.queue.closed) this.queue.close();\n    if (!this.output.closed) this.output.close();\n    if (!this.abortController.signal.aborted) this.abortController.abort();\n    this.resampler?.close();\n    this.closed = true;\n  }\n\n  [Symbol.asyncIterator](): SpeechStream {\n    return this;\n  }\n}\n"],"mappings":"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","names":["SpeechEventType","frame"]}