"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var import_rtc_node = require("@livekit/rtc-node");
var import_web = require("node:stream/web");
var import_vitest = require("vitest");
var import_language = require("../language.cjs");
var import_tool_context = require("../llm/tool_context.cjs");
var import_log = require("../log.cjs");
var import_stt = require("../stt/stt.cjs");
var import_fake_stt = require("../stt/testing/fake_stt.cjs");
var import_vad = require("../vad.cjs");
var import_agent = require("./agent.cjs");
var import_agent_session = require("./agent_session.cjs");
var import_io = require("./io.cjs");
var import_fake_llm = require("./testing/fake_llm.cjs");
import_vitest.vi.mock("ws", async () => {
  const { EventEmitter } = await import("node:events");
  class AutoOpenWebSocket extends EventEmitter {
    static CONNECTING = 0;
    static OPEN = 1;
    static CLOSING = 2;
    static CLOSED = 3;
    readyState = AutoOpenWebSocket.CONNECTING;
    constructor(_url, _opts) {
      super();
      setTimeout(() => {
        this.readyState = AutoOpenWebSocket.OPEN;
        this.emit("open");
      }, 0);
    }
    send(_data, cb) {
      cb == null ? void 0 : cb();
    }
    ping() {
    }
    close() {
      this.readyState = AutoOpenWebSocket.CLOSED;
      this.emit("close", 1e3, Buffer.alloc(0));
    }
    terminate() {
      this.readyState = AutoOpenWebSocket.CLOSED;
      this.emit("close", 1006, Buffer.alloc(0));
    }
  }
  return { default: AutoOpenWebSocket, WebSocket: AutoOpenWebSocket };
});
const STEP_GAP_MS = 2e3;
const ENDPOINTING_MS = 500;
const STEP_INPUTS = ["step one done", "step two done", "step three done"];
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function waitFor(check, timeoutMs, label) {
  const startedAt = Date.now();
  while (!check()) {
    if (Date.now() - startedAt > timeoutMs) throw new Error(`timed out waiting for ${label}`);
    await sleep(20);
  }
}
function silenceFrame(durationMs, sampleRate = 16e3) {
  const samples = Math.floor(sampleRate * durationMs / 1e3);
  return new import_rtc_node.AudioFrame(new Int16Array(samples), sampleRate, 1, samples);
}
class DeafVADStream extends import_vad.VADStream {
  constructor(vad) {
    super(vad);
    void this.#drain();
  }
  // Drain the (tee'd) input so the shared audio pipeline doesn't block, but
  // never emit a VAD event.
  async #drain() {
    try {
      while (!this.closed) {
        const { done } = await this.inputReader.read();
        if (done) break;
      }
    } catch {
    }
  }
}
class DeafVAD extends import_vad.VAD {
  label = "deaf-vad";
  constructor() {
    super({ updateInterval: 1 });
  }
  stream() {
    return new DeafVADStream(this);
  }
}
class ScriptedAudioInput extends import_io.AudioInput {
  #controller;
  constructor() {
    super();
    const source = new import_web.ReadableStream({
      start: (controller) => {
        this.#controller = controller;
      }
    });
    this.multiStream.addInputStream(source);
  }
  push(frame) {
    this.#controller.enqueue(frame);
  }
}
(0, import_vitest.describe)("AgentTask handoff end-of-turn timing", () => {
  (0, import_log.initializeLogger)({ pretty: false, level: "silent" });
  (0, import_vitest.it)("commits end-of-turn promptly after each task handoff when VAD misses the user speech", async () => {
    const savedEnv = {
      LIVEKIT_API_KEY: process.env.LIVEKIT_API_KEY,
      LIVEKIT_API_SECRET: process.env.LIVEKIT_API_SECRET,
      LIVEKIT_INFERENCE_URL: process.env.LIVEKIT_INFERENCE_URL
    };
    process.env.LIVEKIT_API_KEY = "test-api-key";
    process.env.LIVEKIT_API_SECRET = "test-api-secret";
    process.env.LIVEKIT_INFERENCE_URL = "ws://127.0.0.1:1";
    const stt = new import_fake_stt.FakeSTT({
      label: "scripted-stt",
      capabilities: { streaming: true, interimResults: true, alignedTranscript: "word" }
    });
    const sttStreams = [];
    void (async () => {
      for await (const stream of stt.streamCh) {
        sttStreams.push({ stream, openedAt: Date.now() });
      }
    })();
    const injectFinalTranscript = (text) => {
      const opened = sttStreams[0];
      const endS = (Date.now() - opened.openedAt) / 1e3;
      opened.stream.queue.put({
        type: import_stt.SpeechEventType.FINAL_TRANSCRIPT,
        alternatives: [
          {
            text,
            language: (0, import_language.asLanguageCode)("en"),
            startTime: Math.max(0, endS - 0.8),
            endTime: endS,
            confidence: 0.95
          }
        ]
      });
    };
    const fakeLlm = new import_fake_llm.FakeLLM(
      STEP_INPUTS.map((input) => ({ input, toolCalls: [{ name: "completeStep", args: {} }] }))
    );
    const toolExecutedAt = {};
    const makeStepTask = (step) => {
      const task = new import_agent.AgentTask({
        instructions: `You are handling step ${step}. Wait for the caller to finish it.`,
        tools: {
          completeStep: (0, import_tool_context.tool)({
            description: `Record that step ${step} is complete.`,
            execute: async () => {
              toolExecutedAt[step] = Date.now();
              task.complete({ step });
            }
          })
        }
      });
      return task;
    };
    let allTasksDone = false;
    const parent = new class extends import_agent.Agent {
      async onEnter() {
        for (const step of [1, 2, 3]) {
          await makeStepTask(step).run();
        }
        allTasksDone = true;
      }
    }({ instructions: "Supervisor: delegate the caller through three steps." });
    const session = new import_agent_session.AgentSession({
      stt,
      vad: new DeafVAD(),
      llm: fakeLlm,
      turnHandling: {
        interruption: { mode: "adaptive" },
        endpointing: { minDelay: ENDPOINTING_MS, maxDelay: 3e3 }
      }
    });
    const audioInput = new ScriptedAudioInput();
    session.input.audio = audioInput;
    await session.start({ agent: parent });
    const pump = setInterval(() => audioInput.push(silenceFrame(50)), 50);
    const delays = [];
    try {
      await waitFor(() => sttStreams.length >= 1, 5e3, "STT stream to open");
      for (const [i, input] of STEP_INPUTS.entries()) {
        const step = i + 1;
        await sleep(STEP_GAP_MS);
        const injectedAt = Date.now();
        injectFinalTranscript(input);
        await waitFor(() => toolExecutedAt[step] !== void 0, 3e4, `step ${step} tool call`);
        delays.push(toolExecutedAt[step] - injectedAt);
      }
      await waitFor(() => allTasksDone, 5e3, "parent onEnter to finish");
    } finally {
      clearInterval(pump);
      await session.close().catch(() => {
      });
      for (const key of Object.keys(savedEnv)) {
        if (savedEnv[key] === void 0) delete process.env[key];
        else process.env[key] = savedEnv[key];
      }
    }
    (0, import_vitest.expect)(sttStreams.length).toBe(1);
    const [step1, step2, step3] = delays;
    (0, import_vitest.expect)(step1).toBeLessThan(STEP_GAP_MS / 2);
    (0, import_vitest.expect)(step2).toBeLessThan(STEP_GAP_MS / 2);
    (0, import_vitest.expect)(step3).toBeLessThan(STEP_GAP_MS / 2);
  }, 6e4);
});
//# sourceMappingURL=agent_task_handoff_eou.test.cjs.map