{"version":3,"sources":["../src/console.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Job, JobType, Room as RoomModel } from '@livekit/protocol';\nimport { Room } from '@livekit/rtc-node';\nimport { pathToFileURL } from 'node:url';\nimport { type Agent, isAgent } from './generator.js';\nimport type { InferenceExecutor } from './ipc/inference_executor.js';\nimport { InferenceProcExecutor } from './ipc/inference_proc_executor.js';\nimport { JobContext, JobProcess, type RunningJobInfo, runWithJobContextAsync } from './job.js';\nimport {\n  DEFAULT_SESSION_END_TIMEOUT,\n  finalizeSession,\n  flushJobLogs,\n  runShutdownCallbacks,\n  validateSessionEndTimeout,\n} from './job_lifecycle.js';\nimport { log } from './log.js';\nimport { Future, shortuuid } from './utils.js';\nimport { AgentsConsole, TcpAudioInput, TcpAudioOutput } from './voice/console_io.js';\nimport { TcpSessionTransport } from './voice/remote_session.js';\nimport { defaultInitializeProcessFunc } from './worker.js';\n\nconst formatErrorMessage = (error: unknown): string =>\n  error instanceof Error ? error.message : String(error);\n\n/**\n * Fallback executor used when no local inference runners are registered.\n * Cloud inference models (`inference.LLM` & co.) connect to the gateway\n * directly and never touch this executor; only plugins that register an\n * {@link InferenceRunner} (e.g. the livekit turn detector) do, and those get a\n * real {@link InferenceProcExecutor} in {@link runConsole} instead.\n */\nclass ConsoleInferenceExecutor implements InferenceExecutor {\n  async doInference(): Promise<unknown> {\n    throw new Error('no inference runners registered; cannot run local inference');\n  }\n}\n\nasync function loadAgent(agentPath: string): Promise<Agent> {\n  const module = await import(pathToFileURL(agentPath).href);\n  // ESM exposes the agent as `module.default`; CJS interop nests it once more.\n  const agent =\n    typeof module.default === 'function' || isAgent(module.default)\n      ? module.default\n      : module.default?.default;\n  if (agent === undefined || !isAgent(agent)) {\n    throw new Error(`Unable to load agent: missing or invalid default export in ${agentPath}`);\n  }\n  return agent;\n}\n\n/**\n * Run an agent in-process, attached to a local broker (e.g. the LiveKit CLI\n * `lk session` daemon) over a raw TCP socket. This is the JS analogue of\n * python's `_run_tcp_console`: it bypasses the websocket worker and ProcPool\n * entirely and drives the agent entrypoint on the current event loop so the\n * {@link AgentsConsole} singleton is shared with the agent's `AgentSession`.\n *\n * @experimental\n */\nexport async function runConsole({\n  agentPath,\n  connectAddr,\n  record,\n  sessionEndTimeout = DEFAULT_SESSION_END_TIMEOUT,\n}: {\n  agentPath: string;\n  connectAddr: string;\n  record: boolean;\n  sessionEndTimeout?: number;\n}): Promise<void> {\n  validateSessionEndTimeout(sessionEndTimeout);\n  const logger = log();\n\n  const sep = connectAddr.lastIndexOf(':');\n  if (sep <= 0 || sep === connectAddr.length - 1) {\n    throw new Error(`invalid --connect-addr \"${connectAddr}\", expected host:port`);\n  }\n  const host = connectAddr.slice(0, sep);\n  const portStr = connectAddr.slice(sep + 1);\n  const port = Number(portStr);\n  if (!/^\\d+$/.test(portStr) || port < 1 || port > 65535) {\n    throw new Error(`invalid port in --connect-addr \"${connectAddr}\", expected 1-65535`);\n  }\n\n  const agent = await loadAgent(agentPath);\n  const prewarm = agent.prewarm ?? defaultInitializeProcessFunc;\n\n  const transport = new TcpSessionTransport(host, port);\n  const audioInput = new TcpAudioInput();\n  const audioOutput = new TcpAudioOutput(transport);\n\n  const consoleInst = AgentsConsole.getInstance();\n  consoleInst.enabled = true;\n  consoleInst.record = record;\n  consoleInst.transport = transport;\n  consoleInst.audioInput = audioInput;\n  consoleInst.audioOutput = audioOutput;\n\n  const proc = new JobProcess();\n  await prewarm(proc);\n\n  const info: RunningJobInfo = {\n    acceptArguments: { name: '', identity: 'console', metadata: '' },\n    job: new Job({\n      id: shortuuid('console-job-'),\n      type: JobType.JT_ROOM,\n      room: new RoomModel({ name: 'console-room' }),\n      enableRecording: record,\n    }),\n    url: '',\n    token: '',\n    workerId: 'console',\n    fakeJob: true,\n  };\n\n  const room = new Room();\n  // `shutdown` resolves on a termination signal. The entrypoint typically\n  // returns right after `session.start()`, so we then block on this future to\n  // keep the session alive servicing broker requests until the process is\n  // signalled (the broker owns the session lifetime). Mirrors python, whose\n  // console blocks on `server.run()` until interrupted.\n  const shutdown = new Future();\n  const onShutdown = () => {\n    if (!shutdown.done) shutdown.resolve();\n  };\n  process.once('SIGINT', onShutdown);\n  process.once('SIGTERM', onShutdown);\n\n  logger.info({ host, port }, 'starting console session');\n\n  let inferenceProc: InferenceProcExecutor | undefined;\n  let ctx: JobContext | undefined;\n  try {\n    // Plugins that registered an InferenceRunner (e.g. the livekit turn\n    // detector) run inference in a supervised child process, same as the\n    // worker path. Without any runners the fallback executor just raises if\n    // reached.\n    let inferenceExecutor: InferenceExecutor = new ConsoleInferenceExecutor();\n    inferenceProc = InferenceProcExecutor.createIfNeeded();\n    if (inferenceProc) {\n      try {\n        await inferenceProc.start();\n        await inferenceProc.initialize();\n      } catch (error) {\n        throw new Error(\n          `the inference process failed to start (${formatErrorMessage(error)}); ` +\n            'if your agent uses a plugin with local model files (e.g. the livekit ' +\n            'turn detector), make sure they are downloaded: npx livekit-agents download-files',\n        );\n      }\n      inferenceExecutor = inferenceProc;\n    }\n\n    const jobCtx = new JobContext(proc, info, room, () => {}, onShutdown, inferenceExecutor);\n    jobCtx._simulationEndFnc = agent.onSimulationEnd;\n    ctx = jobCtx;\n    await runWithJobContextAsync(jobCtx, async () => agent.entry(jobCtx));\n    await shutdown.await;\n  } finally {\n    process.off('SIGINT', onShutdown);\n    process.off('SIGTERM', onShutdown);\n\n    // Guard every teardown step so one failure can't skip the rest — most\n    // importantly the inference child shutdown at the end.\n    const guarded = async (step: string, fn: () => Promise<unknown>) => {\n      try {\n        await fn();\n      } catch (error) {\n        logger.error({ error }, `error in ${step}`);\n      }\n    };\n\n    const jobCtx = ctx;\n    if (jobCtx) {\n      await finalizeSession(jobCtx, agent.onSessionEnd, sessionEndTimeout, logger);\n    }\n\n    try {\n      await guarded('transport.close', () => transport.close());\n      await guarded('room.disconnect', () => room.disconnect());\n\n      if (jobCtx) {\n        // Run job shutdown callbacks (e.g. AvatarSession.aclose) like the normal\n        // worker path does; runConsole bypasses the ProcPool so it must drain them.\n        await runShutdownCallbacks(jobCtx.shutdownCallbacks, logger);\n      }\n    } finally {\n      await flushJobLogs(logger);\n    }\n\n    const proc_ = inferenceProc;\n    if (proc_) {\n      await guarded('inference process close', () => proc_.close());\n    }\n  }\n}\n"],"mappings":"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","names":["module","RoomModel"]}