import { Job, JobType, Room as RoomModel } from "@livekit/protocol";
import { Room } from "@livekit/rtc-node";
import { pathToFileURL } from "node:url";
import { isAgent } from "./generator.js";
import { InferenceRunner } from "./inference_runner.js";
import { InferenceProcExecutor } from "./ipc/inference_proc_executor.js";
import { JobContext, JobProcess, runWithJobContextAsync } from "./job.js";
import { log } from "./log.js";
import { Future, shortuuid } from "./utils.js";
import { AgentsConsole, TcpAudioInput, TcpAudioOutput } from "./voice/console_io.js";
import { TcpSessionTransport } from "./voice/remote_session.js";
import { defaultInitializeProcessFunc } from "./worker.js";
const formatErrorMessage = (error) => error instanceof Error ? error.message : String(error);
class ConsoleInferenceExecutor {
  async doInference() {
    throw new Error("no inference runners registered; cannot run local inference");
  }
}
async function loadAgent(agentPath) {
  var _a;
  const module = await import(pathToFileURL(agentPath).href);
  const agent = typeof module.default === "function" || isAgent(module.default) ? module.default : (_a = module.default) == null ? void 0 : _a.default;
  if (agent === void 0 || !isAgent(agent)) {
    throw new Error(`Unable to load agent: missing or invalid default export in ${agentPath}`);
  }
  return agent;
}
async function runConsole({
  agentPath,
  connectAddr,
  record
}) {
  const logger = log();
  const sep = connectAddr.lastIndexOf(":");
  if (sep <= 0 || sep === connectAddr.length - 1) {
    throw new Error(`invalid --connect-addr "${connectAddr}", expected host:port`);
  }
  const host = connectAddr.slice(0, sep);
  const portStr = connectAddr.slice(sep + 1);
  const port = Number(portStr);
  if (!/^\d+$/.test(portStr) || port < 1 || port > 65535) {
    throw new Error(`invalid port in --connect-addr "${connectAddr}", expected 1-65535`);
  }
  const agent = await loadAgent(agentPath);
  const prewarm = agent.prewarm ?? defaultInitializeProcessFunc;
  const transport = new TcpSessionTransport(host, port);
  const audioInput = new TcpAudioInput();
  const audioOutput = new TcpAudioOutput(transport);
  const consoleInst = AgentsConsole.getInstance();
  consoleInst.enabled = true;
  consoleInst.record = record;
  consoleInst.transport = transport;
  consoleInst.audioInput = audioInput;
  consoleInst.audioOutput = audioOutput;
  const proc = new JobProcess();
  await prewarm(proc);
  const info = {
    acceptArguments: { name: "", identity: "console", metadata: "" },
    job: new Job({
      id: shortuuid("console-job-"),
      type: JobType.JT_ROOM,
      room: new RoomModel({ name: "console-room" }),
      enableRecording: record
    }),
    url: "",
    token: "",
    workerId: "console",
    fakeJob: true
  };
  const room = new Room();
  const shutdown = new Future();
  const onShutdown = () => {
    if (!shutdown.done) shutdown.resolve();
  };
  process.once("SIGINT", onShutdown);
  process.once("SIGTERM", onShutdown);
  logger.info({ host, port }, "starting console session");
  let inferenceProc;
  let ctx;
  try {
    let inferenceExecutor = new ConsoleInferenceExecutor();
    if (Object.keys(InferenceRunner.registeredRunners).length > 0) {
      inferenceProc = new InferenceProcExecutor({
        runners: InferenceRunner.registeredRunners,
        // 5 minutes, matching python: loading model files into the child can
        // be slow on first run.
        initializeTimeout: 5 * 60 * 1e3,
        closeTimeout: 5e3,
        memoryWarnMB: 2e3,
        memoryLimitMB: 0,
        pingInterval: 5e3,
        pingTimeout: 6e4,
        highPingThreshold: 2500
      });
      try {
        await inferenceProc.start();
        await inferenceProc.initialize();
      } catch (error) {
        throw new Error(
          `the inference process failed to start (${formatErrorMessage(error)}); if your agent uses a plugin with local model files (e.g. the livekit turn detector), make sure they are downloaded: npx livekit-agents download-files`
        );
      }
      inferenceExecutor = inferenceProc;
    }
    const jobCtx = new JobContext(proc, info, room, () => {
    }, onShutdown, inferenceExecutor);
    ctx = jobCtx;
    await runWithJobContextAsync(jobCtx, async () => agent.entry(jobCtx));
    await shutdown.await;
  } finally {
    process.off("SIGINT", onShutdown);
    process.off("SIGTERM", onShutdown);
    const guarded = async (step, fn) => {
      try {
        await fn();
      } catch (error) {
        logger.error({ error }, `error in ${step}`);
      }
    };
    const session = ctx == null ? void 0 : ctx._primaryAgentSession;
    if (session) {
      await guarded("AgentSession.close", () => session.close());
    }
    const jobCtx = ctx;
    if (jobCtx) {
      await guarded("ctx._onSessionEnd", () => jobCtx._onSessionEnd());
    }
    await guarded("transport.close", () => transport.close());
    await guarded("room.disconnect", () => room.disconnect());
    if (jobCtx) {
      const results = await Promise.allSettled(jobCtx.shutdownCallbacks.map((cb) => cb()));
      for (const result of results) {
        if (result.status === "rejected") {
          logger.error({ error: result.reason }, "error while running shutdown callback");
        }
      }
    }
    const proc_ = inferenceProc;
    if (proc_) {
      await guarded("inference process close", () => proc_.close());
    }
  }
}
export {
  runConsole
};
//# sourceMappingURL=console.js.map