{"version":3,"sources":["../../../src/inference/eot/languages.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Per-language `unlikely` thresholds for the mini detector.\n *\n * The cloud `turn-detector-v1` model receives calibrated defaults from the\n * inference gateway (via the `SessionCreated` message); only the local\n * `turn-detector-v1-mini` model ships a hardcoded table here.\n */\nimport { APIError } from '../../_exceptions.js';\nimport type { LanguageCode } from '../../language.js';\n\n/** Full model name (used for telemetry/billing via `detector.model`). */\nexport type TurnDetectorModel = 'turn-detector-v1' | 'turn-detector-v1-mini';\n\n/** Public `version` constructor argument; maps to a {@link TurnDetectorModel}. */\nexport type TurnDetectorVersion = 'v1' | 'v1-mini';\n\nexport const LOCAL_LANGUAGES: Readonly<Record<string, number>> = {\n  ar: 0.35,\n  de: 0.245,\n  en: 0.36,\n  es: 0.35,\n  fr: 0.285,\n  hi: 0.305,\n  id: 0.345,\n  it: 0.23,\n  ja: 0.295,\n  ko: 0.4,\n  nl: 0.2,\n  pt: 0.32,\n  tr: 0.255,\n  zh: 0.355,\n};\n\n/**\n * BCP-47 language tag (or human-readable name) → ISO 639-1 two-letter code.\n *\n * Covers the languages present in the threshold tables. Unknown inputs are\n * returned lowercased and unchanged (callers should pass `en`, `en-US`,\n * `English`, etc.).\n */\nfunction normalizeLanguage(input: string): string {\n  const lower = input.toLowerCase().trim();\n  if (lower.length === 2) return lower;\n  const dashIdx = lower.indexOf('-');\n  if (dashIdx === 2) return lower.slice(0, 2);\n  // long-name aliases for languages in our tables\n  const aliases: Record<string, string> = {\n    arabic: 'ar',\n    german: 'de',\n    english: 'en',\n    spanish: 'es',\n    french: 'fr',\n    hindi: 'hi',\n    indonesian: 'id',\n    italian: 'it',\n    japanese: 'ja',\n    korean: 'ko',\n    dutch: 'nl',\n    portuguese: 'pt',\n    turkish: 'tr',\n    chinese: 'zh',\n    mandarin: 'zh',\n  };\n  return aliases[lower] ?? lower;\n}\n\nconst round4 = (value: number): number => Math.round(value * 1e4) / 1e4;\n\n/**\n * User-supplied threshold override: a single value applied to every language,\n * a per-language map, or `undefined` (use the defaults).\n */\nexport type ThresholdOverride = number | Record<string, number> | undefined;\n\nfunction normalizeOverrides(overrides: ThresholdOverride): ThresholdOverride {\n  if (overrides === undefined || typeof overrides !== 'object') {\n    return overrides;\n  }\n  const out: Record<string, number> = {};\n  for (const [k, v] of Object.entries(overrides)) {\n    out[normalizeLanguage(k)] = Number(v);\n  }\n  return out;\n}\n\n/**\n * Resolves per-language `unlikely` thresholds for the audio EOT detector.\n *\n * Holds three layers and re-materializes the effective map whenever any of\n * them changes:\n *\n * - **overrides** — what the user passed (`unlikelyThreshold`), normalized.\n * - **server/shipped defaults** — for `turn-detector-v1-mini` these are the\n *   shipped `LOCAL_LANGUAGES` table; for the cloud `turn-detector-v1` they arrive\n *   from the gateway via `_updateDefaults` (the `SessionCreated` message) and\n *   are `undefined` until then.\n * - **materialized** — `thresholds` (per-language map) + `defaultThreshold`\n *   (catch-all for languages absent from the map).\n *\n * The detector and its (single) active stream share one instance; the\n * cloud→local fallback mutates it in place via `_toLocalFallback`.\n */\nexport class ThresholdOptions {\n  private _model: TurnDetectorModel;\n  private _overrides: ThresholdOverride;\n  private _bcOverrides: ThresholdOverride;\n\n  // server/shipped defaults\n  private _serverThresholds: Record<string, number> | undefined;\n  private _serverDefault: number | undefined;\n\n  // backchannel server defaults: cloud-only (the local mini model produces no\n  // backchannel probability), arrive via `SessionCreated`.\n  private _serverBcThresholds: Record<string, number> | undefined;\n  private _serverBcDefault: number | undefined;\n\n  // materialized values (server defaults layered with user overrides)\n  private _thresholds: Record<string, number> = {};\n  private _default: number | undefined = undefined;\n  private _bcThresholds: Record<string, number> = {};\n  private _bcDefault: number | undefined = undefined;\n\n  constructor(\n    model: TurnDetectorModel,\n    overrides: ThresholdOverride = undefined,\n    backchannelOverrides: ThresholdOverride = undefined,\n  ) {\n    this._model = model;\n    this._overrides = normalizeOverrides(overrides);\n    this._bcOverrides = normalizeOverrides(backchannelOverrides);\n    if (model === 'turn-detector-v1-mini') {\n      this._serverThresholds = { ...LOCAL_LANGUAGES };\n      this._serverDefault = LOCAL_LANGUAGES.en;\n    }\n    this._resolve();\n  }\n\n  get model(): TurnDetectorModel {\n    return this._model;\n  }\n\n  get overrides(): ThresholdOverride {\n    return this._overrides;\n  }\n\n  get backchannelOverrides(): ThresholdOverride {\n    return this._bcOverrides;\n  }\n\n  get thresholds(): Readonly<Record<string, number>> {\n    return this._thresholds;\n  }\n\n  get defaultThreshold(): number | undefined {\n    return this._default;\n  }\n\n  /** Clean config snapshot: resolved thresholds and user overrides, without internal server state. */\n  toJSON(): Record<string, unknown> {\n    return {\n      model: this._model,\n      thresholds: this._thresholds,\n      defaultThreshold: this._default,\n      overrides: this._overrides,\n      backchannelOverrides: this._bcOverrides,\n    };\n  }\n\n  lookup(language: LanguageCode | string | undefined): number | undefined {\n    const key = language ? normalizeLanguage(language) : 'en';\n    // `key in map`, not `?? default` — a legitimate override of 0 must not\n    // fall through to the catch-all default.\n    return key in this._thresholds ? this._thresholds[key] : this._default;\n  }\n\n  /**\n   * Backchannel threshold for a language, or `undefined` when backchannel is\n   * disabled — no server defaults / overrides resolved, or the resolved value is\n   * non-positive (an explicit \"off\"). Backchannel is server-driven and cloud-only.\n   */\n  lookupBackchannel(language: LanguageCode | string | undefined): number | undefined {\n    if (Object.keys(this._bcThresholds).length === 0 && this._bcDefault === undefined) {\n      return undefined;\n    }\n    const key = language ? normalizeLanguage(language) : 'en';\n    const threshold = key in this._bcThresholds ? this._bcThresholds[key] : this._bcDefault;\n    return threshold !== undefined && threshold > 0 ? threshold : undefined;\n  }\n\n  supports(language: LanguageCode | string | undefined): boolean {\n    // A cloud detector reports every language as supported until its server\n    // defaults arrive, so the first turn (before `SessionCreated`) isn't\n    // skipped by the `audio_recognition` short-circuit.\n    const pending = this._model === 'turn-detector-v1' && this._serverThresholds === undefined;\n    return pending || this.lookup(language) !== undefined;\n  }\n\n  updateOverrides(overrides: ThresholdOverride): void {\n    this._overrides = normalizeOverrides(overrides);\n    this._resolve();\n  }\n\n  updateBackchannelOverrides(overrides: ThresholdOverride): void {\n    this._bcOverrides = normalizeOverrides(overrides);\n    this._resolve();\n  }\n\n  /**\n   * @internal Adopt the calibrated defaults a `turn-detector` gateway sends in\n   * `SessionCreated`. Raises (non-retryable) when the server produced no usable\n   * thresholds — the caller degrades the session to the local model.\n   */\n  _updateDefaults(\n    serverThresholds: Record<string, number>,\n    serverDefault: number,\n    backchannelThresholds?: Record<string, number>,\n    backchannelDefault = 0,\n  ): void {\n    if (!serverThresholds || Object.keys(serverThresholds).length === 0 || serverDefault <= 0) {\n      throw new APIError('turn detector session created without usable default thresholds', {\n        retryable: false,\n      });\n    }\n    const norm: Record<string, number> = {};\n    for (const [lang, value] of Object.entries(serverThresholds)) {\n      norm[normalizeLanguage(lang)] = round4(value);\n    }\n    this._serverThresholds = norm;\n    this._serverDefault = round4(serverDefault);\n\n    // backchannel defaults are optional; an absent/empty map keeps backchannel disabled\n    if (backchannelThresholds && Object.keys(backchannelThresholds).length > 0) {\n      const bcNorm: Record<string, number> = {};\n      for (const [lang, value] of Object.entries(backchannelThresholds)) {\n        bcNorm[normalizeLanguage(lang)] = round4(value);\n      }\n      this._serverBcThresholds = bcNorm;\n    } else {\n      this._serverBcThresholds = undefined;\n    }\n    this._serverBcDefault = backchannelDefault > 0 ? round4(backchannelDefault) : undefined;\n\n    this._resolve();\n  }\n\n  /**\n   * @internal Promote to the local mini model on cloud→local fallback,\n   * preserving the user's effective-vs-default ratio per language:\n   * `local = LOCAL[lang] * (effective_t / server[lang])`.\n   */\n  _toLocalFallback(): void {\n    if (this._model === 'turn-detector-v1-mini') {\n      return;\n    }\n\n    let rescaled: Record<string, number> | undefined;\n    const server = this._serverThresholds;\n    if (server) {\n      rescaled = {};\n      for (const lang of Object.keys(server)) {\n        const activeT = this.lookup(lang);\n        const local = LOCAL_LANGUAGES[lang];\n        if (activeT !== undefined && local !== undefined && server[lang] !== 0) {\n          rescaled[lang] = local * (activeT / server[lang]!);\n        }\n      }\n    }\n\n    this._model = 'turn-detector-v1-mini';\n    this._serverThresholds = { ...LOCAL_LANGUAGES };\n    this._serverDefault = LOCAL_LANGUAGES.en;\n    // the mini model produces no backchannel probability\n    this._serverBcThresholds = undefined;\n    this._serverBcDefault = undefined;\n    this._resolve();\n\n    if (rescaled !== undefined) {\n      this._thresholds = rescaled;\n      this._default = this.lookup('en');\n    }\n  }\n\n  private _resolve(): void {\n    [this._thresholds, this._default] = ThresholdOptions._resolveLayer(\n      this._serverThresholds,\n      this._serverDefault,\n      this._overrides,\n    );\n    [this._bcThresholds, this._bcDefault] = ThresholdOptions._resolveLayer(\n      this._serverBcThresholds,\n      this._serverBcDefault,\n      this._bcOverrides,\n    );\n  }\n\n  /**\n   * Layer a user override onto the server defaults. A scalar override replaces\n   * the whole map (every language resolves through it); a dict override is\n   * merged over the server map. Before server defaults arrive, only a scalar\n   * override resolves up front.\n   */\n  private static _resolveLayer(\n    serverThresholds: Record<string, number> | undefined,\n    serverDefault: number | undefined,\n    overrides: ThresholdOverride,\n  ): [Record<string, number>, number | undefined] {\n    const scalarOverride = typeof overrides === 'number';\n    if (serverThresholds === undefined || serverDefault === undefined) {\n      // cloud defaults not received yet; only a scalar override resolves up front\n      return [{}, scalarOverride ? (overrides as number) : undefined];\n    }\n\n    if (overrides === undefined) {\n      return [{ ...serverThresholds }, serverDefault];\n    }\n\n    if (scalarOverride) {\n      return [{}, overrides as number];\n    }\n\n    return [{ ...serverThresholds, ...(overrides as Record<string, number>) }, serverDefault];\n  }\n}\n"],"mappings":"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","names":[]}