"use strict";
var import_rtc_node = require("@livekit/rtc-node");
var import_vitest = require("vitest");
var import_base = require("../inference/eot/base.cjs");
var import_chat_context = require("../llm/chat_context.cjs");
var import_log = require("../log.cjs");
var import_utils = require("../utils.cjs");
var import_vad = require("../vad.cjs");
var import_audio_recognition = require("./audio_recognition.cjs");
(0, import_log.initializeLogger)({ pretty: false, level: "silent" });
function makeHooks() {
  return {
    onInterruption: import_vitest.vi.fn(),
    onStartOfSpeech: import_vitest.vi.fn(),
    onVADInferenceDone: import_vitest.vi.fn(),
    onEndOfSpeech: import_vitest.vi.fn(),
    onInterimTranscript: import_vitest.vi.fn(),
    onFinalTranscript: import_vitest.vi.fn(),
    onEotPrediction: import_vitest.vi.fn(),
    onAgentBackchannelOpportunity: import_vitest.vi.fn(),
    onPreemptiveGeneration: import_vitest.vi.fn(),
    onUserTurnExceeded: import_vitest.vi.fn(),
    retrieveChatCtx: () => import_chat_context.ChatContext.empty(),
    onEndOfTurn: import_vitest.vi.fn(async () => false)
    // don't commit by default
  };
}
function makeRecognition(opts = {}) {
  const hooks = makeHooks();
  const full = {
    recognitionHooks: hooks,
    stt: void 0,
    vad: void 0,
    interruptionDetection: void 0,
    turnDetectionMode: "vad",
    minEndpointingDelay: 10,
    maxEndpointingDelay: 500,
    getLinkedParticipant: () => ({ sid: "p1", identity: "bob", kind: import_rtc_node.ParticipantKind.AGENT }),
    ...opts
  };
  const recognition = new import_audio_recognition.AudioRecognition(full);
  return { recognition, internals: recognition, hooks };
}
function makeAudioStream() {
  const stream = Object.create(import_base.BaseStreamingTurnDetectorStream.prototype);
  stream.supportsLanguage = import_vitest.vi.fn(async () => true);
  stream.unlikelyThreshold = import_vitest.vi.fn(async () => 0.5);
  stream.backchannelThreshold = import_vitest.vi.fn(async () => void 0);
  stream.predict = import_vitest.vi.fn(() => new import_utils.Future());
  stream.cancelInference = import_vitest.vi.fn();
  stream.flush = import_vitest.vi.fn();
  return stream;
}
function makeAudioDetector(stream) {
  const detector = Object.create(import_base.BaseStreamingTurnDetector.prototype);
  detector.stream = import_vitest.vi.fn(() => stream);
  return detector;
}
function resolvedPrediction(probability, opts = {}) {
  const event = {
    type: "eot_prediction",
    endOfTurnProbability: probability,
    lastSpeakingTimeMs: 0,
    inferenceDuration: opts.inferenceDuration,
    detectionDelay: opts.detectionDelay,
    backchannelProbability: opts.backchannelProbability
  };
  const fut = new import_utils.Future();
  fut.resolve(event);
  return { fut, event };
}
function predictMock(stream) {
  return stream.predict;
}
function cancelInferenceMock(stream) {
  return stream.cancelInference;
}
function inferenceDone(rawAccumulatedSpeech, rawAccumulatedSilence = 0) {
  return {
    type: import_vad.VADEventType.INFERENCE_DONE,
    samplesIndex: 0,
    timestamp: 0,
    speechDuration: 0,
    silenceDuration: 0,
    frames: [],
    probability: 0,
    inferenceDuration: 0,
    speaking: false,
    rawAccumulatedSilence,
    rawAccumulatedSpeech
  };
}
function startOfSpeech() {
  return {
    type: import_vad.VADEventType.START_OF_SPEECH,
    samplesIndex: 0,
    timestamp: 0,
    speechDuration: 500,
    silenceDuration: 0,
    frames: [],
    probability: 0,
    inferenceDuration: 0,
    speaking: true,
    rawAccumulatedSilence: 0,
    rawAccumulatedSpeech: 500
  };
}
function endOfSpeech() {
  return {
    type: import_vad.VADEventType.END_OF_SPEECH,
    samplesIndex: 0,
    timestamp: 0,
    speechDuration: 0,
    silenceDuration: 300,
    frames: [],
    probability: 0,
    inferenceDuration: 0,
    speaking: false,
    rawAccumulatedSilence: 300,
    rawAccumulatedSpeech: 0
  };
}
function flush() {
  return new Promise((resolve) => setImmediate(resolve));
}
function runScriptedVad(internals) {
  let resolveNext = null;
  const buffered = [];
  let closed = false;
  const stream = {
    updateInputStream(_s) {
    },
    detachInputStream() {
    },
    close() {
      closed = true;
      if (resolveNext) {
        resolveNext({ done: true, value: void 0 });
        resolveNext = null;
      }
    },
    [Symbol.asyncIterator]() {
      return {
        next() {
          if (buffered.length > 0) {
            return Promise.resolve({ done: false, value: buffered.shift() });
          }
          if (closed) {
            return Promise.resolve({ done: true, value: void 0 });
          }
          return new Promise((res) => {
            resolveNext = res;
          });
        }
      };
    }
  };
  const vad = { stream: () => stream };
  const controller = new AbortController();
  const task = internals.createVadTask(vad, controller.signal);
  return {
    async feed(ev) {
      if (resolveNext) {
        const res = resolveNext;
        resolveNext = null;
        res({ done: false, value: ev });
      } else {
        buffered.push(ev);
      }
      await flush();
      await flush();
    },
    async stop() {
      controller.abort();
      await task.catch(() => {
      });
    }
  };
}
(0, import_vitest.describe)("TestSpeakingGuardRace", () => {
  (0, import_vitest.it)("cancels the in-flight bounce when speaking starts during endpointing", async () => {
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = resolvedPrediction(0.2).fut;
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "vad");
    await new Promise((r) => setTimeout(r, 50));
    internals.userSpeakingEvent.set();
    (0, import_vitest.expect)(internals.bounceEOUTask).toBeDefined();
    await internals.bounceEOUTask.result.catch(() => {
    });
    (0, import_vitest.expect)(hooks.onEndOfTurn).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("short-circuits without spawning the bounce when already speaking", async () => {
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.speaking = true;
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "vad");
    (0, import_vitest.expect)(internals.bounceEOUTask).toBeDefined();
    await internals.bounceEOUTask.result.catch(() => {
    });
    (0, import_vitest.expect)(hooks.onEndOfTurn).not.toHaveBeenCalled();
    (0, import_vitest.expect)(predictMock(stream).mock.calls.length).toBe(0);
  });
});
(0, import_vitest.describe)("TestEotPredictionDedup", () => {
  (0, import_vitest.it)("emits onEotPrediction once across vad then stt triggers", async () => {
    var _a;
    const { internals, hooks } = makeRecognition();
    const { fut, event } = resolvedPrediction(0.2, { inferenceDuration: 50, detectionDelay: 100 });
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = fut;
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "vad");
    await flush();
    await flush();
    (0, import_vitest.expect)(hooks.onEotPrediction).toHaveBeenCalledTimes(1);
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "stt");
    await flush();
    await flush();
    (0, import_vitest.expect)(hooks.onEotPrediction).toHaveBeenCalledTimes(1);
    (0, import_vitest.expect)(internals.lastEmittedEotPrediction).toBe(event);
    await ((_a = internals.bounceEOUTask) == null ? void 0 : _a.cancelAndWait().catch(() => {
    }));
  });
  (0, import_vitest.it)("emits on every bounce for a text-based detector", async () => {
    var _a;
    const { internals, hooks } = makeRecognition();
    const textDetector = {
      model: "fake",
      provider: "fake",
      supportsLanguage: import_vitest.vi.fn(async () => true),
      unlikelyThreshold: import_vitest.vi.fn(async () => 0.5),
      predictEndOfTurn: import_vitest.vi.fn(async () => 0.2)
    };
    internals.turnDetector = textDetector;
    internals.turnDetectorStream = void 0;
    internals.audioTranscript = "hello there";
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "vad");
    await flush();
    await flush();
    (0, import_vitest.expect)(hooks.onEotPrediction).toHaveBeenCalledTimes(1);
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "stt");
    await flush();
    await flush();
    (0, import_vitest.expect)(hooks.onEotPrediction).toHaveBeenCalledTimes(2);
    await ((_a = internals.bounceEOUTask) == null ? void 0 : _a.cancelAndWait().catch(() => {
    }));
  });
  (0, import_vitest.it)("clearUserTurn resets the dedup guard so the next turn emits again", () => {
    const { internals } = makeRecognition();
    internals.lastEmittedEotPrediction = resolvedPrediction(0.2).event;
    internals.audioInterimTranscript = "";
    internals.audioPreflightTranscript = "";
    internals.sttRequestIds = [];
    internals.clearUserTurn();
    (0, import_vitest.expect)(internals.lastEmittedEotPrediction).toBeUndefined();
  });
});
(0, import_vitest.describe)("TestBackchannelOpportunityEmit", () => {
  async function drive(internals) {
    var _a;
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "vad");
    await flush();
    await flush();
    await ((_a = internals.bounceEOUTask) == null ? void 0 : _a.cancelAndWait().catch(() => {
    }));
  }
  (0, import_vitest.it)("emits with eot context when the turn continues", async () => {
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    stream.backchannelThreshold = import_vitest.vi.fn(async () => 0.5);
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = resolvedPrediction(0.2, {
      backchannelProbability: 0.8
    }).fut;
    await drive(internals);
    (0, import_vitest.expect)(hooks.onAgentBackchannelOpportunity).toHaveBeenCalledTimes(1);
    const ev = hooks.onAgentBackchannelOpportunity.mock.calls[0][0];
    (0, import_vitest.expect)(ev.probability).toBeCloseTo(0.8);
    (0, import_vitest.expect)(ev.threshold).toBeCloseTo(0.5);
    (0, import_vitest.expect)(ev.endOfTurnProbability).toBeCloseTo(0.2);
    (0, import_vitest.expect)(ev.endOfTurnThreshold).toBeCloseTo(0.5);
  });
  (0, import_vitest.it)("emits once across vad then stt triggers (shares the eot dedupe)", async () => {
    var _a;
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    stream.backchannelThreshold = import_vitest.vi.fn(async () => 0.5);
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = resolvedPrediction(0.2, {
      backchannelProbability: 0.8
    }).fut;
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "vad");
    await flush();
    await flush();
    (0, import_vitest.expect)(hooks.onAgentBackchannelOpportunity).toHaveBeenCalledTimes(1);
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "stt");
    await flush();
    await flush();
    (0, import_vitest.expect)(hooks.onAgentBackchannelOpportunity).toHaveBeenCalledTimes(1);
    await ((_a = internals.bounceEOUTask) == null ? void 0 : _a.cancelAndWait().catch(() => {
    }));
  });
  (0, import_vitest.it)("emits with eot context when the turn ends", async () => {
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    stream.backchannelThreshold = import_vitest.vi.fn(async () => 0.5);
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = resolvedPrediction(0.9, {
      backchannelProbability: 0.8
    }).fut;
    await drive(internals);
    (0, import_vitest.expect)(hooks.onAgentBackchannelOpportunity).toHaveBeenCalledTimes(1);
    const ev = hooks.onAgentBackchannelOpportunity.mock.calls[0][0];
    (0, import_vitest.expect)(ev.endOfTurnProbability).toBeCloseTo(0.9);
    (0, import_vitest.expect)(ev.endOfTurnThreshold).toBeCloseTo(0.5);
  });
  (0, import_vitest.it)("does not emit below threshold", async () => {
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    stream.backchannelThreshold = import_vitest.vi.fn(async () => 0.7);
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = resolvedPrediction(0.2, {
      backchannelProbability: 0.4
    }).fut;
    await drive(internals);
    (0, import_vitest.expect)(hooks.onAgentBackchannelOpportunity).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("does not emit when backchannel is disabled", async () => {
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = resolvedPrediction(0.2, {
      backchannelProbability: 0.9
    }).fut;
    await drive(internals);
    (0, import_vitest.expect)(hooks.onAgentBackchannelOpportunity).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("does not emit for a text-based detector", async () => {
    const { internals, hooks } = makeRecognition();
    const textDetector = {
      model: "fake",
      provider: "fake",
      supportsLanguage: import_vitest.vi.fn(async () => true),
      unlikelyThreshold: import_vitest.vi.fn(async () => 0.5),
      predictEndOfTurn: import_vitest.vi.fn(async () => 0.2)
    };
    internals.turnDetector = textDetector;
    internals.turnDetectorStream = void 0;
    internals.audioTranscript = "hello there";
    await drive(internals);
    (0, import_vitest.expect)(hooks.onAgentBackchannelOpportunity).not.toHaveBeenCalled();
  });
});
(0, import_vitest.describe)("TestPredictionFutureLifecycle", () => {
  (0, import_vitest.it)("silence tick starts a request once", async () => {
    const { internals } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.speaking = true;
    const vad = runScriptedVad(internals);
    try {
      await vad.feed(inferenceDone(0, 300));
      await vad.feed(inferenceDone(0, 400));
      (0, import_vitest.expect)(predictMock(stream).mock.calls.length).toBe(1);
      (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBeDefined();
    } finally {
      await vad.stop();
    }
  });
  (0, import_vitest.it)("resumed speech without SOS rearms the next pause", async () => {
    const { internals } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.speaking = true;
    const vad = runScriptedVad(internals);
    try {
      await vad.feed(inferenceDone(0, 300));
      const firstFut = internals.turnDetectorPredictionFut;
      (0, import_vitest.expect)(firstFut).toBeDefined();
      firstFut.resolve(resolvedPrediction(0.1).event);
      await vad.feed(inferenceDone(1, 0));
      (0, import_vitest.expect)(cancelInferenceMock(stream)).toHaveBeenCalledTimes(1);
      (0, import_vitest.expect)(cancelInferenceMock(stream)).toHaveBeenCalledWith();
      (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBeUndefined();
      await vad.feed(inferenceDone(0, 300));
      (0, import_vitest.expect)(predictMock(stream).mock.calls.length).toBe(2);
      (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBeDefined();
      (0, import_vitest.expect)(internals.turnDetectorPredictionFut).not.toBe(firstFut);
    } finally {
      await vad.stop();
    }
  });
  (0, import_vitest.it)("silence tick starts a request even while the agent is speaking", async () => {
    const { internals } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.speaking = true;
    internals.isAgentSpeaking = true;
    const vad = runScriptedVad(internals);
    try {
      await vad.feed(inferenceDone(0, 300));
      (0, import_vitest.expect)(predictMock(stream).mock.calls.length).toBe(1);
      (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBeDefined();
    } finally {
      await vad.stop();
    }
  });
  (0, import_vitest.it)("EOS consumes the silence-tick request without predicting", async () => {
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.speaking = true;
    const { fut } = resolvedPrediction(0.9);
    internals.turnDetectorPredictionFut = fut;
    const vad = runScriptedVad(internals);
    try {
      await vad.feed(endOfSpeech());
      (0, import_vitest.expect)(predictMock(stream).mock.calls.length).toBe(0);
      (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBe(fut);
      (0, import_vitest.expect)(internals.bounceEOUTask).toBeDefined();
      await internals.bounceEOUTask.result.catch(() => {
      });
      (0, import_vitest.expect)(hooks.onEotPrediction).toHaveBeenCalledTimes(1);
    } finally {
      await vad.stop();
    }
  });
  (0, import_vitest.it)("SOS tears down the request and rearms", async () => {
    const { internals } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = new import_utils.Future();
    internals.turnDetectorFlushed = true;
    const vad = runScriptedVad(internals);
    try {
      await vad.feed(startOfSpeech());
      (0, import_vitest.expect)(cancelInferenceMock(stream)).toHaveBeenCalledTimes(1);
      (0, import_vitest.expect)(cancelInferenceMock(stream)).toHaveBeenCalledWith();
      (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBeUndefined();
      (0, import_vitest.expect)(internals.turnDetectorFlushed).toBe(false);
    } finally {
      await vad.stop();
    }
  });
  (0, import_vitest.it)("EOS never starts a request", async () => {
    const { internals } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    const vad = runScriptedVad(internals);
    try {
      await vad.feed(endOfSpeech());
      (0, import_vitest.expect)(predictMock(stream).mock.calls.length).toBe(0);
      (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBeUndefined();
      const { fut } = resolvedPrediction(0.9);
      internals.turnDetectorPredictionFut = fut;
      await vad.feed(endOfSpeech());
      (0, import_vitest.expect)(predictMock(stream).mock.calls.length).toBe(0);
      (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBe(fut);
    } finally {
      await vad.stop();
    }
  });
  (0, import_vitest.it)("late stt final after flush short-circuits and warns once", async () => {
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorFlushed = true;
    for (let i = 0; i < 2; i++) {
      internals.runEOUDetection(import_chat_context.ChatContext.empty(), "stt");
      (0, import_vitest.expect)(internals.bounceEOUTask).toBeDefined();
      await internals.bounceEOUTask.result.catch(() => {
      });
    }
    (0, import_vitest.expect)(predictMock(stream).mock.calls.length).toBe(0);
    (0, import_vitest.expect)(hooks.onEotPrediction).not.toHaveBeenCalled();
    (0, import_vitest.expect)(internals.turnDetectorLatePredictionWarned).toBe(true);
  });
  (0, import_vitest.it)("predict timeout signals fallback and drops the future", async () => {
    const { internals, hooks } = makeRecognition();
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = new import_utils.Future();
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "vad");
    (0, import_vitest.expect)(internals.bounceEOUTask).toBeDefined();
    await internals.bounceEOUTask.result.catch(() => {
    });
    (0, import_vitest.expect)(cancelInferenceMock(stream)).toHaveBeenCalledTimes(1);
    (0, import_vitest.expect)(cancelInferenceMock(stream)).toHaveBeenCalledWith({ timedOut: true });
    (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBeUndefined();
    (0, import_vitest.expect)(hooks.onEotPrediction).not.toHaveBeenCalled();
    (0, import_vitest.expect)(stream.unlikelyThreshold).not.toHaveBeenCalled();
    (0, import_vitest.expect)(hooks.onEndOfTurn).toHaveBeenCalledTimes(1);
  });
  (0, import_vitest.it)("commit flushes the stream and marks the turn flushed", async () => {
    const { internals, hooks } = makeRecognition();
    hooks.onEndOfTurn.mockResolvedValue(true);
    const stream = makeAudioStream();
    internals.turnDetectorStream = stream;
    internals.turnDetector = makeAudioDetector(stream);
    internals.turnDetectorPredictionFut = resolvedPrediction(0.9).fut;
    internals.runEOUDetection(import_chat_context.ChatContext.empty(), "vad");
    (0, import_vitest.expect)(internals.bounceEOUTask).toBeDefined();
    await internals.bounceEOUTask.result.catch(() => {
    });
    (0, import_vitest.expect)(stream.flush).toHaveBeenCalledWith("turn committed");
    (0, import_vitest.expect)(internals.turnDetectorPredictionFut).toBeUndefined();
    (0, import_vitest.expect)(internals.turnDetectorFlushed).toBe(true);
  });
});
(0, import_vitest.describe)("TestVadMinSilenceRequirement", () => {
  const requiredMs = import_base.MIN_SILENCE_DURATION_MS + 50;
  const fakeVad = (minSilenceDuration) => ({ minSilenceDuration });
  (0, import_vitest.it)("raises when min silence is too low for an audio detector", () => {
    const { internals } = makeRecognition();
    internals.vad = fakeVad(requiredMs - 1);
    internals.turnDetector = makeAudioDetector(makeAudioStream());
    (0, import_vitest.expect)(() => internals.checkVadSilenceRequirement()).toThrow(/minSilenceDuration/);
  });
  (0, import_vitest.it)("passes when min silence is adequate", () => {
    const { internals } = makeRecognition();
    internals.vad = fakeVad(requiredMs + 250);
    internals.turnDetector = makeAudioDetector(makeAudioStream());
    (0, import_vitest.expect)(() => internals.checkVadSilenceRequirement()).not.toThrow();
  });
  (0, import_vitest.it)("skips validation for a non-audio detector", () => {
    const { internals } = makeRecognition();
    internals.vad = fakeVad(requiredMs - 1);
    internals.turnDetector = { model: "x", provider: "x" };
    (0, import_vitest.expect)(() => internals.checkVadSilenceRequirement()).not.toThrow();
  });
  (0, import_vitest.it)("skips validation when there is no VAD", () => {
    const { internals } = makeRecognition();
    internals.vad = void 0;
    internals.turnDetector = makeAudioDetector(makeAudioStream());
    (0, import_vitest.expect)(() => internals.checkVadSilenceRequirement()).not.toThrow();
  });
  (0, import_vitest.it)("skips validation when the VAD exposes no min-silence knob", () => {
    const { internals } = makeRecognition();
    internals.vad = fakeVad(null);
    internals.turnDetector = makeAudioDetector(makeAudioStream());
    (0, import_vitest.expect)(() => internals.checkVadSilenceRequirement()).not.toThrow();
  });
  (0, import_vitest.it)("updateTurnDetector validates the pairing before building a stream", () => {
    const { internals } = makeRecognition();
    internals.vad = fakeVad(requiredMs - 1);
    const stream = makeAudioStream();
    const detector = makeAudioDetector(stream);
    (0, import_vitest.expect)(() => internals.updateTurnDetector(detector)).toThrow(/minSilenceDuration/);
    (0, import_vitest.expect)(internals.turnDetectorStream).toBeUndefined();
    (0, import_vitest.expect)(detector.stream.mock.calls.length).toBe(0);
  });
});
//# sourceMappingURL=audio_recognition_turn_detection.test.cjs.map