{"version":3,"sources":["../../../src/inference/eot/base.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Audio EOT (end-of-turn) detector base, the per-window inference stream, and\n * the transport interface that concrete cloud/local backends implement.\n *\n * Concrete implementations live in `agents/src/inference/eot/`.\n */\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { AudioResampler, AudioResamplerQuality } from '@livekit/rtc-node';\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport type { LanguageCode } from '../../language.js';\nimport { log } from '../../log.js';\nimport type { EOTInferenceMetrics } from '../../metrics/base.js';\nimport { type StreamChannel, createStreamChannel } from '../../stream/stream_channel.js';\nimport { Future, Task, cancelAndWait, shortuuid } from '../../utils.js';\nimport type { ThresholdOptions, TurnDetectorModel } from './languages.js';\n\nexport const DEFAULT_SAMPLE_RATE = 16000;\nexport const MIN_SILENCE_DURATION_MS = 200;\nexport const DEFAULT_PREDICTION_TIMEOUT_MS = 1000;\n\n/**\n * Options shared by the audio EOT stream and every transport.\n *\n * Cloud-only transport concerns (base URL, credentials, conn options)\n * live on a separate options class owned by the cloud transport.\n */\nexport interface BaseStreamingTurnDetectorOptions {\n  sampleRate: number;\n  thresholds: ThresholdOptions;\n}\n\n/**\n * Event emitted on each EOT prediction.\n */\nexport interface TurnDetectionEvent {\n  type: 'eot_prediction';\n  endOfTurnProbability: number;\n  /** Wall-clock time when the prediction landed (milliseconds since epoch). */\n  lastSpeakingTimeMs: number;\n  /** Latest input-audio creation time → prediction receive time (ms). */\n  detectionDelay?: number;\n  /** Server-side model inference time (ms). */\n  inferenceDuration?: number;\n  /** How appropriate it is for the agent to backchannel at this pause.\n   * `undefined` when the detector does not produce one (e.g. the local mini model). */\n  backchannelProbability?: number;\n}\n\n/**\n * Sentinel value carried alongside flush requests. Signals a turn boundary\n * to the transport so it can clear its buffered audio.\n */\nexport interface FlushSentinel {\n  readonly kind: 'flush';\n  reason?: string;\n}\n\nexport function isFlushSentinel(value: unknown): value is FlushSentinel {\n  return typeof value === 'object' && value !== null && (value as FlushSentinel).kind === 'flush';\n}\n\n/**\n * Transport adapter for `BaseStreamingTurnDetectorStream` — owns the I/O (WebSocket\n * session, in-process predict, etc.). The stream calls these methods\n * directly; transports report predictions back via\n * `stream._resolvePrediction(requestId, probability, ...)`.\n */\nexport interface StreamingTurnDetectionTransport {\n  attach(stream: BaseStreamingTurnDetectorStream): void;\n  run(): Promise<void>;\n  runInference(requestId: string): void;\n  pushFrame(frame: AudioFrame): Promise<void>;\n  flush(sentinel: FlushSentinel): Promise<void>;\n  detach(): void;\n}\n\nexport type BaseStreamingTurnDetectorCallbacks = {\n  metrics_collected: (metrics: EOTInferenceMetrics) => void;\n};\n\n/**\n * Abstract base for audio EOT detectors. Holds the threshold table and\n * provides `stream()` to create a per-turn FSM instance.\n *\n * Subclasses (`TurnDetector` in `inference/eot/detector.ts`) wire up\n * concrete transports.\n */\nexport abstract class BaseStreamingTurnDetector extends (EventEmitter as new () => TypedEmitter<BaseStreamingTurnDetectorCallbacks>) {\n  protected _opts: BaseStreamingTurnDetectorOptions;\n  /**\n   * Active streams the detector tracks for bulk teardown via `aclose()`.\n   * `Set` rather than `WeakSet` because we need iteration; each stream\n   * removes itself on its own `aclose` (see `BaseStreamingTurnDetectorStream.aclose`)\n   * so the strong refs are released without requiring the caller to call\n   * `detector.aclose()`.\n   */\n  protected _streams: Set<BaseStreamingTurnDetectorStream> = new Set();\n\n  constructor(opts: BaseStreamingTurnDetectorOptions) {\n    super();\n    this._opts = opts;\n  }\n\n  /** @internal Stream lifecycle hook — called by the stream itself on close. */\n  _unregisterStream(stream: BaseStreamingTurnDetectorStream): void {\n    this._streams.delete(stream);\n  }\n\n  abstract get model(): TurnDetectorModel;\n\n  get provider(): string {\n    return 'livekit';\n  }\n\n  /** Most-recent materialized threshold map (after any cloud→local fallback\n   * rescale or server-default adoption). */\n  get thresholds(): Readonly<Record<string, number>> {\n    return this._opts.thresholds.thresholds;\n  }\n\n  /** Threshold below which the detector treats the prediction as \"unlikely\n   * to be end-of-turn\". Returns `undefined` when the language isn't covered. */\n  async unlikelyThreshold(language: LanguageCode | undefined): Promise<number | undefined> {\n    return this._opts.thresholds.lookup(language);\n  }\n\n  /** Threshold above which a pause is a backchannel opportunity, or `undefined`\n   * when backchannel is disabled (server sent none, or the local mini model). */\n  async backchannelThreshold(language: LanguageCode | undefined): Promise<number | undefined> {\n    return this._opts.thresholds.lookupBackchannel(language);\n  }\n\n  async supportsLanguage(language: LanguageCode | undefined): Promise<boolean> {\n    return this._opts.thresholds.supports(language);\n  }\n\n  abstract stream(): BaseStreamingTurnDetectorStream;\n\n  async aclose(): Promise<void> {\n    const streams = Array.from(this._streams);\n    this._streams.clear();\n    await Promise.allSettled(streams.map((s) => s.aclose()));\n  }\n}\n\n/**\n * Per-window inference stream. A thin transport-facing surface: per-request\n * state is one `(requestId, requestFut)` pair.\n *\n * - `predict()` starts a request and returns its future, superseding any\n *   previous request.\n * - the transport's single prediction completes the request by resolving the\n *   future via `_resolvePrediction`.\n * - `cancelInference()` / `flush(reason)` close a pending request, resolving\n *   its future with a default event so waiters never hang.\n *\n * All policy (when to start a request, await timeout, turn commits) lives in\n * `AudioRecognition`.\n */\nexport class SwapAbortError extends Error {\n  constructor() {\n    super('__swap__');\n    this.name = 'SwapAbortError';\n  }\n}\n\nexport class BaseStreamingTurnDetectorStream {\n  protected _detector: BaseStreamingTurnDetector;\n  protected _opts: BaseStreamingTurnDetectorOptions;\n  protected _transport: StreamingTurnDetectionTransport;\n\n  private _audioInputSampleRate: number | undefined;\n  private _audioInputNumChannels: number | undefined;\n  private _audioResampler: AudioResampler | undefined;\n  private _audioChannel: StreamChannel<AudioFrame | FlushSentinel> = createStreamChannel();\n\n  /** Id of the in-flight inference request, or `undefined` when idle. */\n  protected _requestId: string | undefined;\n  /** Future for the in-flight request; resolves to the prediction event (or\n   * a default event when the request is cancelled / flushed). */\n  protected _requestFut: Future<TurnDetectionEvent> | undefined;\n\n  protected _mainTask: Task<void>;\n  protected _logger = log();\n  /**\n   * Aborted whenever the main loop needs to retry on a new transport (e.g.\n   * fallback). The base FSM also aborts it from `aclose()` so idle\n   * transports that are awaiting forever can be unstuck. Listeners check\n   * `signal.aborted` and surface a sentinel rejection so the `_run` loop\n   * can decide whether to continue or exit.\n   */\n  protected _swapController = new AbortController();\n\n  constructor(args: {\n    detector: BaseStreamingTurnDetector;\n    opts: BaseStreamingTurnDetectorOptions;\n    transport: StreamingTurnDetectionTransport;\n  }) {\n    this._detector = args.detector;\n    this._opts = args.opts;\n    this._transport = args.transport;\n    this._transport.attach(this);\n\n    this._mainTask = Task.from((controller) => this._mainTaskBody(controller));\n  }\n\n  // region: _TurnDetector protocol proxies\n\n  get model(): TurnDetectorModel {\n    return this._detector.model;\n  }\n\n  get provider(): string {\n    return this._detector.provider;\n  }\n\n  /** @internal Shared threshold resolver — the cloud transport reads it to\n   * adopt the server-sent defaults from `SessionCreated`. */\n  get thresholdsOptions(): ThresholdOptions {\n    return this._opts.thresholds;\n  }\n\n  async unlikelyThreshold(language: LanguageCode | undefined): Promise<number | undefined> {\n    return this._opts.thresholds.lookup(language);\n  }\n\n  async backchannelThreshold(language: LanguageCode | undefined): Promise<number | undefined> {\n    return this._opts.thresholds.lookupBackchannel(language);\n  }\n\n  async supportsLanguage(language: LanguageCode | undefined): Promise<boolean> {\n    return this._opts.thresholds.supports(language);\n  }\n\n  get predictionTimeout(): number {\n    return DEFAULT_PREDICTION_TIMEOUT_MS;\n  }\n\n  // endregion\n\n  // region: inference requests\n\n  /** Start a new inference request and return its future, superseding any\n   * previous request. */\n  predict(): Future<TurnDetectionEvent> {\n    if (this._audioChannel.closed) {\n      const fut = new Future<TurnDetectionEvent>();\n      fut.resolve(BaseStreamingTurnDetectorStream._defaultEvent(1.0));\n      return fut;\n    }\n\n    this.cancelInference();\n    const fut = new Future<TurnDetectionEvent>();\n    this._requestId = shortuuid('turn_request_');\n    this._requestFut = fut;\n    // A transport may resolve synchronously (e.g. the local no-executor path\n    // defaults to 1.0 inline), which clears `_requestFut` via\n    // `_resolvePrediction`. Hold a local reference so we still return the\n    // resolved future rather than `undefined`.\n    this._transport.runInference(this._requestId);\n    return fut;\n  }\n\n  /** Close the current inference request (new speech, turn boundary,\n   * prediction timeout, mode change) and fall back if needed. */\n  cancelInference(opts: { timedOut?: boolean } = {}): void {\n    if (this._requestId !== undefined) {\n      const fut = this._requestFut;\n      this._requestId = undefined;\n      this._requestFut = undefined;\n      if (fut !== undefined && !fut.done) {\n        fut.resolve(BaseStreamingTurnDetectorStream._defaultEvent(0.0));\n      }\n    }\n\n    // trigger fallback immediately (the subclass timeout hook checks the\n    // model + signals the transport swap; the base hook is a no-op).\n    if (opts.timedOut) {\n      this._onPredictTimeout();\n    }\n  }\n\n  flush(reason?: string): void {\n    // Idempotent: a second call sends another sentinel that transports\n    // treat as a no-op (cloud: redundant session_flush; local: empty trim).\n    if (this._audioChannel.closed) {\n      return;\n    }\n    for (const resampled of this._flushAudioResampler()) {\n      void this._audioChannel.write(resampled);\n    }\n    const sentinel: FlushSentinel = {\n      kind: 'flush',\n      reason,\n    };\n    void this._audioChannel.write(sentinel);\n    this.cancelInference();\n  }\n\n  protected static _defaultEvent(probability: number): TurnDetectionEvent {\n    return {\n      type: 'eot_prediction',\n      endOfTurnProbability: probability,\n      lastSpeakingTimeMs: Date.now(),\n    };\n  }\n\n  // endregion\n\n  // region: audio ingress\n\n  pushAudio(frame: AudioFrame): void {\n    if (this._audioChannel.closed) {\n      return;\n    }\n    for (const resampled of this._resampleAudioFrame(frame)) {\n      void this._audioChannel.write(resampled);\n    }\n  }\n\n  endInput(): void {\n    this.flush();\n    void this._audioChannel.close();\n  }\n\n  private _resampleAudioFrame(frame: AudioFrame): AudioFrame[] {\n    if (this._audioInputSampleRate === undefined || this._audioInputNumChannels === undefined) {\n      this._audioInputSampleRate = frame.sampleRate;\n      this._audioInputNumChannels = frame.channels;\n      if (this._audioInputSampleRate !== this._opts.sampleRate) {\n        this._audioResampler = new AudioResampler(\n          this._audioInputSampleRate,\n          this._opts.sampleRate,\n          this._audioInputNumChannels,\n          AudioResamplerQuality.QUICK,\n        );\n      }\n    } else if (\n      frame.sampleRate !== this._audioInputSampleRate ||\n      frame.channels !== this._audioInputNumChannels\n    ) {\n      this._logger.error(\n        {\n          sampleRate: frame.sampleRate,\n          expectedSampleRate: this._audioInputSampleRate,\n          numChannels: frame.channels,\n          expectedNumChannels: this._audioInputNumChannels,\n        },\n        'a frame with different audio format was already pushed',\n      );\n      return [];\n    }\n    if (this._audioResampler === undefined) {\n      return [frame];\n    }\n    return this._audioResampler.push(frame);\n  }\n\n  private _flushAudioResampler(): AudioFrame[] {\n    const frames = this._audioResampler?.flush() ?? [];\n    this._resetAudioResampler();\n    return frames;\n  }\n\n  private _resetAudioResampler(): void {\n    this._audioResampler = undefined;\n    this._audioInputSampleRate = undefined;\n    this._audioInputNumChannels = undefined;\n  }\n\n  // endregion\n\n  // region: results\n\n  /**\n   * Accept a prediction from a transport. A stale response (request id\n   * mismatch) is ignored; otherwise the in-flight future resolves with the\n   * full `TurnDetectionEvent` and the request completes.\n   */\n  _resolvePrediction(\n    requestId: string,\n    probability: number,\n    opts: {\n      inferenceDuration?: number;\n      detectionDelay?: number;\n      backchannelProbability?: number;\n    } = {},\n  ): void {\n    // Drop predictions that land after teardown — an in-flight transport\n    // predict can resolve after `aclose` closed the channels.\n    if (this._closing) {\n      return;\n    }\n    if (requestId !== this._requestId) {\n      return;\n    }\n    const fut = this._requestFut;\n    this._requestId = undefined;\n    this._requestFut = undefined;\n    if (fut !== undefined && !fut.done) {\n      fut.resolve({\n        type: 'eot_prediction',\n        endOfTurnProbability: probability,\n        lastSpeakingTimeMs: Date.now(),\n        detectionDelay: opts.detectionDelay,\n        inferenceDuration: opts.inferenceDuration,\n        backchannelProbability: opts.backchannelProbability,\n      });\n    }\n  }\n\n  // endregion\n\n  // region: teardown\n\n  /**\n   * Synchronously release this stream's registration on its owning detector,\n   * so a replacement stream can be created before this one's async teardown\n   * finishes. Base is a no-op; detectors that enforce single-stream ownership\n   * override it. Idempotent.\n   */\n  detach(): void {\n    return;\n  }\n\n  async aclose(): Promise<void> {\n    this.endInput(); // the flush inside closes the in-flight request\n    this._closing = true;\n    this._swapController.abort();\n    await cancelAndWait([this._mainTask]);\n    this.cancelInference(); // defensive, normally a no-op\n    // Drop our strong reference on the parent detector so callers that\n    // forget `detector.aclose()` don't leak the stream graph.\n    this._detector._unregisterStream(this);\n  }\n\n  /** True once `aclose()` has been called. The `_run` loop uses this to\n   * distinguish swap-aborts (continue with new transport) from teardown\n   * aborts (exit). */\n  protected _closing = false;\n\n  // endregion\n\n  // region: main task scaffolding\n\n  private async _mainTaskBody(_controller: AbortController): Promise<void> {\n    await this._run();\n  }\n\n  /**\n   * Drain the shared audio channel into the current transport.\n   *\n   * The audio channel exposes a single `ReadableStream` (one underlying\n   * `transform.readable`), so only one reader may hold its lock at a time.\n   * When `signal` aborts (a transport being swapped out — e.g. cloud→local\n   * fallback — fires it via `detach()`), we release the reader lock right\n   * away: on a pending `read()` this rejects that read and frees the lock so\n   * the swapped-in transport's `_drainAudioChannel` can re-acquire it.\n   * Without this an orphaned drain would hold the lock forever and the next\n   * `getReader()` would throw \"ReadableStream is locked\".\n   */\n  async _drainAudioChannel(signal?: AbortSignal): Promise<void> {\n    const stream = this._audioChannel.stream();\n    const reader = stream.getReader();\n    const release = () => {\n      try {\n        reader.releaseLock();\n      } catch {\n        // already released\n      }\n    };\n    if (signal?.aborted) {\n      release();\n      return;\n    }\n    signal?.addEventListener('abort', release, { once: true });\n    try {\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) return;\n        if (isFlushSentinel(value)) {\n          await this._transport.flush(value);\n        } else {\n          await this._transport.pushFrame(value);\n        }\n      }\n    } catch (err) {\n      // The pending `read()` rejects when `release()` runs on abort — a clean\n      // swap-driven exit, not a drain failure.\n      if (signal?.aborted) return;\n      throw err;\n    } finally {\n      signal?.removeEventListener('abort', release);\n      release();\n    }\n  }\n\n  // endregion\n\n  // region: subclass hooks\n\n  /** Default: hand control to the transport. Subclasses override for\n   * cross-transport orchestration (e.g. cloud→local fallback). */\n  protected async _run(): Promise<void> {\n    await this._raceWithSwap(this._transport.run());\n  }\n\n  /**\n   * Race `inner` against `_swapController.signal`. If the signal aborts\n   * while `inner` is still pending, throw a `SwapAbortError` so the\n   * subclass loop can decide whether to continue or exit. Resets the\n   * controller after a swap-abort so subsequent races have a fresh signal.\n   *\n   * `aclose()` aborts during teardown — subclasses observe `_closing` to\n   * exit cleanly instead of looping.\n   */\n  protected async _raceWithSwap<T>(inner: Promise<T>): Promise<T> {\n    const signal = this._swapController.signal;\n    let onAbort: (() => void) | undefined;\n    const abortPromise = new Promise<never>((_, reject) => {\n      if (signal.aborted) {\n        reject(new SwapAbortError());\n        return;\n      }\n      onAbort = () => reject(new SwapAbortError());\n      signal.addEventListener('abort', onAbort, { once: true });\n    });\n    // If `inner` wins the race, the abort listener would otherwise stay\n    // registered and reject this now-orphaned promise when `aclose()` later\n    // aborts the controller — surfacing as an unhandledRejection. Swallow it\n    // (the race result is already settled) and remove the listener below.\n    abortPromise.catch(() => {});\n    try {\n      return await Promise.race([inner, abortPromise]);\n    } finally {\n      if (onAbort !== undefined) {\n        signal.removeEventListener('abort', onAbort);\n      }\n      if (signal.aborted) {\n        // Reset for the next iteration of the subclass loop.\n        this._swapController = new AbortController();\n      }\n    }\n  }\n\n  /** @internal Wake up an idle transport so the main loop can pick up a\n   * new one after fallback. Subclasses call this from their swap logic. */\n  protected _signalSwap(): void {\n    this._swapController.abort();\n  }\n\n  /** `predictEndOfTurn` timed out. Subclasses may override to react (e.g.\n   * promote local on cloud timeout). */\n  protected _onPredictTimeout(): void {\n    return;\n  }\n\n  // endregion\n}\n"],"mappings":"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","names":["fut"]}