@livekit/agents 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/ipc/job_proc_lazy_main.cjs +1 -2
  2. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  3. package/dist/ipc/job_proc_lazy_main.js +2 -3
  4. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  5. package/dist/ipc/proc_pool.cjs +14 -2
  6. package/dist/ipc/proc_pool.cjs.map +1 -1
  7. package/dist/ipc/proc_pool.d.ts.map +1 -1
  8. package/dist/ipc/proc_pool.js +14 -2
  9. package/dist/ipc/proc_pool.js.map +1 -1
  10. package/dist/ipc/supervised_proc.cjs +32 -10
  11. package/dist/ipc/supervised_proc.cjs.map +1 -1
  12. package/dist/ipc/supervised_proc.d.cts +2 -0
  13. package/dist/ipc/supervised_proc.d.ts +2 -0
  14. package/dist/ipc/supervised_proc.d.ts.map +1 -1
  15. package/dist/ipc/supervised_proc.js +22 -10
  16. package/dist/ipc/supervised_proc.js.map +1 -1
  17. package/dist/llm/llm.cjs +4 -1
  18. package/dist/llm/llm.cjs.map +1 -1
  19. package/dist/llm/llm.d.ts.map +1 -1
  20. package/dist/llm/llm.js +4 -1
  21. package/dist/llm/llm.js.map +1 -1
  22. package/dist/vad.cjs +3 -0
  23. package/dist/vad.cjs.map +1 -1
  24. package/dist/vad.d.ts.map +1 -1
  25. package/dist/vad.js +3 -0
  26. package/dist/vad.js.map +1 -1
  27. package/dist/voice/audio_recognition.cjs +3 -0
  28. package/dist/voice/audio_recognition.cjs.map +1 -1
  29. package/dist/voice/audio_recognition.d.ts.map +1 -1
  30. package/dist/voice/audio_recognition.js +3 -0
  31. package/dist/voice/audio_recognition.js.map +1 -1
  32. package/dist/worker.cjs +25 -4
  33. package/dist/worker.cjs.map +1 -1
  34. package/dist/worker.d.ts.map +1 -1
  35. package/dist/worker.js +25 -4
  36. package/dist/worker.js.map +1 -1
  37. package/package.json +4 -2
  38. package/src/ipc/job_proc_lazy_main.ts +2 -3
  39. package/src/ipc/proc_pool.ts +14 -2
  40. package/src/ipc/supervised_proc.ts +23 -10
  41. package/src/llm/llm.ts +4 -2
  42. package/src/vad.ts +3 -0
  43. package/src/voice/audio_recognition.ts +5 -0
  44. package/src/worker.ts +25 -4
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  var import_rtc_node = require("@livekit/rtc-node");
3
- var import_node_crypto = require("node:crypto");
4
3
  var import_node_events = require("node:events");
5
4
  var import_node_url = require("node:url");
6
5
  var import_generator = require("../generator.cjs");
@@ -35,7 +34,7 @@ class InfClient {
35
34
  });
36
35
  }
37
36
  async doInference(method, data) {
38
- const requestId = "inference_job_" + import_node_crypto.randomUUID;
37
+ const requestId = (0, import_utils.shortuuid)("inference_job_");
39
38
  process.send({ case: "inferenceRequest", value: { requestId, method, data } });
40
39
  this.#requests[requestId] = new PendingInference();
41
40
  const resp = await this.#requests[requestId].promise;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ipc/job_proc_lazy_main.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Room, RoomEvent } from '@livekit/rtc-node';\nimport { randomUUID } from 'node:crypto';\nimport { EventEmitter, once } from 'node:events';\nimport { pathToFileURL } from 'node:url';\nimport type { Logger } from 'pino';\nimport { type Agent, isAgent } from '../generator.js';\nimport { CurrentJobContext, JobContext, JobProcess, type RunningJobInfo } from '../job.js';\nimport { initializeLogger, log } from '../log.js';\nimport { Future } from '../utils.js';\nimport { defaultInitializeProcessFunc } from '../worker.js';\nimport type { InferenceExecutor } from './inference_executor.js';\nimport type { IPCMessage } from './message.js';\n\nconst ORPHANED_TIMEOUT = 15 * 1000;\n\ntype JobTask = {\n ctx: JobContext;\n task: Promise<void>;\n};\n\nclass PendingInference {\n promise = new Promise<{ requestId: string; data: unknown; error?: Error }>((resolve) => {\n this.resolve = resolve; // this is how JavaScript lets you resolve promises externally\n });\n resolve(arg: { requestId: string; data: unknown; error?: Error }) {\n arg; // useless call to counteract TypeScript E6133\n }\n}\n\nclass InfClient implements InferenceExecutor {\n #requests: { [id: string]: PendingInference } = {};\n\n constructor() {\n process.on('message', (msg: IPCMessage) => {\n switch (msg.case) {\n case 'inferenceResponse':\n const fut = this.#requests[msg.value.requestId];\n delete this.#requests[msg.value.requestId];\n if (!fut) {\n log().child({ resp: msg.value }).warn('received unexpected inference response');\n return;\n }\n fut.resolve(msg.value);\n break;\n }\n });\n }\n\n async doInference(method: string, data: unknown): Promise<unknown> {\n const requestId = 'inference_job_' + randomUUID;\n process.send!({ case: 'inferenceRequest', value: { requestId, method, data } });\n this.#requests[requestId] = new PendingInference();\n const resp = await this.#requests[requestId]!.promise;\n if (resp.error) {\n throw new Error(`inference of ${method} failed: ${resp.error.message}`);\n }\n return resp.data;\n }\n}\n\nconst startJob = (\n proc: JobProcess,\n func: (ctx: JobContext) => Promise<void>,\n info: RunningJobInfo,\n closeEvent: EventEmitter,\n logger: Logger,\n joinFuture: Future,\n): JobTask => {\n let connect = false;\n let shutdown = false;\n\n const room = new Room();\n room.on(RoomEvent.Disconnected, () => {\n if (!shutdown) {\n closeEvent.emit('close', false);\n }\n });\n\n const onConnect = () => {\n connect = true;\n };\n const onShutdown = (reason: string) => {\n shutdown = true;\n closeEvent.emit('close', true, reason);\n };\n\n const ctx = new JobContext(proc, info, room, onConnect, onShutdown, new InfClient());\n new CurrentJobContext(ctx);\n\n const task = new Promise<void>(async () => {\n const unconnectedTimeout = setTimeout(() => {\n if (!(connect || shutdown)) {\n logger.warn(\n 'room not connect after job_entry was called after 10 seconds, ',\n 'did you forget to call ctx.connect()?',\n );\n }\n }, 10000);\n func(ctx).finally(() => clearTimeout(unconnectedTimeout));\n\n await once(closeEvent, 'close').then((close) => {\n logger.debug('shutting down');\n shutdown = true;\n process.send!({ case: 'exiting', value: { reason: close[1] } });\n });\n\n await room.disconnect();\n logger.debug('disconnected from room');\n\n const shutdownTasks = [];\n for (const callback of ctx.shutdownCallbacks) {\n shutdownTasks.push(callback());\n }\n await Promise.all(shutdownTasks).catch((error) =>\n logger.error('error while shutting down the job', error),\n );\n\n process.send!({ case: 'done' });\n joinFuture.resolve();\n });\n\n return { ctx, task };\n};\n\n(async () => {\n if (process.send) {\n const join = new Future();\n\n // process.argv:\n // [0] `node'\n // [1] import.meta.filename\n // [2] import.meta.filename of function containing entry file\n const moduleFile = process.argv[2];\n const agent: Agent = await import(pathToFileURL(moduleFile!).pathname).then((module) => {\n const agent = module.default;\n if (agent === undefined || !isAgent(agent)) {\n throw new Error(`Unable to load agent: Missing or invalid default export in ${moduleFile}`);\n }\n return agent;\n });\n if (!agent.prewarm) {\n agent.prewarm = defaultInitializeProcessFunc;\n }\n\n // don't do anything on C-c\n // this is handled in cli, triggering a termination of all child processes at once.\n process.on('SIGINT', () => {\n logger.debug('SIGINT received in job proc');\n });\n\n // don't do anything on SIGTERM\n // Render uses SIGTERM in autoscale, this ensures the processes are properly drained if needed\n process.on('SIGTERM', () => {\n logger.debug('SIGTERM received in job proc');\n });\n\n await once(process, 'message').then(([msg]: IPCMessage[]) => {\n msg = msg!;\n if (msg.case !== 'initializeRequest') {\n throw new Error('first message must be InitializeRequest');\n }\n initializeLogger(msg.value.loggerOptions);\n });\n const proc = new JobProcess();\n let logger = log().child({ pid: proc.pid });\n\n process.on('unhandledRejection', (reason) => {\n logger.error(reason);\n });\n\n logger.debug('initializing job runner');\n agent.prewarm(proc);\n logger.debug('job runner initialized');\n process.send({ case: 'initializeResponse' });\n\n let job: JobTask | undefined = undefined;\n const closeEvent = new EventEmitter();\n\n const orphanedTimeout = setTimeout(() => {\n logger.warn('job process orphaned, shutting down.');\n join.resolve();\n }, ORPHANED_TIMEOUT);\n\n const messageHandler = (msg: IPCMessage) => {\n switch (msg.case) {\n case 'pingRequest': {\n orphanedTimeout.refresh();\n process.send!({\n case: 'pongResponse',\n value: { lastTimestamp: msg.value.timestamp, timestamp: Date.now() },\n });\n break;\n }\n case 'startJobRequest': {\n if (job) {\n throw new Error('job task already running');\n }\n\n logger = logger.child({ jobID: msg.value.runningJob.job.id });\n\n job = startJob(proc, agent.entry, msg.value.runningJob, closeEvent, logger, join);\n logger.debug('job started');\n break;\n }\n case 'shutdownRequest': {\n if (!job) {\n join.resolve();\n }\n closeEvent.emit('close', 'shutdownRequest');\n clearTimeout(orphanedTimeout);\n process.off('message', messageHandler);\n }\n }\n };\n\n process.on('message', messageHandler);\n\n await join.await;\n\n logger.debug('Job process shutdown');\n process.exit(0);\n }\n})();\n"],"mappings":"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","names":["module","agent"]}
1
+ {"version":3,"sources":["../../src/ipc/job_proc_lazy_main.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Room, RoomEvent } from '@livekit/rtc-node';\nimport { EventEmitter, once } from 'node:events';\nimport { pathToFileURL } from 'node:url';\nimport type { Logger } from 'pino';\nimport { type Agent, isAgent } from '../generator.js';\nimport { CurrentJobContext, JobContext, JobProcess, type RunningJobInfo } from '../job.js';\nimport { initializeLogger, log } from '../log.js';\nimport { Future, shortuuid } from '../utils.js';\nimport { defaultInitializeProcessFunc } from '../worker.js';\nimport type { InferenceExecutor } from './inference_executor.js';\nimport type { IPCMessage } from './message.js';\n\nconst ORPHANED_TIMEOUT = 15 * 1000;\n\ntype JobTask = {\n ctx: JobContext;\n task: Promise<void>;\n};\n\nclass PendingInference {\n promise = new Promise<{ requestId: string; data: unknown; error?: Error }>((resolve) => {\n this.resolve = resolve; // this is how JavaScript lets you resolve promises externally\n });\n resolve(arg: { requestId: string; data: unknown; error?: Error }) {\n arg; // useless call to counteract TypeScript E6133\n }\n}\n\nclass InfClient implements InferenceExecutor {\n #requests: { [id: string]: PendingInference } = {};\n\n constructor() {\n process.on('message', (msg: IPCMessage) => {\n switch (msg.case) {\n case 'inferenceResponse':\n const fut = this.#requests[msg.value.requestId];\n delete this.#requests[msg.value.requestId];\n if (!fut) {\n log().child({ resp: msg.value }).warn('received unexpected inference response');\n return;\n }\n fut.resolve(msg.value);\n break;\n }\n });\n }\n\n async doInference(method: string, data: unknown): Promise<unknown> {\n const requestId = shortuuid('inference_job_');\n process.send!({ case: 'inferenceRequest', value: { requestId, method, data } });\n this.#requests[requestId] = new PendingInference();\n const resp = await this.#requests[requestId]!.promise;\n if (resp.error) {\n throw new Error(`inference of ${method} failed: ${resp.error.message}`);\n }\n return resp.data;\n }\n}\n\nconst startJob = (\n proc: JobProcess,\n func: (ctx: JobContext) => Promise<void>,\n info: RunningJobInfo,\n closeEvent: EventEmitter,\n logger: Logger,\n joinFuture: Future,\n): JobTask => {\n let connect = false;\n let shutdown = false;\n\n const room = new Room();\n room.on(RoomEvent.Disconnected, () => {\n if (!shutdown) {\n closeEvent.emit('close', false);\n }\n });\n\n const onConnect = () => {\n connect = true;\n };\n const onShutdown = (reason: string) => {\n shutdown = true;\n closeEvent.emit('close', true, reason);\n };\n\n const ctx = new JobContext(proc, info, room, onConnect, onShutdown, new InfClient());\n new CurrentJobContext(ctx);\n\n const task = new Promise<void>(async () => {\n const unconnectedTimeout = setTimeout(() => {\n if (!(connect || shutdown)) {\n logger.warn(\n 'room not connect after job_entry was called after 10 seconds, ',\n 'did you forget to call ctx.connect()?',\n );\n }\n }, 10000);\n func(ctx).finally(() => clearTimeout(unconnectedTimeout));\n\n await once(closeEvent, 'close').then((close) => {\n logger.debug('shutting down');\n shutdown = true;\n process.send!({ case: 'exiting', value: { reason: close[1] } });\n });\n\n await room.disconnect();\n logger.debug('disconnected from room');\n\n const shutdownTasks = [];\n for (const callback of ctx.shutdownCallbacks) {\n shutdownTasks.push(callback());\n }\n await Promise.all(shutdownTasks).catch((error) =>\n logger.error('error while shutting down the job', error),\n );\n\n process.send!({ case: 'done' });\n joinFuture.resolve();\n });\n\n return { ctx, task };\n};\n\n(async () => {\n if (process.send) {\n const join = new Future();\n\n // process.argv:\n // [0] `node'\n // [1] import.meta.filename\n // [2] import.meta.filename of function containing entry file\n const moduleFile = process.argv[2];\n const agent: Agent = await import(pathToFileURL(moduleFile!).pathname).then((module) => {\n const agent = module.default;\n if (agent === undefined || !isAgent(agent)) {\n throw new Error(`Unable to load agent: Missing or invalid default export in ${moduleFile}`);\n }\n return agent;\n });\n if (!agent.prewarm) {\n agent.prewarm = defaultInitializeProcessFunc;\n }\n\n // don't do anything on C-c\n // this is handled in cli, triggering a termination of all child processes at once.\n process.on('SIGINT', () => {\n logger.debug('SIGINT received in job proc');\n });\n\n // don't do anything on SIGTERM\n // Render uses SIGTERM in autoscale, this ensures the processes are properly drained if needed\n process.on('SIGTERM', () => {\n logger.debug('SIGTERM received in job proc');\n });\n\n await once(process, 'message').then(([msg]: IPCMessage[]) => {\n msg = msg!;\n if (msg.case !== 'initializeRequest') {\n throw new Error('first message must be InitializeRequest');\n }\n initializeLogger(msg.value.loggerOptions);\n });\n const proc = new JobProcess();\n let logger = log().child({ pid: proc.pid });\n\n process.on('unhandledRejection', (reason) => {\n logger.error(reason);\n });\n\n logger.debug('initializing job runner');\n agent.prewarm(proc);\n logger.debug('job runner initialized');\n process.send({ case: 'initializeResponse' });\n\n let job: JobTask | undefined = undefined;\n const closeEvent = new EventEmitter();\n\n const orphanedTimeout = setTimeout(() => {\n logger.warn('job process orphaned, shutting down.');\n join.resolve();\n }, ORPHANED_TIMEOUT);\n\n const messageHandler = (msg: IPCMessage) => {\n switch (msg.case) {\n case 'pingRequest': {\n orphanedTimeout.refresh();\n process.send!({\n case: 'pongResponse',\n value: { lastTimestamp: msg.value.timestamp, timestamp: Date.now() },\n });\n break;\n }\n case 'startJobRequest': {\n if (job) {\n throw new Error('job task already running');\n }\n\n logger = logger.child({ jobID: msg.value.runningJob.job.id });\n\n job = startJob(proc, agent.entry, msg.value.runningJob, closeEvent, logger, join);\n logger.debug('job started');\n break;\n }\n case 'shutdownRequest': {\n if (!job) {\n join.resolve();\n }\n closeEvent.emit('close', 'shutdownRequest');\n clearTimeout(orphanedTimeout);\n process.off('message', messageHandler);\n }\n }\n };\n\n process.on('message', messageHandler);\n\n await join.await;\n\n logger.debug('Job process shutdown');\n process.exit(0);\n }\n})();\n"],"mappings":"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","names":["module","agent"]}
@@ -1,11 +1,10 @@
1
1
  import { Room, RoomEvent } from "@livekit/rtc-node";
2
- import { randomUUID } from "node:crypto";
3
2
  import { EventEmitter, once } from "node:events";
4
3
  import { pathToFileURL } from "node:url";
5
4
  import { isAgent } from "../generator.js";
6
5
  import { CurrentJobContext, JobContext, JobProcess } from "../job.js";
7
6
  import { initializeLogger, log } from "../log.js";
8
- import { Future } from "../utils.js";
7
+ import { Future, shortuuid } from "../utils.js";
9
8
  import { defaultInitializeProcessFunc } from "../worker.js";
10
9
  const ORPHANED_TIMEOUT = 15 * 1e3;
11
10
  class PendingInference {
@@ -34,7 +33,7 @@ class InfClient {
34
33
  });
35
34
  }
36
35
  async doInference(method, data) {
37
- const requestId = "inference_job_" + randomUUID;
36
+ const requestId = shortuuid("inference_job_");
38
37
  process.send({ case: "inferenceRequest", value: { requestId, method, data } });
39
38
  this.#requests[requestId] = new PendingInference();
40
39
  const resp = await this.#requests[requestId].promise;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ipc/job_proc_lazy_main.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Room, RoomEvent } from '@livekit/rtc-node';\nimport { randomUUID } from 'node:crypto';\nimport { EventEmitter, once } from 'node:events';\nimport { pathToFileURL } from 'node:url';\nimport type { Logger } from 'pino';\nimport { type Agent, isAgent } from '../generator.js';\nimport { CurrentJobContext, JobContext, JobProcess, type RunningJobInfo } from '../job.js';\nimport { initializeLogger, log } from '../log.js';\nimport { Future } from '../utils.js';\nimport { defaultInitializeProcessFunc } from '../worker.js';\nimport type { InferenceExecutor } from './inference_executor.js';\nimport type { IPCMessage } from './message.js';\n\nconst ORPHANED_TIMEOUT = 15 * 1000;\n\ntype JobTask = {\n ctx: JobContext;\n task: Promise<void>;\n};\n\nclass PendingInference {\n promise = new Promise<{ requestId: string; data: unknown; error?: Error }>((resolve) => {\n this.resolve = resolve; // this is how JavaScript lets you resolve promises externally\n });\n resolve(arg: { requestId: string; data: unknown; error?: Error }) {\n arg; // useless call to counteract TypeScript E6133\n }\n}\n\nclass InfClient implements InferenceExecutor {\n #requests: { [id: string]: PendingInference } = {};\n\n constructor() {\n process.on('message', (msg: IPCMessage) => {\n switch (msg.case) {\n case 'inferenceResponse':\n const fut = this.#requests[msg.value.requestId];\n delete this.#requests[msg.value.requestId];\n if (!fut) {\n log().child({ resp: msg.value }).warn('received unexpected inference response');\n return;\n }\n fut.resolve(msg.value);\n break;\n }\n });\n }\n\n async doInference(method: string, data: unknown): Promise<unknown> {\n const requestId = 'inference_job_' + randomUUID;\n process.send!({ case: 'inferenceRequest', value: { requestId, method, data } });\n this.#requests[requestId] = new PendingInference();\n const resp = await this.#requests[requestId]!.promise;\n if (resp.error) {\n throw new Error(`inference of ${method} failed: ${resp.error.message}`);\n }\n return resp.data;\n }\n}\n\nconst startJob = (\n proc: JobProcess,\n func: (ctx: JobContext) => Promise<void>,\n info: RunningJobInfo,\n closeEvent: EventEmitter,\n logger: Logger,\n joinFuture: Future,\n): JobTask => {\n let connect = false;\n let shutdown = false;\n\n const room = new Room();\n room.on(RoomEvent.Disconnected, () => {\n if (!shutdown) {\n closeEvent.emit('close', false);\n }\n });\n\n const onConnect = () => {\n connect = true;\n };\n const onShutdown = (reason: string) => {\n shutdown = true;\n closeEvent.emit('close', true, reason);\n };\n\n const ctx = new JobContext(proc, info, room, onConnect, onShutdown, new InfClient());\n new CurrentJobContext(ctx);\n\n const task = new Promise<void>(async () => {\n const unconnectedTimeout = setTimeout(() => {\n if (!(connect || shutdown)) {\n logger.warn(\n 'room not connect after job_entry was called after 10 seconds, ',\n 'did you forget to call ctx.connect()?',\n );\n }\n }, 10000);\n func(ctx).finally(() => clearTimeout(unconnectedTimeout));\n\n await once(closeEvent, 'close').then((close) => {\n logger.debug('shutting down');\n shutdown = true;\n process.send!({ case: 'exiting', value: { reason: close[1] } });\n });\n\n await room.disconnect();\n logger.debug('disconnected from room');\n\n const shutdownTasks = [];\n for (const callback of ctx.shutdownCallbacks) {\n shutdownTasks.push(callback());\n }\n await Promise.all(shutdownTasks).catch((error) =>\n logger.error('error while shutting down the job', error),\n );\n\n process.send!({ case: 'done' });\n joinFuture.resolve();\n });\n\n return { ctx, task };\n};\n\n(async () => {\n if (process.send) {\n const join = new Future();\n\n // process.argv:\n // [0] `node'\n // [1] import.meta.filename\n // [2] import.meta.filename of function containing entry file\n const moduleFile = process.argv[2];\n const agent: Agent = await import(pathToFileURL(moduleFile!).pathname).then((module) => {\n const agent = module.default;\n if (agent === undefined || !isAgent(agent)) {\n throw new Error(`Unable to load agent: Missing or invalid default export in ${moduleFile}`);\n }\n return agent;\n });\n if (!agent.prewarm) {\n agent.prewarm = defaultInitializeProcessFunc;\n }\n\n // don't do anything on C-c\n // this is handled in cli, triggering a termination of all child processes at once.\n process.on('SIGINT', () => {\n logger.debug('SIGINT received in job proc');\n });\n\n // don't do anything on SIGTERM\n // Render uses SIGTERM in autoscale, this ensures the processes are properly drained if needed\n process.on('SIGTERM', () => {\n logger.debug('SIGTERM received in job proc');\n });\n\n await once(process, 'message').then(([msg]: IPCMessage[]) => {\n msg = msg!;\n if (msg.case !== 'initializeRequest') {\n throw new Error('first message must be InitializeRequest');\n }\n initializeLogger(msg.value.loggerOptions);\n });\n const proc = new JobProcess();\n let logger = log().child({ pid: proc.pid });\n\n process.on('unhandledRejection', (reason) => {\n logger.error(reason);\n });\n\n logger.debug('initializing job runner');\n agent.prewarm(proc);\n logger.debug('job runner initialized');\n process.send({ case: 'initializeResponse' });\n\n let job: JobTask | undefined = undefined;\n const closeEvent = new EventEmitter();\n\n const orphanedTimeout = setTimeout(() => {\n logger.warn('job process orphaned, shutting down.');\n join.resolve();\n }, ORPHANED_TIMEOUT);\n\n const messageHandler = (msg: IPCMessage) => {\n switch (msg.case) {\n case 'pingRequest': {\n orphanedTimeout.refresh();\n process.send!({\n case: 'pongResponse',\n value: { lastTimestamp: msg.value.timestamp, timestamp: Date.now() },\n });\n break;\n }\n case 'startJobRequest': {\n if (job) {\n throw new Error('job task already running');\n }\n\n logger = logger.child({ jobID: msg.value.runningJob.job.id });\n\n job = startJob(proc, agent.entry, msg.value.runningJob, closeEvent, logger, join);\n logger.debug('job started');\n break;\n }\n case 'shutdownRequest': {\n if (!job) {\n join.resolve();\n }\n closeEvent.emit('close', 'shutdownRequest');\n clearTimeout(orphanedTimeout);\n process.off('message', messageHandler);\n }\n }\n };\n\n process.on('message', messageHandler);\n\n await join.await;\n\n logger.debug('Job process shutdown');\n process.exit(0);\n }\n})();\n"],"mappings":"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","names":["agent"]}
1
+ {"version":3,"sources":["../../src/ipc/job_proc_lazy_main.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Room, RoomEvent } from '@livekit/rtc-node';\nimport { EventEmitter, once } from 'node:events';\nimport { pathToFileURL } from 'node:url';\nimport type { Logger } from 'pino';\nimport { type Agent, isAgent } from '../generator.js';\nimport { CurrentJobContext, JobContext, JobProcess, type RunningJobInfo } from '../job.js';\nimport { initializeLogger, log } from '../log.js';\nimport { Future, shortuuid } from '../utils.js';\nimport { defaultInitializeProcessFunc } from '../worker.js';\nimport type { InferenceExecutor } from './inference_executor.js';\nimport type { IPCMessage } from './message.js';\n\nconst ORPHANED_TIMEOUT = 15 * 1000;\n\ntype JobTask = {\n ctx: JobContext;\n task: Promise<void>;\n};\n\nclass PendingInference {\n promise = new Promise<{ requestId: string; data: unknown; error?: Error }>((resolve) => {\n this.resolve = resolve; // this is how JavaScript lets you resolve promises externally\n });\n resolve(arg: { requestId: string; data: unknown; error?: Error }) {\n arg; // useless call to counteract TypeScript E6133\n }\n}\n\nclass InfClient implements InferenceExecutor {\n #requests: { [id: string]: PendingInference } = {};\n\n constructor() {\n process.on('message', (msg: IPCMessage) => {\n switch (msg.case) {\n case 'inferenceResponse':\n const fut = this.#requests[msg.value.requestId];\n delete this.#requests[msg.value.requestId];\n if (!fut) {\n log().child({ resp: msg.value }).warn('received unexpected inference response');\n return;\n }\n fut.resolve(msg.value);\n break;\n }\n });\n }\n\n async doInference(method: string, data: unknown): Promise<unknown> {\n const requestId = shortuuid('inference_job_');\n process.send!({ case: 'inferenceRequest', value: { requestId, method, data } });\n this.#requests[requestId] = new PendingInference();\n const resp = await this.#requests[requestId]!.promise;\n if (resp.error) {\n throw new Error(`inference of ${method} failed: ${resp.error.message}`);\n }\n return resp.data;\n }\n}\n\nconst startJob = (\n proc: JobProcess,\n func: (ctx: JobContext) => Promise<void>,\n info: RunningJobInfo,\n closeEvent: EventEmitter,\n logger: Logger,\n joinFuture: Future,\n): JobTask => {\n let connect = false;\n let shutdown = false;\n\n const room = new Room();\n room.on(RoomEvent.Disconnected, () => {\n if (!shutdown) {\n closeEvent.emit('close', false);\n }\n });\n\n const onConnect = () => {\n connect = true;\n };\n const onShutdown = (reason: string) => {\n shutdown = true;\n closeEvent.emit('close', true, reason);\n };\n\n const ctx = new JobContext(proc, info, room, onConnect, onShutdown, new InfClient());\n new CurrentJobContext(ctx);\n\n const task = new Promise<void>(async () => {\n const unconnectedTimeout = setTimeout(() => {\n if (!(connect || shutdown)) {\n logger.warn(\n 'room not connect after job_entry was called after 10 seconds, ',\n 'did you forget to call ctx.connect()?',\n );\n }\n }, 10000);\n func(ctx).finally(() => clearTimeout(unconnectedTimeout));\n\n await once(closeEvent, 'close').then((close) => {\n logger.debug('shutting down');\n shutdown = true;\n process.send!({ case: 'exiting', value: { reason: close[1] } });\n });\n\n await room.disconnect();\n logger.debug('disconnected from room');\n\n const shutdownTasks = [];\n for (const callback of ctx.shutdownCallbacks) {\n shutdownTasks.push(callback());\n }\n await Promise.all(shutdownTasks).catch((error) =>\n logger.error('error while shutting down the job', error),\n );\n\n process.send!({ case: 'done' });\n joinFuture.resolve();\n });\n\n return { ctx, task };\n};\n\n(async () => {\n if (process.send) {\n const join = new Future();\n\n // process.argv:\n // [0] `node'\n // [1] import.meta.filename\n // [2] import.meta.filename of function containing entry file\n const moduleFile = process.argv[2];\n const agent: Agent = await import(pathToFileURL(moduleFile!).pathname).then((module) => {\n const agent = module.default;\n if (agent === undefined || !isAgent(agent)) {\n throw new Error(`Unable to load agent: Missing or invalid default export in ${moduleFile}`);\n }\n return agent;\n });\n if (!agent.prewarm) {\n agent.prewarm = defaultInitializeProcessFunc;\n }\n\n // don't do anything on C-c\n // this is handled in cli, triggering a termination of all child processes at once.\n process.on('SIGINT', () => {\n logger.debug('SIGINT received in job proc');\n });\n\n // don't do anything on SIGTERM\n // Render uses SIGTERM in autoscale, this ensures the processes are properly drained if needed\n process.on('SIGTERM', () => {\n logger.debug('SIGTERM received in job proc');\n });\n\n await once(process, 'message').then(([msg]: IPCMessage[]) => {\n msg = msg!;\n if (msg.case !== 'initializeRequest') {\n throw new Error('first message must be InitializeRequest');\n }\n initializeLogger(msg.value.loggerOptions);\n });\n const proc = new JobProcess();\n let logger = log().child({ pid: proc.pid });\n\n process.on('unhandledRejection', (reason) => {\n logger.error(reason);\n });\n\n logger.debug('initializing job runner');\n agent.prewarm(proc);\n logger.debug('job runner initialized');\n process.send({ case: 'initializeResponse' });\n\n let job: JobTask | undefined = undefined;\n const closeEvent = new EventEmitter();\n\n const orphanedTimeout = setTimeout(() => {\n logger.warn('job process orphaned, shutting down.');\n join.resolve();\n }, ORPHANED_TIMEOUT);\n\n const messageHandler = (msg: IPCMessage) => {\n switch (msg.case) {\n case 'pingRequest': {\n orphanedTimeout.refresh();\n process.send!({\n case: 'pongResponse',\n value: { lastTimestamp: msg.value.timestamp, timestamp: Date.now() },\n });\n break;\n }\n case 'startJobRequest': {\n if (job) {\n throw new Error('job task already running');\n }\n\n logger = logger.child({ jobID: msg.value.runningJob.job.id });\n\n job = startJob(proc, agent.entry, msg.value.runningJob, closeEvent, logger, join);\n logger.debug('job started');\n break;\n }\n case 'shutdownRequest': {\n if (!job) {\n join.resolve();\n }\n closeEvent.emit('close', 'shutdownRequest');\n clearTimeout(orphanedTimeout);\n process.off('message', messageHandler);\n }\n }\n };\n\n process.on('message', messageHandler);\n\n await join.await;\n\n logger.debug('Job process shutdown');\n process.exit(0);\n }\n})();\n"],"mappings":"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","names":["agent"]}
@@ -114,7 +114,12 @@ class ProcPool {
114
114
  unlock();
115
115
  await proc.join();
116
116
  } finally {
117
- this.executors.splice(this.executors.indexOf(proc));
117
+ const procIndex = this.executors.indexOf(proc);
118
+ if (procIndex !== -1) {
119
+ this.executors.splice(procIndex, 1);
120
+ } else {
121
+ throw new Error(`proc ${proc} not found in executors`);
122
+ }
118
123
  }
119
124
  }
120
125
  start() {
@@ -130,7 +135,14 @@ class ProcPool {
130
135
  this.procUnlock = await this.procMutex.lock();
131
136
  const task = this.procWatchTask();
132
137
  this.tasks.push(task);
133
- task.finally(() => this.tasks.splice(this.tasks.indexOf(task)));
138
+ task.finally(() => {
139
+ const taskIndex = this.tasks.indexOf(task);
140
+ if (taskIndex !== -1) {
141
+ this.tasks.splice(taskIndex, 1);
142
+ } else {
143
+ throw new Error(`task ${task} not found in tasks`);
144
+ }
145
+ });
134
146
  }
135
147
  }
136
148
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ipc/proc_pool.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { MultiMutex, Mutex } from '@livekit/mutex';\nimport type { RunningJobInfo } from '../job.js';\nimport { Queue } from '../utils.js';\nimport type { InferenceExecutor } from './inference_executor.js';\nimport type { JobExecutor } from './job_executor.js';\nimport { JobProcExecutor } from './job_proc_executor.js';\n\nexport class ProcPool {\n agent: string;\n initializeTimeout: number;\n closeTimeout: number;\n executors: JobExecutor[] = [];\n tasks: Promise<void>[] = [];\n started = false;\n closed = false;\n controller = new AbortController();\n initMutex = new Mutex();\n procMutex?: MultiMutex;\n procUnlock?: () => void;\n warmedProcQueue = new Queue<JobExecutor>();\n inferenceExecutor?: InferenceExecutor;\n memoryWarnMB: number;\n memoryLimitMB: number;\n\n constructor(\n agent: string,\n numIdleProcesses: number,\n initializeTimeout: number,\n closeTimeout: number,\n inferenceExecutor: InferenceExecutor | undefined,\n memoryWarnMB: number,\n memoryLimitMB: number,\n ) {\n this.agent = agent;\n if (numIdleProcesses > 0) {\n this.procMutex = new MultiMutex(numIdleProcesses);\n }\n this.initializeTimeout = initializeTimeout;\n this.closeTimeout = closeTimeout;\n this.inferenceExecutor = inferenceExecutor;\n this.memoryWarnMB = memoryWarnMB;\n this.memoryLimitMB = memoryLimitMB;\n }\n\n get processes(): JobExecutor[] {\n return this.executors;\n }\n\n getByJobId(id: string): JobExecutor | null {\n return this.executors.find((x) => x.runningJob && x.runningJob.job.id === id) || null;\n }\n\n async launchJob(info: RunningJobInfo) {\n let proc: JobExecutor;\n if (this.procMutex) {\n proc = await this.warmedProcQueue.get();\n if (this.procUnlock) {\n this.procUnlock();\n this.procUnlock = undefined;\n }\n } else {\n proc = new JobProcExecutor(\n this.agent,\n this.inferenceExecutor,\n this.initializeTimeout,\n this.closeTimeout,\n this.memoryWarnMB,\n this.memoryLimitMB,\n 2500,\n 60000,\n 500,\n );\n this.executors.push(proc);\n await proc.start();\n await proc.initialize();\n }\n await proc.launchJob(info);\n }\n\n async procWatchTask() {\n const proc = new JobProcExecutor(\n this.agent,\n this.inferenceExecutor,\n this.initializeTimeout,\n this.closeTimeout,\n this.memoryWarnMB,\n this.memoryLimitMB,\n 2500,\n 60000,\n 500,\n );\n\n try {\n this.executors.push(proc);\n\n const unlock = await this.initMutex.lock();\n if (this.closed) {\n return;\n }\n\n await proc.start();\n try {\n await proc.initialize();\n await this.warmedProcQueue.put(proc);\n } catch {\n if (this.procUnlock) {\n this.procUnlock();\n this.procUnlock = undefined;\n }\n }\n\n unlock();\n await proc.join();\n } finally {\n this.executors.splice(this.executors.indexOf(proc));\n }\n }\n\n start() {\n if (this.started) {\n return;\n }\n\n this.started = true;\n this.run(this.controller.signal);\n }\n\n async run(signal: AbortSignal) {\n if (this.procMutex) {\n while (!signal.aborted) {\n this.procUnlock = await this.procMutex.lock();\n const task = this.procWatchTask();\n this.tasks.push(task);\n task.finally(() => this.tasks.splice(this.tasks.indexOf(task)));\n }\n }\n }\n\n async close() {\n if (!this.started) {\n return;\n }\n this.closed = true;\n this.controller.abort();\n this.warmedProcQueue.items.forEach((e) => e.close());\n this.executors.forEach((e) => e.close());\n await Promise.allSettled(this.tasks);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/ipc/proc_pool.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { MultiMutex, Mutex } from '@livekit/mutex';\nimport type { RunningJobInfo } from '../job.js';\nimport { Queue } from '../utils.js';\nimport type { InferenceExecutor } from './inference_executor.js';\nimport type { JobExecutor } from './job_executor.js';\nimport { JobProcExecutor } from './job_proc_executor.js';\n\nexport class ProcPool {\n agent: string;\n initializeTimeout: number;\n closeTimeout: number;\n executors: JobExecutor[] = [];\n tasks: Promise<void>[] = [];\n started = false;\n closed = false;\n controller = new AbortController();\n initMutex = new Mutex();\n procMutex?: MultiMutex;\n procUnlock?: () => void;\n warmedProcQueue = new Queue<JobExecutor>();\n inferenceExecutor?: InferenceExecutor;\n memoryWarnMB: number;\n memoryLimitMB: number;\n\n constructor(\n agent: string,\n numIdleProcesses: number,\n initializeTimeout: number,\n closeTimeout: number,\n inferenceExecutor: InferenceExecutor | undefined,\n memoryWarnMB: number,\n memoryLimitMB: number,\n ) {\n this.agent = agent;\n if (numIdleProcesses > 0) {\n this.procMutex = new MultiMutex(numIdleProcesses);\n }\n this.initializeTimeout = initializeTimeout;\n this.closeTimeout = closeTimeout;\n this.inferenceExecutor = inferenceExecutor;\n this.memoryWarnMB = memoryWarnMB;\n this.memoryLimitMB = memoryLimitMB;\n }\n\n get processes(): JobExecutor[] {\n return this.executors;\n }\n\n getByJobId(id: string): JobExecutor | null {\n return this.executors.find((x) => x.runningJob && x.runningJob.job.id === id) || null;\n }\n\n async launchJob(info: RunningJobInfo) {\n let proc: JobExecutor;\n if (this.procMutex) {\n proc = await this.warmedProcQueue.get();\n if (this.procUnlock) {\n this.procUnlock();\n this.procUnlock = undefined;\n }\n } else {\n proc = new JobProcExecutor(\n this.agent,\n this.inferenceExecutor,\n this.initializeTimeout,\n this.closeTimeout,\n this.memoryWarnMB,\n this.memoryLimitMB,\n 2500,\n 60000,\n 500,\n );\n this.executors.push(proc);\n await proc.start();\n await proc.initialize();\n }\n await proc.launchJob(info);\n }\n\n async procWatchTask() {\n const proc = new JobProcExecutor(\n this.agent,\n this.inferenceExecutor,\n this.initializeTimeout,\n this.closeTimeout,\n this.memoryWarnMB,\n this.memoryLimitMB,\n 2500,\n 60000,\n 500,\n );\n\n try {\n this.executors.push(proc);\n\n const unlock = await this.initMutex.lock();\n if (this.closed) {\n return;\n }\n\n await proc.start();\n try {\n await proc.initialize();\n await this.warmedProcQueue.put(proc);\n } catch {\n if (this.procUnlock) {\n this.procUnlock();\n this.procUnlock = undefined;\n }\n }\n\n unlock();\n await proc.join();\n } finally {\n const procIndex = this.executors.indexOf(proc);\n if (procIndex !== -1) {\n this.executors.splice(procIndex, 1);\n } else {\n throw new Error(`proc ${proc} not found in executors`);\n }\n }\n }\n\n start() {\n if (this.started) {\n return;\n }\n\n this.started = true;\n this.run(this.controller.signal);\n }\n\n async run(signal: AbortSignal) {\n if (this.procMutex) {\n while (!signal.aborted) {\n this.procUnlock = await this.procMutex.lock();\n const task = this.procWatchTask();\n this.tasks.push(task);\n task.finally(() => {\n const taskIndex = this.tasks.indexOf(task);\n if (taskIndex !== -1) {\n this.tasks.splice(taskIndex, 1);\n } else {\n throw new Error(`task ${task} not found in tasks`);\n }\n });\n }\n }\n }\n\n async close() {\n if (!this.started) {\n return;\n }\n this.closed = true;\n this.controller.abort();\n this.warmedProcQueue.items.forEach((e) => e.close());\n this.executors.forEach((e) => e.close());\n await Promise.allSettled(this.tasks);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"proc_pool.d.ts","sourceRoot":"","sources":["../../src/ipc/proc_pool.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,qBAAa,QAAQ;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,WAAW,EAAE,CAAM;IAC9B,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAM;IAC5B,OAAO,UAAS;IAChB,MAAM,UAAS;IACf,UAAU,kBAAyB;IACnC,SAAS,QAAe;IACxB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,eAAe,qBAA4B;IAC3C,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;gBAGpB,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,EAChD,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM;IAavB,IAAI,SAAS,IAAI,WAAW,EAAE,CAE7B;IAED,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAIpC,SAAS,CAAC,IAAI,EAAE,cAAc;IA2B9B,aAAa;IAuCnB,KAAK;IASC,GAAG,CAAC,MAAM,EAAE,WAAW;IAWvB,KAAK;CAUZ"}
1
+ {"version":3,"file":"proc_pool.d.ts","sourceRoot":"","sources":["../../src/ipc/proc_pool.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,qBAAa,QAAQ;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,WAAW,EAAE,CAAM;IAC9B,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAM;IAC5B,OAAO,UAAS;IAChB,MAAM,UAAS;IACf,UAAU,kBAAyB;IACnC,SAAS,QAAe;IACxB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,eAAe,qBAA4B;IAC3C,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;gBAGpB,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,EAChD,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM;IAavB,IAAI,SAAS,IAAI,WAAW,EAAE,CAE7B;IAED,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAIpC,SAAS,CAAC,IAAI,EAAE,cAAc;IA2B9B,aAAa;IA4CnB,KAAK;IASC,GAAG,CAAC,MAAM,EAAE,WAAW;IAkBvB,KAAK;CAUZ"}
@@ -91,7 +91,12 @@ class ProcPool {
91
91
  unlock();
92
92
  await proc.join();
93
93
  } finally {
94
- this.executors.splice(this.executors.indexOf(proc));
94
+ const procIndex = this.executors.indexOf(proc);
95
+ if (procIndex !== -1) {
96
+ this.executors.splice(procIndex, 1);
97
+ } else {
98
+ throw new Error(`proc ${proc} not found in executors`);
99
+ }
95
100
  }
96
101
  }
97
102
  start() {
@@ -107,7 +112,14 @@ class ProcPool {
107
112
  this.procUnlock = await this.procMutex.lock();
108
113
  const task = this.procWatchTask();
109
114
  this.tasks.push(task);
110
- task.finally(() => this.tasks.splice(this.tasks.indexOf(task)));
115
+ task.finally(() => {
116
+ const taskIndex = this.tasks.indexOf(task);
117
+ if (taskIndex !== -1) {
118
+ this.tasks.splice(taskIndex, 1);
119
+ } else {
120
+ throw new Error(`task ${task} not found in tasks`);
121
+ }
122
+ });
111
123
  }
112
124
  }
113
125
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ipc/proc_pool.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { MultiMutex, Mutex } from '@livekit/mutex';\nimport type { RunningJobInfo } from '../job.js';\nimport { Queue } from '../utils.js';\nimport type { InferenceExecutor } from './inference_executor.js';\nimport type { JobExecutor } from './job_executor.js';\nimport { JobProcExecutor } from './job_proc_executor.js';\n\nexport class ProcPool {\n agent: string;\n initializeTimeout: number;\n closeTimeout: number;\n executors: JobExecutor[] = [];\n tasks: Promise<void>[] = [];\n started = false;\n closed = false;\n controller = new AbortController();\n initMutex = new Mutex();\n procMutex?: MultiMutex;\n procUnlock?: () => void;\n warmedProcQueue = new Queue<JobExecutor>();\n inferenceExecutor?: InferenceExecutor;\n memoryWarnMB: number;\n memoryLimitMB: number;\n\n constructor(\n agent: string,\n numIdleProcesses: number,\n initializeTimeout: number,\n closeTimeout: number,\n inferenceExecutor: InferenceExecutor | undefined,\n memoryWarnMB: number,\n memoryLimitMB: number,\n ) {\n this.agent = agent;\n if (numIdleProcesses > 0) {\n this.procMutex = new MultiMutex(numIdleProcesses);\n }\n this.initializeTimeout = initializeTimeout;\n this.closeTimeout = closeTimeout;\n this.inferenceExecutor = inferenceExecutor;\n this.memoryWarnMB = memoryWarnMB;\n this.memoryLimitMB = memoryLimitMB;\n }\n\n get processes(): JobExecutor[] {\n return this.executors;\n }\n\n getByJobId(id: string): JobExecutor | null {\n return this.executors.find((x) => x.runningJob && x.runningJob.job.id === id) || null;\n }\n\n async launchJob(info: RunningJobInfo) {\n let proc: JobExecutor;\n if (this.procMutex) {\n proc = await this.warmedProcQueue.get();\n if (this.procUnlock) {\n this.procUnlock();\n this.procUnlock = undefined;\n }\n } else {\n proc = new JobProcExecutor(\n this.agent,\n this.inferenceExecutor,\n this.initializeTimeout,\n this.closeTimeout,\n this.memoryWarnMB,\n this.memoryLimitMB,\n 2500,\n 60000,\n 500,\n );\n this.executors.push(proc);\n await proc.start();\n await proc.initialize();\n }\n await proc.launchJob(info);\n }\n\n async procWatchTask() {\n const proc = new JobProcExecutor(\n this.agent,\n this.inferenceExecutor,\n this.initializeTimeout,\n this.closeTimeout,\n this.memoryWarnMB,\n this.memoryLimitMB,\n 2500,\n 60000,\n 500,\n );\n\n try {\n this.executors.push(proc);\n\n const unlock = await this.initMutex.lock();\n if (this.closed) {\n return;\n }\n\n await proc.start();\n try {\n await proc.initialize();\n await this.warmedProcQueue.put(proc);\n } catch {\n if (this.procUnlock) {\n this.procUnlock();\n this.procUnlock = undefined;\n }\n }\n\n unlock();\n await proc.join();\n } finally {\n this.executors.splice(this.executors.indexOf(proc));\n }\n }\n\n start() {\n if (this.started) {\n return;\n }\n\n this.started = true;\n this.run(this.controller.signal);\n }\n\n async run(signal: AbortSignal) {\n if (this.procMutex) {\n while (!signal.aborted) {\n this.procUnlock = await this.procMutex.lock();\n const task = this.procWatchTask();\n this.tasks.push(task);\n task.finally(() => this.tasks.splice(this.tasks.indexOf(task)));\n }\n }\n }\n\n async close() {\n if (!this.started) {\n return;\n }\n this.closed = true;\n this.controller.abort();\n this.warmedProcQueue.items.forEach((e) => e.close());\n this.executors.forEach((e) => e.close());\n await Promise.allSettled(this.tasks);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/ipc/proc_pool.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { MultiMutex, Mutex } from '@livekit/mutex';\nimport type { RunningJobInfo } from '../job.js';\nimport { Queue } from '../utils.js';\nimport type { InferenceExecutor } from './inference_executor.js';\nimport type { JobExecutor } from './job_executor.js';\nimport { JobProcExecutor } from './job_proc_executor.js';\n\nexport class ProcPool {\n agent: string;\n initializeTimeout: number;\n closeTimeout: number;\n executors: JobExecutor[] = [];\n tasks: Promise<void>[] = [];\n started = false;\n closed = false;\n controller = new AbortController();\n initMutex = new Mutex();\n procMutex?: MultiMutex;\n procUnlock?: () => void;\n warmedProcQueue = new Queue<JobExecutor>();\n inferenceExecutor?: InferenceExecutor;\n memoryWarnMB: number;\n memoryLimitMB: number;\n\n constructor(\n agent: string,\n numIdleProcesses: number,\n initializeTimeout: number,\n closeTimeout: number,\n inferenceExecutor: InferenceExecutor | undefined,\n memoryWarnMB: number,\n memoryLimitMB: number,\n ) {\n this.agent = agent;\n if (numIdleProcesses > 0) {\n this.procMutex = new MultiMutex(numIdleProcesses);\n }\n this.initializeTimeout = initializeTimeout;\n this.closeTimeout = closeTimeout;\n this.inferenceExecutor = inferenceExecutor;\n this.memoryWarnMB = memoryWarnMB;\n this.memoryLimitMB = memoryLimitMB;\n }\n\n get processes(): JobExecutor[] {\n return this.executors;\n }\n\n getByJobId(id: string): JobExecutor | null {\n return this.executors.find((x) => x.runningJob && x.runningJob.job.id === id) || null;\n }\n\n async launchJob(info: RunningJobInfo) {\n let proc: JobExecutor;\n if (this.procMutex) {\n proc = await this.warmedProcQueue.get();\n if (this.procUnlock) {\n this.procUnlock();\n this.procUnlock = undefined;\n }\n } else {\n proc = new JobProcExecutor(\n this.agent,\n this.inferenceExecutor,\n this.initializeTimeout,\n this.closeTimeout,\n this.memoryWarnMB,\n this.memoryLimitMB,\n 2500,\n 60000,\n 500,\n );\n this.executors.push(proc);\n await proc.start();\n await proc.initialize();\n }\n await proc.launchJob(info);\n }\n\n async procWatchTask() {\n const proc = new JobProcExecutor(\n this.agent,\n this.inferenceExecutor,\n this.initializeTimeout,\n this.closeTimeout,\n this.memoryWarnMB,\n this.memoryLimitMB,\n 2500,\n 60000,\n 500,\n );\n\n try {\n this.executors.push(proc);\n\n const unlock = await this.initMutex.lock();\n if (this.closed) {\n return;\n }\n\n await proc.start();\n try {\n await proc.initialize();\n await this.warmedProcQueue.put(proc);\n } catch {\n if (this.procUnlock) {\n this.procUnlock();\n this.procUnlock = undefined;\n }\n }\n\n unlock();\n await proc.join();\n } finally {\n const procIndex = this.executors.indexOf(proc);\n if (procIndex !== -1) {\n this.executors.splice(procIndex, 1);\n } else {\n throw new Error(`proc ${proc} not found in executors`);\n }\n }\n }\n\n start() {\n if (this.started) {\n return;\n }\n\n this.started = true;\n this.run(this.controller.signal);\n }\n\n async run(signal: AbortSignal) {\n if (this.procMutex) {\n while (!signal.aborted) {\n this.procUnlock = await this.procMutex.lock();\n const task = this.procWatchTask();\n this.tasks.push(task);\n task.finally(() => {\n const taskIndex = this.tasks.indexOf(task);\n if (taskIndex !== -1) {\n this.tasks.splice(taskIndex, 1);\n } else {\n throw new Error(`task ${task} not found in tasks`);\n }\n });\n }\n }\n }\n\n async close() {\n if (!this.started) {\n return;\n }\n this.closed = true;\n this.controller.abort();\n this.warmedProcQueue.items.forEach((e) => e.close());\n this.executors.forEach((e) => e.close());\n await Promise.allSettled(this.tasks);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var supervised_proc_exports = {};
20
30
  __export(supervised_proc_exports, {
@@ -22,6 +32,7 @@ __export(supervised_proc_exports, {
22
32
  });
23
33
  module.exports = __toCommonJS(supervised_proc_exports);
24
34
  var import_node_events = require("node:events");
35
+ var import_pidusage = __toESM(require("pidusage"), 1);
25
36
  var import_log = require("../log.cjs");
26
37
  var import_utils = require("../utils.cjs");
27
38
  class SupervisedProc {
@@ -31,7 +42,7 @@ class SupervisedProc {
31
42
  #runningJob = void 0;
32
43
  proc;
33
44
  #pingInterval;
34
- #memoryWatch;
45
+ #memoryMonitorInterval;
35
46
  #pongTimeout;
36
47
  init = new import_utils.Future();
37
48
  #join = new import_utils.Future();
@@ -75,8 +86,8 @@ class SupervisedProc {
75
86
  this.proc.kill();
76
87
  this.#join.resolve();
77
88
  }, this.#opts.pingTimeout);
78
- this.#memoryWatch = setInterval(() => {
79
- const memoryMB = process.memoryUsage().heapUsed / (1024 * 1024);
89
+ this.#memoryMonitorInterval = setInterval(async () => {
90
+ const memoryMB = await this.getChildMemoryUsageMB();
80
91
  if (this.#opts.memoryLimitMB > 0 && memoryMB > this.#opts.memoryLimitMB) {
81
92
  this.#logger.child({ memoryUsageMB: memoryMB, memoryLimitMB: this.#opts.memoryLimitMB }).error("process exceeded memory limit, killing process");
82
93
  this.close();
@@ -85,9 +96,9 @@ class SupervisedProc {
85
96
  memoryUsageMB: memoryMB,
86
97
  memoryWarnMB: this.#opts.memoryWarnMB,
87
98
  memoryLimitMB: this.#opts.memoryLimitMB
88
- }).error("process memory usage is high");
99
+ }).warn("process memory usage is high");
89
100
  }
90
- });
101
+ }, 5e3);
91
102
  const listener = (msg) => {
92
103
  var _a;
93
104
  switch (msg.case) {
@@ -114,9 +125,7 @@ class SupervisedProc {
114
125
  this.proc.on("error", (err) => {
115
126
  if (this.#closing) return;
116
127
  this.#logger.child({ err }).warn("job process exited unexpectedly; this likely means the error above caused a crash");
117
- clearTimeout(this.#pongTimeout);
118
- clearInterval(this.#pingInterval);
119
- clearInterval(this.#memoryWatch);
128
+ this.clearTimers();
120
129
  this.#join.resolve();
121
130
  });
122
131
  this.proc.on("exit", () => {
@@ -166,8 +175,7 @@ class SupervisedProc {
166
175
  }, this.#opts.closeTimeout);
167
176
  await this.#join.await.then(() => {
168
177
  clearTimeout(timer);
169
- clearTimeout(this.#pongTimeout);
170
- clearInterval(this.#pingInterval);
178
+ this.clearTimers();
171
179
  });
172
180
  }
173
181
  async launchJob(info) {
@@ -177,6 +185,20 @@ class SupervisedProc {
177
185
  this.#runningJob = info;
178
186
  this.proc.send({ case: "startJobRequest", value: { runningJob: info } });
179
187
  }
188
+ async getChildMemoryUsageMB() {
189
+ var _a;
190
+ const pid = (_a = this.proc) == null ? void 0 : _a.pid;
191
+ if (!pid) {
192
+ return 0;
193
+ }
194
+ const stats = await (0, import_pidusage.default)(pid);
195
+ return stats.memory / (1024 * 1024);
196
+ }
197
+ clearTimers() {
198
+ clearTimeout(this.#pongTimeout);
199
+ clearInterval(this.#pingInterval);
200
+ clearInterval(this.#memoryMonitorInterval);
201
+ }
180
202
  }
181
203
  // Annotate the CommonJS export names for ESM import in node:
182
204
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ipc/supervised_proc.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChildProcess } from 'node:child_process';\nimport { once } from 'node:events';\nimport type { RunningJobInfo } from '../job.js';\nimport { log, loggerOptions } from '../log.js';\nimport { Future } from '../utils.js';\nimport type { IPCMessage } from './message.js';\n\nexport interface ProcOpts {\n initializeTimeout: number;\n closeTimeout: number;\n memoryWarnMB: number;\n memoryLimitMB: number;\n pingInterval: number;\n pingTimeout: number;\n highPingThreshold: number;\n}\n\nexport abstract class SupervisedProc {\n #opts: ProcOpts;\n #started = false;\n #closing = false;\n #runningJob?: RunningJobInfo = undefined;\n proc?: ChildProcess;\n #pingInterval?: ReturnType<typeof setInterval>;\n #memoryWatch?: ReturnType<typeof setInterval>;\n #pongTimeout?: ReturnType<typeof setTimeout>;\n protected init = new Future();\n #join = new Future();\n #logger = log().child({ runningJob: this.#runningJob });\n\n constructor(\n initializeTimeout: number,\n closeTimeout: number,\n memoryWarnMB: number,\n memoryLimitMB: number,\n pingInterval: number,\n pingTimeout: number,\n highPingThreshold: number,\n ) {\n this.#opts = {\n initializeTimeout,\n closeTimeout,\n memoryWarnMB,\n memoryLimitMB,\n pingInterval,\n pingTimeout,\n highPingThreshold,\n };\n }\n\n abstract createProcess(): ChildProcess;\n abstract mainTask(child: ChildProcess): Promise<void>;\n\n get started(): boolean {\n return this.#started;\n }\n\n get runningJob(): RunningJobInfo | undefined {\n return this.#runningJob;\n }\n\n async start() {\n if (this.#started) {\n throw new Error('runner already started');\n } else if (this.#closing) {\n throw new Error('runner is closed');\n }\n\n this.proc = this.createProcess();\n\n this.#started = true;\n this.run();\n }\n\n async run() {\n await this.init.await;\n\n this.#pingInterval = setInterval(() => {\n this.proc!.send({ case: 'pingRequest', value: { timestamp: Date.now() } });\n }, this.#opts.pingInterval);\n\n this.#pongTimeout = setTimeout(() => {\n this.#logger.warn('job is unresponsive');\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n this.proc!.kill();\n this.#join.resolve();\n }, this.#opts.pingTimeout);\n\n this.#memoryWatch = setInterval(() => {\n const memoryMB = process.memoryUsage().heapUsed / (1024 * 1024);\n if (this.#opts.memoryLimitMB > 0 && memoryMB > this.#opts.memoryLimitMB) {\n this.#logger\n .child({ memoryUsageMB: memoryMB, memoryLimitMB: this.#opts.memoryLimitMB })\n .error('process exceeded memory limit, killing process');\n this.close();\n } else if (this.#opts.memoryWarnMB > 0 && memoryMB > this.#opts.memoryWarnMB) {\n this.#logger\n .child({\n memoryUsageMB: memoryMB,\n memoryWarnMB: this.#opts.memoryWarnMB,\n memoryLimitMB: this.#opts.memoryLimitMB,\n })\n .error('process memory usage is high');\n }\n });\n\n const listener = (msg: IPCMessage) => {\n switch (msg.case) {\n case 'pongResponse': {\n const delay = Date.now() - msg.value.timestamp;\n if (delay > this.#opts.highPingThreshold) {\n this.#logger.child({ delay }).warn('job executor is unresponsive');\n }\n this.#pongTimeout?.refresh();\n break;\n }\n case 'exiting': {\n this.#logger.child({ reason: msg.value.reason }).debug('job exiting');\n break;\n }\n case 'done': {\n this.#closing = true;\n this.proc!.off('message', listener);\n break;\n }\n }\n };\n this.proc!.on('message', listener);\n this.proc!.on('error', (err) => {\n if (this.#closing) return;\n this.#logger\n .child({ err })\n .warn('job process exited unexpectedly; this likely means the error above caused a crash');\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n clearInterval(this.#memoryWatch);\n this.#join.resolve();\n });\n\n this.proc!.on('exit', () => {\n this.#join.resolve();\n });\n\n this.mainTask(this.proc!);\n\n await this.#join.await;\n }\n\n async join() {\n if (!this.#started) {\n throw new Error('runner not started');\n }\n\n await this.#join.await;\n }\n\n async initialize() {\n const timer = setTimeout(() => {\n const err = new Error('runner initialization timed out');\n this.init.reject(err);\n throw err;\n }, this.#opts.initializeTimeout);\n this.proc!.send({\n case: 'initializeRequest',\n value: {\n loggerOptions,\n pingInterval: this.#opts.pingInterval,\n pingTimeout: this.#opts.pingTimeout,\n highPingThreshold: this.#opts.highPingThreshold,\n },\n });\n await once(this.proc!, 'message').then(([msg]: IPCMessage[]) => {\n clearTimeout(timer);\n if (msg!.case !== 'initializeResponse') {\n throw new Error('first message must be InitializeResponse');\n }\n });\n this.init.resolve();\n }\n\n async close() {\n if (!this.#started) {\n return;\n }\n this.#closing = true;\n\n this.proc!.send({ case: 'shutdownRequest' });\n\n const timer = setTimeout(() => {\n this.#logger.error('job shutdown is taking too much time');\n this.proc!.kill();\n }, this.#opts.closeTimeout);\n await this.#join.await.then(() => {\n clearTimeout(timer);\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n });\n }\n\n async launchJob(info: RunningJobInfo) {\n if (this.#runningJob) {\n throw new Error('executor already has a running job');\n }\n this.#runningJob = info;\n this.proc!.send({ case: 'startJobRequest', value: { runningJob: info } });\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/ipc/supervised_proc.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChildProcess } from 'node:child_process';\nimport { once } from 'node:events';\nimport pidusage from 'pidusage';\nimport type { RunningJobInfo } from '../job.js';\nimport { log, loggerOptions } from '../log.js';\nimport { Future } from '../utils.js';\nimport type { IPCMessage } from './message.js';\n\nexport interface ProcOpts {\n initializeTimeout: number;\n closeTimeout: number;\n memoryWarnMB: number;\n memoryLimitMB: number;\n pingInterval: number;\n pingTimeout: number;\n highPingThreshold: number;\n}\n\nexport abstract class SupervisedProc {\n #opts: ProcOpts;\n #started = false;\n #closing = false;\n #runningJob?: RunningJobInfo = undefined;\n proc?: ChildProcess;\n #pingInterval?: ReturnType<typeof setInterval>;\n #memoryMonitorInterval?: ReturnType<typeof setInterval>;\n #pongTimeout?: ReturnType<typeof setTimeout>;\n protected init = new Future();\n #join = new Future();\n #logger = log().child({ runningJob: this.#runningJob });\n\n constructor(\n initializeTimeout: number,\n closeTimeout: number,\n memoryWarnMB: number,\n memoryLimitMB: number,\n pingInterval: number,\n pingTimeout: number,\n highPingThreshold: number,\n ) {\n this.#opts = {\n initializeTimeout,\n closeTimeout,\n memoryWarnMB,\n memoryLimitMB,\n pingInterval,\n pingTimeout,\n highPingThreshold,\n };\n }\n\n abstract createProcess(): ChildProcess;\n abstract mainTask(child: ChildProcess): Promise<void>;\n\n get started(): boolean {\n return this.#started;\n }\n\n get runningJob(): RunningJobInfo | undefined {\n return this.#runningJob;\n }\n\n async start() {\n if (this.#started) {\n throw new Error('runner already started');\n } else if (this.#closing) {\n throw new Error('runner is closed');\n }\n\n this.proc = this.createProcess();\n\n this.#started = true;\n this.run();\n }\n\n async run() {\n await this.init.await;\n\n this.#pingInterval = setInterval(() => {\n this.proc!.send({ case: 'pingRequest', value: { timestamp: Date.now() } });\n }, this.#opts.pingInterval);\n\n this.#pongTimeout = setTimeout(() => {\n this.#logger.warn('job is unresponsive');\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n this.proc!.kill();\n this.#join.resolve();\n }, this.#opts.pingTimeout);\n\n this.#memoryMonitorInterval = setInterval(async () => {\n const memoryMB = await this.getChildMemoryUsageMB();\n if (this.#opts.memoryLimitMB > 0 && memoryMB > this.#opts.memoryLimitMB) {\n this.#logger\n .child({ memoryUsageMB: memoryMB, memoryLimitMB: this.#opts.memoryLimitMB })\n .error('process exceeded memory limit, killing process');\n this.close();\n } else if (this.#opts.memoryWarnMB > 0 && memoryMB > this.#opts.memoryWarnMB) {\n this.#logger\n .child({\n memoryUsageMB: memoryMB,\n memoryWarnMB: this.#opts.memoryWarnMB,\n memoryLimitMB: this.#opts.memoryLimitMB,\n })\n .warn('process memory usage is high');\n }\n }, 5000);\n\n const listener = (msg: IPCMessage) => {\n switch (msg.case) {\n case 'pongResponse': {\n const delay = Date.now() - msg.value.timestamp;\n if (delay > this.#opts.highPingThreshold) {\n this.#logger.child({ delay }).warn('job executor is unresponsive');\n }\n this.#pongTimeout?.refresh();\n break;\n }\n case 'exiting': {\n this.#logger.child({ reason: msg.value.reason }).debug('job exiting');\n break;\n }\n case 'done': {\n this.#closing = true;\n this.proc!.off('message', listener);\n break;\n }\n }\n };\n this.proc!.on('message', listener);\n this.proc!.on('error', (err) => {\n if (this.#closing) return;\n this.#logger\n .child({ err })\n .warn('job process exited unexpectedly; this likely means the error above caused a crash');\n this.clearTimers();\n this.#join.resolve();\n });\n\n this.proc!.on('exit', () => {\n this.#join.resolve();\n });\n\n this.mainTask(this.proc!);\n\n await this.#join.await;\n }\n\n async join() {\n if (!this.#started) {\n throw new Error('runner not started');\n }\n\n await this.#join.await;\n }\n\n async initialize() {\n const timer = setTimeout(() => {\n const err = new Error('runner initialization timed out');\n this.init.reject(err);\n throw err;\n }, this.#opts.initializeTimeout);\n this.proc!.send({\n case: 'initializeRequest',\n value: {\n loggerOptions,\n pingInterval: this.#opts.pingInterval,\n pingTimeout: this.#opts.pingTimeout,\n highPingThreshold: this.#opts.highPingThreshold,\n },\n });\n await once(this.proc!, 'message').then(([msg]: IPCMessage[]) => {\n clearTimeout(timer);\n if (msg!.case !== 'initializeResponse') {\n throw new Error('first message must be InitializeResponse');\n }\n });\n this.init.resolve();\n }\n\n async close() {\n if (!this.#started) {\n return;\n }\n this.#closing = true;\n\n this.proc!.send({ case: 'shutdownRequest' });\n\n const timer = setTimeout(() => {\n this.#logger.error('job shutdown is taking too much time');\n this.proc!.kill();\n }, this.#opts.closeTimeout);\n await this.#join.await.then(() => {\n clearTimeout(timer);\n this.clearTimers();\n });\n }\n\n async launchJob(info: RunningJobInfo) {\n if (this.#runningJob) {\n throw new Error('executor already has a running job');\n }\n this.#runningJob = info;\n this.proc!.send({ case: 'startJobRequest', value: { runningJob: info } });\n }\n\n private async getChildMemoryUsageMB(): Promise<number> {\n const pid = this.proc?.pid;\n if (!pid) {\n return 0;\n }\n const stats = await pidusage(pid);\n return stats.memory / (1024 * 1024); // Convert bytes to MB\n }\n\n private clearTimers() {\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n clearInterval(this.#memoryMonitorInterval);\n }\n}\n"],"mappings":"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","names":["pidusage"]}
@@ -26,5 +26,7 @@ export declare abstract class SupervisedProc {
26
26
  initialize(): Promise<void>;
27
27
  close(): Promise<void>;
28
28
  launchJob(info: RunningJobInfo): Promise<void>;
29
+ private getChildMemoryUsageMB;
30
+ private clearTimers;
29
31
  }
30
32
  //# sourceMappingURL=supervised_proc.d.ts.map
@@ -26,5 +26,7 @@ export declare abstract class SupervisedProc {
26
26
  initialize(): Promise<void>;
27
27
  close(): Promise<void>;
28
28
  launchJob(info: RunningJobInfo): Promise<void>;
29
+ private getChildMemoryUsageMB;
30
+ private clearTimers;
29
31
  }
30
32
  //# sourceMappingURL=supervised_proc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"supervised_proc.d.ts","sourceRoot":"","sources":["../../src/ipc/supervised_proc.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,MAAM,WAAW,QAAQ;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,8BAAsB,cAAc;;IAKlC,IAAI,CAAC,EAAE,YAAY,CAAC;IAIpB,SAAS,CAAC,IAAI,eAAgB;gBAK5B,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM;IAa3B,QAAQ,CAAC,aAAa,IAAI,YAAY;IACtC,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAErD,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,UAAU,IAAI,cAAc,GAAG,SAAS,CAE3C;IAEK,KAAK;IAaL,GAAG;IA2EH,IAAI;IAQJ,UAAU;IAwBV,KAAK;IAmBL,SAAS,CAAC,IAAI,EAAE,cAAc;CAOrC"}
1
+ {"version":3,"file":"supervised_proc.d.ts","sourceRoot":"","sources":["../../src/ipc/supervised_proc.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,MAAM,WAAW,QAAQ;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,8BAAsB,cAAc;;IAKlC,IAAI,CAAC,EAAE,YAAY,CAAC;IAIpB,SAAS,CAAC,IAAI,eAAgB;gBAK5B,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM;IAa3B,QAAQ,CAAC,aAAa,IAAI,YAAY;IACtC,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAErD,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,UAAU,IAAI,cAAc,GAAG,SAAS,CAE3C;IAEK,KAAK;IAaL,GAAG;IAyEH,IAAI;IAQJ,UAAU;IAwBV,KAAK;IAkBL,SAAS,CAAC,IAAI,EAAE,cAAc;YAQtB,qBAAqB;IASnC,OAAO,CAAC,WAAW;CAKpB"}
@@ -1,4 +1,5 @@
1
1
  import { once } from "node:events";
2
+ import pidusage from "pidusage";
2
3
  import { log, loggerOptions } from "../log.js";
3
4
  import { Future } from "../utils.js";
4
5
  class SupervisedProc {
@@ -8,7 +9,7 @@ class SupervisedProc {
8
9
  #runningJob = void 0;
9
10
  proc;
10
11
  #pingInterval;
11
- #memoryWatch;
12
+ #memoryMonitorInterval;
12
13
  #pongTimeout;
13
14
  init = new Future();
14
15
  #join = new Future();
@@ -52,8 +53,8 @@ class SupervisedProc {
52
53
  this.proc.kill();
53
54
  this.#join.resolve();
54
55
  }, this.#opts.pingTimeout);
55
- this.#memoryWatch = setInterval(() => {
56
- const memoryMB = process.memoryUsage().heapUsed / (1024 * 1024);
56
+ this.#memoryMonitorInterval = setInterval(async () => {
57
+ const memoryMB = await this.getChildMemoryUsageMB();
57
58
  if (this.#opts.memoryLimitMB > 0 && memoryMB > this.#opts.memoryLimitMB) {
58
59
  this.#logger.child({ memoryUsageMB: memoryMB, memoryLimitMB: this.#opts.memoryLimitMB }).error("process exceeded memory limit, killing process");
59
60
  this.close();
@@ -62,9 +63,9 @@ class SupervisedProc {
62
63
  memoryUsageMB: memoryMB,
63
64
  memoryWarnMB: this.#opts.memoryWarnMB,
64
65
  memoryLimitMB: this.#opts.memoryLimitMB
65
- }).error("process memory usage is high");
66
+ }).warn("process memory usage is high");
66
67
  }
67
- });
68
+ }, 5e3);
68
69
  const listener = (msg) => {
69
70
  var _a;
70
71
  switch (msg.case) {
@@ -91,9 +92,7 @@ class SupervisedProc {
91
92
  this.proc.on("error", (err) => {
92
93
  if (this.#closing) return;
93
94
  this.#logger.child({ err }).warn("job process exited unexpectedly; this likely means the error above caused a crash");
94
- clearTimeout(this.#pongTimeout);
95
- clearInterval(this.#pingInterval);
96
- clearInterval(this.#memoryWatch);
95
+ this.clearTimers();
97
96
  this.#join.resolve();
98
97
  });
99
98
  this.proc.on("exit", () => {
@@ -143,8 +142,7 @@ class SupervisedProc {
143
142
  }, this.#opts.closeTimeout);
144
143
  await this.#join.await.then(() => {
145
144
  clearTimeout(timer);
146
- clearTimeout(this.#pongTimeout);
147
- clearInterval(this.#pingInterval);
145
+ this.clearTimers();
148
146
  });
149
147
  }
150
148
  async launchJob(info) {
@@ -154,6 +152,20 @@ class SupervisedProc {
154
152
  this.#runningJob = info;
155
153
  this.proc.send({ case: "startJobRequest", value: { runningJob: info } });
156
154
  }
155
+ async getChildMemoryUsageMB() {
156
+ var _a;
157
+ const pid = (_a = this.proc) == null ? void 0 : _a.pid;
158
+ if (!pid) {
159
+ return 0;
160
+ }
161
+ const stats = await pidusage(pid);
162
+ return stats.memory / (1024 * 1024);
163
+ }
164
+ clearTimers() {
165
+ clearTimeout(this.#pongTimeout);
166
+ clearInterval(this.#pingInterval);
167
+ clearInterval(this.#memoryMonitorInterval);
168
+ }
157
169
  }
158
170
  export {
159
171
  SupervisedProc
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ipc/supervised_proc.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChildProcess } from 'node:child_process';\nimport { once } from 'node:events';\nimport type { RunningJobInfo } from '../job.js';\nimport { log, loggerOptions } from '../log.js';\nimport { Future } from '../utils.js';\nimport type { IPCMessage } from './message.js';\n\nexport interface ProcOpts {\n initializeTimeout: number;\n closeTimeout: number;\n memoryWarnMB: number;\n memoryLimitMB: number;\n pingInterval: number;\n pingTimeout: number;\n highPingThreshold: number;\n}\n\nexport abstract class SupervisedProc {\n #opts: ProcOpts;\n #started = false;\n #closing = false;\n #runningJob?: RunningJobInfo = undefined;\n proc?: ChildProcess;\n #pingInterval?: ReturnType<typeof setInterval>;\n #memoryWatch?: ReturnType<typeof setInterval>;\n #pongTimeout?: ReturnType<typeof setTimeout>;\n protected init = new Future();\n #join = new Future();\n #logger = log().child({ runningJob: this.#runningJob });\n\n constructor(\n initializeTimeout: number,\n closeTimeout: number,\n memoryWarnMB: number,\n memoryLimitMB: number,\n pingInterval: number,\n pingTimeout: number,\n highPingThreshold: number,\n ) {\n this.#opts = {\n initializeTimeout,\n closeTimeout,\n memoryWarnMB,\n memoryLimitMB,\n pingInterval,\n pingTimeout,\n highPingThreshold,\n };\n }\n\n abstract createProcess(): ChildProcess;\n abstract mainTask(child: ChildProcess): Promise<void>;\n\n get started(): boolean {\n return this.#started;\n }\n\n get runningJob(): RunningJobInfo | undefined {\n return this.#runningJob;\n }\n\n async start() {\n if (this.#started) {\n throw new Error('runner already started');\n } else if (this.#closing) {\n throw new Error('runner is closed');\n }\n\n this.proc = this.createProcess();\n\n this.#started = true;\n this.run();\n }\n\n async run() {\n await this.init.await;\n\n this.#pingInterval = setInterval(() => {\n this.proc!.send({ case: 'pingRequest', value: { timestamp: Date.now() } });\n }, this.#opts.pingInterval);\n\n this.#pongTimeout = setTimeout(() => {\n this.#logger.warn('job is unresponsive');\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n this.proc!.kill();\n this.#join.resolve();\n }, this.#opts.pingTimeout);\n\n this.#memoryWatch = setInterval(() => {\n const memoryMB = process.memoryUsage().heapUsed / (1024 * 1024);\n if (this.#opts.memoryLimitMB > 0 && memoryMB > this.#opts.memoryLimitMB) {\n this.#logger\n .child({ memoryUsageMB: memoryMB, memoryLimitMB: this.#opts.memoryLimitMB })\n .error('process exceeded memory limit, killing process');\n this.close();\n } else if (this.#opts.memoryWarnMB > 0 && memoryMB > this.#opts.memoryWarnMB) {\n this.#logger\n .child({\n memoryUsageMB: memoryMB,\n memoryWarnMB: this.#opts.memoryWarnMB,\n memoryLimitMB: this.#opts.memoryLimitMB,\n })\n .error('process memory usage is high');\n }\n });\n\n const listener = (msg: IPCMessage) => {\n switch (msg.case) {\n case 'pongResponse': {\n const delay = Date.now() - msg.value.timestamp;\n if (delay > this.#opts.highPingThreshold) {\n this.#logger.child({ delay }).warn('job executor is unresponsive');\n }\n this.#pongTimeout?.refresh();\n break;\n }\n case 'exiting': {\n this.#logger.child({ reason: msg.value.reason }).debug('job exiting');\n break;\n }\n case 'done': {\n this.#closing = true;\n this.proc!.off('message', listener);\n break;\n }\n }\n };\n this.proc!.on('message', listener);\n this.proc!.on('error', (err) => {\n if (this.#closing) return;\n this.#logger\n .child({ err })\n .warn('job process exited unexpectedly; this likely means the error above caused a crash');\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n clearInterval(this.#memoryWatch);\n this.#join.resolve();\n });\n\n this.proc!.on('exit', () => {\n this.#join.resolve();\n });\n\n this.mainTask(this.proc!);\n\n await this.#join.await;\n }\n\n async join() {\n if (!this.#started) {\n throw new Error('runner not started');\n }\n\n await this.#join.await;\n }\n\n async initialize() {\n const timer = setTimeout(() => {\n const err = new Error('runner initialization timed out');\n this.init.reject(err);\n throw err;\n }, this.#opts.initializeTimeout);\n this.proc!.send({\n case: 'initializeRequest',\n value: {\n loggerOptions,\n pingInterval: this.#opts.pingInterval,\n pingTimeout: this.#opts.pingTimeout,\n highPingThreshold: this.#opts.highPingThreshold,\n },\n });\n await once(this.proc!, 'message').then(([msg]: IPCMessage[]) => {\n clearTimeout(timer);\n if (msg!.case !== 'initializeResponse') {\n throw new Error('first message must be InitializeResponse');\n }\n });\n this.init.resolve();\n }\n\n async close() {\n if (!this.#started) {\n return;\n }\n this.#closing = true;\n\n this.proc!.send({ case: 'shutdownRequest' });\n\n const timer = setTimeout(() => {\n this.#logger.error('job shutdown is taking too much time');\n this.proc!.kill();\n }, this.#opts.closeTimeout);\n await this.#join.await.then(() => {\n clearTimeout(timer);\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n });\n }\n\n async launchJob(info: RunningJobInfo) {\n if (this.#runningJob) {\n throw new Error('executor already has a running job');\n }\n this.#runningJob = info;\n this.proc!.send({ case: 'startJobRequest', value: { runningJob: info } });\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/ipc/supervised_proc.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChildProcess } from 'node:child_process';\nimport { once } from 'node:events';\nimport pidusage from 'pidusage';\nimport type { RunningJobInfo } from '../job.js';\nimport { log, loggerOptions } from '../log.js';\nimport { Future } from '../utils.js';\nimport type { IPCMessage } from './message.js';\n\nexport interface ProcOpts {\n initializeTimeout: number;\n closeTimeout: number;\n memoryWarnMB: number;\n memoryLimitMB: number;\n pingInterval: number;\n pingTimeout: number;\n highPingThreshold: number;\n}\n\nexport abstract class SupervisedProc {\n #opts: ProcOpts;\n #started = false;\n #closing = false;\n #runningJob?: RunningJobInfo = undefined;\n proc?: ChildProcess;\n #pingInterval?: ReturnType<typeof setInterval>;\n #memoryMonitorInterval?: ReturnType<typeof setInterval>;\n #pongTimeout?: ReturnType<typeof setTimeout>;\n protected init = new Future();\n #join = new Future();\n #logger = log().child({ runningJob: this.#runningJob });\n\n constructor(\n initializeTimeout: number,\n closeTimeout: number,\n memoryWarnMB: number,\n memoryLimitMB: number,\n pingInterval: number,\n pingTimeout: number,\n highPingThreshold: number,\n ) {\n this.#opts = {\n initializeTimeout,\n closeTimeout,\n memoryWarnMB,\n memoryLimitMB,\n pingInterval,\n pingTimeout,\n highPingThreshold,\n };\n }\n\n abstract createProcess(): ChildProcess;\n abstract mainTask(child: ChildProcess): Promise<void>;\n\n get started(): boolean {\n return this.#started;\n }\n\n get runningJob(): RunningJobInfo | undefined {\n return this.#runningJob;\n }\n\n async start() {\n if (this.#started) {\n throw new Error('runner already started');\n } else if (this.#closing) {\n throw new Error('runner is closed');\n }\n\n this.proc = this.createProcess();\n\n this.#started = true;\n this.run();\n }\n\n async run() {\n await this.init.await;\n\n this.#pingInterval = setInterval(() => {\n this.proc!.send({ case: 'pingRequest', value: { timestamp: Date.now() } });\n }, this.#opts.pingInterval);\n\n this.#pongTimeout = setTimeout(() => {\n this.#logger.warn('job is unresponsive');\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n this.proc!.kill();\n this.#join.resolve();\n }, this.#opts.pingTimeout);\n\n this.#memoryMonitorInterval = setInterval(async () => {\n const memoryMB = await this.getChildMemoryUsageMB();\n if (this.#opts.memoryLimitMB > 0 && memoryMB > this.#opts.memoryLimitMB) {\n this.#logger\n .child({ memoryUsageMB: memoryMB, memoryLimitMB: this.#opts.memoryLimitMB })\n .error('process exceeded memory limit, killing process');\n this.close();\n } else if (this.#opts.memoryWarnMB > 0 && memoryMB > this.#opts.memoryWarnMB) {\n this.#logger\n .child({\n memoryUsageMB: memoryMB,\n memoryWarnMB: this.#opts.memoryWarnMB,\n memoryLimitMB: this.#opts.memoryLimitMB,\n })\n .warn('process memory usage is high');\n }\n }, 5000);\n\n const listener = (msg: IPCMessage) => {\n switch (msg.case) {\n case 'pongResponse': {\n const delay = Date.now() - msg.value.timestamp;\n if (delay > this.#opts.highPingThreshold) {\n this.#logger.child({ delay }).warn('job executor is unresponsive');\n }\n this.#pongTimeout?.refresh();\n break;\n }\n case 'exiting': {\n this.#logger.child({ reason: msg.value.reason }).debug('job exiting');\n break;\n }\n case 'done': {\n this.#closing = true;\n this.proc!.off('message', listener);\n break;\n }\n }\n };\n this.proc!.on('message', listener);\n this.proc!.on('error', (err) => {\n if (this.#closing) return;\n this.#logger\n .child({ err })\n .warn('job process exited unexpectedly; this likely means the error above caused a crash');\n this.clearTimers();\n this.#join.resolve();\n });\n\n this.proc!.on('exit', () => {\n this.#join.resolve();\n });\n\n this.mainTask(this.proc!);\n\n await this.#join.await;\n }\n\n async join() {\n if (!this.#started) {\n throw new Error('runner not started');\n }\n\n await this.#join.await;\n }\n\n async initialize() {\n const timer = setTimeout(() => {\n const err = new Error('runner initialization timed out');\n this.init.reject(err);\n throw err;\n }, this.#opts.initializeTimeout);\n this.proc!.send({\n case: 'initializeRequest',\n value: {\n loggerOptions,\n pingInterval: this.#opts.pingInterval,\n pingTimeout: this.#opts.pingTimeout,\n highPingThreshold: this.#opts.highPingThreshold,\n },\n });\n await once(this.proc!, 'message').then(([msg]: IPCMessage[]) => {\n clearTimeout(timer);\n if (msg!.case !== 'initializeResponse') {\n throw new Error('first message must be InitializeResponse');\n }\n });\n this.init.resolve();\n }\n\n async close() {\n if (!this.#started) {\n return;\n }\n this.#closing = true;\n\n this.proc!.send({ case: 'shutdownRequest' });\n\n const timer = setTimeout(() => {\n this.#logger.error('job shutdown is taking too much time');\n this.proc!.kill();\n }, this.#opts.closeTimeout);\n await this.#join.await.then(() => {\n clearTimeout(timer);\n this.clearTimers();\n });\n }\n\n async launchJob(info: RunningJobInfo) {\n if (this.#runningJob) {\n throw new Error('executor already has a running job');\n }\n this.#runningJob = info;\n this.proc!.send({ case: 'startJobRequest', value: { runningJob: info } });\n }\n\n private async getChildMemoryUsageMB(): Promise<number> {\n const pid = this.proc?.pid;\n if (!pid) {\n return 0;\n }\n const stats = await pidusage(pid);\n return stats.memory / (1024 * 1024); // Convert bytes to MB\n }\n\n private clearTimers() {\n clearTimeout(this.#pongTimeout);\n clearInterval(this.#pingInterval);\n clearInterval(this.#memoryMonitorInterval);\n }\n}\n"],"mappings":"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","names":[]}
package/dist/llm/llm.cjs CHANGED
@@ -150,7 +150,10 @@ class LLMStream {
150
150
  promptTokens: (usage == null ? void 0 : usage.promptTokens) || 0,
151
151
  promptCachedTokens: (usage == null ? void 0 : usage.promptCachedTokens) || 0,
152
152
  totalTokens: (usage == null ? void 0 : usage.totalTokens) || 0,
153
- tokensPerSecond: ((usage == null ? void 0 : usage.completionTokens) || 0) / Math.trunc(Number(duration / BigInt(1e9)))
153
+ tokensPerSecond: (() => {
154
+ const durationSeconds = Math.trunc(Number(duration / BigInt(1e9)));
155
+ return durationSeconds > 0 ? ((usage == null ? void 0 : usage.completionTokens) || 0) / durationSeconds : 0;
156
+ })()
154
157
  };
155
158
  this.#llm.emit("metrics_collected", metrics);
156
159
  }